8220302: Better Kerberos ccache handling
authorweijun
Fri, 19 Apr 2019 10:22:20 +0800
changeset 58611 53ddf218eddd
parent 58610 94710bb2a5bb
child 58612 32aff2b7585b
8220302: Better Kerberos ccache handling Reviewed-by: valeriep
src/java.base/share/conf/security/java.security
src/java.security.jgss/share/classes/javax/security/auth/kerberos/JavaxSecurityAuthKerberosAccessImpl.java
src/java.security.jgss/share/classes/javax/security/auth/kerberos/KerberosTicket.java
src/java.security.jgss/share/classes/sun/security/jgss/krb5/Krb5Context.java
src/java.security.jgss/share/classes/sun/security/jgss/krb5/Krb5InitCredential.java
src/java.security.jgss/share/classes/sun/security/jgss/krb5/Krb5MechFactory.java
src/java.security.jgss/share/classes/sun/security/jgss/krb5/Krb5NameElement.java
src/java.security.jgss/share/classes/sun/security/jgss/krb5/Krb5ProxyCredential.java
src/java.security.jgss/share/classes/sun/security/jgss/krb5/Krb5Util.java
src/java.security.jgss/share/classes/sun/security/krb5/Credentials.java
src/java.security.jgss/share/classes/sun/security/krb5/JavaxSecurityAuthKerberosAccess.java
src/java.security.jgss/share/classes/sun/security/krb5/Realm.java
src/java.security.jgss/share/classes/sun/security/krb5/internal/ccache/CCacheInputStream.java
src/java.security.jgss/share/classes/sun/security/krb5/internal/ccache/CCacheOutputStream.java
src/java.security.jgss/share/classes/sun/security/krb5/internal/ccache/Credentials.java
src/java.security.jgss/share/classes/sun/security/krb5/internal/ccache/CredentialsCache.java
src/java.security.jgss/share/classes/sun/security/krb5/internal/ccache/FileCredentialsCache.java
src/java.security.jgss/windows/classes/sun/security/krb5/internal/tools/Klist.java
src/jdk.security.auth/share/classes/com/sun/security/auth/module/Krb5LoginModule.java
--- a/src/java.base/share/conf/security/java.security	Mon Mar 25 17:15:27 2019 +0000
+++ b/src/java.base/share/conf/security/java.security	Fri Apr 19 10:22:20 2019 +0800
@@ -1213,3 +1213,32 @@
 # if this property is not enabled.
 #
 jdk.security.caDistrustPolicies=SYMANTEC_TLS
+
+#
+# Policies for the proxy_impersonator Kerberos ccache configuration entry
+#
+# The proxy_impersonator ccache configuration entry indicates that the ccache
+# is a synthetic delegated credential for use with S4U2Proxy by an intermediate
+# server. The ccache file should also contain the TGT of this server and
+# an evidence ticket from the default principal of the ccache to this server.
+#
+# This security property determines how Java uses this configuration entry.
+# There are 3 possible values:
+#
+#  no-impersonate     - Ignore this configuration entry, and always act as
+#                       the owner of the TGT (if it exists).
+#
+#  try-impersonate    - Try impersonation when this configuration entry exists.
+#                       If no matching TGT or evidence ticket is found,
+#                       fallback to no-impersonate.
+#
+#  always-impersonate - Always impersonate when this configuration entry exists.
+#                       If no matching TGT or evidence ticket is found,
+#                       no initial credential is read from the ccache.
+#
+# The default value is "always-impersonate".
+#
+# If a system property of the same name is also specified, it supersedes the
+# security property value defined here.
+#
+#jdk.security.krb5.default.initiate.credential=always-impersonate
--- a/src/java.security.jgss/share/classes/javax/security/auth/kerberos/JavaxSecurityAuthKerberosAccessImpl.java	Mon Mar 25 17:15:27 2019 +0000
+++ b/src/java.security.jgss/share/classes/javax/security/auth/kerberos/JavaxSecurityAuthKerberosAccessImpl.java	Fri Apr 19 10:22:20 2019 +0800
@@ -49,4 +49,12 @@
     public void kerberosTicketSetServerAlias(KerberosTicket t, KerberosPrincipal a) {
         t.serverAlias = a;
     }
+
+    public KerberosTicket kerberosTicketGetProxy(KerberosTicket t) {
+        return t.proxy;
+    }
+
+    public void kerberosTicketSetProxy(KerberosTicket t, KerberosTicket p) {
+        t.proxy = p;
+    }
 }
