8215032: Support Kerberos cross-realm referrals (RFC 6806)
authormbalao
Wed, 05 Jun 2019 01:42:11 -0300
changeset 55258 d65d3c37232c
parent 55257 442b86eb633c
child 55259 61fff1345ee6
8215032: Support Kerberos cross-realm referrals (RFC 6806) Reviewed-by: weijun
src/java.base/share/conf/security/java.security
src/java.security.jgss/share/classes/javax/security/auth/kerberos/KerberosPrincipal.java
src/java.security.jgss/share/classes/sun/security/krb5/Checksum.java
src/java.security.jgss/share/classes/sun/security/krb5/Config.java
src/java.security.jgss/share/classes/sun/security/krb5/KrbAsRep.java
src/java.security.jgss/share/classes/sun/security/krb5/KrbAsReq.java
src/java.security.jgss/share/classes/sun/security/krb5/KrbAsReqBuilder.java
src/java.security.jgss/share/classes/sun/security/krb5/KrbKdcRep.java
src/java.security.jgss/share/classes/sun/security/krb5/KrbTgsRep.java
src/java.security.jgss/share/classes/sun/security/krb5/KrbTgsReq.java
src/java.security.jgss/share/classes/sun/security/krb5/PrincipalName.java
src/java.security.jgss/share/classes/sun/security/krb5/internal/CredentialsUtil.java
src/java.security.jgss/share/classes/sun/security/krb5/internal/EncASRepPart.java
src/java.security.jgss/share/classes/sun/security/krb5/internal/EncKDCRepPart.java
src/java.security.jgss/share/classes/sun/security/krb5/internal/EncTGSRepPart.java
src/java.security.jgss/share/classes/sun/security/krb5/internal/KDCOptions.java
src/java.security.jgss/share/classes/sun/security/krb5/internal/KDCReq.java
src/java.security.jgss/share/classes/sun/security/krb5/internal/KRBError.java
src/java.security.jgss/share/classes/sun/security/krb5/internal/Krb5.java
src/java.security.jgss/share/classes/sun/security/krb5/internal/PAData.java
src/java.security.jgss/share/classes/sun/security/krb5/internal/ReferralsCache.java
src/java.security.jgss/share/classes/sun/security/krb5/internal/TicketFlags.java
src/java.security.jgss/share/classes/sun/security/krb5/internal/crypto/KeyUsage.java
test/jdk/sun/security/krb5/auto/KDC.java
test/jdk/sun/security/krb5/auto/ReferralsTest.java
--- a/src/java.base/share/conf/security/java.security	Thu Jun 06 10:03:22 2019 -0400
+++ b/src/java.base/share/conf/security/java.security	Wed Jun 05 01:42:11 2019 -0300
@@ -475,6 +475,31 @@
 krb5.kdc.bad.policy = tryLast
 
 #
+# Kerberos cross-realm referrals (RFC 6806)
+#
+# OpenJDK's Kerberos client supports cross-realm referrals as defined in
+# RFC 6806. This allows to setup more dynamic environments in which clients
+# do not need to know in advance how to reach the realm of a target principal
+# (either a user or service).
+#
+# When a client issues an AS or a TGS request, the "canonicalize" option
+# is set to announce support of this feature. A KDC server may fulfill the
+# request or reply referring the client to a different one. If referred,
+# the client will issue a new request and the cycle repeats.
+#
+# In addition to referrals, the "canonicalize" option allows the KDC server
+# to change the client name in response to an AS request. For security reasons,
+# RFC 6806 (section 11) FAST scheme is enforced.
+#
+# Disable Kerberos cross-realm referrals. Value may be overwritten with a
+# System property (-Dsun.security.krb5.disableReferrals).
+sun.security.krb5.disableReferrals=false
+
+# Maximum number of AS or TGS referrals to avoid infinite loops. Value may
+# be overwritten with a System property (-Dsun.security.krb5.maxReferrals).
+sun.security.krb5.maxReferrals=5
+
+#
 # Algorithm restrictions for certification path (CertPath) processing
 #
 # In some environments, certain algorithms or key lengths may be undesirable
--- a/src/java.security.jgss/share/classes/javax/security/auth/kerberos/KerberosPrincipal.java	Thu Jun 06 10:03:22 2019 -0400
+++ b/src/java.security.jgss/share/classes/javax/security/auth/kerberos/KerberosPrincipal.java	Wed Jun 05 01:42:11 2019 -0300
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000, 2017, 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
@@ -80,6 +80,11 @@
 
     public static final int KRB_NT_UID = 5;
 
+    /**
+     * Enterprise name (alias)
+     */
+    public static final int KRB_NT_ENTERPRISE = 10;
+
     private transient String fullName;
 
     private transient String realm;
--- a/src/java.security.jgss/share/classes/sun/security/krb5/Checksum.java	Thu Jun 06 10:03:22 2019 -0400
+++ b/src/java.security.jgss/share/classes/sun/security/krb5/Checksum.java	Wed Jun 05 01:42:11 2019 -0300
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000, 2017, 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
@@ -218,7 +218,7 @@
      * @exception IOException if an I/O error occurs while reading encoded data.
      *
      */
