changeset 3482:4aaa66ce712d

6710360: export Kerberos session key to applications Reviewed-by: valeriep
author weijun
date Tue, 11 Aug 2009 12:17:13 +0800
parents 6ae7a2a6c956
children a16fce1820ef
files jdk/src/share/classes/com/sun/security/jgss/ExtendedGSSContext.java jdk/src/share/classes/com/sun/security/jgss/InquireSecContextPermission.java jdk/src/share/classes/com/sun/security/jgss/InquireType.java jdk/src/share/classes/sun/security/jgss/GSSContextImpl.java jdk/src/share/classes/sun/security/jgss/krb5/Krb5Context.java jdk/src/share/classes/sun/security/jgss/spi/GSSContextSpi.java jdk/src/share/classes/sun/security/jgss/spnego/SpNegoContext.java jdk/src/share/classes/sun/security/jgss/wrapper/NativeGSSContext.java jdk/src/share/classes/sun/security/tools/PolicyTool.java jdk/test/com/sun/security/jgss/InquireSecContextPermissionCheck.java jdk/test/sun/security/krb5/auto/Context.java
diffstat 11 files changed, 368 insertions(+), 17 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/com/sun/security/jgss/ExtendedGSSContext.java	Tue Aug 11 12:17:13 2009 +0800
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2009 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package com.sun.security.jgss;
+
+import org.ietf.jgss.*;
+
+/**
+ * The extended GSSContext interface for supporting additional
+ * functionalities not defined by {@code org.ietf.jgss.GSSContext},
+ * such as querying context-specific attributes.
+ */
+public interface ExtendedGSSContext extends GSSContext {
+    /**
+     * Return the mechanism-specific attribute associated with {@code type}.
+     * <br><br>
+     * For each supported attribute type, the type for the output are
+     * defined below.
+     * <ol>
+     * <li>{@code KRB5_GET_SESSION_KEY}:
+     * the returned object is an instance of {@link java.security.Key},
+     * which has the following properties:
+     *    <ul>
+     *    <li>Algorithm: enctype as a string, where
+     *        enctype is defined in RFC 3961, section 8.
+     *    <li>Format: "RAW"
+     *    <li>Encoded form: the raw key bytes, not in any ASN.1 encoding
+     *    </ul>
+     * </ol>
+     *
+     * If there is a security manager, an {@link InquireSecContextPermission}
+     * with the name {@code type.mech} must be granted. Otherwise, this could
+     * result in a {@link SecurityException}.<p>
+     *
+     * Example:
+     * <pre>
+     *      GSSContext ctxt = m.createContext(...)
+     *      // Establishing the context
+     *      if (ctxt instanceof ExtendedGSSContext) {
+     *          ExtendedGSSContext ex = (ExtendedGSSContext)ctxt;
+     *          try {
+     *              Key key = (key)ex.inquireSecContext(
+     *                      InquireType.KRB5_GET_SESSION_KEY);
+     *              // read key info
+     *          } catch (GSSException gsse) {
+     *              // deal with exception
+     *          }
+     *      }
+     * </pre>
+     * @param type the type of the attribute requested
+     * @return the attribute, see the method documentation for details.
+     * @throws GSSException containing  the following
+     * major error codes:
+     *   {@link GSSException#BAD_MECH GSSException.BAD_MECH} if the mechanism
+     *   does not support this method,
+     *   {@link GSSException#UNAVAILABLE GSSException.UNAVAILABLE} if the
+     *   type specified is not supported,
+     *   {@link GSSException#NO_CONTEXT GSSException.NO_CONTEXT} if the
+     *   security context is invalid,
+     *   {@link GSSException#FAILURE GSSException.FAILURE} for other
+     *   unspecified failures.
+     * @throws SecurityException if a security manager exists and a proper
+     *   {@link InquireSecContextPermission} is not granted.
+     * @see InquireSecContextPermission
+     */
+    public Object inquireSecContext(InquireType type)
+            throws GSSException;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/com/sun/security/jgss/InquireSecContextPermission.java	Tue Aug 11 12:17:13 2009 +0800
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2009 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package com.sun.security.jgss;
+
+import java.security.BasicPermission;
+
+/**
+ * This class is used to protect various attributes of an established
+ * GSS security context that can be accessed using the
+ * {@link com.sun.security.jgss.ExtendedGSSContext#inquireSecContext}
+ * method.
+ *
+ * <p>The target name is the {@link InquireType} allowed.
+ */
+public final class InquireSecContextPermission extends BasicPermission {
+
+    /**
+     * Constructs a new {@code InquireSecContextPermission} object with
+     * the specified name. The name is the symbolic name of the
+     * {@link InquireType} allowed.
+     *
+     * @param name the {@link InquireType} allowed by this
+     * permission. "*" means all {@link InquireType}s are allowed.
+     *
+     * @throws NullPointerException if <code>name</code> is <code>null</code>.
+     * @throws IllegalArgumentException if <code>name</code> is empty.
+     */
+    public InquireSecContextPermission(String name) {
+        super(name);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/com/sun/security/jgss/InquireType.java	Tue Aug 11 12:17:13 2009 +0800
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2009 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package com.sun.security.jgss;
+
+/**
+ * Attribute types that can be specified as an argument of
+ * {@link com.sun.security.jgss.ExtendedGSSContext#inquireSecContext}
+ */
+public enum InquireType {
+    /**
+     * Attribute type for retrieving the session key of an
+     * established security context.
+     */
+    KRB5_GET_SESSION_KEY
+}
--- a/jdk/src/share/classes/sun/security/jgss/GSSContextImpl.java	Tue Aug 11 12:15:24 2009 +0800
+++ b/jdk/src/share/classes/sun/security/jgss/GSSContextImpl.java	Tue Aug 11 12:17:13 2009 +0800
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2008 Sun Microsystems, Inc.  All Rights Reserved.
+ * Copyright 2000-2009 Sun Microsystems, Inc.  All Rights Reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -27,14 +27,13 @@
 
 import org.ietf.jgss.*;
 import sun.security.jgss.spi.*;
-import sun.security.jgss.*;
 import sun.security.util.ObjectIdentifier;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
-
+import com.sun.security.jgss.*;
 
 /**
  * This class represents the JGSS security context and its associated
@@ -88,7 +87,7 @@
  * per-message operations are returned in an instance of the MessageProp
  * class, which is used as an argument in these calls.</dl>
  */
-class GSSContextImpl implements GSSContext {
+class GSSContextImpl implements ExtendedGSSContext {
 
     private GSSManagerImpl gssManager = null;
 
@@ -630,4 +629,16 @@
         srcName = null;
         targName = null;
     }
+
+    @Override
+    public Object inquireSecContext(InquireType type) throws GSSException {
+        SecurityManager security = System.getSecurityManager();
+        if (security != null) {
+            security.checkPermission(new InquireSecContextPermission(type.toString()));
+        }
+        if (mechCtxt == null) {
+            throw new GSSException(GSSException.NO_CONTEXT);
+        }
+        return mechCtxt.inquireSecContext(type);
+    }
 }
--- a/jdk/src/share/classes/sun/security/jgss/krb5/Krb5Context.java	Tue Aug 11 12:15:24 2009 +0800
+++ b/jdk/src/share/classes/sun/security/jgss/krb5/Krb5Context.java	Tue Aug 11 12:17:13 2009 +0800
@@ -25,6 +25,7 @@
 
 package sun.security.jgss.krb5;
 
+import com.sun.security.jgss.InquireType;
 import org.ietf.jgss.*;
 import sun.misc.HexDumpEncoder;
 import sun.security.jgss.GSSUtil;
@@ -38,6 +39,7 @@
 import java.security.Provider;
 import java.security.AccessController;
 import java.security.AccessControlContext;
+import java.security.Key;
 import java.security.PrivilegedExceptionAction;
 import java.security.PrivilegedActionException;
 import javax.crypto.Cipher;
@@ -1283,4 +1285,54 @@
         // Currently used by InitialToken only
         return caller;
     }
+
+    /**
+     * The session key returned by inquireSecContext(KRB5_INQ_SSPI_SESSION_KEY)
+     */
+    static class KerberosSessionKey implements Key {
+        private EncryptionKey key;
+
+        KerberosSessionKey(EncryptionKey key) {
+            this.key = key;
+        }
+
+        @Override
+        public String getAlgorithm() {
+            return Integer.toString(key.getEType());
+        }
+
+        @Override
+        public String getFormat() {
+            return "RAW";
+        }
+
+        @Override
+        public byte[] getEncoded() {
+            return key.getBytes().clone();
+        }
+
+        @Override
+        public String toString() {
+            return "Kerberos session key: etype: " + key.getEType() + "\n" +
+                    new sun.misc.HexDumpEncoder().encodeBuffer(key.getBytes());
+        }
+    }
+
+    /**
+     * Return the mechanism-specific attribute associated with {@code type}.
+     * Only KRB5_GET_SESSION_KEY is supported now.
+     */
+    public Object inquireSecContext(InquireType type)
+            throws GSSException {
+        if (type == InquireType.KRB5_GET_SESSION_KEY) {
+            if (key == null) {
+                throw new GSSException(GSSException.NO_CONTEXT, -1,
+                        "Session key not established.");
+            } else {
+                return new KerberosSessionKey(key);
+            }
+        }
+        throw new GSSException(GSSException.UNAVAILABLE, -1,
+                "Inquire type not supported.");
+    }
 }
--- a/jdk/src/share/classes/sun/security/jgss/spi/GSSContextSpi.java	Tue Aug 11 12:15:24 2009 +0800
+++ b/jdk/src/share/classes/sun/security/jgss/spi/GSSContextSpi.java	Tue Aug 11 12:17:13 2009 +0800
@@ -1,5 +1,5 @@
 /*
- * Portions Copyright 2000-2005 Sun Microsystems, Inc.  All Rights Reserved.
+ * Portions Copyright 2000-2009 Sun Microsystems, Inc.  All Rights Reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -46,6 +46,7 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.security.Provider;
+import com.sun.security.jgss.*;
 
 /**
  * This interface is implemented by a mechanism specific instance of a GSS
@@ -265,7 +266,6 @@
      * @param msgPro on input it contains the requested qop and
      *    confidentiality state, on output, the applied values
      * @exception GSSException may be thrown
-     * @see MessageInfo
      * @see unwrap
      */
     public void wrap(InputStream is, OutputStream os, MessageProp msgProp)
@@ -315,7 +315,6 @@
      * @param msgProp will contain the applied qop and confidentiality
      *    of the input token and any informatory status values
      * @exception GSSException may be thrown
-     * @see MessageInfo
      * @see wrap
      */
     public void unwrap(InputStream is, OutputStream os,
@@ -403,4 +402,15 @@
      * @exception GSSException may be thrown
      */
     public void dispose() throws GSSException;
+
+    /**
+     * Return the mechanism-specific attribute associated with (@code type}.
+     *
+     * @param type the type of the attribute requested
+     * @return the attribute
+     * @throws GSSException see {@link ExtendedGSSContext#inquireSecContext}
+     * for details
+     */
+    public Object inquireSecContext(InquireType type)
+            throws GSSException;
 }
--- a/jdk/src/share/classes/sun/security/jgss/spnego/SpNegoContext.java	Tue Aug 11 12:15:24 2009 +0800
+++ b/jdk/src/share/classes/sun/security/jgss/spnego/SpNegoContext.java	Tue Aug 11 12:17:13 2009 +0800
@@ -25,10 +25,10 @@
 
 package sun.security.jgss.spnego;
 
+import com.sun.security.jgss.ExtendedGSSContext;
+import com.sun.security.jgss.InquireType;
 import java.io.*;
 import java.security.Provider;
-import java.util.List;
-import java.util.ArrayList;
 import org.ietf.jgss.*;
 import sun.security.jgss.*;
 import sun.security.jgss.spi.*;
@@ -1185,4 +1185,22 @@
                 return ("Unknown state " + state);
         }
     }
