changeset 6090:a1bbb8805e22

6355584: Introduce constrained Kerberos delegation Reviewed-by: valeriep
author weijun
date Wed, 07 Nov 2012 14:13:01 +0800
parents c9fd61d23dbe
children 59e88d3b9b17
files src/share/classes/com/sun/security/jgss/ExtendedGSSCredential.java src/share/classes/sun/security/jgss/GSSCaller.java src/share/classes/sun/security/jgss/GSSCredentialImpl.java src/share/classes/sun/security/jgss/HttpCaller.java src/share/classes/sun/security/jgss/krb5/Krb5AcceptCredential.java src/share/classes/sun/security/jgss/krb5/Krb5Context.java src/share/classes/sun/security/jgss/krb5/Krb5InitCredential.java src/share/classes/sun/security/jgss/krb5/Krb5ProxyCredential.java src/share/classes/sun/security/jgss/krb5/Krb5Util.java src/share/classes/sun/security/jgss/spi/GSSCredentialSpi.java src/share/classes/sun/security/jgss/spnego/SpNegoContext.java src/share/classes/sun/security/jgss/spnego/SpNegoCredElement.java src/share/classes/sun/security/jgss/wrapper/GSSCredElement.java src/share/classes/sun/security/krb5/Credentials.java src/share/classes/sun/security/krb5/EncryptedData.java src/share/classes/sun/security/krb5/KrbApReq.java src/share/classes/sun/security/krb5/KrbKdcRep.java src/share/classes/sun/security/krb5/KrbTgsRep.java src/share/classes/sun/security/krb5/KrbTgsReq.java src/share/classes/sun/security/krb5/internal/CredentialsUtil.java src/share/classes/sun/security/krb5/internal/EncKDCRepPart.java src/share/classes/sun/security/krb5/internal/KDCOptions.java src/share/classes/sun/security/krb5/internal/Krb5.java src/share/classes/sun/security/krb5/internal/PAData.java src/share/classes/sun/security/krb5/internal/PAForUserEnc.java src/share/classes/sun/security/krb5/internal/crypto/KeyUsage.java src/share/classes/sun/security/krb5/internal/ktab/KeyTab.java test/sun/security/krb5/auto/Basic.java test/sun/security/krb5/auto/Context.java test/sun/security/krb5/auto/CrossRealm.java test/sun/security/krb5/auto/KDC.java test/sun/security/krb5/auto/OkAsDelegate.java test/sun/security/krb5/auto/S4U2proxy.java test/sun/security/krb5/auto/S4U2proxyGSS.java test/sun/security/krb5/auto/S4U2self.java test/sun/security/krb5/auto/S4U2selfAsServer.java test/sun/security/krb5/auto/S4U2selfAsServerGSS.java test/sun/security/krb5/auto/S4U2selfGSS.java
diffstat 38 files changed, 1619 insertions(+), 239 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/share/classes/com/sun/security/jgss/ExtendedGSSCredential.java	Wed Nov 07 14:13:01 2012 +0800
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2012, Oracle and/or its affiliates. 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.sun.security.jgss;
+
+import org.ietf.jgss.*;
+
+/**
+ * The extended GSSCredential interface for supporting additional
+ * functionalities not defined by {@code org.ietf.jgss.GSSCredential}.
+ * @since 1.8
+ */
+public interface ExtendedGSSCredential extends GSSCredential {
+    /**
+     * Impersonates a principal. In Kerberos, this can be implemented
+     * using the Microsoft S4U2self extension.
+     * <p>
+     * A {@link GSSException#NO_CRED GSSException.NO_CRED} will be thrown if the
+     * impersonation fails. A {@link GSSException#FAILURE GSSException.FAILURE}
+     * will be  thrown if the impersonation method is not available to this
+     * credential object.
+     * @param name the name of the principal to impersonate
+     * @return a credential for that principal
+     * @throws GSSException  containing the following
+     * major error codes:
+     *   {@link GSSException#NO_CRED GSSException.NO_CRED}
+     *   {@link GSSException#FAILURE GSSException.FAILURE}
+     */
+    public GSSCredential impersonate(GSSName name) throws GSSException;
+}
--- a/src/share/classes/sun/security/jgss/GSSCaller.java	Tue Nov 06 18:41:01 2012 -0800
+++ b/src/share/classes/sun/security/jgss/GSSCaller.java	Wed Nov 07 14:13:01 2012 +0800
@@ -31,10 +31,19 @@
  * different callers.
  */
 public class GSSCaller {
-    public static final GSSCaller CALLER_UNKNOWN = new GSSCaller();
-    public static final GSSCaller CALLER_INITIATE = new GSSCaller();
-    public static final GSSCaller CALLER_ACCEPT = new GSSCaller();
-    public static final GSSCaller CALLER_SSL_CLIENT = new GSSCaller();
-    public static final GSSCaller CALLER_SSL_SERVER = new GSSCaller();
+    public static final GSSCaller CALLER_UNKNOWN = new GSSCaller("UNKNOWN");
+    public static final GSSCaller CALLER_INITIATE = new GSSCaller("INITIATE");
+    public static final GSSCaller CALLER_ACCEPT = new GSSCaller("ACCEPT");
+    public static final GSSCaller CALLER_SSL_CLIENT = new GSSCaller("SSL_CLIENT");
+    public static final GSSCaller CALLER_SSL_SERVER = new GSSCaller("SSL_SERVER");
+
+    private String name;
+    GSSCaller(String s) {
+        name = s;
+    }
+    @Override
+    public String toString() {
+        return "GSSCaller{" + name + '}';
+    }
 }
 
--- a/src/share/classes/sun/security/jgss/GSSCredentialImpl.java	Tue Nov 06 18:41:01 2012 -0800
+++ b/src/share/classes/sun/security/jgss/GSSCredentialImpl.java	Wed Nov 07 14:13:01 2012 +0800
@@ -28,8 +28,9 @@
 import org.ietf.jgss.*;
 import sun.security.jgss.spi.*;
 import java.util.*;
+import com.sun.security.jgss.*;
 
-public class GSSCredentialImpl implements GSSCredential {
+public class GSSCredentialImpl implements ExtendedGSSCredential {
 
     private GSSManagerImpl gssManager = null;
     private boolean destroyed = false;
@@ -122,6 +123,19 @@
         }
     }
 
+    public GSSCredential impersonate(GSSName name) throws GSSException {
+        if (destroyed) {
+            throw new IllegalStateException("This credential is " +
+                                        "no longer valid");
+        }
+        Oid mech = tempCred.getMechanism();
+        GSSNameSpi nameElement = (name == null ? null :
+                                  ((GSSNameImpl)name).getElement(mech));
+        GSSCredentialSpi cred = tempCred.impersonate(nameElement);
+        return (cred == null ?
+            null : new GSSCredentialImpl(gssManager, cred));
+    }
+
     public GSSName getName() throws GSSException {
         if (destroyed) {
             throw new IllegalStateException("This credential is " +
--- a/src/share/classes/sun/security/jgss/HttpCaller.java	Tue Nov 06 18:41:01 2012 -0800
+++ b/src/share/classes/sun/security/jgss/HttpCaller.java	Wed Nov 07 14:13:01 2012 +0800
@@ -35,6 +35,7 @@
     final private HttpCallerInfo hci;
 
     public HttpCaller(HttpCallerInfo hci) {
+        super("HTTP_CLIENT");
         this.hci = hci;
     }
 
--- a/src/share/classes/sun/security/jgss/krb5/Krb5AcceptCredential.java	Tue Nov 06 18:41:01 2012 -0800
+++ b/src/share/classes/sun/security/jgss/krb5/Krb5AcceptCredential.java	Wed Nov 07 14:13:01 2012 +0800
@@ -25,6 +25,7 @@
 
 package sun.security.jgss.krb5;
 
+import java.io.IOException;
 import org.ietf.jgss.*;
 import sun.security.jgss.GSSCaller;
 import sun.security.jgss.spi.*;
@@ -177,4 +178,21 @@
     public void destroy() throws DestroyFailedException {
         screds.destroy();
     }
+
+    /**
+     * Impersonation is only available on the initiator side. The
+     * service must starts as an initiator to get an initial TGT to complete
+     * the S4U2self protocol.
+     */
+    @Override
+    public GSSCredentialSpi impersonate(GSSNameSpi name) throws GSSException {
+        Credentials cred = screds.getInitCred();
+        if (cred != null) {
+            return Krb5InitCredential.getInstance(this.name, cred)
+                    .impersonate(name);
+        } else {
+            throw new GSSException(GSSException.FAILURE, -1,
+                "Only an initiate credentials can impersonate");
+        }
+    }
 }
--- a/src/share/classes/sun/security/jgss/krb5/Krb5Context.java	Tue Nov 06 18:41:01 2012 -0800
+++ b/src/share/classes/sun/security/jgss/krb5/Krb5Context.java	Wed Nov 07 14:13:01 2012 +0800
@@ -45,6 +45,7 @@
 import javax.crypto.Cipher;
 import javax.security.auth.Subject;
 import javax.security.auth.kerberos.*;
+import sun.security.krb5.internal.Ticket;
 
 /**
  * Implements the mechanism specific context class for the Kerberos v5
@@ -76,7 +77,7 @@
      * values.
      */
 
-    private boolean credDelegState  = false;
+    private boolean credDelegState  = false;    // now only useful at client
     private boolean mutualAuthState  = true;
     private boolean replayDetState  = true;
     private boolean sequenceDetState  = true;
@@ -84,6 +85,8 @@
     private boolean integState  = true;
     private boolean delegPolicyState = false;
 
+    private boolean isConstrainedDelegationTried = false;
+
     private int mySeqNumber;
     private int peerSeqNumber;
     private int keySrc;
@@ -113,13 +116,11 @@
     private Krb5CredElement myCred;
     private Krb5CredElement delegatedCred; // Set only on acceptor side
 
-    /* DESCipher instance used by the corresponding GSSContext */
-    private Cipher desCipher = null;
-
     // XXX See if the required info from these can be extracted and
     // stored elsewhere
     private Credentials serviceCreds;
     private KrbApReq apReq;
+    Ticket serviceTicket;
     final private GSSCaller caller;
     private static final boolean DEBUG = Krb5Util.DEBUG;
 
@@ -248,7 +249,14 @@
      * Is credential delegation enabled?
      */
     public final boolean getCredDelegState() {
-        return credDelegState;
+        if (isInitiator()) {
+            return credDelegState;
+        } else {
+            // Server side deleg state is not flagged by credDelegState.
+            // It can use constrained delegation.
+            tryConstrainedDelegation();
+            return delegatedCred != null;
+        }
     }
 
     /**
@@ -498,7 +506,8 @@
      * Returns the delegated credential for the context. This
      * is an optional feature of contexts which not all
      * mechanisms will support. A context can be requested to
-     * support credential delegation by using the <b>CRED_DELEG</b>.
+     * support credential delegation by using the <b>CRED_DELEG</b>,
+     * or it can request for a constrained delegation.
      * This is only valid on the acceptor side of the context.
      * @return GSSCredentialSpi object for the delegated credential
      * @exception GSSException
@@ -507,11 +516,41 @@
     public final GSSCredentialSpi getDelegCred() throws GSSException {
         if (state != STATE_IN_PROCESS && state != STATE_DONE)
             throw new GSSException(GSSException.NO_CONTEXT);
-        if (delegatedCred == null)
+        if (isInitiator()) {
             throw new GSSException(GSSException.NO_CRED);
+        }
+        tryConstrainedDelegation();
+        if (delegatedCred == null) {
+            throw new GSSException(GSSException.NO_CRED);
+        }
         return delegatedCred;
     }
 
+    private void tryConstrainedDelegation() {
+        if (state != STATE_IN_PROCESS && state != STATE_DONE) {
+            return;
+        }
+        // We will only try constrained delegation once (if necessary).
+        if (!isConstrainedDelegationTried) {
+            if (delegatedCred == null) {
+                if (DEBUG) {
+                    System.out.println(">>> Constrained deleg from " + caller);
+                }
+                // The constrained delegation part. The acceptor needs to have
+                // isInitiator=true in order to get a TGT, either earlier at
+                // logon stage, if useSubjectCredsOnly, or now.
+                try {
+                    delegatedCred = new Krb5ProxyCredential(
+                        Krb5InitCredential.getInstance(
+                            GSSCaller.CALLER_ACCEPT, myName, lifetime),
+                        peerName, serviceTicket);
+                } catch (GSSException gsse) {
+                    // OK, delegatedCred is null then
+                }
+            }
+            isConstrainedDelegationTried = true;
+        }
+    }
     /**
      * Tests if this is the initiator side of the context.
      *
@@ -577,8 +616,15 @@
                                            "No TGT available");
                     }
                     myName = (Krb5NameElement) myCred.getName();
-                    Credentials tgt =
-                    ((Krb5InitCredential) myCred).getKrb5Credentials();
+                    Credentials tgt;
+                    final Krb5ProxyCredential second;
+                    if (myCred instanceof Krb5InitCredential) {
+                        second = null;
+                        tgt = ((Krb5InitCredential) myCred).getKrb5Credentials();
+                    } else {
+                        second = (Krb5ProxyCredential) myCred;
+                        tgt = second.self.getKrb5Credentials();
+                    }
 
                     checkPermission(peerName.getKrb5PrincipalName().getName(),
                                     "initiate");
@@ -607,7 +653,9 @@
                                         GSSCaller.CALLER_UNKNOWN,
                                         // since it's useSubjectCredsOnly here,
                                         // don't worry about the null
-                                        myName.getKrb5PrincipalName().getName(),
+                                        second == null ?
+                                            myName.getKrb5PrincipalName().getName():
+                                            second.getName().getKrb5PrincipalName().getName(),
                                         peerName.getKrb5PrincipalName().getName(),
                                         acc);
                                 }});
@@ -638,9 +686,17 @@
                                                "the subject");
                         }
                         // Get Service ticket using the Kerberos protocols
-                        serviceCreds = Credentials.acquireServiceCreds(
+                        if (second == null) {
+                            serviceCreds = Credentials.acquireServiceCreds(
                                      peerName.getKrb5PrincipalName().getName(),
                                      tgt);
+                        } else {
+                            serviceCreds = Credentials.acquireS4U2proxyCreds(
+                                    peerName.getKrb5PrincipalName().getName(),
+                                    second.tkt,
+                                    second.getName().getKrb5PrincipalName(),
+                                    tgt);
+                        }
                         if (GSSUtil.useSubjectCredsOnly(caller)) {
                             final Subject subject =
                                 AccessController.doPrivileged(
@@ -776,6 +832,7 @@
                         retVal = new AcceptSecContextToken(this,
                                           token.getKrbApReq()).encode();
                 }
+                serviceTicket = token.getKrbApReq().getCreds().getTicket();
                 myCred = null;
                 state = STATE_DONE;
             } else  {
@@ -802,8 +859,6 @@
         return retVal;
     }
 
-
-
     /**
      * Queries the context for largest data size to accomodate
      * the specified protection and be <= maxTokSize.
--- a/src/share/classes/sun/security/jgss/krb5/Krb5InitCredential.java	Tue Nov 06 18:41:01 2012 -0800
+++ b/src/share/classes/sun/security/jgss/krb5/Krb5InitCredential.java	Wed Nov 07 14:13:01 2012 +0800
@@ -309,8 +309,7 @@
                                                  int initLifetime)
         throws GSSException {
 
-        String realm = null;
-        final String clientPrincipal, tgsPrincipal = null;
+        final String clientPrincipal;
 
         /*
          * Find the TGT for the realm that the client is in. If the client
@@ -318,20 +317,8 @@
          */
         if (name != null) {
             clientPrincipal = (name.getKrb5PrincipalName()).getName();
-            realm = (name.getKrb5PrincipalName()).getRealmAsString();
         } else {
             clientPrincipal = null;
-            try {
-                Config config = Config.getInstance();
-                realm = config.getDefaultRealm();
-            } catch (KrbException e) {
-                GSSException ge =
-                        new GSSException(GSSException.NO_CRED, -1,
-                            "Attempt to obtain INITIATE credentials failed!" +
-                            " (" + e.getMessage() + ")");
-                ge.initCause(e);
-                throw ge;
-            }
         }
 
         final AccessControlContext acc = AccessController.getContext();
@@ -343,9 +330,11 @@
             return AccessController.doPrivileged(
                 new PrivilegedExceptionAction<KerberosTicket>() {
                 public KerberosTicket run() throws Exception {
+                    // It's OK to use null as serverPrincipal. TGT is almost
+                    // the first ticket for a principal and we use list.
                     return Krb5Util.getTicket(
                         realCaller,
-                        clientPrincipal, tgsPrincipal, acc);
+                        clientPrincipal, null, acc);
                         }});
         } catch (PrivilegedActionException e) {
             GSSException ge =
@@ -356,4 +345,20 @@
             throw ge;
         }
     }