-    private Checksum(DerValue encoding) throws Asn1Exception, IOException {
+    public Checksum(DerValue encoding) throws Asn1Exception, IOException {
         DerValue der;
         if (encoding.getTag() != DerValue.tag_Sequence) {
             throw new Asn1Exception(Krb5.ASN1_BAD_ID);
--- a/src/java.security.jgss/share/classes/sun/security/krb5/Config.java	Thu Jun 06 10:03:22 2019 -0400
+++ b/src/java.security.jgss/share/classes/sun/security/krb5/Config.java	Wed Jun 05 01:42:11 2019 -0300
@@ -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
@@ -48,6 +48,7 @@
 import sun.security.action.GetPropertyAction;
 import sun.security.krb5.internal.crypto.EType;
 import sun.security.krb5.internal.Krb5;
+import sun.security.util.SecurityProperties;
 
 /**
  * This class maintains key-value pairs of Kerberos configurable constants
@@ -56,6 +57,41 @@
 
 public class Config {
 
+    /**
+     * {@systemProperty sun.security.krb5.disableReferrals} property
+     * indicating whether or not cross-realm referrals (RFC 6806) are
+     * enabled.
+     */
+    public static final boolean DISABLE_REFERRALS;
+
+    /**
+     * {@systemProperty sun.security.krb5.maxReferrals} property
+     * indicating the maximum number of cross-realm referral
+     * hops allowed.
+     */
+    public static final int MAX_REFERRALS;
+
+    static {
+        String disableReferralsProp =
+                SecurityProperties.privilegedGetOverridable(
+                        "sun.security.krb5.disableReferrals");
+        if (disableReferralsProp != null) {
+            DISABLE_REFERRALS = "true".equalsIgnoreCase(disableReferralsProp);
+        } else {
+            DISABLE_REFERRALS = false;
+        }
+
+        int maxReferralsValue = 5;
+        String maxReferralsProp =
+                SecurityProperties.privilegedGetOverridable(
+                        "sun.security.krb5.maxReferrals");
+        try {
+            maxReferralsValue = Integer.parseInt(maxReferralsProp);
+        } catch (NumberFormatException e) {
+        }
+        MAX_REFERRALS = maxReferralsValue;
+    }
+
     /*
      * Only allow a single instance of Config.
      */
--- a/src/java.security.jgss/share/classes/sun/security/krb5/KrbAsRep.java	Thu Jun 06 10:03:22 2019 -0400
+++ b/src/java.security.jgss/share/classes/sun/security/krb5/KrbAsRep.java	Wed Jun 05 01:42:11 2019 -0300
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000, 2012, 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
@@ -155,11 +155,11 @@
         rep.encKDCRepPart = enc_part;
 
         ASReq req = asReq.getMessage();
-        check(true, req, rep);
+        check(true, req, rep, dkey);
 
         creds = new Credentials(
                                 rep.ticket,
-                                req.reqBody.cname,
+                                rep.cname,
                                 enc_part.sname,
                                 enc_part.key,
                                 enc_part.flags,
--- a/src/java.security.jgss/share/classes/sun/security/krb5/KrbAsReq.java	Thu Jun 06 10:03:22 2019 -0400
+++ b/src/java.security.jgss/share/classes/sun/security/krb5/KrbAsReq.java	Wed Jun 05 01:42:11 2019 -0300
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000, 2012, 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,7 @@
 import sun.security.krb5.internal.crypto.KeyUsage;
 import java.io.IOException;
 import java.time.Instant;
+import java.util.Arrays;
 
 /**
  * This class encapsulates the KRB-AS-REQ message that the client
@@ -58,7 +59,8 @@
                       KerberosTime till,        // ok, will use
                       KerberosTime rtime,       // ok
                       int[] eTypes,             // NO
-                      HostAddresses addresses   // ok
+                      HostAddresses addresses,  // ok
+                      PAData[] extraPAs         // ok
                       )
             throws KrbException, IOException {
 
@@ -93,6 +95,15 @@
             paData[0] = new PAData( Krb5.PA_ENC_TIMESTAMP,
                                     encTs.asn1Encode());
         }
+        if (extraPAs != null && extraPAs.length > 0) {
+            if (paData == null) {
+                paData = new PAData[extraPAs.length];
+            } else {
+                paData = Arrays.copyOf(paData, paData.length + extraPAs.length);
+            }
+            System.arraycopy(extraPAs, 0, paData,
+                    paData.length - extraPAs.length, extraPAs.length);
+        }
 
         if (cname.getRealm() == null) {
             throw new RealmException(Krb5.REALM_NULL,
--- a/src/java.security.jgss/share/classes/sun/security/krb5/KrbAsReqBuilder.java	Thu Jun 06 10:03:22 2019 -0400
+++ b/src/java.security.jgss/share/classes/sun/security/krb5/KrbAsReqBuilder.java	Wed Jun 05 01:42:11 2019 -0300
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 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
@@ -262,7 +262,9 @@
      * @throws KrbException
      * @throws IOException
      */
-    private KrbAsReq build(EncryptionKey key) throws KrbException, IOException {
+    private KrbAsReq build(EncryptionKey key, ReferralsState referralsState)
+            throws KrbException, IOException {
+        PAData[] extraPAs = null;
         int[] eTypes;
         if (password != null) {
             eTypes = EType.getDefaults("default_tkt_enctypes");
@@ -272,6 +274,14 @@
                     ks);
             for (EncryptionKey k: ks) k.destroy();
         }
+        options = (options == null) ? new KDCOptions() : options;
+        if (referralsState.isEnabled()) {
+            options.set(KDCOptions.CANONICALIZE, true);
+            extraPAs = new PAData[]{ new PAData(Krb5.PA_REQ_ENC_PA_REP,
+                    new byte[]{}) };
+        } else {
+            options.set(KDCOptions.CANONICALIZE, false);
+        }
         return new KrbAsReq(key,
             options,
             cname,
@@ -280,7 +290,8 @@
             till,
             rtime,
             eTypes,
-            addresses);
+            addresses,
+            extraPAs);
     }
 
     /**
@@ -318,11 +329,15 @@
      */
     private KrbAsReqBuilder send() throws KrbException, IOException {
         boolean preAuthFailedOnce = false;
-        KdcComm comm = new KdcComm(cname.getRealmAsString());
+        KdcComm comm = null;
         EncryptionKey pakey = null;
+        ReferralsState referralsState = new ReferralsState();
         while (true) {
+            if (referralsState.refreshComm()) {
+                comm = new KdcComm(cname.getRealmAsString());
+            }
             try {
-                req = build(pakey);
+                req = build(pakey, referralsState);
                 rep = new KrbAsRep(comm.send(req.encoding()));
                 return this;
             } catch (KrbException ke) {
@@ -351,12 +366,69 @@
                     }
                     paList = kerr.getPA();  // Update current paList
                 } else {
+                    if (referralsState.handleError(ke)) {
+                        continue;
+                    }
                     throw ke;
                 }
             }
         }
     }
 
+    private final class ReferralsState {
+        private boolean enabled;
+        private int count;
+        private boolean refreshComm;
+
+        ReferralsState() throws KrbException {
+            if (Config.DISABLE_REFERRALS) {
+                if (cname.getNameType() == PrincipalName.KRB_NT_ENTERPRISE) {
+                    throw new KrbException("NT-ENTERPRISE principals only allowed" +
+                            " when referrals are enabled.");
+                }
+                enabled = false;
+            } else {
+                enabled = true;
+            }
+            refreshComm = true;
+        }
+
+        boolean handleError(KrbException ke) throws RealmException {
+            if (enabled) {
+                if (ke.returnCode() == Krb5.KRB_ERR_WRONG_REALM) {
+                    Realm referredRealm = ke.getError().getClientRealm();
+                    if (req.getMessage().reqBody.kdcOptions.get(KDCOptions.CANONICALIZE) &&
+                            referredRealm != null && referredRealm.toString().length() > 0 &&
+                            count < Config.MAX_REFERRALS) {
+                        cname = new PrincipalName(cname.getNameType(),
+                                cname.getNameStrings(), referredRealm);
+                        refreshComm = true;
+                        count++;
+                        return true;
+                    }
+                }
+                if (count < Config.MAX_REFERRALS &&
+                        cname.getNameType() != PrincipalName.KRB_NT_ENTERPRISE) {
+                    // Server may raise an error if CANONICALIZE is true.
+                    // Try CANONICALIZE false.
+                    enabled = false;
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        boolean refreshComm() {
+            boolean retRefreshComm = refreshComm;
+            refreshComm = false;
+            return retRefreshComm;
+        }
+
+        boolean isEnabled() {
+            return enabled;
+        }
+    }
+
     /**
      * Performs AS-REQ send and AS-REP receive.
      * Maybe a state is needed here, to divide prepare process and getCreds.
--- a/src/java.security.jgss/share/classes/sun/security/krb5/KrbKdcRep.java	Thu Jun 06 10:03:22 2019 -0400
+++ b/src/java.security.jgss/share/classes/sun/security/krb5/KrbKdcRep.java	Wed Jun 05 01:42:11 2019 -0300
@@ -31,23 +31,41 @@
 package sun.security.krb5;
 
 import sun.security.krb5.internal.*;
+import sun.security.krb5.internal.crypto.KeyUsage;
+import sun.security.util.DerInputStream;
 
 abstract class KrbKdcRep {
 
     static void check(
                       boolean isAsReq,
                       KDCReq req,
-                      KDCRep rep
+                      KDCRep rep,
+                      EncryptionKey replyKey
                       ) throws KrbApErrException {
 
-        if (isAsReq && !req.reqBody.cname.equals(rep.cname)) {
+        // cname change in AS-REP is allowed only if the client
+        // sent CANONICALIZE and the server supports RFC 6806 - Section 11
+        // FAST scheme (ENC-PA-REP flag).
+        if (isAsReq && !req.reqBody.cname.equals(rep.cname) &&
+                (!req.reqBody.kdcOptions.get(KDCOptions.CANONICALIZE) ||
+                 !rep.encKDCRepPart.flags.get(Krb5.TKT_OPTS_ENC_PA_REP))) {
             rep.encKDCRepPart.key.destroy();
             throw new KrbApErrException(Krb5.KRB_AP_ERR_MODIFIED);
         }
 
+        // sname change in TGS-REP is allowed only if client
+        // sent CANONICALIZE and new sname is a referral of
+        // the form krbtgt/TO-REALM.COM@FROM-REALM.COM.
         if (!req.reqBody.sname.equals(rep.encKDCRepPart.sname)) {
-            rep.encKDCRepPart.key.destroy();
-            throw new KrbApErrException(Krb5.KRB_AP_ERR_MODIFIED);
+            String[] snameStrings = rep.encKDCRepPart.sname.getNameStrings();
+            if (isAsReq || !req.reqBody.kdcOptions.get(KDCOptions.CANONICALIZE) ||
+                    snameStrings == null || snameStrings.length != 2 ||
+                    !snameStrings[0].equals(PrincipalName.TGS_DEFAULT_SRV_NAME) ||
+                    !rep.encKDCRepPart.sname.getRealmString().equals(
+                            req.reqBody.sname.getRealmString())) {
+                rep.encKDCRepPart.key.destroy();
+                throw new KrbApErrException(Krb5.KRB_AP_ERR_MODIFIED);
+            }
         }
 
         if (req.reqBody.getNonce() != rep.encKDCRepPart.nonce) {
@@ -118,5 +136,45 @@
                 }
             }
         }
+
+        // RFC 6806 - Section 11 mechanism check
+        if (rep.encKDCRepPart.flags.get(Krb5.TKT_OPTS_ENC_PA_REP) &&
+                req.reqBody.kdcOptions.get(KDCOptions.CANONICALIZE)) {
+            boolean reqPaReqEncPaRep = false;
+            boolean repPaReqEncPaRepValid = false;
+
+            // PA_REQ_ENC_PA_REP only required for AS requests
+            for (PAData pa : req.pAData) {
+                if (pa.getType() == Krb5.PA_REQ_ENC_PA_REP) {
+                    reqPaReqEncPaRep = true;
+                    break;
+                }
+            }
+
+            if (rep.encKDCRepPart.pAData != null) {
+                for (PAData pa : rep.encKDCRepPart.pAData) {
+                    if (pa.getType() == Krb5.PA_REQ_ENC_PA_REP) {
+                        try {
+                            Checksum repCksum = new Checksum(
+                                    new DerInputStream(
+                                            pa.getValue()).getDerValue());
+                            repPaReqEncPaRepValid =
+                                    repCksum.verifyKeyedChecksum(
+                                            req.asn1Encode(), replyKey,
+                                            KeyUsage.KU_AS_REQ);
+                        } catch (Exception e) {
+                            if (Krb5.DEBUG) {
+                                e.printStackTrace();
+                            }
+                        }
+                        break;
+                    }
+                }
+            }
+
+            if (reqPaReqEncPaRep && !repPaReqEncPaRepValid) {
+                throw new KrbApErrException(Krb5.KRB_AP_ERR_MODIFIED);
+            }
+        }
     }
 }
--- a/src/java.security.jgss/share/classes/sun/security/krb5/KrbTgsRep.java	Thu Jun 06 10:03:22 2019 -0400
+++ b/src/java.security.jgss/share/classes/sun/security/krb5/KrbTgsRep.java	Wed Jun 05 01:42:11 2019 -0300
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000, 2012, 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
@@ -84,7 +84,7 @@
         EncTGSRepPart enc_part = new EncTGSRepPart(ref);
         rep.encKDCRepPart = enc_part;
 
-        check(false, req, rep);
+        check(false, req, rep, tgsReq.tgsReqKey);
 
         this.creds = new Credentials(rep.ticket,
                                 rep.cname,
--- a/src/java.security.jgss/share/classes/sun/security/krb5/KrbTgsReq.java	Thu Jun 06 10:03:22 2019 -0400
+++ b/src/java.security.jgss/share/classes/sun/security/krb5/KrbTgsReq.java	Wed Jun 05 01:42:11 2019 -0300
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000, 2017, 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,7 @@
 import java.io.IOException;
 import java.net.UnknownHostException;
 import java.time.Instant;
+import java.util.Arrays;
 
 /**
  * This class encapsulates a Kerberos TGS-REQ that is sent from the
@@ -57,59 +58,23 @@
     private byte[] ibuf;
 
     // Used in CredentialsUtil
-    public KrbTgsReq(Credentials asCreds,
-                     PrincipalName sname)
+    public KrbTgsReq(KDCOptions options, Credentials asCreds,
+            PrincipalName cname, PrincipalName sname,
+            Ticket[] additionalTickets, PAData[] extraPAs)
         throws KrbException, IOException {
-        this(new KDCOptions(),
-            asCreds,
-            sname,
-            null, // KerberosTime from
-            null, // KerberosTime till
-            null, // KerberosTime rtime
-            null, // eTypes, // null, // int[] eTypes
-            null, // HostAddresses addresses
-            null, // AuthorizationData authorizationData
-            null, // Ticket[] additionalTickets
-            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
+        this(options,
+             asCreds,
+             cname,
+             sname,
+             null, // KerberosTime from
+             null, // KerberosTime till
+             null, // KerberosTime rtime
+             null, // int[] eTypes
+             null, // HostAddresses addresses
+             null, // AuthorizationData authorizationData
+             additionalTickets,
+             null, // EncryptionKey subKey
+             extraPAs);
     }
 
     // Called by Credentials, KrbCred
@@ -143,7 +108,7 @@
             AuthorizationData authorizationData,
             Ticket[] additionalTickets,
             EncryptionKey subKey,
-            PAData extraPA) throws KrbException, IOException {
+            PAData[] extraPAs) throws KrbException, IOException {
 
         princName = cname;
         servName = sname;
@@ -216,7 +181,7 @@
                 authorizationData,
                 additionalTickets,
                 subKey,
-                extraPA);
+                extraPAs);
         obuf = tgsReqMessg.asn1Encode();
 
         // XXX We need to revisit this to see if can't move it
@@ -282,7 +247,7 @@
                          AuthorizationData authorizationData,
                          Ticket[] additionalTickets,
                          EncryptionKey subKey,
-                         PAData extraPA)
+                         PAData[] extraPAs)
         throws IOException, KrbException, UnknownHostException {
         KerberosTime req_till = null;
         if (till == null) {
@@ -382,11 +347,14 @@
                                          null).getMessage();
 
         PAData tgsPAData = new PAData(Krb5.PA_TGS_REQ, tgs_ap_req);
-        return new TGSReq(
-                extraPA != null ?
-                    new PAData[] {extraPA, tgsPAData } :
-                    new PAData[] {tgsPAData},
-                reqBody);
+        PAData[] pa;
+        if (extraPAs != null) {
+            pa = Arrays.copyOf(extraPAs, extraPAs.length + 1);
+            pa[extraPAs.length] = tgsPAData;
+        } else {
+            pa = new PAData[] {tgsPAData};
+        }
+        return new TGSReq(pa, reqBody);
     }
 
     TGSReq getMessage() {
--- a/src/java.security.jgss/share/classes/sun/security/krb5/PrincipalName.java	Thu Jun 06 10:03:22 2019 -0400
+++ b/src/java.security.jgss/share/classes/sun/security/krb5/PrincipalName.java	Wed Jun 05 01:42:11 2019 -0300
@@ -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
@@ -91,6 +91,11 @@
     public static final int KRB_NT_UID = 5;
 
     /**
+     * Enterprise name (alias)
+     */
+    public static final int KRB_NT_ENTERPRISE = 10;
+
+    /**
      * TGS Name
      */
     public static final String TGS_DEFAULT_SRV_NAME = "krbtgt";
@@ -465,6 +470,7 @@
         case KRB_NT_SRV_INST:
         case KRB_NT_SRV_XHST:
         case KRB_NT_UID:
+        case KRB_NT_ENTERPRISE:
             nameStrings = nameParts;
             nameType = type;
             if (realm != null) {
--- a/src/java.security.jgss/share/classes/sun/security/krb5/internal/CredentialsUtil.java	Thu Jun 06 10:03:22 2019 -0400
+++ b/src/java.security.jgss/share/classes/sun/security/krb5/internal/CredentialsUtil.java	Wed Jun 05 01:42:11 2019 -0300
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2001, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2001, 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
@@ -33,6 +33,8 @@
 
 import sun.security.krb5.*;
 import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
 
 /**
  * This class is a utility that contains much of the TGS-Exchange
@@ -61,13 +63,11 @@
         if (!ccreds.isForwardable()) {
             throw new KrbException("S4U2self needs a FORWARDABLE ticket");
         }
-        KrbTgsReq req = new KrbTgsReq(
-                ccreds,
-                ccreds.getClient(),
-                new PAData(Krb5.PA_FOR_USER,
-                    new PAForUserEnc(client,
-                        ccreds.getSessionKey()).asn1Encode()));
-        Credentials creds = req.sendAndGetCreds();
+        Credentials creds = serviceCreds(KDCOptions.with(KDCOptions.FORWARDABLE),
+                ccreds, ccreds.getClient(), ccreds.getClient(), null,
+                new PAData[] {new PAData(Krb5.PA_FOR_USER,
+                        new PAForUserEnc(client,
+                            ccreds.getSessionKey()).asn1Encode())});
         if (!creds.getClient().equals(client)) {
             throw new KrbException("S4U2self request not honored by KDC");
         }
@@ -89,11 +89,10 @@
                 String backend, Ticket second,
                 PrincipalName client, Credentials ccreds)
             throws KrbException, IOException {
-        KrbTgsReq req = new KrbTgsReq(
-                ccreds,
-                second,
-                new PrincipalName(backend));
-        Credentials creds = req.sendAndGetCreds();
+        Credentials creds = serviceCreds(KDCOptions.with(
+                KDCOptions.CNAME_IN_ADDL_TKT, KDCOptions.FORWARDABLE),
+                ccreds, ccreds.getClient(), new PrincipalName(backend),
+                new Ticket[] {second}, null);
         if (!creds.getClient().equals(client)) {
             throw new KrbException("S4U2proxy request not honored by KDC");
         }
@@ -114,53 +113,9 @@
     public static Credentials acquireServiceCreds(
                 String service, Credentials ccreds)
             throws KrbException, IOException {
-        PrincipalName sname = new PrincipalName(service);
-        String serviceRealm = sname.getRealmString();
-        String localRealm = ccreds.getClient().getRealmString();
-
-        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");
+        PrincipalName sname = new PrincipalName(service,
+                PrincipalName.KRB_NT_SRV_HST);
+        return serviceCreds(sname, ccreds);
     }
 
     /**
@@ -305,6 +260,148 @@
     private static Credentials serviceCreds(
             PrincipalName service, Credentials ccreds)
             throws KrbException, IOException {
-        return new KrbTgsReq(ccreds, service).sendAndGetCreds();
+        return serviceCreds(new KDCOptions(), ccreds,
+                ccreds.getClient(), service, null, null);
+    }
+
+    /*
+     * Obtains credentials for a service (TGS).
+     * Cross-realm referrals are handled if enabled. A fallback scheme
+     * without cross-realm referrals supports is used in case of server
+     * error to maintain backward compatibility.
+     */
+    private static Credentials serviceCreds(
+            KDCOptions options, Credentials asCreds,
+            PrincipalName cname, PrincipalName sname,
+            Ticket[] additionalTickets, PAData[] extraPAs)
+            throws KrbException, IOException {
+        if (!Config.DISABLE_REFERRALS) {
+            try {
+                return serviceCredsReferrals(options, asCreds,
+                        cname, sname, additionalTickets, extraPAs);
+            } catch (KrbException e) {
+                // Server may raise an error if CANONICALIZE is true.
+                // Try CANONICALIZE false.
+            }
+        }
+        return serviceCredsSingle(options, asCreds,
+                cname, sname, additionalTickets, extraPAs);
+    }
+
+    /*
+     * Obtains credentials for a service (TGS).
+     * May handle and follow cross-realm referrals as defined by RFC 6806.
+     */
+    private static Credentials serviceCredsReferrals(
+            KDCOptions options, Credentials asCreds,
+            PrincipalName cname, PrincipalName sname,
+            Ticket[] additionalTickets, PAData[] extraPAs)
+            throws KrbException, IOException {
+        options = new KDCOptions(options.toBooleanArray());
+        options.set(KDCOptions.CANONICALIZE, true);
+        PrincipalName cSname = sname;
+        Credentials creds = null;
+        boolean isReferral = false;
+        List<String> referrals = new LinkedList<>();
+        while (referrals.size() <= Config.MAX_REFERRALS) {
+            ReferralsCache.ReferralCacheEntry ref =
+                    ReferralsCache.get(sname, cSname.getRealmString());
+            String toRealm = null;
+            if (ref == null) {
+                creds = serviceCredsSingle(options, asCreds,
+                        cname, cSname, additionalTickets, extraPAs);
+                PrincipalName server = creds.getServer();
+                if (!cSname.equals(server)) {
+                    String[] serverNameStrings = server.getNameStrings();
+                    if (serverNameStrings.length == 2 &&
+                        serverNameStrings[0].equals(
+                                PrincipalName.TGS_DEFAULT_SRV_NAME) &&
+                        !cSname.getRealmAsString().equals(serverNameStrings[1])) {
+                        // Server Name (sname) has the following format:
+                        //      krbtgt/TO-REALM.COM@FROM-REALM.COM
+                        ReferralsCache.put(sname, server.getRealmString(),
+                                serverNameStrings[1], creds);
+                        toRealm = serverNameStrings[1];
+                        isReferral = true;
+                        asCreds = creds;
+                    }
+                }
+            } else {
+                toRealm = ref.getToRealm();
+                asCreds = ref.getCreds();
+                isReferral = true;
+            }
+            if (isReferral) {
+                if (referrals.contains(toRealm)) {
+                    // Referrals loop detected
+                    return null;
+                }
+                cSname = new PrincipalName(cSname.getNameString(),
+                        cSname.getNameType(), toRealm);
+                referrals.add(toRealm);
+                isReferral = false;
+                continue;
+            }
+            break;
+        }
+        return creds;
+    }
+
+    /*
+     * Obtains credentials for a service (TGS).
+     * If the service realm is different than the one in the TGT, a new TGT for
+     * the service realm is obtained first (see getTGTforRealm call). This is
+     * not expected when following cross-realm referrals because the referral
+     * TGT realm matches the service realm.
+     */
+    private static Credentials serviceCredsSingle(
+            KDCOptions options, Credentials asCreds,
+            PrincipalName cname, PrincipalName sname,
+            Ticket[] additionalTickets, PAData[] extraPAs)
+            throws KrbException, IOException {
+        Credentials theCreds = null;
+        boolean[] okAsDelegate = new boolean[]{true};
+        String[] serverAsCredsNames = asCreds.getServer().getNameStrings();
+        String tgtRealm = serverAsCredsNames[1];
+        String serviceRealm = sname.getRealmString();
+        if (!serviceRealm.equals(tgtRealm)) {
+            // This is a cross-realm service request
+            if (DEBUG) {
+                System.out.println(">>> serviceCredsSingle:" +
+                        " cross-realm authentication");
+                System.out.println(">>> serviceCredsSingle:" +
+                        " obtaining credentials from " + tgtRealm +
+                        " to " + serviceRealm);
+            }
+            Credentials newTgt = getTGTforRealm(tgtRealm, serviceRealm,
+                    asCreds, okAsDelegate);
+            if (newTgt == null) {
+                throw new KrbApErrException(Krb5.KRB_AP_ERR_GEN_CRED,
+                        "No service creds");
+            }
+            if (DEBUG) {
+                System.out.println(">>> Cross-realm TGT Credentials" +
+                        " serviceCredsSingle: ");
+                Credentials.printDebug(newTgt);
+            }
+            asCreds = newTgt;
+            cname = asCreds.getClient();
+        } else if (DEBUG) {
+            System.out.println(">>> Credentials serviceCredsSingle:" +
+                    " same realm");
+        }
+        KrbTgsReq req = new KrbTgsReq(options, asCreds,
+                cname, sname, additionalTickets, extraPAs);
+        theCreds = req.sendAndGetCreds();
+        if (theCreds != null) {
+            if (DEBUG) {
+                System.out.println(">>> TGS credentials serviceCredsSingle:");
+                Credentials.printDebug(theCreds);
+            }
+            if (!okAsDelegate[0]) {
+                theCreds.resetDelegate();
+            }
+        }
+        return theCreds;
     }
 }
--- a/src/java.security.jgss/share/classes/sun/security/krb5/internal/EncASRepPart.java	Thu Jun 06 10:03:22 2019 -0400
+++ b/src/java.security.jgss/share/classes/sun/security/krb5/internal/EncASRepPart.java	Wed Jun 05 01:42:11 2019 -0300
@@ -47,7 +47,8 @@
             KerberosTime new_endtime,
             KerberosTime new_renewTill,
             PrincipalName new_sname,
-            HostAddresses new_caddr) {
+            HostAddresses new_caddr,
+            PAData[] new_pAData) {
         super(
                 new_key,
                 new_lastReq,
@@ -60,6 +61,7 @@
                 new_renewTill,
                 new_sname,
                 new_caddr,
+                new_pAData,
                 Krb5.KRB_ENC_AS_REP_PART
                 );
         //may need to use Krb5.KRB_ENC_TGS_REP_PART to mimic
--- a/src/java.security.jgss/share/classes/sun/security/krb5/internal/EncKDCRepPart.java	Thu Jun 06 10:03:22 2019 -0400
+++ b/src/java.security.jgss/share/classes/sun/security/krb5/internal/EncKDCRepPart.java	Wed Jun 05 01:42:11 2019 -0300
@@ -31,7 +31,6 @@
 package sun.security.krb5.internal;
 
 import sun.security.krb5.*;
-import sun.security.krb5.EncryptionKey;
 import sun.security.util.*;
 import java.util.Vector;
 import java.io.IOException;
@@ -41,19 +40,20 @@
  * Implements the ASN.1 EncKDCRepPart type.
  *
  * <pre>{@code
- * EncKDCRepPart        ::= SEQUENCE {
- *      key             [0] EncryptionKey,
- *      last-req        [1] LastReq,
- *      nonce           [2] UInt32,
- *      key-expiration  [3] KerberosTime OPTIONAL,
- *      flags           [4] TicketFlags,
- *      authtime        [5] KerberosTime,
- *      starttime       [6] KerberosTime OPTIONAL,
- *      endtime         [7] KerberosTime,
- *      renew-till      [8] KerberosTime OPTIONAL,
- *      srealm          [9] Realm,
- *      sname           [10] PrincipalName,
- *      caddr           [11] HostAddresses OPTIONAL
+ * EncKDCRepPart          ::= SEQUENCE {
+ *      key               [0] EncryptionKey,
+ *      last-req          [1] LastReq,
+ *      nonce             [2] UInt32,
+ *      key-expiration    [3] KerberosTime OPTIONAL,
+ *      flags             [4] TicketFlags,
+ *      authtime          [5] KerberosTime,
+ *      starttime         [6] KerberosTime OPTIONAL,
+ *      endtime           [7] KerberosTime,
+ *      renew-till        [8] KerberosTime OPTIONAL,
+ *      srealm            [9] Realm,
+ *      sname             [10] PrincipalName,
+ *      caddr             [11] HostAddresses OPTIONAL,
+ *      encrypted-pa-data [12] SEQUENCE OF PA-DATA OPTIONAL
  * }
  * }</pre>
  *
@@ -76,6 +76,7 @@
     public KerberosTime renewTill; //optional
     public PrincipalName sname;
     public HostAddresses caddr; //optional
+    public PAData[] pAData; //optional
     public int msgType; //not included in sequence
 
     public EncKDCRepPart(
@@ -90,6 +91,7 @@
             KerberosTime new_renewTill,
             PrincipalName new_sname,
             HostAddresses new_caddr,
+            PAData[] new_pAData,
             int new_msgType) {
         key = new_key;
         lastReq = new_lastReq;
@@ -102,6 +104,7 @@
         renewTill = new_renewTill;
         sname = new_sname;
         caddr = new_caddr;
+        pAData = new_pAData;
         msgType = new_msgType;
     }
 
@@ -160,6 +163,9 @@
         if (der.getData().available() > 0) {
             caddr = HostAddresses.parse(der.getData(), (byte) 0x0B, true);
         }
+        if (der.getData().available() > 0) {
+            pAData = PAData.parseSequence(der.getData(), (byte) 0x0C, true);
+        }
         // We observe extra data from MSAD
         /*if (der.getData().available() > 0) {
             throw new Asn1Exception(Krb5.ASN1_BAD_ID);
@@ -175,47 +181,58 @@
      */
     public byte[] asn1Encode(int rep_type) throws Asn1Exception,
             IOException {
+        DerOutputStream bytes;
         DerOutputStream temp = new DerOutputStream();
-        DerOutputStream bytes = new DerOutputStream();
-        bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT,
+        DerOutputStream out = new DerOutputStream();
+        out.write(DerValue.createTag(DerValue.TAG_CONTEXT,
                 true, (byte) 0x00), key.asn1Encode());
-        bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT,
+        out.write(DerValue.createTag(DerValue.TAG_CONTEXT,
                 true, (byte) 0x01), lastReq.asn1Encode());
         temp.putInteger(BigInteger.valueOf(nonce));
-        bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT,
+        out.write(DerValue.createTag(DerValue.TAG_CONTEXT,
                 true, (byte) 0x02), temp);
 
         if (keyExpiration != null) {
-            bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT,
+            out.write(DerValue.createTag(DerValue.TAG_CONTEXT,
                     true, (byte) 0x03), keyExpiration.asn1Encode());
         }
-        bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT,
+        out.write(DerValue.createTag(DerValue.TAG_CONTEXT,
                 true, (byte) 0x04), flags.asn1Encode());
-        bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT,
+        out.write(DerValue.createTag(DerValue.TAG_CONTEXT,
                 true, (byte) 0x05), authtime.asn1Encode());
         if (starttime != null) {
-            bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT,
+            out.write(DerValue.createTag(DerValue.TAG_CONTEXT,
                     true, (byte) 0x06), starttime.asn1Encode());
         }
-        bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT,
+        out.write(DerValue.createTag(DerValue.TAG_CONTEXT,
                 true, (byte) 0x07), endtime.asn1Encode());
         if (renewTill != null) {
-            bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT,
+            out.write(DerValue.createTag(DerValue.TAG_CONTEXT,
                     true, (byte) 0x08), renewTill.asn1Encode());
         }
-        bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT,
+        out.write(DerValue.createTag(DerValue.TAG_CONTEXT,
                 true, (byte) 0x09), sname.getRealm().asn1Encode());
-        bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT,
+        out.write(DerValue.createTag(DerValue.TAG_CONTEXT,
                 true, (byte) 0x0A), sname.asn1Encode());
         if (caddr != null) {
-            bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT,
+            out.write(DerValue.createTag(DerValue.TAG_CONTEXT,
                     true, (byte) 0x0B), caddr.asn1Encode());
         }
+        if (pAData != null && pAData.length > 0) {
+            temp = new DerOutputStream();
+            for (int i = 0; i < pAData.length; i++) {
+                temp.write(pAData[i].asn1Encode());
+            }
+            bytes = new DerOutputStream();
+            bytes.write(DerValue.tag_SequenceOf, temp);
+            out.write(DerValue.createTag(DerValue.TAG_CONTEXT,
+                    true, (byte) 0x0C), bytes);
+        }
         //should use the rep_type to build the encoding
         //but other implementations do not; it is ignored and
         //the cached msgType is used instead
         temp = new DerOutputStream();
-        temp.write(DerValue.tag_Sequence, bytes);
+        temp.write(DerValue.tag_Sequence, out);
         bytes = new DerOutputStream();
         bytes.write(DerValue.createTag(DerValue.TAG_APPLICATION,
                 true, (byte) msgType), temp);
--- a/src/java.security.jgss/share/classes/sun/security/krb5/internal/EncTGSRepPart.java	Thu Jun 06 10:03:22 2019 -0400
+++ b/src/java.security.jgss/share/classes/sun/security/krb5/internal/EncTGSRepPart.java	Wed Jun 05 01:42:11 2019 -0300
@@ -46,7 +46,8 @@
             KerberosTime new_endtime,
             KerberosTime new_renewTill,
             PrincipalName new_sname,
-            HostAddresses new_caddr) {
+            HostAddresses new_caddr,
+            PAData[] new_pAData) {
         super(
                 new_key,
                 new_lastReq,
@@ -59,6 +60,7 @@
                 new_renewTill,
                 new_sname,
                 new_caddr,
+                new_pAData,
                 Krb5.KRB_ENC_TGS_REP_PART);
     }
 
--- a/src/java.security.jgss/share/classes/sun/security/krb5/internal/KDCOptions.java	Thu Jun 06 10:03:22 2019 -0400
+++ b/src/java.security.jgss/share/classes/sun/security/krb5/internal/KDCOptions.java	Wed Jun 05 01:42:11 2019 -0300
@@ -140,6 +140,7 @@
     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 CANONICALIZE    = 15;
     public static final int RENEWABLE_OK    = 27;
     public static final int ENC_TKT_IN_SKEY = 28;
     public static final int RENEW           = 30;
@@ -160,7 +161,8 @@
         "UNUSED11",         //11;
         null,null,
         "CNAME_IN_ADDL_TKT",//14;
-        null,null,null,null,null,null,null,null,null,null,null,null,
+        "CANONICALIZE",     //15;
+        null,null,null,null,null,null,null,null,null,null,null,
         "RENEWABLE_OK",     //27;
         "ENC_TKT_IN_SKEY",  //28;
         null,
--- a/src/java.security.jgss/share/classes/sun/security/krb5/internal/KDCReq.java	Thu Jun 06 10:03:22 2019 -0400
+++ b/src/java.security.jgss/share/classes/sun/security/krb5/internal/KDCReq.java	Wed Jun 05 01:42:11 2019 -0300
@@ -59,9 +59,9 @@
 public class KDCReq {
 
     public KDCReqBody reqBody;
+    public PAData[] pAData = null; //optional
     private int pvno;
     private int msgType;
-    private PAData[] pAData = null; //optional
 
     public KDCReq(PAData[] new_pAData, KDCReqBody new_reqBody,
             int req_type) throws IOException {
@@ -144,23 +144,7 @@
         } else {
             throw new Asn1Exception(Krb5.ASN1_BAD_ID);
         }
-        if ((der.getData().peekByte() & 0x1F) == 0x03) {
-            subDer = der.getData().getDerValue();
-            DerValue subsubDer = subDer.getData().getDerValue();
-            if (subsubDer.getTag() != DerValue.tag_SequenceOf) {
-                throw new Asn1Exception(Krb5.ASN1_BAD_ID);
-            }
-            Vector<PAData> v = new Vector<>();
-            while (subsubDer.getData().available() > 0) {
-                v.addElement(new PAData(subsubDer.getData().getDerValue()));
-            }
-            if (v.size() > 0) {
-                pAData = new PAData[v.size()];
-                v.copyInto(pAData);
-            }
-        } else {
-            pAData = null;
-        }
+        pAData = PAData.parseSequence(der.getData(), (byte) 0x03, true);
         subDer = der.getData().getDerValue();
         if ((subDer.getTag() & 0x01F) == 0x04) {
             DerValue subsubDer = subDer.getData().getDerValue();
--- a/src/java.security.jgss/share/classes/sun/security/krb5/internal/KRBError.java	Thu Jun 06 10:03:22 2019 -0400
+++ b/src/java.security.jgss/share/classes/sun/security/krb5/internal/KRBError.java	Wed Jun 05 01:42:11 2019 -0300
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000, 2012, 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
@@ -90,6 +90,7 @@
     private KerberosTime sTime;
     private Integer suSec;
     private int errorCode;
+    private Realm crealm; //optional
     private PrincipalName cname; //optional
     private PrincipalName sname;
     private String eText; //optional
@@ -138,6 +139,7 @@
         sTime = new_sTime;
         suSec = new_suSec;
         errorCode = new_errorCode;
+        crealm = new_cname.getRealm();
         cname = new_cname;
         sname = new_sname;
         eText = new_eText;
@@ -166,6 +168,7 @@
         sTime = new_sTime;
         suSec = new_suSec;
         errorCode = new_errorCode;
+        crealm = new_cname.getRealm();
         cname = new_cname;
         sname = new_sname;
         eText = new_eText;
@@ -262,6 +265,10 @@
         pa = paList.toArray(new PAData[paList.size()]);
     }
 
+    public final Realm getClientRealm() {
+        return crealm;
+    }
+
     public final KerberosTime getServerTime() {
         return sTime;
     }
@@ -349,7 +356,7 @@
             errorCode = subDer.getData().getBigInteger().intValue();
         }
         else  throw new Asn1Exception(Krb5.ASN1_BAD_ID);
-        Realm crealm = Realm.parse(der.getData(), (byte)0x07, true);
+        crealm = Realm.parse(der.getData(), (byte)0x07, true);
         cname = PrincipalName.parse(der.getData(), (byte)0x08, true, crealm);
         Realm realm = Realm.parse(der.getData(), (byte)0x09, false);
         sname = PrincipalName.parse(der.getData(), (byte)0x0A, false, realm);
@@ -393,6 +400,9 @@
             System.out.println("\t suSec is " + suSec);
             System.out.println("\t error code is " + errorCode);
             System.out.println("\t error Message is " + Krb5.getErrorMessage(errorCode));
+            if (crealm != null) {
+                System.out.println("\t crealm is " + crealm.toString());
+            }
             if (cname != null) {
                 System.out.println("\t cname is " + cname.toString());
             }
@@ -442,8 +452,10 @@
         temp.putInteger(BigInteger.valueOf(errorCode));
         bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte)0x06), temp);
 
+        if (crealm != null) {
+            bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte)0x07), crealm.asn1Encode());
+        }
         if (cname != null) {
-            bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte)0x07), cname.getRealm().asn1Encode());
             bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte)0x08), cname.asn1Encode());
         }
 