--- a/src/java.security.jgss/share/classes/javax/security/auth/kerberos/KerberosTicket.java	Mon Mar 25 17:15:27 2019 +0000
+++ b/src/java.security.jgss/share/classes/javax/security/auth/kerberos/KerberosTicket.java	Fri Apr 19 10:22:20 2019 +0800
@@ -29,11 +29,13 @@
 import java.util.Date;
 import java.util.Arrays;
 import java.net.InetAddress;
+import java.util.Objects;
 import javax.crypto.SecretKey;
 import javax.security.auth.Refreshable;
 import javax.security.auth.Destroyable;
 import javax.security.auth.RefreshFailedException;
 import javax.security.auth.DestroyFailedException;
+
 import sun.security.util.HexDumpEncoder;
 
 /**
@@ -190,8 +192,13 @@
      * @serial
      */
 
+    private InetAddress[] clientAddresses;
 
-    private InetAddress[] clientAddresses;
+    /**
+     * Evidence ticket if proxy_impersonator. This field can be accessed
+     * by KerberosSecrets. It's serialized.
+     */
+    KerberosTicket proxy = null;
 
     private transient boolean destroyed = false;
 
@@ -711,6 +718,7 @@
                 "Renew Till = " + String.valueOf(renewTill) + "\n" +
                 "Client Addresses " +
                 (clientAddresses == null ? " Null " : caddrString.toString() +
+                (proxy == null ? "" : "\nwith a proxy ticket") +
                 "\n"));
     }
 
@@ -748,6 +756,10 @@
 
         // clientAddress may be null, the array's hashCode is 0
         result = result * 37 + Arrays.hashCode(clientAddresses);
+
+        if (proxy != null) {
+            result = result * 37 + proxy.hashCode();
+        }
         return result * 37 + Arrays.hashCode(flags);
     }
 
@@ -820,6 +832,10 @@
             }
         }
 
+        if (!Objects.equals(proxy, otherTicket.proxy)) {
+            return false;
+        }
+
         return true;
     }
 