+
+    @Override
+    public GSSCredentialSpi impersonate(GSSNameSpi name) throws GSSException {
+        try {
+            Krb5NameElement kname = (Krb5NameElement)name;
+            Credentials newCred = Credentials.acquireS4U2selfCreds(
+                    kname.getKrb5PrincipalName(), krb5Credentials);
+            return new Krb5ProxyCredential(this, kname, newCred.getTicket());
+        } catch (IOException | KrbException ke) {
+            GSSException ge =
+                new GSSException(GSSException.FAILURE, -1,
+                    "Attempt to obtain S4U2self credentials failed!");
+            ge.initCause(ke);
+            throw ge;
+        }
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/share/classes/sun/security/jgss/krb5/Krb5ProxyCredential.java	Wed Nov 07 14:13:01 2012 +0800
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2012, Oracle and/or its affiliates. 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.security.jgss.krb5;
+
+import org.ietf.jgss.*;
+import sun.security.jgss.spi.*;
+import java.util.Date;
+import sun.security.krb5.internal.Ticket;
+
+/**
+ * Implements the krb5 proxy credential element used in constrained
+ * delegation. It is used in both impersonation (where there is no Kerberos 5
+ * communication between the middle server and the client) and normal
+ * constrained delegation (where there is, but client has not called
+ * requestCredDeleg(true)).
+ * @since 1.8
+ */
+
+public class Krb5ProxyCredential
+    implements Krb5CredElement {
+
+    public final Krb5InitCredential self;   // the middle server
+    private final Krb5NameElement client;     // the client
+
+    // The ticket with cname=client and sname=self. This can be a normal
+    // service ticket or an S4U2self ticket.
+    public final Ticket tkt;
+
+    Krb5ProxyCredential(Krb5InitCredential self, Krb5NameElement client,
+            Ticket tkt) {
+        this.self = self;
+        this.tkt = tkt;
+        this.client = client;
+    }
+
+    // The client name behind the proxy
+    @Override
+    public final Krb5NameElement getName() throws GSSException {
+        return client;
+    }
+
+    @Override
+    public int getInitLifetime() throws GSSException {
+        // endTime of tkt is not used by KDC, and it's also not
+        // available in the case of kerberos constr deleg
+        return self.getInitLifetime();
+    }
+
+    @Override
+    public int getAcceptLifetime() throws GSSException {
+        return 0;
+    }
+
+    @Override
+    public boolean isInitiatorCredential() throws GSSException {
+        return true;
+    }
+
+    @Override
+    public boolean isAcceptorCredential() throws GSSException {
+        return false;
+    }
+
+    @Override
+    public final Oid getMechanism() {
+        return Krb5MechFactory.GSS_KRB5_MECH_OID;
+    }
+
+    @Override
+    public final java.security.Provider getProvider() {
+        return Krb5MechFactory.PROVIDER;
+    }
+
+    @Override
+    public void dispose() throws GSSException {
+        try {
+            self.destroy();
+        } catch (javax.security.auth.DestroyFailedException e) {
+            GSSException gssException =
+                new GSSException(GSSException.FAILURE, -1,
+                 "Could not destroy credentials - " + e.getMessage());
+            gssException.initCause(e);
+        }
+    }
+
+    @Override
+    public GSSCredentialSpi impersonate(GSSNameSpi name) throws GSSException {
+        // Cannot impersonate multiple levels without the impersonatee's TGT.
+        throw new GSSException(GSSException.FAILURE, -1,
+                "Only an initiate credentials can impersonate");
+    }
+}
--- a/src/share/classes/sun/security/jgss/krb5/Krb5Util.java	Tue Nov 06 18:41:01 2012 -0800
+++ b/src/share/classes/sun/security/jgss/krb5/Krb5Util.java	Wed Nov 07 14:13:01 2012 +0800
@@ -206,7 +206,7 @@
      * identity, which can be:
      *   1. Some KerberosKeys (generated from password)
      *   2. A KeyTab (for a typical service)
-     *   3. A TGT (for a user2user service. Not supported yet)
+     *   3. A TGT (for S4U2proxy extension)
      *
      * Note that some creds can coexist. For example, a user2user service
      * can use its keytab (or keys) if the client can successfully obtain a
@@ -219,7 +219,7 @@
         private List<KeyTab> ktabs;
         private List<KerberosKey> kk;
         private Subject subj;
-        //private KerberosTicket tgt;   // user2user, not supported yet
+        private KerberosTicket tgt;
 
         private static ServiceCreds getInstance(
                 Subject subj, String serverPrincipal) {
@@ -255,6 +255,8 @@
                         subj, null, null, KeyTab.class);
             sc.kk = SubjectComber.findMany(
                         subj, serverPrincipal, null, KerberosKey.class);
+            sc.tgt = SubjectComber.find(subj, null, null, KerberosTicket.class);
+
             if (sc.ktabs.isEmpty() && sc.kk.isEmpty()) {
                 return null;
             }
@@ -310,10 +312,22 @@
             return ekeys;
         }
 
+        public Credentials getInitCred() {
+            if (tgt == null) {
+                return null;
+            }
+            try {
+                return ticketToCreds(tgt);
+            } catch (KrbException | IOException e) {
+                return null;
+            }
+        }
+
         public void destroy() {
             kp = null;
             ktabs = null;
             kk = null;
+            tgt = null;
         }
     }
     /**
@@ -357,7 +371,7 @@
     };
 
     public static Credentials ticketToCreds(KerberosTicket kerbTicket)
-        throws KrbException, IOException {
+            throws KrbException, IOException {
         return new Credentials(
             kerbTicket.getEncoded(),
             kerbTicket.getClient().getName(),
--- a/src/share/classes/sun/security/jgss/spi/GSSCredentialSpi.java	Tue Nov 06 18:41:01 2012 -0800
+++ b/src/share/classes/sun/security/jgss/spi/GSSCredentialSpi.java	Wed Nov 07 14:13:01 2012 +0800
@@ -96,4 +96,13 @@
      * @exception GSSException may be thrown
      */
     public Oid getMechanism();
+
+    /**
+     * Impersonates another client.
+     *
+     * @param name the client to impersonate
+     * @return the new credential
+     * @exception GSSException may be thrown
+     */
+    public GSSCredentialSpi impersonate(GSSNameSpi name) throws GSSException;
 }