@@ -488,6 +500,7 @@
                 isEqual(sTime, other.sTime) &&
                 isEqual(suSec, other.suSec) &&
                 errorCode == other.errorCode &&
+                isEqual(crealm, other.crealm) &&
                 isEqual(cname, other.cname) &&
                 isEqual(sname, other.sname) &&
                 isEqual(eText, other.eText) &&
@@ -508,6 +521,7 @@
         if (sTime != null) result = 37 * result + sTime.hashCode();
         if (suSec != null) result = 37 * result + suSec.hashCode();
         result = 37 * result + errorCode;
+        if (crealm != null) result = 37 * result + crealm.hashCode();
         if (cname != null) result = 37 * result + cname.hashCode();
         if (sname != null) result = 37 * result + sname.hashCode();
         if (eText != null) result = 37 * result + eText.hashCode();
--- a/src/java.security.jgss/share/classes/sun/security/krb5/internal/Krb5.java	Thu Jun 06 10:03:22 2019 -0400
+++ b/src/java.security.jgss/share/classes/sun/security/krb5/internal/Krb5.java	Wed Jun 05 01:42:11 2019 -0300
@@ -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
@@ -72,6 +72,7 @@
     public static final int TKT_OPTS_PRE_AUTHENT  = 10;
     public static final int TKT_OPTS_HW_AUTHENT   = 11;
     public static final int TKT_OPTS_DELEGATE     = 13;