--- a/src/java.security.jgss/share/classes/sun/security/jgss/krb5/Krb5Context.java	Mon Mar 25 17:15:27 2019 +0000
+++ b/src/java.security.jgss/share/classes/sun/security/jgss/krb5/Krb5Context.java	Fri Apr 19 10:22:20 2019 +0800
@@ -617,6 +617,8 @@
                     if (myCred == null) {
                         myCred = Krb5InitCredential.getInstance(caller, myName,
                                               GSSCredential.DEFAULT_LIFETIME);
+                        myCred = Krb5ProxyCredential.tryImpersonation(
+                                caller, (Krb5InitCredential)myCred);
                     } else if (!myCred.isInitiatorCredential()) {
                         throw new GSSException(errorCode, -1,
                                            "No TGT available");
@@ -653,8 +655,8 @@
                                     // highly consider just calling:
                                     // Subject.getSubject
                                     // SubjectComber.find
-                                    // instead of Krb5Util.getTicket
-                                    return Krb5Util.getTicket(
+                                    // instead of Krb5Util.getServiceTicket
+                                    return Krb5Util.getServiceTicket(
                                         GSSCaller.CALLER_UNKNOWN,
                                         // since it's useSubjectCredsOnly here,
                                         // don't worry about the null
--- a/src/java.security.jgss/share/classes/sun/security/jgss/krb5/Krb5InitCredential.java	Mon Mar 25 17:15:27 2019 +0000
+++ b/src/java.security.jgss/share/classes/sun/security/jgss/krb5/Krb5InitCredential.java	Fri Apr 19 10:22:20 2019 +0800
@@ -55,6 +55,7 @@
 
     private Krb5NameElement name;
     private Credentials krb5Credentials;
+    public KerberosTicket proxyTicket;
 
     private Krb5InitCredential(Krb5NameElement name,
                                byte[] asn1Encoding,
@@ -173,7 +174,7 @@
         KerberosPrincipal serverAlias = KerberosSecrets
                 .getJavaxSecurityAuthKerberosAccess()
                 .kerberosTicketGetServerAlias(tgt);
-        return new Krb5InitCredential(name,
+        Krb5InitCredential result = new Krb5InitCredential(name,
                                       tgt.getEncoded(),
                                       tgt.getClient(),
                                       clientAlias,
@@ -187,6 +188,9 @@
                                       tgt.getEndTime(),
                                       tgt.getRenewTill(),
                                       tgt.getClientAddresses());
+        result.proxyTicket = KerberosSecrets.getJavaxSecurityAuthKerberosAccess().
+            kerberosTicketGetProxy(tgt);
+        return result;
     }
 
     static Krb5InitCredential getInstance(Krb5NameElement name,
@@ -369,9 +373,9 @@
                 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(
+                    return Krb5Util.getInitialTicket(
                         realCaller,
-                        clientPrincipal, null, acc);
+                        clientPrincipal, acc);
                         }});
         } catch (PrivilegedActionException e) {
             GSSException ge =
--- a/src/java.security.jgss/share/classes/sun/security/jgss/krb5/Krb5MechFactory.java	Mon Mar 25 17:15:27 2019 +0000
+++ b/src/java.security.jgss/share/classes/sun/security/jgss/krb5/Krb5MechFactory.java	Fri Apr 19 10:22:20 2019 +0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2019, 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
@@ -124,6 +124,8 @@
                 usage == GSSCredential.INITIATE_AND_ACCEPT) {
                 credElement = Krb5InitCredential.getInstance
                     (caller, (Krb5NameElement) name, initLifetime);
+                credElement = Krb5ProxyCredential.tryImpersonation(
+                        caller, (Krb5InitCredential)credElement);
                 checkInitCredPermission
                     ((Krb5NameElement) credElement.getName());
             } else if (usage == GSSCredential.ACCEPT_ONLY) {
--- a/src/java.security.jgss/share/classes/sun/security/jgss/krb5/Krb5NameElement.java	Mon Mar 25 17:15:27 2019 +0000
+++ b/src/java.security.jgss/share/classes/sun/security/jgss/krb5/Krb5NameElement.java	Fri Apr 19 10:22:20 2019 +0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2019, 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
@@ -144,7 +144,7 @@
         return new Krb5NameElement(principalName, gssNameStr, gssNameType);
     }
 
-    static Krb5NameElement getInstance(PrincipalName principalName) {
+    public static Krb5NameElement getInstance(PrincipalName principalName) {
         return new Krb5NameElement(principalName,
                                    principalName.getName(),
                                    Krb5MechFactory.NT_GSS_KRB5_PRINCIPAL);
--- a/src/java.security.jgss/share/classes/sun/security/jgss/krb5/Krb5ProxyCredential.java	Mon Mar 25 17:15:27 2019 +0000
+++ b/src/java.security.jgss/share/classes/sun/security/jgss/krb5/Krb5ProxyCredential.java	Fri Apr 19 10:22:20 2019 +0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2019, 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
@@ -26,10 +26,17 @@
 package sun.security.jgss.krb5;
 
 import org.ietf.jgss.*;
+import sun.security.jgss.GSSCaller;
 import sun.security.jgss.spi.*;
-import java.util.Date;
+
+import java.io.IOException;
+
+import sun.security.krb5.Credentials;
+import sun.security.krb5.KrbException;
 import sun.security.krb5.internal.Ticket;
 
+import javax.security.auth.kerberos.KerberosTicket;
+
 /**
  * Implements the krb5 proxy credential element used in constrained
  * delegation. It is used in both impersonation (where there is no Kerberos 5
@@ -112,4 +119,24 @@
         throw new GSSException(GSSException.FAILURE, -1,
                 "Only an initiate credentials can impersonate");
     }
+
+    // Try to see if a default credential should act as an impersonator.
+    static Krb5CredElement tryImpersonation(GSSCaller caller,
+            Krb5InitCredential initiator) throws GSSException {
+
+        try {
+            KerberosTicket proxy = initiator.proxyTicket;
+            if (proxy != null) {
+                Credentials proxyCreds = Krb5Util.ticketToCreds(proxy);
+                return new Krb5ProxyCredential(initiator,
+                        Krb5NameElement.getInstance(proxyCreds.getClient()),
+                        proxyCreds.getTicket());
+            } else {
+                return initiator;
+            }
+        } catch (KrbException | IOException e) {
+            throw new GSSException(GSSException.DEFECTIVE_CREDENTIAL, -1,
+                    "Cannot create proxy credential");
+        }
+    }
 }
--- a/src/java.security.jgss/share/classes/sun/security/jgss/krb5/Krb5Util.java	Mon Mar 25 17:15:27 2019 +0000
+++ b/src/java.security.jgss/share/classes/sun/security/jgss/krb5/Krb5Util.java	Fri Apr 19 10:22:20 2019 +0800
@@ -60,11 +60,8 @@
     /**
      * Retrieves the ticket corresponding to the client/server principal
      * pair from the Subject in the specified AccessControlContext.
-     * If the ticket can not be found in the Subject, and if
-     * useSubjectCredsOnly is false, then obtain ticket from
-     * a LoginContext.
      */
-    static KerberosTicket getTicket(GSSCaller caller,
+    static KerberosTicket getServiceTicket(GSSCaller caller,
         String clientPrincipal, String serverPrincipal,
         AccessControlContext acc) throws LoginException {
 
@@ -74,11 +71,31 @@
             SubjectComber.find(accSubj, serverPrincipal, clientPrincipal,
                   KerberosTicket.class);
 
+        return ticket;
+    }
+
+    /**
+     * Retrieves the initial TGT corresponding to the client principal
+     * from the Subject in the specified AccessControlContext.
+     * If the ticket can not be found in the Subject, and if
+     * useSubjectCredsOnly is false, then obtain ticket from
+     * a LoginContext.
+     */
+    static KerberosTicket getInitialTicket(GSSCaller caller,
+            String clientPrincipal,
+            AccessControlContext acc) throws LoginException {
+
+        // Try to get ticket from acc's Subject
+        Subject accSubj = Subject.getSubject(acc);
+        KerberosTicket ticket =
+                SubjectComber.find(accSubj, null, clientPrincipal,
+                        KerberosTicket.class);
+
         // Try to get ticket from Subject obtained from GSSUtil
         if (ticket == null && !GSSUtil.useSubjectCredsOnly(caller)) {
             Subject subject = GSSUtil.login(caller, GSSUtil.GSS_KRB5_MECH_OID);
             ticket = SubjectComber.find(subject,
-                serverPrincipal, clientPrincipal, KerberosTicket.class);
+                    null, clientPrincipal, KerberosTicket.class);
         }
         return ticket;
     }
--- a/src/java.security.jgss/share/classes/sun/security/krb5/Credentials.java	Mon Mar 25 17:15:27 2019 +0000
+++ b/src/java.security.jgss/share/classes/sun/security/krb5/Credentials.java	Fri Apr 19 10:22:20 2019 +0800
@@ -59,13 +59,23 @@
     KerberosTime endTime;
     KerberosTime renewTill;
     HostAddresses cAddr;
-    EncryptionKey serviceKey;
     AuthorizationData authzData;
     private static boolean DEBUG = Krb5.DEBUG;
     private static CredentialsCache cache;
     static boolean alreadyLoaded = false;
     private static boolean alreadyTried = false;
 
+    private Credentials proxy = null;
+
+    public Credentials getProxy() {
+        return proxy;
+    }
+
+    public Credentials setProxy(Credentials proxy) {
+        this.proxy = proxy;
+        return this;
+    }
+
     // Read native ticket with session key type in the given list
     private static native Credentials acquireDefaultNativeCreds(int[] eTypes);
 
@@ -361,20 +371,19 @@
             return null;
         }
 
-        sun.security.krb5.internal.ccache.Credentials tgtCred  =
-            ccache.getDefaultCreds();
+        Credentials tgtCred = ccache.getInitialCreds();
 
         if (tgtCred == null) {
             return null;
         }
 
-        if (EType.isSupported(tgtCred.getEType())) {
-            return tgtCred.setKrbCreds();
+        if (EType.isSupported(tgtCred.key.getEType())) {
+            return tgtCred;
         } else {
             if (DEBUG) {
                 System.out.println(
                     ">>> unsupported key type found the default TGT: " +
-                    tgtCred.getEType());
+                    tgtCred.key.getEType());
             }
             return null;
         }
@@ -409,20 +418,19 @@
             cache = CredentialsCache.getInstance();
         }
         if (cache != null) {
-            sun.security.krb5.internal.ccache.Credentials temp =
-                cache.getDefaultCreds();
+            Credentials temp = cache.getInitialCreds();
             if (temp != null) {
                 if (DEBUG) {
                     System.out.println(">>> KrbCreds found the default ticket"
                             + " granting ticket in credential cache.");
                 }
-                if (EType.isSupported(temp.getEType())) {
-                    result = temp.setKrbCreds();
+                if (EType.isSupported(temp.key.getEType())) {
+                    result = temp;
                 } else {
                     if (DEBUG) {
                         System.out.println(
                             ">>> unsupported key type found the default TGT: " +
-                            temp.getEType());
+                            temp.key.getEType());
                     }
                 }
             }
@@ -499,10 +507,6 @@
         return cache;
     }
 
-    public EncryptionKey getServiceKey() {
-        return serviceKey;
-    }
-
     /*
      * Prints out debug info.
      */
--- a/src/java.security.jgss/share/classes/sun/security/krb5/JavaxSecurityAuthKerberosAccess.java	Mon Mar 25 17:15:27 2019 +0000
+++ b/src/java.security.jgss/share/classes/sun/security/krb5/JavaxSecurityAuthKerberosAccess.java	Fri Apr 19 10:22:20 2019 +0800
@@ -28,8 +28,6 @@
 import javax.security.auth.kerberos.KerberosPrincipal;
 import javax.security.auth.kerberos.KerberosTicket;
 import javax.security.auth.kerberos.KeyTab;
-import sun.security.krb5.EncryptionKey;
-import sun.security.krb5.PrincipalName;
 
 /**
  * An unsafe tunnel to get non-public access to classes in the
@@ -49,4 +47,13 @@
     public KerberosPrincipal kerberosTicketGetServerAlias(KerberosTicket t);
 
     public void kerberosTicketSetServerAlias(KerberosTicket t, KerberosPrincipal a);
+    /**
+     * Returns the proxy for a KerberosTicket.
+     */
+    public KerberosTicket kerberosTicketGetProxy(KerberosTicket t);
+
+    /**
+     * Sets the proxy for a KerberosTicket.
+     */
+    public void kerberosTicketSetProxy(KerberosTicket t, KerberosTicket p);
 }