--- a/src/share/classes/sun/security/jgss/spnego/SpNegoContext.java	Tue Nov 06 18:41:01 2012 -0800
+++ b/src/share/classes/sun/security/jgss/spnego/SpNegoContext.java	Wed Nov 07 14:13:01 2012 +0800
@@ -1059,6 +1059,9 @@
         if (mechContext != null) {
             GSSCredentialImpl delegCred =
                         (GSSCredentialImpl)mechContext.getDelegCred();
+            if (delegCred == null) {
+                return null;
+            }
             // determine delegated cred element usage
             boolean initiate = false;
             if (delegCred.getUsage() == GSSCredential.INITIATE_ONLY) {
--- a/src/share/classes/sun/security/jgss/spnego/SpNegoCredElement.java	Tue Nov 06 18:41:01 2012 -0800
+++ b/src/share/classes/sun/security/jgss/spnego/SpNegoCredElement.java	Wed Nov 07 14:13:01 2012 +0800
@@ -88,4 +88,9 @@
     public Oid getMechanism() {
         return GSSUtil.GSS_SPNEGO_MECH_OID;
     }
+
+    @Override
+    public GSSCredentialSpi impersonate(GSSNameSpi name) throws GSSException {
+        return cred.impersonate(name);
+    }
 }
--- a/src/share/classes/sun/security/jgss/wrapper/GSSCredElement.java	Tue Nov 06 18:41:01 2012 -0800
+++ b/src/share/classes/sun/security/jgss/wrapper/GSSCredElement.java	Wed Nov 07 14:13:01 2012 +0800
@@ -28,6 +28,7 @@
 import java.security.Provider;
 import sun.security.jgss.GSSUtil;
 import sun.security.jgss.spi.GSSCredentialSpi;
+import sun.security.jgss.spi.GSSNameSpi;
 
 /**
  * This class is essentially a wrapper class for the gss_cred_id_t
@@ -132,4 +133,10 @@
     protected void finalize() throws Throwable {
         dispose();
     }
+
+    @Override
+    public GSSCredentialSpi impersonate(GSSNameSpi name) throws GSSException {
+        throw new GSSException(GSSException.FAILURE, -1,
+                "Not supported yet");
+    }
 }
--- a/src/share/classes/sun/security/krb5/Credentials.java	Tue Nov 06 18:41:01 2012 -0800
+++ b/src/share/classes/sun/security/krb5/Credentials.java	Wed Nov 07 14:13:01 2012 +0800
@@ -449,6 +449,18 @@
         return CredentialsUtil.acquireServiceCreds(service, ccreds);
     }
 
+    public static Credentials acquireS4U2selfCreds(PrincipalName user,
+            Credentials ccreds) throws KrbException, IOException {
+        return CredentialsUtil.acquireS4U2selfCreds(user, ccreds);
+    }
+
+    public static Credentials acquireS4U2proxyCreds(String service,
+            Ticket second, PrincipalName client, Credentials ccreds)
+        throws KrbException, IOException {
+        return CredentialsUtil.acquireS4U2proxyCreds(
+                service, second, client, ccreds);
+    }
+
     public CredentialsCache getCache() {
         return cache;
     }
@@ -490,18 +502,19 @@
 
     public String toString() {
         StringBuffer buffer = new StringBuffer("Credentials:");
-        buffer.append("\nclient=").append(client);
-        buffer.append("\nserver=").append(server);
+        buffer.append(    "\n      client=").append(client);
+        buffer.append(    "\n      server=").append(server);
         if (authTime != null) {
-            buffer.append("\nauthTime=").append(authTime);
+            buffer.append("\n    authTime=").append(authTime);
         }
         if (startTime != null) {
-            buffer.append("\nstartTime=").append(startTime);
+            buffer.append("\n   startTime=").append(startTime);
         }
-        buffer.append("\nendTime=").append(endTime);
-        buffer.append("\nrenewTill=").append(renewTill);
-        buffer.append("\nflags: ").append(flags);
-        buffer.append("\nEType (int): ").append(key.getEType());
+        buffer.append(    "\n     endTime=").append(endTime);
+        buffer.append(    "\n   renewTill=").append(renewTill);
+        buffer.append(    "\n       flags=").append(flags);
+        buffer.append(    "\nEType (skey)=").append(key.getEType());
+        buffer.append(    "\n   (tkt key)=").append(ticket.encPart.eType);
         return buffer.toString();
     }
 
--- a/src/share/classes/sun/security/krb5/EncryptedData.java	Tue Nov 06 18:41:01 2012 -0800
+++ b/src/share/classes/sun/security/krb5/EncryptedData.java	Wed Nov 07 14:13:01 2012 +0800
@@ -160,8 +160,6 @@
         kvno = key.getKeyVersionNumber();
     }
     */
-
-    // currently destructive on cipher
     public byte[] decrypt(
                           EncryptionKey key, int usage)
         throws KdcErrException, KrbApErrException, KrbCryptoException {
@@ -175,7 +173,9 @@
 
             EType etypeEngine = EType.getInstance(eType);
             plain = etypeEngine.decrypt(cipher, key.getBytes(), usage);
-            cipher = null;
+            // The service ticket will be used in S4U2proxy request. Therefore
+            // the raw ticket is still needed.
+            //cipher = null;
             return etypeEngine.decryptedData(plain);
         }
 
--- a/src/share/classes/sun/security/krb5/KrbApReq.java	Tue Nov 06 18:41:01 2012 -0800
+++ b/src/share/classes/sun/security/krb5/KrbApReq.java	Wed Nov 07 14:13:01 2012 +0800
@@ -287,8 +287,9 @@
         cusec = authenticator.cusec;
         authenticator.ctime.setMicroSeconds(authenticator.cusec);
 
-        if (!authenticator.cname.equals(enc_ticketPart.cname))
+        if (!authenticator.cname.equals(enc_ticketPart.cname)) {
             throw new KrbApErrException(Krb5.KRB_AP_ERR_BADMATCH);
+        }
 
         KerberosTime currTime = new KerberosTime(KerberosTime.NOW);
         if (!authenticator.ctime.inClockSkew(currTime))
--- a/src/share/classes/sun/security/krb5/KrbKdcRep.java	Tue Nov 06 18:41:01 2012 -0800
+++ b/src/share/classes/sun/security/krb5/KrbKdcRep.java	Wed Nov 07 14:13:01 2012 +0800
@@ -64,7 +64,12 @@
 
         for (int i = 1; i < 6; i++) {
             if (req.reqBody.kdcOptions.get(i) !=
-                rep.encKDCRepPart.flags.get(i)) {
+                   rep.encKDCRepPart.flags.get(i)) {
+                if (Krb5.DEBUG) {
+                    System.out.println("> KrbKdcRep.check: at #" + i
+                            + ". request for " + req.reqBody.kdcOptions.get(i)
+                            + ", received " + rep.encKDCRepPart.flags.get(i));
+                }
                 throw new KrbApErrException(Krb5.KRB_AP_ERR_MODIFIED);
             }
         }
--- a/src/share/classes/sun/security/krb5/KrbTgsRep.java	Tue Nov 06 18:41:01 2012 -0800
+++ b/src/share/classes/sun/security/krb5/KrbTgsRep.java	Wed Nov 07 14:13:01 2012 +0800
@@ -87,7 +87,7 @@
         check(false, req, rep);
 
         this.creds = new Credentials(rep.ticket,
-                                req.reqBody.cname,
+                                rep.cname,
                                 rep.ticket.sname,
                                 enc_part.key,
                                 enc_part.flags,
--- a/src/share/classes/sun/security/krb5/KrbTgsReq.java	Tue Nov 06 18:41:01 2012 -0800
+++ b/src/share/classes/sun/security/krb5/KrbTgsReq.java	Wed Nov 07 14:13:01 2012 +0800
@@ -35,6 +35,7 @@
 import sun.security.krb5.internal.crypto.*;
 import java.io.IOException;
 import java.net.UnknownHostException;
+import java.util.Arrays;
 
 /**
  * This class encapsulates a Kerberos TGS-REQ that is sent from the
@@ -55,7 +56,7 @@
     private byte[] obuf;
     private byte[] ibuf;
 
-     // Used in CredentialsUtil
+    // Used in CredentialsUtil
     public KrbTgsReq(Credentials asCreds,
                      PrincipalName sname)
         throws KrbException, IOException {
@@ -72,6 +73,45 @@
             null); // EncryptionKey subSessionKey
     }
 
+    // S4U2proxy
+    public KrbTgsReq(Credentials asCreds,
+                     Ticket second,
+                     PrincipalName sname)
+            throws KrbException, IOException {
+        this(KDCOptions.with(KDCOptions.CNAME_IN_ADDL_TKT,
+                KDCOptions.FORWARDABLE),
+            asCreds,
+            sname,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            new Ticket[] {second}, // the service ticket
+            null);
+    }
+
+    // S4U2user
+    public KrbTgsReq(Credentials asCreds,
+                     PrincipalName sname,
+                     PAData extraPA)
+        throws KrbException, IOException {
+        this(KDCOptions.with(KDCOptions.FORWARDABLE),
+            asCreds,
+            asCreds.getClient(),
+            sname,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            extraPA); // the PA-FOR-USER
+    }
+
     // Called by Credentials, KrbCred
     KrbTgsReq(
             KDCOptions options,
@@ -85,14 +125,42 @@
             AuthorizationData authorizationData,
             Ticket[] additionalTickets,
             EncryptionKey subKey) throws KrbException, IOException {
+        this(options, asCreds, asCreds.getClient(), sname,
+                from, till, rtime, eTypes, addresses,
+                authorizationData, additionalTickets, subKey, null);
+    }
 
-        princName = asCreds.client;
+    private KrbTgsReq(
+            KDCOptions options,
+            Credentials asCreds,
+            PrincipalName cname,
+            PrincipalName sname,
+            KerberosTime from,
+            KerberosTime till,
+            KerberosTime rtime,
+            int[] eTypes,
+            HostAddresses addresses,
+            AuthorizationData authorizationData,
+            Ticket[] additionalTickets,
+            EncryptionKey subKey,
+            PAData extraPA) throws KrbException, IOException {
+
+        princName = cname;
         servName = sname;
         ctime = new KerberosTime(KerberosTime.NOW);
 
 
         // check if they are valid arguments. The optional fields
         // should be  consistent with settings in KDCOptions.
+
+        // TODO: Is this necessary? If the TGT is not FORWARDABLE,
+        // you can still request for a FORWARDABLE ticket, just the
+        // KDC will give you a non-FORWARDABLE one. Even if you
+        // cannot use the ticket expected, it still contains info.
+        // This means there will be problem later. We already have
+        // flags check in KrbTgsRep. Of course, sometimes the KDC
+        // will not issue the ticket at all.
+
         if (options.get(KDCOptions.FORWARDABLE) &&
                 (!(asCreds.flags.get(Krb5.TKT_OPTS_FORWARDABLE)))) {
             throw new KrbException(Krb5.KRB_AP_ERR_REQ_OPTIONS);
@@ -130,13 +198,13 @@
         } else {
             if (rtime != null)  rtime = null;
         }
-        if (options.get(KDCOptions.ENC_TKT_IN_SKEY)) {
+        if (options.get(KDCOptions.ENC_TKT_IN_SKEY) || options.get(KDCOptions.CNAME_IN_ADDL_TKT)) {
             if (additionalTickets == null)
                 throw new KrbException(Krb5.KRB_AP_ERR_REQ_OPTIONS);
             // in TGS_REQ there could be more than one additional
             // tickets,  but in file-based credential cache,
             // there is only one additional ticket field.
-                secondTicket = additionalTickets[0];
+            secondTicket = additionalTickets[0];
         } else {
             if (additionalTickets != null)
                 additionalTickets = null;
@@ -156,7 +224,8 @@
                 addresses,
                 authorizationData,
                 additionalTickets,
-                subKey);
+                subKey,
+                extraPA);
         obuf = tgsReqMessg.asn1Encode();
 
         // XXX We need to revisit this to see if can't move it
@@ -221,7 +290,8 @@
                          HostAddresses addresses,
                          AuthorizationData authorizationData,
                          Ticket[] additionalTickets,
-                         EncryptionKey subKey)
+                         EncryptionKey subKey,
+                         PAData extraPA)
         throws Asn1Exception, IOException, KdcErrException, KrbApErrException,
                UnknownHostException, KrbCryptoException {
         KerberosTime req_till = null;
@@ -318,10 +388,12 @@
                                          null,
                                          null).getMessage();
 
-        PAData[] tgsPAData = new PAData[1];
-        tgsPAData[0] = new PAData(Krb5.PA_TGS_REQ, tgs_ap_req);
-
-        return new TGSReq(tgsPAData, reqBody);
+        PAData tgsPAData = new PAData(Krb5.PA_TGS_REQ, tgs_ap_req);
+        return new TGSReq(
+                extraPA != null ?
+                    new PAData[] {extraPA, tgsPAData } :
+                    new PAData[] {tgsPAData},
+                reqBody);
     }
 
     TGSReq getMessage() {
--- a/src/share/classes/sun/security/krb5/internal/CredentialsUtil.java	Tue Nov 06 18:41:01 2012 -0800
+++ b/src/share/classes/sun/security/krb5/internal/CredentialsUtil.java	Wed Nov 07 14:13:01 2012 +0800
@@ -32,17 +32,7 @@
 package sun.security.krb5.internal;
 
 import sun.security.krb5.*;
-import sun.security.krb5.internal.ccache.CredentialsCache;
-import java.util.StringTokenizer;
-import sun.security.krb5.internal.ktab.*;
-import java.io.File;
 import java.io.IOException;
-import java.util.Date;
-import java.util.Vector;
-import java.io.BufferedReader;
-import java.io.InputStreamReader;
-import java.io.UnsupportedEncodingException;
-import java.net.InetAddress;
 
 /**
  * This class is a utility that contains much of the TGS-Exchange
@@ -53,77 +43,158 @@
 
     private static boolean DEBUG = sun.security.krb5.internal.Krb5.DEBUG;
 
-   /**
-    * Acquires credentials for a specified service using initial credential. Wh
-en the service has a different realm
-    * from the initial credential, we do cross-realm authentication - first, we
- use the current credential to get
-    * a cross-realm credential from the local KDC, then use that cross-realm cr
-edential to request service credential
-    * from the foreigh KDC.
-    *
-    * @param service the name of service principal using format components@real
-m
-    * @param ccreds client's initial credential.
-    * @exception Exception general exception will be thrown when any error occu
-rs.
-    * @return a <code>Credentials</code> object.
-    */
+    /**
+     * Used by a middle server to acquire credentials on behalf of a
+     * client to itself using the S4U2self extension.
+     * @param client the client to impersonate
+     * @param ccreds the TGT of the middle service
+     * @return the new creds (cname=client, sname=middle)
+     */
+    public static Credentials acquireS4U2selfCreds(PrincipalName client,
+            Credentials ccreds) throws KrbException, IOException {
+        String uRealm = client.getRealmString();
+        String localRealm = ccreds.getClient().getRealmString();
+        if (!uRealm.equals(localRealm)) {
+            // TODO: we do not support kerberos referral now
+            throw new KrbException("Cross realm impersonation not supported");
+        }
+        KrbTgsReq req = new KrbTgsReq(
+                ccreds,
+                ccreds.getClient(),
+                new PAData(Krb5.PA_FOR_USER,
+                    new PAForUserEnc(client,
+                        ccreds.getSessionKey()).asn1Encode()));
+        Credentials creds = req.sendAndGetCreds();
+        if (!creds.getClient().equals(client)) {
+            throw new KrbException("S4U2self request not honored by KDC");
+        }
+        return creds;
+    }
+
+    /**
+     * Used by a middle server to acquire a service ticket to a backend
+     * server using the S4U2proxy extension.
+     * @param backend the name of the backend service
+     * @param second the client's service ticket to the middle server
+     * @param ccreds the TGT of the middle server
+     * @return the creds (cname=client, sname=backend)
+     */
+    public static Credentials acquireS4U2proxyCreds(
+                String backend, Ticket second,
+                PrincipalName client, Credentials ccreds)
+            throws KrbException, IOException {
+        KrbTgsReq req = new KrbTgsReq(
+                ccreds,
+                second,
+                new PrincipalName(backend));
+        Credentials creds = req.sendAndGetCreds();
+        if (!creds.getClient().equals(client)) {
+            throw new KrbException("S4U2proxy request not honored by KDC");
+        }
+        return creds;
+    }
+
+    /**
+     * Acquires credentials for a specified service using initial
+     * credential. When the service has a different realm from the initial
+     * credential, we do cross-realm authentication - first, we use the
+     * current credential to get a cross-realm credential from the local KDC,
+     * then use that cross-realm credential to request service credential
+     * from the foreign KDC.
+     *
+     * @param service the name of service principal
+     * @param ccreds client's initial credential
+     */
     public static Credentials acquireServiceCreds(
                 String service, Credentials ccreds)
-    throws KrbException, IOException {
+            throws KrbException, IOException {
         PrincipalName sname = new PrincipalName(service);
         String serviceRealm = sname.getRealmString();
         String localRealm = ccreds.getClient().getRealmString();
 
-        /*
-          if (!localRealm.equalsIgnoreCase(serviceRealm)) { //do cross-realm auth entication
-          if (DEBUG) {
-          System.out.println(">>>DEBUG: Credentails request cross realm ticket for " + "krbtgt/" + serviceRealm + "@" + localRealm);
-          }
-          Credentials crossCreds = serviceCreds(new ServiceName("krbtgt/" + serviceRealm + "@" + localRealm), ccreds);
-          if (DEBUG) {
-          printDebug(crossCreds);
-          }
-          Credentials result = serviceCreds(sname, crossCreds);
-          if (DEBUG) {
-          printDebug(result);
-          }
-          return result;
-          }
-          else return serviceCreds(sname, ccreds);
-        */
-
-        if (localRealm.equals(serviceRealm))
-        {
-            if (DEBUG)
-                System.out.println(">>> Credentials acquireServiceCreds: same realm");
+        if (localRealm.equals(serviceRealm)) {
+            if (DEBUG) {
+                System.out.println(
+                        ">>> Credentials acquireServiceCreds: same realm");
+            }
             return serviceCreds(sname, ccreds);
         }
+        Credentials theCreds = null;
+
+        boolean[] okAsDelegate = new boolean[1];
+        Credentials theTgt = getTGTforRealm(localRealm, serviceRealm,
+                ccreds, okAsDelegate);
+        if (theTgt != null) {
+            if (DEBUG) {
+                System.out.println(">>> Credentials acquireServiceCreds: "
+                        + "got right tgt");
+                System.out.println(">>> Credentials acquireServiceCreds: "
+                        + "obtaining service creds for " + sname);
+            }
+
+            try {
+                theCreds = serviceCreds(sname, theTgt);
+            } catch (Exception exc) {
+                if (DEBUG) {
+                    System.out.println(exc);
+                }
+                theCreds = null;
+            }
+        }
+
+        if (theCreds != null) {
+            if (DEBUG) {
+                System.out.println(">>> Credentials acquireServiceCreds: "
+                        + "returning creds:");
+                Credentials.printDebug(theCreds);
+            }
+            if (!okAsDelegate[0]) {
+                theCreds.resetDelegate();
+            }
+            return theCreds;
+        }
+        throw new KrbApErrException(Krb5.KRB_AP_ERR_GEN_CRED,
+                                    "No service creds");
+    }
+
+    /**
+     * Gets a TGT to another realm
+     * @param localRealm this realm
+     * @param serviceRealm the other realm
+     * @param ccreds TGT in this realm
+     * @param okAsDelegate an [out] argument to receive the okAsDelegate
+     * property. True only if all realms allow delegation.
+     * @return the TGT for the other realm, null if cannot find a path
+     * @throws KrbException if something goes wrong
+     */
+    private static Credentials getTGTforRealm(String localRealm,
+            String serviceRealm, Credentials ccreds, boolean[] okAsDelegate)
+            throws KrbException {
 
         // Get a list of realms to traverse
         String[] realms = Realm.getRealmsList(localRealm, serviceRealm);
-        boolean okAsDelegate = true;
 
-        if (realms == null || realms.length == 0)
-        {
-            if (DEBUG)
-                System.out.println(">>> Credentials acquireServiceCreds: no realms list");
+        if (realms == null || realms.length == 0) {
+            if (DEBUG) {
+                System.out.println(
+                        ">>> Credentials acquireServiceCreds: no realms list");
+            }
             return null;
         }
 
         int i = 0, k = 0;
         Credentials cTgt = null, newTgt = null, theTgt = null;
         PrincipalName tempService = null;
-        String realm = null, newTgtRealm = null, theTgtRealm = null;
+        String newTgtRealm = null;
 
-        for (cTgt = ccreds, i = 0; i < realms.length;)
-        {
+        okAsDelegate[0] = true;
+        for (cTgt = ccreds, i = 0; i < realms.length;) {
             tempService = PrincipalName.tgsService(serviceRealm, realms[i]);
 
-            if (DEBUG)
-            {
-                System.out.println(">>> Credentials acquireServiceCreds: main loop: [" + i +"] tempService=" + tempService);
+            if (DEBUG) {
+                System.out.println(
+                        ">>> Credentials acquireServiceCreds: main loop: ["
+                        + i +"] tempService=" + tempService);
             }
 
             try {
@@ -132,11 +203,10 @@
                 newTgt = null;
             }
 
-            if (newTgt == null)
-            {
-                if (DEBUG)
-                {
-                    System.out.println(">>> Credentials acquireServiceCreds: no tgt; searching backwards");
+            if (newTgt == null) {
+                if (DEBUG) {
+                    System.out.println(">>> Credentials acquireServiceCreds: "
+                            + "no tgt; searching backwards");
                 }
 
                 /*
@@ -144,17 +214,15 @@
                  * realm as close to the target as possible.
                  * That means traversing the realms list backwards.
                  */
-
                 for (newTgt = null, k = realms.length - 1;
-                     newTgt == null && k > i; k--)
-                {
-
+                        newTgt == null && k > i; k--) {
                     tempService = PrincipalName.tgsService(realms[k], realms[i]);
-                    if (DEBUG)
-                    {
-                        System.out.println(">>> Credentials acquireServiceCreds: inner loop: [" + k +"] tempService=" + tempService);
+                    if (DEBUG) {
+                        System.out.println(
+                                ">>> Credentials acquireServiceCreds: "
+                                + "inner loop: [" + k
+                                + "] tempService=" + tempService);
                     }
-
                     try {
                         newTgt = serviceCreds(tempService, cTgt);
                     } catch (Exception exc) {
@@ -163,11 +231,10 @@
                 }
             } // Ends 'if (newTgt == null)'
 
-            if (newTgt == null)
-            {
-                if (DEBUG)
-                {
-                    System.out.println(">>> Credentials acquireServiceCreds: no tgt; cannot get creds");
+            if (newTgt == null) {
+                if (DEBUG) {
+                    System.out.println(">>> Credentials acquireServiceCreds: "
+                            + "no tgt; cannot get creds");
                 }
                 break;
             }
@@ -176,29 +243,24 @@
              * We have a tgt. It may or may not be for the target.
              * If it's for the target realm, we're done looking for a tgt.
              */
-
             newTgtRealm = newTgt.getServer().getInstanceComponent();
-            if (okAsDelegate && !newTgt.checkDelegate()) {
-                if (DEBUG)
-                {
+            if (okAsDelegate[0] && !newTgt.checkDelegate()) {
+                if (DEBUG) {
                     System.out.println(">>> Credentials acquireServiceCreds: " +
                             "global OK-AS-DELEGATE turned off at " +
                             newTgt.getServer());
                 }
-                okAsDelegate = false;
+                okAsDelegate[0] = false;
             }
 
-            if (DEBUG)
-            {
-                System.out.println(">>> Credentials acquireServiceCreds: got tgt");
-                //printDebug(newTgt);
+            if (DEBUG) {
+                System.out.println(">>> Credentials acquireServiceCreds: "
+                        + "got tgt");
             }
 
-            if (newTgtRealm.equals(serviceRealm))
-            {
+            if (newTgtRealm.equals(serviceRealm)) {
                 /* We got the right tgt */
                 theTgt = newTgt;
-                theTgtRealm = newTgtRealm;
                 break;
             }
 
@@ -207,17 +269,13 @@
              * See if the realm of the new tgt is in the list of realms
              * and continue looking from there.
              */
-
-            for (k = i+1; k < realms.length; k++)
-            {
-                if (newTgtRealm.equals(realms[k]))
-                {
+            for (k = i+1; k < realms.length; k++) {
+                if (newTgtRealm.equals(realms[k])) {
                     break;
                 }
             }
 
-            if (k < realms.length)
-            {
+            if (k < realms.length) {
                 /*
                  * (re)set the counter so we start looking
                  * from the realm we just obtained a tgt for.
@@ -225,64 +283,24 @@
                 i = k;
                 cTgt = newTgt;
 
-                if (DEBUG)
-                {
-                    System.out.println(">>> Credentials acquireServiceCreds: continuing with main loop counter reset to " + i);
+                if (DEBUG) {
+                    System.out.println(">>> Credentials acquireServiceCreds: "
+                            + "continuing with main loop counter reset to " + i);
                 }
-
                 continue;
             }
-            else
-            {
+            else {
                 /*
                  * The new tgt's realm is not in the heirarchy of realms.
                  * It's probably not safe to get a tgt from
                  * a tgs that is outside the known list of realms.
                  * Give up now.
                  */
-
                 break;
             }
         } // Ends outermost/main 'for' loop
 
-        Credentials theCreds = null;
-
-        if (theTgt != null)
-        {
-            /* We have the right tgt. Let's get the service creds */
-
-            if (DEBUG)
-            {
-                System.out.println(">>> Credentials acquireServiceCreds: got right tgt");
-
-                //printDebug(theTgt);
-
-                System.out.println(">>> Credentials acquireServiceCreds: obtaining service creds for " + sname);
-            }
-
-            try {
-                theCreds = serviceCreds(sname, theTgt);
-            } catch (Exception exc) {
-              if (DEBUG)
-                System.out.println(exc);
-              theCreds = null;
-            }
-        }
-
-        if (theCreds != null)
-        {
-            if (DEBUG)
-            {
-                System.out.println(">>> Credentials acquireServiceCreds: returning creds:");
-                Credentials.printDebug(theCreds);
-            }
-            if (!okAsDelegate) {
-                theCreds.resetDelegate();
-            }
-            return theCreds;
-        }
-        throw new KrbApErrException(Krb5.KRB_AP_ERR_GEN_CRED,
-                                    "No service creds");
+        return theTgt;
     }
 
    /*
--- a/src/share/classes/sun/security/krb5/internal/EncKDCRepPart.java	Tue Nov 06 18:41:01 2012 -0800
+++ b/src/share/classes/sun/security/krb5/internal/EncKDCRepPart.java	Wed Nov 07 14:13:01 2012 +0800
@@ -160,9 +160,10 @@
         if (der.getData().available() > 0) {
             caddr = HostAddresses.parse(der.getData(), (byte) 0x0B, true);
         }
-        if (der.getData().available() > 0) {
+        // We observe extra data from MSAD
+        /*if (der.getData().available() > 0) {
             throw new Asn1Exception(Krb5.ASN1_BAD_ID);
-        }
+        }*/
     }
 
     /**
--- a/src/share/classes/sun/security/krb5/internal/KDCOptions.java	Tue Nov 06 18:41:01 2012 -0800
+++ b/src/share/classes/sun/security/krb5/internal/KDCOptions.java	Wed Nov 07 14:13:01 2012 +0800
@@ -139,13 +139,45 @@
     public static final int UNUSED9         = 9;
     public static final int UNUSED10        = 10;
     public static final int UNUSED11        = 11;
+    public static final int CNAME_IN_ADDL_TKT = 14;
     public static final int RENEWABLE_OK    = 27;
     public static final int ENC_TKT_IN_SKEY = 28;
     public static final int RENEW           = 30;
     public static final int VALIDATE        = 31;
 
+    private static final String[] names = {
+        "RESERVED",         //0
+        "FORWARDABLE",      //1;
+        "FORWARDED",        //2;
+        "PROXIABLE",        //3;
+        "PROXY",            //4;
+        "ALLOW_POSTDATE",   //5;
+        "POSTDATED",        //6;
+        "UNUSED7",          //7;
+        "RENEWABLE",        //8;
+        "UNUSED9",          //9;
+        "UNUSED10",         //10;
+        "UNUSED11",         //11;
+        null,null,
+        "CNAME_IN_ADDL_TKT",//14;
+        null,null,null,null,null,null,null,null,null,null,null,null,
+        "RENEWABLE_OK",     //27;
+        "ENC_TKT_IN_SKEY",  //28;
+        null,
+        "RENEW",            //30;
+        "VALIDATE",         //31;
+    };
+
     private boolean DEBUG = Krb5.DEBUG;
 
+    public static KDCOptions with(int... flags) {
+        KDCOptions options = new KDCOptions();
+        for (int flag: flags) {
+            options.set(flag, true);
+        }
+        return options;
+    }
+
     public KDCOptions() {
         super(Krb5.KDC_OPTS_MAX + 1);
         setDefault();
@@ -238,6 +270,20 @@
         return super.get(option);
     }
 
+    @Override public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("KDCOptions: ");
+        for (int i=0; i<Krb5.KDC_OPTS_MAX+1; i++) {
+            if (get(i)) {
+                if (names[i] != null) {
+                    sb.append(names[i]).append(",");
+                } else {
+                    sb.append(i).append(",");
+                }
+            }
+        }
+        return sb.toString();
+    }
 
     private void setDefault() {
         try {
--- a/src/share/classes/sun/security/krb5/internal/Krb5.java	Tue Nov 06 18:41:01 2012 -0800
+++ b/src/share/classes/sun/security/krb5/internal/Krb5.java	Wed Nov 07 14:13:01 2012 +0800
@@ -158,6 +158,9 @@
     public static final int PA_ETYPE_INFO    = 11;
     public static final int PA_ETYPE_INFO2   = 19;
 
+    // S4U2user info
+    public static final int PA_FOR_USER      = 129;
+
     //-------------------------------+-------------
     //authorization data type        |ad-type value
     //-------------------------------+-------------
--- a/src/share/classes/sun/security/krb5/internal/PAData.java	Tue Nov 06 18:41:01 2012 -0800
+++ b/src/share/classes/sun/security/krb5/internal/PAData.java	Wed Nov 07 14:13:01 2012 +0800
@@ -312,6 +312,9 @@
                     }
                 }
                 break;
+            case Krb5.PA_FOR_USER:
+                sb.append("\t PA-FOR-USER\n");
+                break;
             default:
                 // Unknown Pre-auth type
                 break;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/share/classes/sun/security/krb5/internal/PAForUserEnc.java	Wed Nov 07 14:13:01 2012 +0800
@@ -0,0 +1,190 @@
+/*
+ * Copyright (c) 2012, Oracle and/or its affiliates. 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.security.krb5.internal;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import sun.security.krb5.*;
+import sun.security.krb5.internal.crypto.KeyUsage;
+import sun.security.krb5.internal.util.KerberosString;
+import sun.security.util.DerOutputStream;
+import sun.security.util.DerValue;
+
+/**
+ * Implements the ASN.1 PA-FOR-USER type.
+ *
+ * <xmp>
+ * padata-type  ::= PA-FOR-USER
+ *                  -- value 129
+ * padata-value ::= EncryptedData
+ *                  -- PA-FOR-USER-ENC
+ * PA-FOR-USER-ENC ::= SEQUENCE {
+ *     userName[0] PrincipalName,
+ *     userRealm[1] Realm,
+ *     cksum[2] Checksum,
+ *     auth-package[3] KerberosString
+ * }
+ * </xmp>
+ *
+ * <p>
+ * This definition reflects MS-SFU.
+ */
+
+public class PAForUserEnc {
+    final public PrincipalName name;
+    final private EncryptionKey key;
+    final public static String AUTH_PACKAGE = "Kerberos";
+
+    public PAForUserEnc(PrincipalName name, EncryptionKey key) {
+        this.name = name;
+        this.key = key;
+    }
+
+    /**
+     * Constructs a PA-FOR-USER object from a DER encoding.
+     * @param encoding the input object
+     * @param key the key to verify the checksum inside encoding
+     * @throws KrbException if the verification fails.
+     * Note: this method is now only used by test KDC, therefore
+     * the verification is ignored (at the moment).
+     */
+    public PAForUserEnc(DerValue encoding, EncryptionKey key)
+            throws Asn1Exception, KrbException, IOException {
+        DerValue der = null;
+        this.key = key;
+
+        if (encoding.getTag() != DerValue.tag_Sequence) {
+            throw new Asn1Exception(Krb5.ASN1_BAD_ID);
+        }
+
+        // Realm after name? Quite abnormal.
+        PrincipalName tmpName = null;
+        der = encoding.getData().getDerValue();
+        if ((der.getTag() & 0x1F) == 0x00) {
+            try {
+                tmpName = new PrincipalName(der.getData().getDerValue(),
+                    new Realm("PLACEHOLDER"));
+            } catch (RealmException re) {
+                // Impossible
+            }
+        } else {
+            throw new Asn1Exception(Krb5.ASN1_BAD_ID);
+        }
+
+        der = encoding.getData().getDerValue();
+        if ((der.getTag() & 0x1F) == 0x01) {
+            try {
+                Realm realm = new Realm(der.getData().getDerValue());
+                name = new PrincipalName(
+                        tmpName.getNameType(), tmpName.getNameStrings(), realm);
+            } catch (RealmException re) {
+                throw new IOException(re);
+            }
+        } else {
+            throw new Asn1Exception(Krb5.ASN1_BAD_ID);
+        }
+
+        der = encoding.getData().getDerValue();
+        if ((der.getTag() & 0x1F) == 0x02) {
+            // Deal with the checksum
+        } else {
+            throw new Asn1Exception(Krb5.ASN1_BAD_ID);
+        }
+
+        der = encoding.getData().getDerValue();
+        if ((der.getTag() & 0x1F) == 0x03) {
+            String authPackage = new KerberosString(der.getData().getDerValue()).toString();
+            if (!authPackage.equalsIgnoreCase(AUTH_PACKAGE)) {
+                throw new IOException("Incorrect auth-package");
+            }
+        } else {
+            throw new Asn1Exception(Krb5.ASN1_BAD_ID);
+        }
+        if (encoding.getData().available() > 0)
+            throw new Asn1Exception(Krb5.ASN1_BAD_ID);
+    }
+
+    public byte[] asn1Encode() throws Asn1Exception, IOException {
+        DerOutputStream bytes = new DerOutputStream();
+        bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte)0x00), name.asn1Encode());
+        bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte)0x01), name.getRealm().asn1Encode());
+
+        try {
+            Checksum cks = new Checksum(
+                    Checksum.CKSUMTYPE_HMAC_MD5_ARCFOUR,
+                    getS4UByteArray(),
+                    key,
+                    KeyUsage.KU_PA_FOR_USER_ENC_CKSUM);
+            bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte)0x02), cks.asn1Encode());
+        } catch (KrbException ke) {
+            throw new IOException(ke);
+        }
+
+        DerOutputStream temp = new DerOutputStream();
+        temp.putDerValue(new KerberosString(AUTH_PACKAGE).toDerValue());
+        bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte)0x03), temp);
+
+        temp = new DerOutputStream();
+        temp.write(DerValue.tag_Sequence, bytes);
+        return temp.toByteArray();
+    }
+
+    /**
+     * Returns S4UByteArray, the block to calculate checksum inside a
+     * PA-FOR-USER-ENC data structure. It includes:
+     * 1. userName.name-type encoded as a 4-byte integer in little endian
+     *    byte order
+     * 2. all string values in the sequence of strings contained in the
+     *    userName.name-string field
+     * 3. the string value of the userRealm field
+     * 4. the string value of auth-package field
+     */
+    public byte[] getS4UByteArray() {
+        try {
+            ByteArrayOutputStream ba = new ByteArrayOutputStream();
+            ba.write(new byte[4]);
+            for (String s: name.getNameStrings()) {
+                ba.write(s.getBytes("UTF-8"));
+            }
+            ba.write(name.getRealm().toString().getBytes("UTF-8"));
+            ba.write(AUTH_PACKAGE.getBytes("UTF-8"));
+            byte[] output = ba.toByteArray();
+            int pnType = name.getNameType();
+            output[0] = (byte)(pnType & 0xff);
+            output[1] = (byte)((pnType>>8) & 0xff);
+            output[2] = (byte)((pnType>>16) & 0xff);
+            output[3] = (byte)((pnType>>24) & 0xff);
+            return output;
+        } catch (IOException ioe) {
+            // not possible
+            throw new AssertionError("Cannot write ByteArrayOutputStream", ioe);
+        }
+    }
+
+    public String toString() {
+        return "PA-FOR-USER: " + name;
+    }
+}
--- a/src/share/classes/sun/security/krb5/internal/crypto/KeyUsage.java	Tue Nov 06 18:41:01 2012 -0800
+++ b/src/share/classes/sun/security/krb5/internal/crypto/KeyUsage.java	Wed Nov 07 14:13:01 2012 +0800
@@ -54,6 +54,7 @@
     public static final int KU_ENC_KRB_PRIV_PART = 13;          // KrbPriv
     public static final int KU_ENC_KRB_CRED_PART = 14;          // KrbCred
     public static final int KU_KRB_SAFE_CKSUM = 15;             // KrbSafe
+    public static final int KU_PA_FOR_USER_ENC_CKSUM = 17;      // S4U2user
     public static final int KU_AD_KDC_ISSUED_CKSUM = 19;
 
     public static final boolean isValid(int usage) {
--- a/src/share/classes/sun/security/krb5/internal/ktab/KeyTab.java	Tue Nov 06 18:41:01 2012 -0800
+++ b/src/share/classes/sun/security/krb5/internal/ktab/KeyTab.java	Wed Nov 07 14:13:01 2012 +0800
@@ -279,6 +279,9 @@
         EncryptionKey key;
         int size = entries.size();
         ArrayList<EncryptionKey> keys = new ArrayList<>(size);
+        if (DEBUG) {
+            System.out.println("Looking for keys for: " + service);
+        }
         for (int i = size-1; i >= 0; i--) {
             entry = entries.elementAt(i);
             if (entry.service.match(service)) {
--- a/test/sun/security/krb5/auto/Basic.java	Tue Nov 06 18:41:01 2012 -0800
+++ b/test/sun/security/krb5/auto/Basic.java	Wed Nov 07 14:13:01 2012 +0800
@@ -38,11 +38,13 @@
 
         new OneKDC(null).writeJAASConf();
 
-        Context c, s;
+        Context c, s, s2, b;
         c = Context.fromJAAS("client");
         s = Context.fromJAAS("server");
+        b = Context.fromJAAS("backend");
 
         c.startAsClient(OneKDC.SERVER, GSSUtil.GSS_KRB5_MECH_OID);
+        c.x().requestCredDeleg(true);
         s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID);
 
         Context.handshake(c, s);
@@ -50,7 +52,13 @@
         Context.transmit("i say high --", c, s);
         Context.transmit("   you say low", s, c);
 
+        s2 = s.delegated();
         s.dispose();
         c.dispose();
+
+        s2.startAsClient(OneKDC.BACKEND, GSSUtil.GSS_KRB5_MECH_OID);
+        b.startAsServer(GSSUtil.GSS_KRB5_MECH_OID);
+
+        Context.handshake(s2, b);
     }
 }
--- a/test/sun/security/krb5/auto/Context.java	Tue Nov 06 18:41:01 2012 -0800
+++ b/test/sun/security/krb5/auto/Context.java	Wed Nov 07 14:13:01 2012 +0800
@@ -42,9 +42,10 @@
 import com.sun.security.jgss.ExtendedGSSContext;
 import com.sun.security.jgss.InquireType;
 import com.sun.security.jgss.AuthorizationDataEntry;
+import com.sun.security.jgss.ExtendedGSSCredential;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
-import javax.security.auth.kerberos.KeyTab;
+import java.security.Principal;
 
 /**
  * Context of a JGSS subject, encapsulating Subject and GSSContext.
@@ -90,7 +91,21 @@
     public Context delegated() throws Exception {
         Context out = new Context();
         out.s = s;
-        out.cred = x.getDelegCred();
+        try {
+            out.cred = Subject.doAs(s, new PrivilegedExceptionAction<GSSCredential>() {
+                @Override
+                public GSSCredential run() throws Exception {
+                    GSSCredential cred = x.getDelegCred();
+                    if (cred == null && x.getCredDelegState() ||
+                            cred != null && !x.getCredDelegState()) {
+                        throw new Exception("getCredDelegState not match");
+                    }
+                    return cred;
+                }
+            });
+        } catch (PrivilegedActionException pae) {
+            throw pae.getException();
+        }
         out.name = name + " as " + out.cred.getName().toString();
         return out;
     }
@@ -212,28 +227,34 @@
      * @throws java.lang.Exception
      */
     public void startAsServer(final Oid mech) throws Exception {
-        startAsServer(null, mech);
+        startAsServer(null, mech, false);
     }
 
+    public void startAsServer(final String name, final Oid mech) throws Exception {
+        startAsServer(name, mech, false);
+    }
     /**
      * Starts as a server with the specified service name
      * @param name the service name
      * @param mech GSS mech
      * @throws java.lang.Exception
      */
-    public void startAsServer(final String name, final Oid mech) throws Exception {
+    public void startAsServer(final String name, final Oid mech, final boolean asInitiator) throws Exception {
         doAs(new Action() {
             @Override
             public byte[] run(Context me, byte[] dummy) throws Exception {
                 GSSManager m = GSSManager.getInstance();
-                me.x = (ExtendedGSSContext)m.createContext(m.createCredential(
+                me.cred = m.createCredential(
                         name == null ? null :
                           (name.indexOf('@') < 0 ?
                             m.createName(name, null) :
                             m.createName(name, GSSName.NT_HOSTBASED_SERVICE)),
                         GSSCredential.INDEFINITE_LIFETIME,
                         mech,
-                        GSSCredential.ACCEPT_ONLY));
+                        asInitiator?
+                                GSSCredential.INITIATE_AND_ACCEPT:
+                                GSSCredential.ACCEPT_ONLY);
+                me.x = (ExtendedGSSContext)m.createContext(me.cred);
                 return null;
             }
         }, null);
@@ -331,27 +352,38 @@
         } catch (Exception e) {
             ;// Don't care
         }
-        System.out.println("====== Private Credentials Set ======");
-        for (Object o : s.getPrivateCredentials()) {
-            System.out.println("    " + o.getClass());
-            if (o instanceof KerberosTicket) {
-                KerberosTicket kt = (KerberosTicket) o;
-                System.out.println("        " + kt.getServer() + " for " + kt.getClient());
-            } else if (o instanceof KerberosKey) {
-                KerberosKey kk = (KerberosKey) o;
-                System.out.print("        " + kk.getKeyType() + " " + kk.getVersionNumber() + " " + kk.getAlgorithm() + " ");
-                for (byte b : kk.getEncoded()) {
-                    System.out.printf("%02X", b & 0xff);
-                }
-                System.out.println();
-            } else if (o instanceof Map) {
-                Map map = (Map) o;
-                for (Object k : map.keySet()) {
-                    System.out.println("        " + k + ": " + map.get(k));
-                }
-            } else {
+        if (s != null) {
+            System.out.println("====== START SUBJECT CONTENT =====");
+            for (Principal p: s.getPrincipals()) {
+                System.out.println("    Principal: " + p);
+            }
+            for (Object o : s.getPublicCredentials()) {
+                System.out.println("    " + o.getClass());
                 System.out.println("        " + o);
             }
+            System.out.println("====== Private Credentials Set ======");
+            for (Object o : s.getPrivateCredentials()) {
+                System.out.println("    " + o.getClass());
+                if (o instanceof KerberosTicket) {
+                    KerberosTicket kt = (KerberosTicket) o;
+                    System.out.println("        " + kt.getServer() + " for " + kt.getClient());
+                } else if (o instanceof KerberosKey) {
+                    KerberosKey kk = (KerberosKey) o;
+                    System.out.print("        " + kk.getKeyType() + " " + kk.getVersionNumber() + " " + kk.getAlgorithm() + " ");
+                    for (byte b : kk.getEncoded()) {
+                        System.out.printf("%02X", b & 0xff);
+                    }
+                    System.out.println();
+                } else if (o instanceof Map) {
+                    Map map = (Map) o;
+                    for (Object k : map.keySet()) {
+                        System.out.println("        " + k + ": " + map.get(k));
+                    }
+                } else {
+                    System.out.println("        " + o);
+                }
+            }
+            System.out.println("====== END SUBJECT CONTENT =====");
         }
         if (x != null && x instanceof ExtendedGSSContext) {
             if (x.isEstablished()) {
@@ -510,6 +542,29 @@
         return sb.toString();
     }
 
+    public Context impersonate(final String someone) throws Exception {
+        try {
+            GSSCredential creds = Subject.doAs(s, new PrivilegedExceptionAction<GSSCredential>() {
+                @Override
+                public GSSCredential run() throws Exception {
+                    GSSManager m = GSSManager.getInstance();
+                    GSSName other = m.createName(someone, GSSName.NT_USER_NAME);
+                    if (Context.this.cred == null) {
+                        Context.this.cred = m.createCredential(GSSCredential.INITIATE_ONLY);
+                    }
+                    return ((ExtendedGSSCredential)Context.this.cred).impersonate(other);
+                }
+            });
+            Context out = new Context();
+            out.s = s;
+            out.cred = creds;
+            out.name = name + " as " + out.cred.getName().toString();
+            return out;
+        } catch (PrivilegedActionException pae) {
+            throw pae.getException();
+        }
+    }
+
     public byte[] take(final byte[] in) throws Exception {
         return doAs(new Action() {
             @Override
@@ -522,10 +577,11 @@
                     }
                     return null;
                 } else {
-                    System.out.println(name + " call initSecContext");
                     if (me.x.isInitiator()) {
+                        System.out.println(name + " call initSecContext");
                         return me.x.initSecContext(input, 0, input.length);
                     } else {
+                        System.out.println(name + " call acceptSecContext");
                         return me.x.acceptSecContext(input, 0, input.length);
                     }
                 }
--- a/test/sun/security/krb5/auto/CrossRealm.java	Tue Nov 06 18:41:01 2012 -0800
+++ b/test/sun/security/krb5/auto/CrossRealm.java	Wed Nov 07 14:13:01 2012 +0800
@@ -24,6 +24,7 @@
 /*
  * @test
  * @bug 6706974
+ * @compile -XDignore.symbol.file CrossRealm.java
  * @run main/othervm CrossRealm
  * @summary Add krb5 test infrastructure
  */
--- a/test/sun/security/krb5/auto/KDC.java	Tue Nov 06 18:41:01 2012 -0800
+++ b/test/sun/security/krb5/auto/KDC.java	Wed Nov 07 14:13:01 2012 +0800
@@ -178,6 +178,20 @@
          * What backend server can be delegated to
          */
         OK_AS_DELEGATE,
+        /**
+         * Allow S4U2self, List<String> of middle servers.
+         * If not set, means KDC does not understand S4U2self at all, therefore
+         * would ignore any PA-FOR-USER request and send a ticket using the
+         * cname of teh requestor. If set, it returns FORWARDABLE tickets to
+         * a server with its name in the list
+         */
+        ALLOW_S4U2SELF,
+        /**
+         * Allow S4U2proxy, Map<String,List<String>> of middle servers to
+         * backends. If not set or a backend not in a server's list,
+         * Krb5.KDC_ERR_POLICY will be send for S4U2proxy request.
+         */
+        ALLOW_S4U2PROXY,
     };
 
     static {
@@ -618,13 +632,18 @@
             int e2 = eTypes[0];     // etype for outgoing session key
             int e3 = eTypes[0];     // etype for outgoing ticket
 
-            PAData[] pas = kDCReqDotPAData(tgsReq);
+            PAData[] pas = KDCReqDotPAData(tgsReq);
 
             Ticket tkt = null;
             EncTicketPart etp = null;
+
+            PrincipalName cname = null;
+            boolean allowForwardable = true;
+
             if (pas == null || pas.length == 0) {
                 throw new KrbException(Krb5.KDC_ERR_PADATA_TYPE_NOSUPP);
             } else {
+                PrincipalName forUserCName = null;
                 for (PAData pa: pas) {
                     if (pa.getType() == Krb5.PA_TGS_REQ) {
                         APReq apReq = new APReq(pa.getValue());
@@ -636,8 +655,31 @@
                         DerInputStream derIn = new DerInputStream(bb);
                         DerValue der = derIn.getDerValue();
                         etp = new EncTicketPart(der.toByteArray());
+                        // Finally, cname will be overwritten by PA-FOR-USER
+                        // if it exists.
+                        cname = etp.cname;
+                        System.out.println(realm + "> presenting a ticket of "
+                                + etp.cname + " to " + tkt.sname);
+                    } else if (pa.getType() == Krb5.PA_FOR_USER) {
+                        if (options.containsKey(Option.ALLOW_S4U2SELF)) {
+                            PAForUserEnc p4u = new PAForUserEnc(
+                                    new DerValue(pa.getValue()), null);
+                            forUserCName = p4u.name;
+                            System.out.println(realm + "> presenting a PA_FOR_USER "
+                                    + " in the name of " + p4u.name);
+                        }
                     }
                 }
+                if (forUserCName != null) {
+                    List<String> names = (List<String>)options.get(Option.ALLOW_S4U2SELF);
+                    if (!names.contains(cname.toString())) {
+                        // Mimic the normal KDC behavior. When a server is not
+                        // allowed to send S4U2self, do not send an error.
+                        // Instead, send a ticket which is useless later.
+                        allowForwardable = false;
+                    }
+                    cname = forUserCName;
+                }
                 if (tkt == null) {
                     throw new KrbException(Krb5.KDC_ERR_PADATA_TYPE_NOSUPP);
                 }
@@ -658,7 +700,8 @@
             }
 
             boolean[] bFlags = new boolean[Krb5.TKT_OPTS_MAX+1];
-            if (body.kdcOptions.get(KDCOptions.FORWARDABLE)) {
+            if (body.kdcOptions.get(KDCOptions.FORWARDABLE)
+                    && allowForwardable) {
                 bFlags[Krb5.TKT_OPTS_FORWARDABLE] = true;
             }
             if (body.kdcOptions.get(KDCOptions.FORWARDED) ||
@@ -678,6 +721,37 @@
             if (body.kdcOptions.get(KDCOptions.ALLOW_POSTDATE)) {
                 bFlags[Krb5.TKT_OPTS_MAY_POSTDATE] = true;
             }
+            if (body.kdcOptions.get(KDCOptions.CNAME_IN_ADDL_TKT)) {
+                if (!options.containsKey(Option.ALLOW_S4U2PROXY)) {
+                    // Don't understand CNAME_IN_ADDL_TKT
+                    throw new KrbException(Krb5.KDC_ERR_BADOPTION);
+                } else {
+                    Map<String,List<String>> map = (Map<String,List<String>>)
+                            options.get(Option.ALLOW_S4U2PROXY);
+                    Ticket second = KDCReqBodyDotFirstAdditionalTicket(body);
+                    EncryptionKey key2 = keyForUser(second.sname, second.encPart.getEType(), true);
+                    byte[] bb = second.encPart.decrypt(key2, KeyUsage.KU_TICKET);
+                    DerInputStream derIn = new DerInputStream(bb);
+                    DerValue der = derIn.getDerValue();
+                    EncTicketPart tktEncPart = new EncTicketPart(der.toByteArray());
+                    if (!tktEncPart.flags.get(Krb5.TKT_OPTS_FORWARDABLE)) {
+                        //throw new KrbException(Krb5.KDC_ERR_BADOPTION);
+                    }
+                    PrincipalName client = tktEncPart.cname;
+                    System.out.println(realm + "> and an additional ticket of "
+                            + client + " to " + second.sname);
+                    if (map.containsKey(cname.toString())) {
+                        if (map.get(cname.toString()).contains(service.toString())) {
+                            System.out.println(realm + "> S4U2proxy OK");
+                        } else {
+                            throw new KrbException(Krb5.KDC_ERR_BADOPTION);
+                        }
+                    } else {
+                        throw new KrbException(Krb5.KDC_ERR_BADOPTION);
+                    }
+                    cname = client;
+                }
+            }
 
             String okAsDelegate = (String)options.get(Option.OK_AS_DELEGATE);
             if (okAsDelegate != null && (
@@ -691,7 +765,7 @@
             EncTicketPart enc = new EncTicketPart(
                     tFlags,
                     key,
-                    etp.cname,
+                    cname,
                     new TransitedEncoding(1, new byte[0]),  // TODO
                     new KerberosTime(new Date()),
                     body.from,
@@ -729,7 +803,7 @@
                     );
             EncryptedData edata = new EncryptedData(ckey, enc_part.asn1Encode(), KeyUsage.KU_ENC_TGS_REP_PART_SESSKEY);
             TGSRep tgsRep = new TGSRep(null,
-                    etp.cname,
+                    cname,
                     t,
                     edata);
             System.out.println("     Return " + tgsRep.cname
@@ -942,7 +1016,7 @@
                 outPAs.add(new PAData(Krb5.PA_ETYPE_INFO, eid.toByteArray()));
             }
 
-            PAData[] inPAs = kDCReqDotPAData(asReq);
+            PAData[] inPAs = KDCReqDotPAData(asReq);
             if (inPAs == null || inPAs.length == 0) {
                 Object preauth = options.get(Option.PREAUTH_REQUIRED);
                 if (preauth == null || preauth.equals(Boolean.TRUE)) {
@@ -1252,6 +1326,7 @@
     private static final Field getEType;
     private static final Constructor<EncryptedData> ctorEncryptedData;
     private static final Method stringToKey;
+    private static final Field getAddlTkt;
 
     static {
         try {
@@ -1265,6 +1340,8 @@
                     "stringToKey",
                     char[].class, String.class, byte[].class, Integer.TYPE);
             stringToKey.setAccessible(true);
+            getAddlTkt = KDCReqBody.class.getDeclaredField("additionalTickets");
+            getAddlTkt.setAccessible(true);
         } catch (NoSuchFieldException nsfe) {
             throw new AssertionError(nsfe);
         } catch (NoSuchMethodException nsme) {
@@ -1278,7 +1355,7 @@
             throw new AssertionError(e);
         }
     }
-    private static PAData[] kDCReqDotPAData(KDCReq req) {
+    private static PAData[] KDCReqDotPAData(KDCReq req) {
         try {
             return (PAData[])getPADataField.get(req);
         } catch (Exception e) {
@@ -1303,4 +1380,11 @@
             throw new AssertionError(e);
         }
     }
+    private static Ticket KDCReqBodyDotFirstAdditionalTicket(KDCReqBody body) {
+        try {
+            return ((Ticket[])getAddlTkt.get(body))[0];
+        } catch (Exception e) {
+            throw new AssertionError(e);
+        }
+    }
 }
--- a/test/sun/security/krb5/auto/OkAsDelegate.java	Tue Nov 06 18:41:01 2012 -0800
+++ b/test/sun/security/krb5/auto/OkAsDelegate.java	Wed Nov 07 14:13:01 2012 +0800
@@ -91,7 +91,7 @@
 
         Context c, s;
         c = Context.fromJAAS("client");
-        s = Context.fromJAAS("server");
+        s = Context.fromJAAS("com.sun.security.jgss.krb5.accept");
 
         Oid mech = GSSUtil.GSS_KRB5_MECH_OID;
         if (System.getProperty("test.spnego") != null) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/sun/security/krb5/auto/S4U2proxy.java	Wed Nov 07 14:13:01 2012 +0800
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2012, Oracle and/or its affiliates. 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 6355584
+ * @summary Introduce constrained Kerberos delegation
+ * @compile -XDignore.symbol.file S4U2proxy.java
+ * @run main/othervm S4U2proxy krb5
+ * @run main/othervm S4U2proxy spnego
+ */
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.ietf.jgss.Oid;
+import sun.security.jgss.GSSUtil;
+
+public class S4U2proxy {
+
+    public static void main(String[] args) throws Exception {
+        Oid mech;
+        if (args[0].equals("spnego")) {
+            mech = GSSUtil.GSS_SPNEGO_MECH_OID;
+        } else if (args[0].contains("krb5")) {
+            mech = GSSUtil.GSS_KRB5_MECH_OID;
+        } else {
+            throw new Exception("Unknown mech");
+        }
+
+        OneKDC kdc = new OneKDC(null);
+        kdc.writeJAASConf();
+        kdc.setOption(KDC.Option.PREAUTH_REQUIRED, false);
+        Map<String,List<String>> map = new HashMap<>();
+        map.put(OneKDC.SERVER + "@" + OneKDC.REALM, Arrays.asList(
+                new String[]{OneKDC.BACKEND + "@" + OneKDC.REALM}));
+        kdc.setOption(KDC.Option.ALLOW_S4U2PROXY, map);
+
+        Context c, s, b;
+        c = Context.fromJAAS("client");
+        s = Context.fromJAAS("server");
+        b = Context.fromJAAS("backend");
+
+        c.startAsClient(OneKDC.SERVER, mech);
+        s.startAsServer(null, mech, false);
+
+        Context.handshake(c, s);
+        Context p = s.delegated();
+
+        p.startAsClient(OneKDC.BACKEND, mech);
+        b.startAsServer(mech);
+        Context.handshake(p, b);
+
+        p.startAsClient(OneKDC.BACKEND, mech);
+        b.startAsServer(mech);
+        Context.handshake(p, b);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/sun/security/krb5/auto/S4U2proxyGSS.java	Wed Nov 07 14:13:01 2012 +0800
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2012, Oracle and/or its affiliates. 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 6355584
+ * @summary Introduce constrained Kerberos delegation
+ * @compile -XDignore.symbol.file S4U2proxyGSS.java
+ * @run main/othervm -Djavax.security.auth.useSubjectCredsOnly=false S4U2proxyGSS krb5
+ * @run main/othervm -Djavax.security.auth.useSubjectCredsOnly=false S4U2proxyGSS spnego
+ */
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.security.Security;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.ietf.jgss.Oid;
+import sun.security.jgss.GSSUtil;
+
+public class S4U2proxyGSS {
+
+    public static void main(String[] args) throws Exception {
+        Oid mech;
+        if (args[0].equals("spnego")) {
+            mech = GSSUtil.GSS_SPNEGO_MECH_OID;
+        } else if (args[0].contains("krb5")) {
+            mech = GSSUtil.GSS_KRB5_MECH_OID;
+        } else {
+            throw new Exception("Unknown mech");
+        }
+
+        OneKDC kdc = new OneKDC(null);
+        kdc.writeJAASConf();
+        kdc.setOption(KDC.Option.PREAUTH_REQUIRED, false);
+        Map<String,List<String>> map = new HashMap<>();
+        map.put(OneKDC.SERVER + "@" + OneKDC.REALM, Arrays.asList(
+                new String[]{OneKDC.SERVER + "@" + OneKDC.REALM}));
+        kdc.setOption(KDC.Option.ALLOW_S4U2PROXY, map);
+
+        Context c, s, b;
+        System.setProperty("javax.security.auth.useSubjectCredsOnly", "false");
+        System.setProperty("java.security.auth.login.config", OneKDC.JAAS_CONF);
+        File f = new File(OneKDC.JAAS_CONF);
+        FileOutputStream fos = new FileOutputStream(f);
+        fos.write((
+                "com.sun.security.jgss.krb5.initiate {\n" +
+                "    com.sun.security.auth.module.Krb5LoginModule required;\n};\n" +
+                "com.sun.security.jgss.krb5.accept {\n" +
+                "    com.sun.security.auth.module.Krb5LoginModule required\n" +
+                "    principal=\"" + OneKDC.SERVER + "\"\n" +
+                "    useKeyTab=true\n" +
+                "    storeKey=true;\n};\n"
+                ).getBytes());
+        fos.close();
+        Security.setProperty("auth.login.defaultCallbackHandler", "OneKDC$CallbackForClient");
+        c = Context.fromThinAir();
+        s = Context.fromThinAir();
+        b = Context.fromThinAir();
+        c.startAsClient(OneKDC.SERVER, mech);
+        c.x().requestCredDeleg(false);
+        s.startAsServer(mech);
+
+        Context.handshake(c, s);
+        Context p = s.delegated();
+        p.startAsClient(OneKDC.SERVER, mech);
+        b.startAsServer(mech);
+        Context.handshake(p, b);
+
+        String n1 = p.x().getSrcName().toString().split("@")[0];
+        String n2 = b.x().getSrcName().toString().split("@")[0];
+        if (!n1.equals(OneKDC.USER) || !n2.equals(OneKDC.USER)) {
+            throw new Exception("Delegation failed");
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/sun/security/krb5/auto/S4U2self.java	Wed Nov 07 14:13:01 2012 +0800
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2012, Oracle and/or its affiliates. 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 6355584
+ * @summary Introduce constrained Kerberos delegation
+ * @compile -XDignore.symbol.file S4U2self.java
+ * @run main/othervm -Dsun.security.krb5.debug=false S4U2self krb5 0
+ * @run main/othervm/fail -Dsun.security.krb5.debug=false S4U2self krb5 1
+ * @run main/othervm/fail -Dsun.security.krb5.debug=false S4U2self krb5 2
+ * @run main/othervm/fail -Dsun.security.krb5.debug=false S4U2self krb5 3
+ * @run main/othervm/fail -Dsun.security.krb5.debug=false S4U2self krb5 4
+ * @run main/othervm/fail -Dsun.security.krb5.debug=false S4U2self krb5 5
+ * @run main/othervm -Dsun.security.krb5.debug=false S4U2self spnego
+ */
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.ietf.jgss.Oid;
+import sun.security.jgss.GSSUtil;
+
+public class S4U2self {
+
+    public static void main(String[] args) throws Exception {
+        // Test case, different policy settings in KDC:
+        //                   |     ALLOW_S4U2SELF on
+        //                   |   USER    USER2    none
+        // ALLOW_S4U2PORXY   |-------------------------
+        // USER to BACKEND   |   0       1        2
+        // USER2 to BACKEND  |   3
+        // USER to SERVER    |   4
+        //      none         |   5
+        //
+        // 0 should succeed, all other fail
+        int test = 0;
+        Oid mech;
+        if (args[0].equals("spnego")) {
+            mech = GSSUtil.GSS_SPNEGO_MECH_OID;
+        } else if (args[0].contains("krb5")) {
+            mech = GSSUtil.GSS_KRB5_MECH_OID;
+            test = Integer.parseInt(args[1]);
+        } else {
+            throw new Exception("Unknown mech");
+        }
+
+        OneKDC kdc = new OneKDC(null);
+        kdc.writeJAASConf();
+
+        switch (test) {
+            case 1:
+                kdc.setOption(KDC.Option.ALLOW_S4U2SELF, Arrays.asList(
+                        new String[]{OneKDC.USER2 + "@" + OneKDC.REALM}));
+                break;
+            case 2:
+                // No S4U2self
+                break;
+            default:
+                kdc.setOption(KDC.Option.ALLOW_S4U2SELF, Arrays.asList(
+                        new String[]{OneKDC.USER + "@" + OneKDC.REALM}));
+                break;
+        }
+
+        Map<String,List<String>> map = new HashMap<>();
+        switch (test) {
+            case 3:
+                map.put(OneKDC.USER2 + "@" + OneKDC.REALM, Arrays.asList(
+                        new String[]{OneKDC.BACKEND + "@" + OneKDC.REALM}));
+                kdc.setOption(KDC.Option.ALLOW_S4U2PROXY, map);
+                break;
+            case 4:
+                map.put(OneKDC.USER + "@" + OneKDC.REALM, Arrays.asList(
+                        new String[]{OneKDC.SERVER + "@" + OneKDC.REALM}));
+                kdc.setOption(KDC.Option.ALLOW_S4U2PROXY, map);
+                break;
+            case 5:
+                // No S4U2proxy set
+                break;
+            default:
+                map.put(OneKDC.USER + "@" + OneKDC.REALM, Arrays.asList(
+                        new String[]{OneKDC.BACKEND + "@" + OneKDC.REALM}));
+                kdc.setOption(KDC.Option.ALLOW_S4U2PROXY, map);
+                break;
+        }
+
+        Context c, s;
+        c = Context.fromJAAS("client");
+
+        c = c.impersonate(OneKDC.USER2);
+        c.status();
+
+        c.startAsClient(OneKDC.BACKEND, mech);
+
+        s = Context.fromJAAS("backend");
+        s.startAsServer(mech);
+
+        Context.handshake(c, s);
+
+        Context.transmit("i say high --", c, s);
+        Context.transmit("   you say low", s, c);
+
+        c.status();
+        s.status();
+
+        String n1 = c.x().getSrcName().toString().split("@")[0];
+        String n2 = s.x().getSrcName().toString().split("@")[0];
+        if (!n1.equals(OneKDC.USER2) || !n2.equals(OneKDC.USER2)) {
+            throw new Exception("Impersonate failed");
+        }
+
+        s.dispose();
+        c.dispose();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/sun/security/krb5/auto/S4U2selfAsServer.java	Wed Nov 07 14:13:01 2012 +0800
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2012, Oracle and/or its affiliates. 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 6355584
+ * @summary Introduce constrained Kerberos delegation
+ * @compile -XDignore.symbol.file S4U2selfAsServer.java
+ * @run main/othervm S4U2selfAsServer krb5
+ * @run main/othervm S4U2selfAsServer spnego
+ */
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.ietf.jgss.Oid;
+import sun.security.jgss.GSSUtil;
+
+public class S4U2selfAsServer {
+
+    public static void main(String[] args) throws Exception {
+        Oid mech;
+        if (args[0].equals("spnego")) {
+            mech = GSSUtil.GSS_SPNEGO_MECH_OID;
+        } else if (args[0].contains("krb5")) {
+            mech = GSSUtil.GSS_KRB5_MECH_OID;
+        } else {
+            throw new Exception("Unknown mech");
+        }
+
+        OneKDC kdc = new OneKDC(null);
+        kdc.writeJAASConf();
+        kdc.setOption(KDC.Option.PREAUTH_REQUIRED, false);
+        Map<String,List<String>> map = new HashMap<>();
+        map.put(OneKDC.SERVER + "@" + OneKDC.REALM, Arrays.asList(
+                new String[]{OneKDC.BACKEND + "@" + OneKDC.REALM}));
+        kdc.setOption(KDC.Option.ALLOW_S4U2PROXY, map);
+        kdc.setOption(KDC.Option.ALLOW_S4U2SELF, Arrays.asList(
+                new String[]{OneKDC.SERVER + "@" + OneKDC.REALM}));
+
+        Context s, b;
+        s = Context.fromJAAS("server");
+        b = Context.fromJAAS("backend");
+
+        s.startAsServer(null, mech, false);
+
+        Context p = s.impersonate(OneKDC.USER);
+
+        p.startAsClient(OneKDC.BACKEND, mech);
+        b.startAsServer(mech);
+        Context.handshake(p, b);
+
+        p.startAsClient(OneKDC.BACKEND, mech);
+        b.startAsServer(mech);
+        Context.handshake(p, b);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/sun/security/krb5/auto/S4U2selfAsServerGSS.java	Wed Nov 07 14:13:01 2012 +0800
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2012, Oracle and/or its affiliates. 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 6355584
+ * @summary Introduce constrained Kerberos delegation
+ * @compile -XDignore.symbol.file S4U2selfAsServerGSS.java
+ * @run main/othervm -Djavax.security.auth.useSubjectCredsOnly=false S4U2selfAsServerGSS krb5
+ * @run main/othervm -Djavax.security.auth.useSubjectCredsOnly=false S4U2selfAsServerGSS spnego
+ */
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.security.Security;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.ietf.jgss.Oid;
+import sun.security.jgss.GSSUtil;
+
+public class S4U2selfAsServerGSS {
+
+    public static void main(String[] args) throws Exception {
+        Oid mech;
+        if (args[0].equals("spnego")) {
+            mech = GSSUtil.GSS_SPNEGO_MECH_OID;
+        } else if (args[0].contains("krb5")) {
+            mech = GSSUtil.GSS_KRB5_MECH_OID;
+        } else {
+            throw new Exception("Unknown mech");
+        }
+
+        OneKDC kdc = new OneKDC(null);
+        kdc.writeJAASConf();
+        kdc.setOption(KDC.Option.PREAUTH_REQUIRED, false);
+        Map<String,List<String>> map = new HashMap<>();
+        map.put(OneKDC.SERVER + "@" + OneKDC.REALM, Arrays.asList(
+                new String[]{OneKDC.SERVER + "@" + OneKDC.REALM}));
+        kdc.setOption(KDC.Option.ALLOW_S4U2PROXY, map);
+        kdc.setOption(KDC.Option.ALLOW_S4U2SELF, Arrays.asList(
+                new String[]{OneKDC.SERVER + "@" + OneKDC.REALM}));
+
+        Context s, b;
+        System.setProperty("javax.security.auth.useSubjectCredsOnly", "false");
+        System.setProperty("java.security.auth.login.config", OneKDC.JAAS_CONF);
+        File f = new File(OneKDC.JAAS_CONF);
+        FileOutputStream fos = new FileOutputStream(f);
+        fos.write((
+                "com.sun.security.jgss.krb5.accept {\n" +
+                "    com.sun.security.auth.module.Krb5LoginModule required\n" +
+                "    principal=\"" + OneKDC.SERVER + "\"\n" +
+                "    useKeyTab=true\n" +
+                "    storeKey=true;\n};\n"
+                ).getBytes());
+        fos.close();
+        Security.setProperty("auth.login.defaultCallbackHandler", "OneKDC$CallbackForClient");
+        s = Context.fromThinAir();
+        b = Context.fromThinAir();
+        s.startAsServer(mech);
+
+        Context p = s.impersonate(OneKDC.USER);
+
+        p.startAsClient(OneKDC.SERVER, mech);
+        b.startAsServer(mech);
+        Context.handshake(p, b);
+
+        String n1 = p.x().getSrcName().toString().split("@")[0];
+        String n2 = b.x().getSrcName().toString().split("@")[0];
+        if (!n1.equals(OneKDC.USER) || !n2.equals(OneKDC.USER)) {
+            throw new Exception("Delegation failed");
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/sun/security/krb5/auto/S4U2selfGSS.java	Wed Nov 07 14:13:01 2012 +0800
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2012, Oracle and/or its affiliates. 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 6355584
+ * @summary Introduce constrained Kerberos delegation
+ * @compile -XDignore.symbol.file S4U2selfGSS.java
+ * @run main/othervm -Dsun.security.krb5.debug=false S4U2selfGSS krb5
+ * @run main/othervm -Dsun.security.krb5.debug=false S4U2selfGSS spnego
+ */
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.ietf.jgss.Oid;
+import sun.security.jgss.GSSUtil;
+
+public class S4U2selfGSS {
+
+    public static void main(String[] args) throws Exception {
+        Oid mech;
+        if (args[0].equals("spnego")) {
+            mech = GSSUtil.GSS_SPNEGO_MECH_OID;
+        } else if (args[0].contains("krb5")) {
+            mech = GSSUtil.GSS_KRB5_MECH_OID;
+        } else {
+            throw new Exception("Unknown mech");
+        }
+
+        OneKDC kdc = new OneKDC(null);
+        kdc.writeJAASConf();
+        kdc.setOption(KDC.Option.ALLOW_S4U2SELF, Arrays.asList(
+                new String[]{OneKDC.USER + "@" + OneKDC.REALM}));
+        Map<String,List<String>> map = new HashMap<>();
+        map.put(OneKDC.USER + "@" + OneKDC.REALM, Arrays.asList(
+                new String[]{OneKDC.SERVER + "@" + OneKDC.REALM}));
+        kdc.setOption(KDC.Option.ALLOW_S4U2PROXY, map);
+
+        Context c, s;
+        System.setProperty("javax.security.auth.useSubjectCredsOnly", "false");
+        c = Context.fromThinAir();
+        s = Context.fromThinAir();
+
+        c = c.impersonate(OneKDC.USER2);
+
+        c.startAsClient(OneKDC.SERVER, mech);
+        s.startAsServer(mech);
+
+        Context.handshake(c, s);
+
+        String n1 = c.x().getSrcName().toString().split("@")[0];
+        String n2 = s.x().getSrcName().toString().split("@")[0];
+        if (!n1.equals(OneKDC.USER2) || !n2.equals(OneKDC.USER2)) {
+            throw new Exception("Impersonate failed");
+        }
+
+        s.dispose();
+        c.dispose();
+    }
+}