+    public static final int TKT_OPTS_ENC_PA_REP   = 15;
     public static final int TKT_OPTS_MAX          = 31;
 
     // KDC Options
@@ -165,6 +166,9 @@
     // S4U2user info
     public static final int PA_FOR_USER      = 129;
 
+    // FAST (RFC 6806)
+    public static final int PA_REQ_ENC_PA_REP = 149;
+
     //-------------------------------+-------------
     //authorization data type        |ad-type value
     //-------------------------------+-------------
@@ -267,6 +271,7 @@
     public static final int KRB_ERR_RESPONSE_TOO_BIG     = 52;   //Response too big for UDP, retry with TCP
     public static final int KRB_ERR_GENERIC              = 60;   //Generic error (description in e-text)
     public static final int KRB_ERR_FIELD_TOOLONG        = 61;   //Field is too long for this implementation
+    public static final int KRB_ERR_WRONG_REALM          = 68;   //Wrong realm
     public static final int KRB_CRYPTO_NOT_SUPPORT      = 100;    //Client does not support this crypto type
     public static final int KRB_AP_ERR_NOREALM          = 62;
     public static final int KRB_AP_ERR_GEN_CRED         = 63;
--- a/src/java.security.jgss/share/classes/sun/security/krb5/internal/PAData.java	Thu Jun 06 10:03:22 2019 -0400
+++ b/src/java.security.jgss/share/classes/sun/security/krb5/internal/PAData.java	Wed Jun 05 01:42:11 2019 -0300
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 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
@@ -35,6 +35,8 @@
 import sun.security.util.*;
 import sun.security.krb5.Asn1Exception;
 import java.io.IOException;