--- a/src/java.security.jgss/share/classes/sun/security/krb5/Realm.java	Mon Mar 25 17:15:27 2019 +0000
+++ b/src/java.security.jgss/share/classes/sun/security/krb5/Realm.java	Fri Apr 19 10:22:20 2019 +0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2019, 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
@@ -186,7 +186,6 @@
             return false;
         for (int i = 0; i < name.length(); i++) {
             if (name.charAt(i) == '/' ||
-                name.charAt(i) == ':' ||
                 name.charAt(i) == '\0') {
                 return false;
             }
--- a/src/java.security.jgss/share/classes/sun/security/krb5/internal/ccache/CCacheInputStream.java	Mon Mar 25 17:15:27 2019 +0000
+++ b/src/java.security.jgss/share/classes/sun/security/krb5/internal/ccache/CCacheInputStream.java	Fri Apr 19 10:22:20 2019 +0800
@@ -325,16 +325,13 @@
     }
 
     /**
-     * Reads the next cred in stream.
-     * @return the next cred, null if ticket or second_ticket unparseable.
+     * Reads the next cred or config entry in stream.
+     * @return the next cred or config entry, null if data unparseable.
      *
-     * Note: MIT krb5 1.8.1 might generate a config entry with server principal
-     * X-CACHECONF:/krb5_ccache_conf_data/fast_avail/krbtgt/REALM@REALM. The
-     * entry is used by KDC to inform the client that it support certain
-     * features. Its ticket is not a valid krb5 ticket and thus this method
-     * returns null.
+     * When data is unparseable, this method makes sure the correct number of
+     * bytes are consumed so it's safe to start reading the next element.
      */