+
+    /**
+     * Retrieve attribute of the context for {@code type}.
+     */
+    public Object inquireSecContext(InquireType type)
+            throws GSSException {
+        if (mechContext == null) {
+            throw new GSSException(GSSException.NO_CONTEXT, -1,
+                    "Underlying mech not established.");
+        }
+        if (mechContext instanceof ExtendedGSSContext) {
+            return ((ExtendedGSSContext)mechContext).inquireSecContext(type);
+        } else {
+            throw new GSSException(GSSException.BAD_MECH, -1,
+                    "inquireSecContext not supported by underlying mech.");
+        }
+    }
+
 }
--- a/jdk/src/share/classes/sun/security/jgss/wrapper/NativeGSSContext.java	Tue Aug 11 12:15:24 2009 +0800
+++ b/jdk/src/share/classes/sun/security/jgss/wrapper/NativeGSSContext.java	Tue Aug 11 12:17:13 2009 +0800
@@ -1,5 +1,5 @@
 /*
- * Copyright 2005 Sun Microsystems, Inc.  All Rights Reserved.
+ * Copyright 2005-2009 Sun Microsystems, Inc.  All Rights Reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -36,6 +36,7 @@
 import sun.security.jgss.spnego.NegTokenInit;
 import sun.security.jgss.spnego.NegTokenTarg;
 import javax.security.auth.kerberos.DelegationPermission;
+import com.sun.security.jgss.InquireType;
 import java.io.*;
 
 
@@ -615,4 +616,10 @@
     protected void finalize() throws Throwable {
         dispose();
     }
+
+    public Object inquireSecContext(InquireType type)
+            throws GSSException {
+        throw new GSSException(GSSException.UNAVAILABLE, -1,
+                "Inquire type not supported.");
+    }
 }
--- a/jdk/src/share/classes/sun/security/tools/PolicyTool.java	Tue Aug 11 12:15:24 2009 +0800
+++ b/jdk/src/share/classes/sun/security/tools/PolicyTool.java	Tue Aug 11 12:17:13 2009 +0800
@@ -1,5 +1,5 @@
 /*
- * Copyright 1997-2008 Sun Microsystems, Inc.  All Rights Reserved.
+ * Copyright 1997-2009 Sun Microsystems, Inc.  All Rights Reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -35,21 +35,16 @@
 import java.lang.reflect.*;
 import java.text.Collator;
 import java.text.MessageFormat;
-import sun.misc.BASE64Decoder;
-import sun.security.provider.PolicyParser.PermissionEntry;
 import sun.security.util.PropertyExpander;
 import sun.security.util.PropertyExpander.ExpandException;
 import java.awt.*;
 import java.awt.event.*;
 import java.security.cert.Certificate;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
 import java.security.cert.CertificateException;
 import java.security.*;
 import sun.security.provider.*;
 import sun.security.util.PolicyUtil;
 import javax.security.auth.x500.X500Principal;
-import java.util.HashSet;
 
 /**
  * PolicyTool may be used by users and administrators to configure the
@@ -1459,6 +1454,7 @@
         PERM_ARRAY.add(new AWTPerm());
         PERM_ARRAY.add(new DelegationPerm());
         PERM_ARRAY.add(new FilePerm());
+        PERM_ARRAY.add(new InqSecContextPerm());
         PERM_ARRAY.add(new LogPerm());
         PERM_ARRAY.add(new MgmtPerm());
         PERM_ARRAY.add(new MBeanPerm());
@@ -3961,6 +3957,17 @@
     }
 }
 
+class InqSecContextPerm extends Perm {
+    public InqSecContextPerm() {
+    super("InquireSecContextPermission",
+        "com.sun.security.jgss.InquireSecContextPermission",
+        new String[]    {
+                "KRB5_GET_SESSION_KEY"
+                },
+        null);
+    }
+}
+
 class LogPerm extends Perm {
     public LogPerm() {
     super("LoggingPermission",
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/com/sun/security/jgss/InquireSecContextPermissionCheck.java	Tue Aug 11 12:17:13 2009 +0800
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2009 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+/**
+ * @test
+ * @bug 6710360
+ * @summary export Kerberos session key to applications
+ */
+
+import com.sun.security.jgss.InquireSecContextPermission;
+
+public class InquireSecContextPermissionCheck {
+
+    public static void main(String[] args) throws Exception {
+
+        InquireSecContextPermission p0, p1;
+        p0 = new InquireSecContextPermission(
+                "KRB5_GET_SESSION_KEY");
+        p1 = new InquireSecContextPermission("*");
+
+        if (!p1.implies(p0) || !p1.implies(p1) || !p0.implies(p0)) {
+            throw new Exception("Check failed");
+        }
+
+        if (p0.implies(p1)) {
+            throw new Exception("This is bad");
+        }
+    }
+}
+
--- a/jdk/test/sun/security/krb5/auto/Context.java	Tue Aug 11 12:15:24 2009 +0800
+++ b/jdk/test/sun/security/krb5/auto/Context.java	Tue Aug 11 12:17:13 2009 +0800
@@ -1,5 +1,5 @@
 /*
- * Copyright 2008 Sun Microsystems, Inc.  All Rights Reserved.
+ * Copyright 2008-2009 Sun Microsystems, Inc.  All Rights Reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -22,6 +22,7 @@
  */
 
 import com.sun.security.auth.module.Krb5LoginModule;
+import java.security.Key;
 import java.security.PrivilegedActionException;
 import java.security.PrivilegedExceptionAction;
 import java.util.Arrays;
@@ -38,6 +39,8 @@
 import org.ietf.jgss.GSSName;
 import org.ietf.jgss.MessageProp;
 import org.ietf.jgss.Oid;
+import com.sun.security.jgss.ExtendedGSSContext;
+import com.sun.security.jgss.InquireType;
 
 /**
  * Context of a JGSS subject, encapsulating Subject and GSSContext.
@@ -276,6 +279,17 @@
                 }
             }
         }
+        if (x != null && x instanceof ExtendedGSSContext) {
+            if (x.isEstablished()) {
+                ExtendedGSSContext ex = (ExtendedGSSContext)x;
+                Key k = (Key)ex.inquireSecContext(
+                        InquireType.KRB5_GET_SESSION_KEY);
+                if (k == null) {
+                    throw new Exception("Session key cannot be null");
+                }
+                System.out.println("Session key is: " + k);
+            }
+        }
     }
 
     /**