+import java.util.Vector;
+
 import sun.security.krb5.internal.util.KerberosString;
 
 /**
@@ -140,6 +142,41 @@
     }
 
     /**
+     * Parse (unmarshal) a PAData from a DER input stream.  This form
+     * parsing might be used when expanding a value which is part of
+     * a constructed sequence and uses explicitly tagged type.
+     *
+     * @exception Asn1Exception if an Asn1Exception occurs.
+     * @param data the Der input stream value, which contains one or more
+     *        marshaled values.
+     * @param explicitTag tag number.
+     * @param optional indicates if this data field is optional.
+     * @return an array of PAData.
+     */
+    public static PAData[] parseSequence(DerInputStream data,
+                                      byte explicitTag, boolean optional)
+        throws Asn1Exception, IOException {
+        if ((optional) &&
+                (((byte)data.peekByte() & (byte)0x1F) != explicitTag))
+                return null;
+        DerValue subDer = data.getDerValue();
+        DerValue subsubDer = subDer.getData().getDerValue();
+        if (subsubDer.getTag() != DerValue.tag_SequenceOf) {
+            throw new Asn1Exception(Krb5.ASN1_BAD_ID);
+        }
+        Vector<PAData> v = new Vector<>();
+        while (subsubDer.getData().available() > 0) {
+            v.addElement(new PAData(subsubDer.getData().getDerValue()));
+        }
+        if (v.size() > 0) {
+            PAData[] pas = new PAData[v.size()];
+            v.copyInto(pas);
+            return pas;
+        }
+        return null;
+    }
+
+    /**
      * Gets the preferred etype from the PAData array.
      * <ol>
      * <li>ETYPE-INFO2-ENTRY with unknown s2kparams ignored</li>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.security.jgss/share/classes/sun/security/krb5/internal/ReferralsCache.java	Wed Jun 05 01:42:11 2019 -0300
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2019, Red Hat, Inc.
+ * 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.util.Date;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import sun.security.krb5.Credentials;
+import sun.security.krb5.PrincipalName;
+
+/*
+ * ReferralsCache class implements a cache scheme for referral TGTs as
+ * described in RFC 6806 - 10. Caching Information. The goal is to optimize
+ * resources (such as network traffic) when a client requests credentials for a
+ * service principal to a given KDC. If a referral TGT was previously received,
+ * cached information is used instead of issuing a new query. Once a referral
+ * TGT expires, the corresponding referral entry in the cache is removed.
+ */
+final class ReferralsCache {
+
+    private static Map<PrincipalName, Map<String, ReferralCacheEntry>> referralsMap =
+            new HashMap<>();
+
+    static final class ReferralCacheEntry {
+        private final Credentials creds;
+        private final String toRealm;
+        ReferralCacheEntry(Credentials creds, String toRealm) {
+            this.creds = creds;
+            this.toRealm = toRealm;
+        }
+        Credentials getCreds() {
+            return creds;
+        }
+        String getToRealm() {
+            return toRealm;
+        }
+    }
+
+    /*
+     * Add a new referral entry to the cache, including: service principal,
+     * source KDC realm, destination KDC realm and referral TGT.
+     *
+     * If a loop is generated when adding the new referral, the first hop is
+     * automatically removed. For example, let's assume that adding a
+     * REALM-3.COM -> REALM-1.COM referral generates the following loop:
+     * REALM-1.COM -> REALM-2.COM -> REALM-3.COM -> REALM-1.COM. Then,
+     * REALM-1.COM -> REALM-2.COM referral entry is removed from the cache.
+     */
+    static synchronized void put(PrincipalName service,
+            String fromRealm, String toRealm, Credentials creds) {
+        pruneExpired(service);
+        if (creds.getEndTime().before(new Date())) {
+            return;
+        }
+        Map<String, ReferralCacheEntry> entries = referralsMap.get(service);
+        if (entries == null) {
+            entries = new HashMap<String, ReferralCacheEntry>();
+            referralsMap.put(service, entries);
+        }
+        entries.remove(fromRealm);
+        ReferralCacheEntry newEntry = new ReferralCacheEntry(creds, toRealm);
+        entries.put(fromRealm, newEntry);
+
+        // Remove loops within the cache
+        ReferralCacheEntry current = newEntry;
+        List<ReferralCacheEntry> seen = new LinkedList<>();
+        while (current != null) {
+            if (seen.contains(current)) {
+                // Loop found. Remove the first referral to cut the loop.
+                entries.remove(newEntry.getToRealm());
+                break;
+            }
+            seen.add(current);
+            current = entries.get(current.getToRealm());
+        }
+    }
+
+    /*
+     * Obtain a referral entry from the cache given a service principal and a
+     * source KDC realm.
+     */
+    static synchronized ReferralCacheEntry get(PrincipalName service,
+            String fromRealm) {
+        pruneExpired(service);
+        Map<String, ReferralCacheEntry> entries = referralsMap.get(service);
+        if (entries != null) {
+            ReferralCacheEntry toRef = entries.get(fromRealm);
+            if (toRef != null) {
+                return toRef;
+            }
+        }
+        return null;
+    }
+
+    /*
+     * Remove referral entries from the cache when referral TGTs expire.
+     */
+    private static void pruneExpired(PrincipalName service) {
+        Date now = new Date();
+        Map<String, ReferralCacheEntry> entries = referralsMap.get(service);
+        if (entries != null) {
+            for (Entry<String, ReferralCacheEntry> mapEntry :
+                    entries.entrySet()) {
+                if (mapEntry.getValue().getCreds().getEndTime().before(now)) {
+                    entries.remove(mapEntry.getKey());
+                }
+            }
+        }
+    }
+}
--- a/src/java.security.jgss/share/classes/sun/security/krb5/internal/TicketFlags.java	Thu Jun 06 10:03:22 2019 -0400
+++ b/src/java.security.jgss/share/classes/sun/security/krb5/internal/TicketFlags.java	Wed Jun 05 01:42:11 2019 -0300
@@ -51,7 +51,8 @@
  *                   renewable(8),
  *                   initial(9),
  *                   pre-authent(10),