-    Credentials readCred(int version) throws IOException,RealmException, KrbApErrException, Asn1Exception {
+    Object readCred(int version) throws IOException,RealmException, KrbApErrException, Asn1Exception {
         PrincipalName cpname = null;
         try {
             cpname = readPrincipal(version);
@@ -396,12 +393,23 @@
         }
 
         try {
+            if (spname.getRealmString().equals("X-CACHECONF:")) {
+                String[] nameParts = spname.getNameStrings();
+                if (nameParts[0].equals("krb5_ccache_conf_data")) {
+                    return new CredentialsCache.ConfigEntry(nameParts[1],
+                            nameParts.length > 2 ? new PrincipalName(nameParts[2]) : null,
+                            ticketData);
+                }
+            }
             return new Credentials(cpname, spname, key, authtime, starttime,
-                endtime, renewTill, skey, tFlags,
-                addrs, auData,
-                ticketData != null ? new Ticket(ticketData) : null,
-                ticketData2 != null ? new Ticket(ticketData2) : null);
+                    endtime, renewTill, skey, tFlags,
+                    addrs, auData,
+                    ticketData != null ? new Ticket(ticketData) : null,
+                    ticketData2 != null ? new Ticket(ticketData2) : null);
         } catch (Exception e) {     // If any of new Ticket(*) fails.
+            if (DEBUG) {
+                e.printStackTrace(System.out);
+            }
             return null;
         }
     }