- *                   hw-authent(11)
+ *                   hw-authent(11),
+ *                   enc-pa-rep(15)
  *                  }
  */
 public class TicketFlags extends KerberosFlags {
@@ -178,6 +179,9 @@
                 case 11:
                     sb.append("HW-AUTHENT;");
                     break;
+                case 15:
+                    sb.append("ENC-PA-REP;");
+                    break;
                 }
             }
         }
--- a/src/java.security.jgss/share/classes/sun/security/krb5/internal/crypto/KeyUsage.java	Thu Jun 06 10:03:22 2019 -0400
+++ b/src/java.security.jgss/share/classes/sun/security/krb5/internal/crypto/KeyUsage.java	Wed Jun 05 01:42:11 2019 -0300
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2004, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2004, 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
@@ -56,6 +56,7 @@
     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 int KU_AS_REQ = 56;
 
     public static final boolean isValid(int usage) {
         return usage >= 0;
--- a/test/jdk/sun/security/krb5/auto/KDC.java	Thu Jun 06 10:03:22 2019 -0400
+++ b/test/jdk/sun/security/krb5/auto/KDC.java	Wed Jun 05 01:42:11 2019 -0300
@@ -165,6 +165,14 @@
     private TreeMap<String,byte[]> s2kparamses = new TreeMap<>
             (String.CASE_INSENSITIVE_ORDER);
 
+    // Alias for referrals.
+    private TreeMap<String,KDC> aliasReferrals = new TreeMap<>
+            (String.CASE_INSENSITIVE_ORDER);
+
+    // Alias for local resolution.
+    private TreeMap<String,PrincipalName> alias2Principals = new TreeMap<>
+            (String.CASE_INSENSITIVE_ORDER);
+
     // Realm name
     private String realm;
     // KDC
@@ -553,6 +561,29 @@
         return port;
     }
 
+    /**
+     * Register an alias name to be referred to a different KDC for
+     * resolution, according to RFC 6806.
+     * @param alias Alias name (i.e. user@REALM.COM).
+     * @param referredKDC KDC to which the alias is referred for resolution.
+     */
+    public void registerAlias(String alias, KDC referredKDC) {
+        aliasReferrals.remove(alias);
+        aliasReferrals.put(alias, referredKDC);
+    }
+
+    /**
+     * Register an alias to be resolved to a Principal Name locally,
+     * according to RFC 6806.
+     * @param alias Alias name (i.e. user@REALM.COM).
+     * @param user Principal Name to which the alias is resolved.
+     */
+    public void registerAlias(String alias, String user)
+            throws RealmException {
+        alias2Principals.remove(alias);
+        alias2Principals.put(alias, new PrincipalName(user));
+    }
+
     // Private helper methods
 
     /**
@@ -778,6 +809,17 @@
             PrincipalName cname = null;
             boolean allowForwardable = true;
 
+            if (body.kdcOptions.get(KDCOptions.CANONICALIZE)) {
+                KDC referral = aliasReferrals.get(body.sname.getNameString());
+                if (referral != null) {
+                    service = new PrincipalName(
+                            PrincipalName.TGS_DEFAULT_SRV_NAME +
+                            PrincipalName.NAME_COMPONENT_SEPARATOR_STR +
+                            referral.getRealm(), PrincipalName.KRB_NT_SRV_INST,
+                            this.getRealm());
+                }
+            }
+
             if (pas == null || pas.length == 0) {
                 throw new KrbException(Krb5.KDC_ERR_PADATA_TYPE_NOSUPP);
             } else {
@@ -964,7 +1006,8 @@
                     from,
                     till, renewTill,
                     service,
-                    body.addresses
+                    body.addresses,
+                    null
                     );
             EncryptedData edata = new EncryptedData(ckey, enc_part.asn1Encode(),
                     KeyUsage.KU_ENC_TGS_REP_PART_SESSKEY);
@@ -1008,6 +1051,7 @@
      */
     protected byte[] processAsReq(byte[] in) throws Exception {
         ASReq asReq = new ASReq(in);
+        byte[] asReqbytes = asReq.asn1Encode();
         int[] eTypes = null;
         List<PAData> outPAs = new ArrayList<>();
 
@@ -1030,6 +1074,24 @@
             }
             int eType = eTypes[0];
 
+            if (body.kdcOptions.get(KDCOptions.CANONICALIZE) &&
+                    body.cname.getNameType() == PrincipalName.KRB_NT_ENTERPRISE) {
+                PrincipalName principal = alias2Principals.get(
+                        body.cname.getNameString());
+                if (principal != null) {
+                    body.cname = principal;
+                } else {
+                    KDC referral = aliasReferrals.get(body.cname.getNameString());
+                    if (referral != null) {
+                        body.cname = new PrincipalName(
+                                PrincipalName.TGS_DEFAULT_SRV_NAME,
+                                PrincipalName.KRB_NT_SRV_INST,
+                                referral.getRealm());
+                        throw new KrbException(Krb5.KRB_ERR_WRONG_REALM);
+                    }
+                }
+            }
+
             EncryptionKey ckey = keyForUser(body.cname, eType, false);
             EncryptionKey skey = keyForUser(service, eType, true);
 
@@ -1211,17 +1273,18 @@
             }
 
             PAData[] inPAs = KDCReqDotPAData(asReq);