--- a/src/java.security.jgss/share/classes/sun/security/krb5/internal/ccache/CCacheOutputStream.java	Mon Mar 25 17:15:27 2019 +0000
+++ b/src/java.security.jgss/share/classes/sun/security/krb5/internal/ccache/CCacheOutputStream.java	Fri Apr 19 10:22:20 2019 +0800
@@ -31,7 +31,6 @@
 package sun.security.krb5.internal.ccache;
 
 import java.io.IOException;
-import java.io.FileOutputStream;
 import java.io.OutputStream;
 import sun.security.krb5.internal.util.KrbDataOutputStream;
 import sun.security.krb5.*;
@@ -98,6 +97,21 @@
         writeTicket(creds.secondTicket);
     }
 
+    public void addConfigEntry(PrincipalName cname, CredentialsCache.ConfigEntry e)
+            throws IOException {
+        cname.writePrincipal(this);
+        e.getSName().writePrincipal(this);
+        write16(0); write16(0); write32(0);
+        write32(0); write32(0); write32(0); write32(0);
+        write8(0);
+        write32(0);
+        write32(0);
+        write32(0);
+        write32(e.getData().length);
+        write(e.getData());
+        write32(0);
+    }
+
     void writeTicket(Ticket t) throws IOException, Asn1Exception {
         if (t == null) {
             write32(0);
--- a/src/java.security.jgss/share/classes/sun/security/krb5/internal/ccache/Credentials.java	Mon Mar 25 17:15:27 2019 +0000
+++ b/src/java.security.jgss/share/classes/sun/security/krb5/internal/ccache/Credentials.java	Fri Apr 19 10:22:20 2019 +0800
@@ -169,6 +169,18 @@
         return sname;
     }
 
+    public Ticket getTicket() throws RealmException {
+        return ticket;
+    }
+
+    public PrincipalName getServicePrincipal2() throws RealmException {
+        return secondTicket == null ? null : secondTicket.sname;
+    }
+
+    public PrincipalName getClientPrincipal() throws RealmException {
+        return cname;
+    }
+
     public sun.security.krb5.Credentials setKrbCreds() {
         // Note: We will not pass authorizationData to s.s.k.Credentials. The
         // field in that class will be passed to Krb5Context as the return
@@ -209,7 +221,15 @@
         return key.getEType();
     }
 
+    public EncryptionKey getKey() {
+        return key;
+    }
+
     public int getTktEType() {
         return ticket.encPart.getEType();
     }
+
+    public int getTktEType2() {
+        return (secondTicket == null) ? 0 : secondTicket.encPart.getEType();
+    }
 }
--- a/src/java.security.jgss/share/classes/sun/security/krb5/internal/ccache/CredentialsCache.java	Mon Mar 25 17:15:27 2019 +0000
+++ b/src/java.security.jgss/share/classes/sun/security/krb5/internal/ccache/CredentialsCache.java	Fri Apr 19 10:22:20 2019 +0800
@@ -32,14 +32,9 @@
 
 import sun.security.krb5.*;
 import sun.security.krb5.internal.*;
-import java.util.StringTokenizer;
-import java.util.Vector;
+
+import java.util.List;
 import java.io.IOException;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.BufferedReader;
-import java.io.InputStreamReader;
 
 /**
  * CredentialsCache stores credentials(tickets, session keys, etc) in a semi-permanent store
@@ -120,6 +115,62 @@
     public abstract void save() throws IOException, KrbException;
     public abstract Credentials[] getCredsList();
     public abstract Credentials getDefaultCreds();
+    public abstract sun.security.krb5.Credentials getInitialCreds();
     public abstract Credentials getCreds(PrincipalName sname);
     public abstract Credentials getCreds(LoginOptions options, PrincipalName sname);
+    public abstract void addConfigEntry(ConfigEntry e);
+    public abstract List<ConfigEntry> getConfigEntries();
+
+    public ConfigEntry getConfigEntry(String name) {
+        List<ConfigEntry> entries = getConfigEntries();
+        if (entries != null) {
+            for (ConfigEntry e : entries) {
+                if (e.getName().equals(name)) {
+                    return e;
+                }
+            }
+        }
+        return null;
+    }
+
+    public static class ConfigEntry {
+
+        public ConfigEntry(String name, PrincipalName princ, byte[] data) {
+            this.name = name;
+            this.princ = princ;
+            this.data = data;
+        }
+
+        private final String name;
+        private final PrincipalName princ;
+        private final byte[] data; // not worth cloning
+
+        public String getName() {
+            return name;
+        }
+
+        public PrincipalName getPrinc() {
+            return princ;
+        }
+
+        public byte[] getData() {
+            return data;
+        }
+
+        @Override
+        public String toString() {
+            return name + (princ != null ? ("." + princ) : "")
+                    + ": " + new String(data);
+        }
+
+        public PrincipalName getSName() {
+            try {
+                return new PrincipalName("krb5_ccache_conf_data/" + name
+                        + (princ != null ? ("/" + princ) : "")
+                        + "@X-CACHECONF:");
+            } catch (RealmException e) {
+                throw new AssertionError(e);
+            }
+        }
+    }
 }
--- a/src/java.security.jgss/share/classes/sun/security/krb5/internal/ccache/FileCredentialsCache.java	Mon Mar 25 17:15:27 2019 +0000
+++ b/src/java.security.jgss/share/classes/sun/security/krb5/internal/ccache/FileCredentialsCache.java	Fri Apr 19 10:22:20 2019 +0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2019, 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
@@ -36,6 +36,12 @@
 import sun.security.action.GetPropertyAction;
 import sun.security.krb5.*;
 import sun.security.krb5.internal.*;
+import sun.security.util.SecurityProperties;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 import java.util.StringTokenizer;
 import java.util.Vector;
 import java.io.IOException;
@@ -182,9 +188,13 @@
                 primaryPrincipal = p;
             credentialsList = new Vector<Credentials>();
             while (cis.available() > 0) {
-                Credentials cred = cis.readCred(version);
+                Object cred = cis.readCred(version);
                 if (cred != null) {
-                    credentialsList.addElement(cred);
+                    if (cred instanceof Credentials) {
+                        credentialsList.addElement((Credentials)cred);
+                    } else {
+                        addConfigEntry((CredentialsCache.ConfigEntry)cred);
+                    }
                 }
             }
         }
@@ -255,6 +265,9 @@
                     cos.addCreds(tmp[i]);
                 }
             }
+            for (ConfigEntry e : getConfigEntries()) {
+                cos.addConfigEntry(primaryPrincipal, e);
+            }
         }
     }
 
@@ -307,6 +320,17 @@
         }
     }
 
+    private List<ConfigEntry> configEntries = new ArrayList<>();
+
+    @Override
+    public void addConfigEntry(ConfigEntry e) {
+        configEntries.add(e);
+    }
+
+    @Override
+    public List<ConfigEntry> getConfigEntries() {
+        return Collections.unmodifiableList(configEntries);
+    }
 
     /**
      * Gets a credentials for a specified service.
@@ -326,6 +350,81 @@
         return null;
     }
 
+    public sun.security.krb5.Credentials getInitialCreds() {
+
+        Credentials defaultCreds = getDefaultCreds();
+        if (defaultCreds == null) {
+            return null;
+        }
+        sun.security.krb5.Credentials tgt = defaultCreds.setKrbCreds();
+
+        CredentialsCache.ConfigEntry entry = getConfigEntry("proxy_impersonator");
+        if (entry == null) {
+            if (DEBUG) {
+                System.out.println("get normal credential");
+            }
+            return tgt;
+        }
+
+        boolean force;
+        String prop = SecurityProperties.privilegedGetOverridable(
+                "jdk.security.krb5.default.initiate.credential");
+        if (prop == null) {
+            prop = "always-impersonate";
+        }
+        switch (prop) {
+            case "no-impersonate": // never try impersonation
+                if (DEBUG) {
+                    System.out.println("get normal credential");
+                }
+                return tgt;
+            case "try-impersonate":
+                force = false;
+                break;
+            case "always-impersonate":
+                force = true;
+                break;
+            default:
+                throw new RuntimeException(
+                        "Invalid jdk.security.krb5.default.initiate.credential");
+        }
+
+        try {
+            PrincipalName service = new PrincipalName(
+                    new String(entry.getData(), StandardCharsets.UTF_8));
+            if (!tgt.getClient().equals(service)) {
+                if (DEBUG) {
+                    System.out.println("proxy_impersonator does not match service name");
+                }
+                return force ? null : tgt;
+            }
+            PrincipalName client = getPrimaryPrincipal();
+            Credentials proxy = null;
+            for (Credentials c : getCredsList()) {
+                if (c.getClientPrincipal().equals(client)
+                        && c.getServicePrincipal().equals(service)) {
+                    proxy = c;
+                    break;
+                }
+            }
+            if (proxy == null) {
+                if (DEBUG) {
+                    System.out.println("Cannot find evidence ticket in ccache");
+                }
+                return force ? null : tgt;
+            }
+            if (DEBUG) {
+                System.out.println("Get proxied credential");
+            }
+            return tgt.setProxy(proxy.setKrbCreds());
+        } catch (KrbException e) {
+            if (DEBUG) {
+                System.out.println("Impersonation with ccache failed");
+            }
+            return force ? null : tgt;
+        }
+    }
+
     public Credentials getDefaultCreds() {
         Credentials[] list = getCredsList();
         if (list == null) {
--- a/src/java.security.jgss/windows/classes/sun/security/krb5/internal/tools/Klist.java	Mon Mar 25 17:15:27 2019 +0000
+++ b/src/java.security.jgss/windows/classes/sun/security/krb5/internal/tools/Klist.java	Fri Apr 19 10:22:20 2019 +0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2003, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2003, 2019, 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
@@ -31,6 +31,8 @@
 package sun.security.krb5.internal.tools;
 
 import java.net.InetAddress;
+import java.util.List;
+
 import sun.security.krb5.*;
 import sun.security.krb5.internal.*;
 import sun.security.krb5.internal.ccache.*;
@@ -249,6 +251,8 @@
                     String endtime;
                     String renewTill;
                     String servicePrincipal;
+                    PrincipalName servicePrincipal2;
+                    String clientPrincipal;
                     if (creds[i].getStartTime() != null) {
                         starttime = format(creds[i].getStartTime());
                     } else {
@@ -260,6 +264,18 @@
                     System.out.println("[" + (i + 1) + "] " +
                                        " Service Principal:  " +
                                        servicePrincipal);
+                    servicePrincipal2 =
+                            creds[i].getServicePrincipal2();
+                    if (servicePrincipal2 != null) {
+                        System.out.println("     Second Service:     "
+                                + servicePrincipal2);
+                    }
+                    clientPrincipal =
+                            creds[i].getClientPrincipal().toString();
+                    if (!clientPrincipal.equals(defaultPrincipal)) {
+                        System.out.println("     Client Principal:   " +
+                                clientPrincipal);
+                    }
                     System.out.println("     Valid starting:     " + starttime);
                     System.out.println("     Expires:            " + endtime);
                     if (creds[i].getRenewTill() != null) {
@@ -270,8 +286,15 @@
                     if (options[0] == 'e') {
                         String eskey = EType.toString(creds[i].getEType());
                         String etkt = EType.toString(creds[i].getTktEType());
-                        System.out.println("     EType (skey, tkt):  "
-                                + eskey + ", " + etkt);
+                        if (creds[i].getTktEType2() == 0) {
+                            System.out.println("     EType (skey, tkt):  "
+                                    + eskey + ", " + etkt);
+                        } else {
+                            String etkt2 = EType.toString(creds[i].getTktEType2());
+                            System.out.println("     EType (skey, tkts): "
+                                    + eskey + ", " + etkt
+                                    + ", " + etkt2);
+                        }
                     }
                     if (options[1] == 'f') {
                         System.out.println("     Flags:              " +
@@ -310,6 +333,15 @@
         } else {
             System.out.println("\nNo entries found.");
         }
+
+        List<CredentialsCache.ConfigEntry> configEntries
+                = cache.getConfigEntries();
+        if (configEntries != null && !configEntries.isEmpty()) {
+            System.out.println("\nConfig entries:");
+            for (CredentialsCache.ConfigEntry e : configEntries) {
+                System.out.println("     " + e);
+            }
+        }
     }
 
     void displayMessage(String target) {
--- a/src/jdk.security.auth/share/classes/com/sun/security/auth/module/Krb5LoginModule.java	Mon Mar 25 17:15:27 2019 +0000
+++ b/src/jdk.security.auth/share/classes/com/sun/security/auth/module/Krb5LoginModule.java	Fri Apr 19 10:22:20 2019 +0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2019, 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
@@ -644,6 +644,7 @@
                         // renew if ticket is old.
                         Credentials newCred = renewCredentials(cred);
                         if (newCred != null) {
+                            newCred.setProxy(cred.getProxy());
                             cred = newCred;
                         }
                     }
@@ -1070,6 +1071,10 @@
             // create Kerberos Ticket
             if (isInitiator) {
                 kerbTicket = Krb5Util.credsToTicket(cred);
+                if (cred.getProxy() != null) {
+                    KerberosSecrets.getJavaxSecurityAuthKerberosAccess()
+                            .kerberosTicketSetProxy(kerbTicket,Krb5Util.credsToTicket(cred.getProxy()));
+                }
             }
 
             if (storeKey && encKeys != null) {