+            List<PAData> enc_outPAs = new ArrayList<>();
             if (inPAs == null || inPAs.length == 0) {
                 Object preauth = options.get(Option.PREAUTH_REQUIRED);
                 if (preauth == null || preauth.equals(Boolean.TRUE)) {
                     throw new KrbException(Krb5.KDC_ERR_PREAUTH_REQUIRED);
                 }
             } else {
+                EncryptionKey pakey = null;
                 try {
                     EncryptedData data = newEncryptedData(
                             new DerValue(inPAs[0].getValue()));
-                    EncryptionKey pakey
-                            = keyForUser(body.cname, data.getEType(), false);
+                    pakey = keyForUser(body.cname, data.getEType(), false);
                     data.decrypt(pakey, KeyUsage.KU_PA_ENC_TS);
                 } catch (Exception e) {
                     KrbException ke = new KrbException(Krb5.KDC_ERR_PREAUTH_FAILED);
@@ -1229,6 +1292,17 @@
                     throw ke;
                 }
                 bFlags[Krb5.TKT_OPTS_PRE_AUTHENT] = true;
+                for (PAData pa : inPAs) {
+                    if (pa.getType() == Krb5.PA_REQ_ENC_PA_REP) {
+                        Checksum ckSum = new Checksum(
+                                Checksum.CKSUMTYPE_HMAC_SHA1_96_AES128,
+                                asReqbytes, ckey, KeyUsage.KU_AS_REQ);
+                        enc_outPAs.add(new PAData(Krb5.PA_REQ_ENC_PA_REP,
+                                ckSum.asn1Encode()));
+                        bFlags[Krb5.TKT_OPTS_ENC_PA_REP] = true;
+                        break;
+                    }
+                }
             }
 
             TicketFlags tFlags = new TicketFlags(bFlags);
@@ -1259,7 +1333,8 @@
                     from,
                     till, rtime,
                     service,
-                    body.addresses
+                    body.addresses,
+                    enc_outPAs.toArray(new PAData[enc_outPAs.size()])
                     );
             EncryptedData edata = new EncryptedData(ckey, enc_part.asn1Encode(),
                     KeyUsage.KU_ENC_AS_REP_PART);
@@ -1307,8 +1382,10 @@
             if (kerr == null) {
                 if (ke.returnCode() == Krb5.KDC_ERR_PREAUTH_REQUIRED ||
                         ke.returnCode() == Krb5.KDC_ERR_PREAUTH_FAILED) {
+                    outPAs.add(new PAData(Krb5.PA_ENC_TIMESTAMP, new byte[0]));
+                }
+                if (outPAs.size() > 0) {
                     DerOutputStream bytes = new DerOutputStream();
-                    bytes.write(new PAData(Krb5.PA_ENC_TIMESTAMP, new byte[0]).asn1Encode());
                     for (PAData p: outPAs) {
                         bytes.write(p.asn1Encode());
                     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/sun/security/krb5/auto/ReferralsTest.java	Wed Jun 05 01:42:11 2019 -0300
@@ -0,0 +1,169 @@
+/*
+ * Copyright (c) 2019, Red Hat, Inc.
+ * 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 8215032
+ * @library /test/lib
+ * @run main/othervm/timeout=120 -Dsun.security.krb5.debug=true ReferralsTest
+ * @summary Test Kerberos cross-realm referrals (RFC 6806)
+ */
+
+import java.io.File;
+import sun.security.krb5.Credentials;
+import sun.security.krb5.internal.CredentialsUtil;
+import sun.security.krb5.KrbAsReqBuilder;
+import sun.security.krb5.PrincipalName;
+
+public class ReferralsTest {
+    private static final boolean DEBUG = true;
+    private static final String krbConfigName = "krb5-localkdc.conf";
+    private static final String realmKDC1 = "RABBIT.HOLE";
+    private static final String realmKDC2 = "DEV.RABBIT.HOLE";
+    private static final char[] password = "123qwe@Z".toCharArray();
+    private static final String clientName = "test";
+
+    private static final String clientAlias = clientName +
+            PrincipalName.NAME_REALM_SEPARATOR_STR + realmKDC1;
+
+    private static final String clientKDC1QueryName = clientAlias.replaceAll(
+            PrincipalName.NAME_REALM_SEPARATOR_STR, "\\\\" +
+            PrincipalName.NAME_REALM_SEPARATOR_STR) +
+            PrincipalName.NAME_REALM_SEPARATOR_STR + realmKDC1;
+    private static PrincipalName clientKDC1QueryPrincipal = null;
+    static {
+        try {
+            clientKDC1QueryPrincipal = new PrincipalName(
+                    clientKDC1QueryName, PrincipalName.KRB_NT_ENTERPRISE,
+                    null);
+        } catch (Throwable t) {}
+    }
+
+    private static final String clientKDC2Name = clientName +
+            PrincipalName.NAME_REALM_SEPARATOR_STR + realmKDC2;
+
+    private static final String serviceName = "http" +
+            PrincipalName.NAME_COMPONENT_SEPARATOR_STR +
+            "server.dev.rabbit.hole";
+
+    private static Credentials tgt;
+    private static Credentials tgs;
+
+    public static void main(String[] args) throws Exception {
+        try {
+            initializeKDCs();
+            getTGT();
+            getTGS();
+        } finally {
+            cleanup();
+        }
+    }
+
+    private static void initializeKDCs() throws Exception {
+        KDC kdc1 = KDC.create(realmKDC1, "localhost", 0, true);
+        kdc1.addPrincipalRandKey(PrincipalName.TGS_DEFAULT_SRV_NAME +
+                PrincipalName.NAME_COMPONENT_SEPARATOR_STR + realmKDC1);
+        kdc1.addPrincipal(PrincipalName.TGS_DEFAULT_SRV_NAME +
+                PrincipalName.NAME_COMPONENT_SEPARATOR_STR + realmKDC1 +
+                PrincipalName.NAME_REALM_SEPARATOR_STR + realmKDC2,
+                password);
+        kdc1.addPrincipal(PrincipalName.TGS_DEFAULT_SRV_NAME +
+                PrincipalName.NAME_COMPONENT_SEPARATOR_STR + realmKDC2,
+                password);
+
+        KDC kdc2 = KDC.create(realmKDC2, "localhost", 0, true);
+        kdc2.addPrincipalRandKey(PrincipalName.TGS_DEFAULT_SRV_NAME +
+                PrincipalName.NAME_COMPONENT_SEPARATOR_STR + realmKDC2);
+        kdc2.addPrincipal(clientKDC2Name, password);
+        kdc2.addPrincipal(serviceName, password);
+        kdc2.addPrincipal(PrincipalName.TGS_DEFAULT_SRV_NAME +
+                PrincipalName.NAME_COMPONENT_SEPARATOR_STR + realmKDC1,
+                password);
+        kdc2.addPrincipal(PrincipalName.TGS_DEFAULT_SRV_NAME +
+                PrincipalName.NAME_COMPONENT_SEPARATOR_STR + realmKDC2 +
+                PrincipalName.NAME_REALM_SEPARATOR_STR + realmKDC1,
+                password);
+
+        kdc1.registerAlias(clientAlias, kdc2);
+        kdc1.registerAlias(serviceName, kdc2);
+        kdc2.registerAlias(clientAlias, clientKDC2Name);
+
+        KDC.saveConfig(krbConfigName, kdc1, kdc2,
+                    "forwardable=true");
+        System.setProperty("java.security.krb5.conf", krbConfigName);
+    }
+
+    private static void cleanup() {
+        File f = new File(krbConfigName);
+        if (f.exists()) {
+            f.delete();
+        }
+    }
+
+    private static void getTGT() throws Exception {
+        KrbAsReqBuilder builder = new KrbAsReqBuilder(clientKDC1QueryPrincipal,
+                password);
+        tgt = builder.action().getCreds();
+        builder.destroy();
+        if (DEBUG) {
+            System.out.println("TGT");
+            System.out.println("----------------------");
+            System.out.println(tgt);
+            System.out.println("----------------------");
+        }
+        if (tgt == null) {
+            throw new Exception("TGT is null");
+        }
+        if (!tgt.getClient().getName().equals(clientKDC2Name)) {
+            throw new Exception("Unexpected TGT client");
+        }
+        String[] tgtServerNames = tgt.getServer().getNameStrings();
+        if (tgtServerNames.length != 2 || !tgtServerNames[0].equals(
+                PrincipalName.TGS_DEFAULT_SRV_NAME) ||
+                !tgtServerNames[1].equals(realmKDC2) ||
+                !tgt.getServer().getRealmString().equals(realmKDC2)) {
+            throw new Exception("Unexpected TGT server");
+        }
+    }
+
+    private static void getTGS() throws Exception {
+        tgs = CredentialsUtil.acquireServiceCreds(serviceName +
+                PrincipalName.NAME_REALM_SEPARATOR_STR + realmKDC1, tgt);
+        if (DEBUG) {
+            System.out.println("TGS");
+            System.out.println("----------------------");
+            System.out.println(tgs);
+            System.out.println("----------------------");
+        }
+        if (tgs == null) {
+            throw new Exception("TGS is null");
+        }
+        if (!tgs.getClient().getName().equals(clientKDC2Name)) {
+            throw new Exception("Unexpected TGS client");
+        }
+        if (!tgs.getServer().getNameString().equals(serviceName) ||
+                !tgs.getServer().getRealmString().equals(realmKDC2)) {
+            throw new Exception("Unexpected TGS server");
+        }
+    }
+}