diff --git a/src/share/classes/javax/security/auth/kerberos/KerberosPrincipal.java b/src/share/classes/javax/security/auth/kerberos/KerberosPrincipal.java index 308b619e99881731054d9f20eda28ac5a186671e..f896998857ae03d8a3e85d846314f481c8a63dca 100644 --- a/src/share/classes/javax/security/auth/kerberos/KerberosPrincipal.java +++ b/src/share/classes/javax/security/auth/kerberos/KerberosPrincipal.java @@ -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 @@ -80,6 +80,11 @@ public final class KerberosPrincipal 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; diff --git a/src/share/classes/sun/security/krb5/Checksum.java b/src/share/classes/sun/security/krb5/Checksum.java index 6a7f491257d8e70d3960200b745780bb0d028fae..377a07b9731b98043fb30713dc87d9a4b13dc9b5 100644 --- a/src/share/classes/sun/security/krb5/Checksum.java +++ b/src/share/classes/sun/security/krb5/Checksum.java @@ -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 @@ -214,7 +214,7 @@ public class Checksum { * @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); diff --git a/src/share/classes/sun/security/krb5/Config.java b/src/share/classes/sun/security/krb5/Config.java index 7ee9231b0f9efcf84805040c95cb9bfc574b9e4c..84f80c6dd89a3476430666ddf7a4132a04a14077 100644 --- a/src/share/classes/sun/security/krb5/Config.java +++ b/src/share/classes/sun/security/krb5/Config.java @@ -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 @@ -49,6 +49,7 @@ import java.util.Locale; import sun.net.dns.ResolverConfiguration; 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 @@ -57,6 +58,41 @@ import sun.security.krb5.internal.Krb5; 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. */ diff --git a/src/share/classes/sun/security/krb5/KrbAsRep.java b/src/share/classes/sun/security/krb5/KrbAsRep.java index b03276fd09e4d1ac6c566d6d3b9388a356085145..d647b82ab16b425598a37869348f8fac7d7449a6 100644 --- a/src/share/classes/sun/security/krb5/KrbAsRep.java +++ b/src/share/classes/sun/security/krb5/KrbAsRep.java @@ -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 @@ -155,11 +155,11 @@ class KrbAsRep extends KrbKdcRep { 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, diff --git a/src/share/classes/sun/security/krb5/KrbAsReq.java b/src/share/classes/sun/security/krb5/KrbAsReq.java index 8de29b346ae1a741bce16243162271e184185c68..55930ff870e51b9cebec70cb1949fac36629a682 100644 --- a/src/share/classes/sun/security/krb5/KrbAsReq.java +++ b/src/share/classes/sun/security/krb5/KrbAsReq.java @@ -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 @@ -35,6 +35,7 @@ import sun.security.krb5.internal.*; import sun.security.krb5.internal.crypto.Nonce; import sun.security.krb5.internal.crypto.KeyUsage; import java.io.IOException; +import java.util.Arrays; /** * This class encapsulates the KRB-AS-REQ message that the client @@ -57,7 +58,8 @@ public class KrbAsReq { KerberosTime till, // ok, will use KerberosTime rtime, // ok int[] eTypes, // NO - HostAddresses addresses // ok + HostAddresses addresses, // ok + PAData[] extraPAs // ok ) throws KrbException, IOException { @@ -99,6 +101,15 @@ public class KrbAsReq { 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, diff --git a/src/share/classes/sun/security/krb5/KrbAsReqBuilder.java b/src/share/classes/sun/security/krb5/KrbAsReqBuilder.java index 3c8fb904017d2a9a7f46ecdbad91763b6b7b68a5..3ca7b6d71ac8a09d167aef71c9eb6125c11305e3 100644 --- a/src/share/classes/sun/security/krb5/KrbAsReqBuilder.java +++ b/src/share/classes/sun/security/krb5/KrbAsReqBuilder.java @@ -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 @@ -252,7 +252,9 @@ public final class KrbAsReqBuilder { * @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"); @@ -262,6 +264,14 @@ public final class KrbAsReqBuilder { 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, @@ -270,7 +280,8 @@ public final class KrbAsReqBuilder { till, rtime, eTypes, - addresses); + addresses, + extraPAs); } /** @@ -308,11 +319,15 @@ public final class KrbAsReqBuilder { */ 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) { @@ -341,12 +356,69 @@ public final class KrbAsReqBuilder { } 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. diff --git a/src/share/classes/sun/security/krb5/KrbKdcRep.java b/src/share/classes/sun/security/krb5/KrbKdcRep.java index 6d79afe222cccbea7e06fd6f18e1f07532398c1e..6ecf00b49296c80374e0a73e3bf189af40172c8b 100644 --- a/src/share/classes/sun/security/krb5/KrbKdcRep.java +++ b/src/share/classes/sun/security/krb5/KrbKdcRep.java @@ -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) { @@ -104,6 +122,46 @@ abstract class KrbKdcRep { throw new KrbApErrException(Krb5.KRB_AP_ERR_MODIFIED); } + // 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); + } + } + if (req.reqBody.kdcOptions.get(KDCOptions.RENEWABLE)) if (req.reqBody.rtime != null && !req.reqBody.rtime.isZero()) // verify this is required diff --git a/src/share/classes/sun/security/krb5/KrbTgsRep.java b/src/share/classes/sun/security/krb5/KrbTgsRep.java index 4f5c2d6ed9048492a3a100e078f8347ddac8cd36..d1f469d37ab9205f4bd501948e746aa724b2ec7c 100644 --- a/src/share/classes/sun/security/krb5/KrbTgsRep.java +++ b/src/share/classes/sun/security/krb5/KrbTgsRep.java @@ -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 @@ -84,7 +84,7 @@ public class KrbTgsRep extends KrbKdcRep { 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, diff --git a/src/share/classes/sun/security/krb5/KrbTgsReq.java b/src/share/classes/sun/security/krb5/KrbTgsReq.java index 85b7cb2e05c5703c041f417125ecfbf616b0cd35..c924da6967e1abed44de2d090131862c23a0672a 100644 --- a/src/share/classes/sun/security/krb5/KrbTgsReq.java +++ b/src/share/classes/sun/security/krb5/KrbTgsReq.java @@ -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 @@ -57,59 +57,23 @@ public class KrbTgsReq { 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 +107,7 @@ public class KrbTgsReq { AuthorizationData authorizationData, Ticket[] additionalTickets, EncryptionKey subKey, - PAData extraPA) throws KrbException, IOException { + PAData[] extraPAs) throws KrbException, IOException { princName = cname; servName = sname; @@ -216,7 +180,7 @@ public class KrbTgsReq { authorizationData, additionalTickets, subKey, - extraPA); + extraPAs); obuf = tgsReqMessg.asn1Encode(); // XXX We need to revisit this to see if can't move it @@ -282,7 +246,7 @@ public class KrbTgsReq { AuthorizationData authorizationData, Ticket[] additionalTickets, EncryptionKey subKey, - PAData extraPA) + PAData[] extraPAs) throws IOException, KrbException, UnknownHostException { KerberosTime req_till = null; if (till == null) { @@ -375,11 +339,14 @@ public class KrbTgsReq { 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() { diff --git a/src/share/classes/sun/security/krb5/PrincipalName.java b/src/share/classes/sun/security/krb5/PrincipalName.java index 6d5ed3dde67108af35824791df65be7a0ea373df..b8e855a6a12b6c66ccc0491c97dc3e7254b74b66 100644 --- a/src/share/classes/sun/security/krb5/PrincipalName.java +++ b/src/share/classes/sun/security/krb5/PrincipalName.java @@ -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 @@ -90,6 +90,11 @@ public class PrincipalName implements Cloneable { */ public static final int KRB_NT_UID = 5; + /** + * Enterprise name (alias) + */ + public static final int KRB_NT_ENTERPRISE = 10; + /** * TGS Name */ @@ -454,6 +459,7 @@ public class PrincipalName implements Cloneable { 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) { diff --git a/src/share/classes/sun/security/krb5/internal/CredentialsUtil.java b/src/share/classes/sun/security/krb5/internal/CredentialsUtil.java index 14620e94483ece9940deaeaaa9ce4c58c6b8bdc4..25e29387a2f7c9f4e9b6e14e154476efbd92f350 100644 --- a/src/share/classes/sun/security/krb5/internal/CredentialsUtil.java +++ b/src/share/classes/sun/security/krb5/internal/CredentialsUtil.java @@ -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 @@ package sun.security.krb5.internal; 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 @@ public class CredentialsUtil { 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 @@ public class CredentialsUtil { 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 class CredentialsUtil { 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 @@ public class CredentialsUtil { 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 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; } } diff --git a/src/share/classes/sun/security/krb5/internal/EncASRepPart.java b/src/share/classes/sun/security/krb5/internal/EncASRepPart.java index 7e5d037de5d56a17cc571f2c11a917fb347c75c1..ff9382e83f44fb48dc234eb5935c2c946d178b70 100644 --- a/src/share/classes/sun/security/krb5/internal/EncASRepPart.java +++ b/src/share/classes/sun/security/krb5/internal/EncASRepPart.java @@ -47,7 +47,8 @@ public class EncASRepPart extends EncKDCRepPart { 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 @@ public class EncASRepPart extends EncKDCRepPart { 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 diff --git a/src/share/classes/sun/security/krb5/internal/EncKDCRepPart.java b/src/share/classes/sun/security/krb5/internal/EncKDCRepPart.java index 2dd4c841ccc7c72cbb5e4b776a2b62d0cc12aca3..5c603649633e891495176b696ff2dbe7b12af33d 100644 --- a/src/share/classes/sun/security/krb5/internal/EncKDCRepPart.java +++ b/src/share/classes/sun/security/krb5/internal/EncKDCRepPart.java @@ -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 @@ import java.math.BigInteger; * Implements the ASN.1 EncKDCRepPart type. * * - * 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 * } * * @@ -76,6 +76,7 @@ public class EncKDCRepPart { 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 @@ public class EncKDCRepPart { 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 @@ public class EncKDCRepPart { renewTill = new_renewTill; sname = new_sname; caddr = new_caddr; + pAData = new_pAData; msgType = new_msgType; } @@ -160,6 +163,9 @@ public class EncKDCRepPart { 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 class EncKDCRepPart { */ 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); diff --git a/src/share/classes/sun/security/krb5/internal/EncTGSRepPart.java b/src/share/classes/sun/security/krb5/internal/EncTGSRepPart.java index cdca881ebe44e3972bff5b78f8253f86b4b42546..b1f192e72e4d8f0c09f86eddbd1e6c59ca61e92c 100644 --- a/src/share/classes/sun/security/krb5/internal/EncTGSRepPart.java +++ b/src/share/classes/sun/security/krb5/internal/EncTGSRepPart.java @@ -46,7 +46,8 @@ public class EncTGSRepPart extends EncKDCRepPart { 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 @@ public class EncTGSRepPart extends EncKDCRepPart { new_renewTill, new_sname, new_caddr, + new_pAData, Krb5.KRB_ENC_TGS_REP_PART); } diff --git a/src/share/classes/sun/security/krb5/internal/KDCOptions.java b/src/share/classes/sun/security/krb5/internal/KDCOptions.java index a3d93021710d75df29a9f6b10d922b008b6dec6b..eb1b6f596c9502dd24943acba2e1a8c974cd4220 100644 --- a/src/share/classes/sun/security/krb5/internal/KDCOptions.java +++ b/src/share/classes/sun/security/krb5/internal/KDCOptions.java @@ -140,6 +140,7 @@ public class KDCOptions extends KerberosFlags { 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 @@ public class KDCOptions extends KerberosFlags { "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, diff --git a/src/share/classes/sun/security/krb5/internal/KDCReq.java b/src/share/classes/sun/security/krb5/internal/KDCReq.java index 16591e80404f263fa7850699d55a1f48779187c5..12d410745d5ee1c5a0475a9cb1e5ec6c0b7b0fee 100644 --- a/src/share/classes/sun/security/krb5/internal/KDCReq.java +++ b/src/share/classes/sun/security/krb5/internal/KDCReq.java @@ -59,9 +59,9 @@ import java.math.BigInteger; 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 @@ public class KDCReq { } 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 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(); diff --git a/src/share/classes/sun/security/krb5/internal/KRBError.java b/src/share/classes/sun/security/krb5/internal/KRBError.java index 6569cbc4fb5f8ac402fe9ab44569d5a988106e85..34247ab469b8561462939683db5fdc62ed71236b 100644 --- a/src/share/classes/sun/security/krb5/internal/KRBError.java +++ b/src/share/classes/sun/security/krb5/internal/KRBError.java @@ -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 @@ public class KRBError implements java.io.Serializable { 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 @@ public class KRBError implements java.io.Serializable { 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 @@ public class KRBError implements java.io.Serializable { 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 @@ public class KRBError implements java.io.Serializable { pa = paList.toArray(new PAData[paList.size()]); } + public final Realm getClientRealm() { + return crealm; + } + public final KerberosTime getServerTime() { return sTime; } @@ -349,7 +356,7 @@ public class KRBError implements java.io.Serializable { 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 @@ public class KRBError implements java.io.Serializable { 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 @@ public class KRBError implements java.io.Serializable { 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 @@ public class KRBError implements java.io.Serializable { 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 @@ public class KRBError implements java.io.Serializable { 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(); diff --git a/src/share/classes/sun/security/krb5/internal/Krb5.java b/src/share/classes/sun/security/krb5/internal/Krb5.java index 2be21dc78a8e609799cb4590a5b8b18ed9e35584..a2e7e3b86991325b36464e6aba937f86467dc819 100644 --- a/src/share/classes/sun/security/krb5/internal/Krb5.java +++ b/src/share/classes/sun/security/krb5/internal/Krb5.java @@ -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 @@ -70,6 +70,7 @@ public class Krb5 { 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 @@ -163,6 +164,9 @@ public class Krb5 { // 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 //-------------------------------+------------- @@ -265,6 +269,7 @@ public class Krb5 { 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; diff --git a/src/share/classes/sun/security/krb5/internal/PAData.java b/src/share/classes/sun/security/krb5/internal/PAData.java index f3fdb0b94578639b5c1aafdf019cdcc2e1920d45..96db24f4efc7ee1fa8937f1921d0f84e278cb0ed 100644 --- a/src/share/classes/sun/security/krb5/internal/PAData.java +++ b/src/share/classes/sun/security/krb5/internal/PAData.java @@ -1,4 +1,5 @@ /* + * 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 @@ -34,6 +35,8 @@ import sun.security.krb5.KrbException; import sun.security.util.*; import sun.security.krb5.Asn1Exception; import java.io.IOException; +import java.util.Vector; + import sun.security.krb5.internal.util.KerberosString; /** @@ -138,6 +141,41 @@ public class PAData { return ((pADataValue == null) ? null : pADataValue.clone()); } + /** + * 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 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. * 1. ETYPE-INFO2-ENTRY with unknown s2kparams ignored diff --git a/src/share/classes/sun/security/krb5/internal/ReferralsCache.java b/src/share/classes/sun/security/krb5/internal/ReferralsCache.java new file mode 100644 index 0000000000000000000000000000000000000000..d21181600055535c818c8c52636ce181bcbfa020 --- /dev/null +++ b/src/share/classes/sun/security/krb5/internal/ReferralsCache.java @@ -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> 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 entries = referralsMap.get(service); + if (entries == null) { + entries = new HashMap(); + 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 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 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 entries = referralsMap.get(service); + if (entries != null) { + for (Entry mapEntry : + entries.entrySet()) { + if (mapEntry.getValue().getCreds().getEndTime().before(now)) { + entries.remove(mapEntry.getKey()); + } + } + } + } +} diff --git a/src/share/classes/sun/security/krb5/internal/TicketFlags.java b/src/share/classes/sun/security/krb5/internal/TicketFlags.java index 1f77e0f070ee7d25e3c7c60e556fd91e18b5ae75..1b3a18215f4e33db2971b966fe22e8344e77c49d 100644 --- a/src/share/classes/sun/security/krb5/internal/TicketFlags.java +++ b/src/share/classes/sun/security/krb5/internal/TicketFlags.java @@ -51,7 +51,8 @@ import java.io.IOException; * 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 @@ public class TicketFlags extends KerberosFlags { case 11: sb.append("HW-AUTHENT;"); break; + case 15: + sb.append("ENC-PA-REP;"); + break; } } } diff --git a/src/share/classes/sun/security/krb5/internal/crypto/KeyUsage.java b/src/share/classes/sun/security/krb5/internal/crypto/KeyUsage.java index f9be46c9beb36b7dc77612879f1fd6c97143766d..5179f520024045af4878cd140a38d7ae69d2a864 100644 --- a/src/share/classes/sun/security/krb5/internal/crypto/KeyUsage.java +++ b/src/share/classes/sun/security/krb5/internal/crypto/KeyUsage.java @@ -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 class KeyUsage { 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; diff --git a/src/share/lib/security/java.security-aix b/src/share/lib/security/java.security-aix index 69cc59fa650281b296e3fba31b1e93fd55a72bc5..a752a070739bdd4f2680c00731eab8d11de0dde5 100644 --- a/src/share/lib/security/java.security-aix +++ b/src/share/lib/security/java.security-aix @@ -420,6 +420,32 @@ networkaddress.cache.negative.ttl=10 # krb5.kdc.bad.policy = tryLess:2,2000 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 diff --git a/src/share/lib/security/java.security-linux b/src/share/lib/security/java.security-linux index ae28ce7b7b7641669ad0d41f6b2549a011e27774..c8c8232df3509d5645ba9a2b60848ab91a59abbe 100644 --- a/src/share/lib/security/java.security-linux +++ b/src/share/lib/security/java.security-linux @@ -420,6 +420,32 @@ networkaddress.cache.negative.ttl=10 # krb5.kdc.bad.policy = tryLess:2,2000 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 diff --git a/src/share/lib/security/java.security-macosx b/src/share/lib/security/java.security-macosx index 8af804b714158ff1ce31cd4b1a5ac5eaa85a88e6..55aa65933a25a27aa437d9d45acb247e23d729d6 100644 --- a/src/share/lib/security/java.security-macosx +++ b/src/share/lib/security/java.security-macosx @@ -423,6 +423,32 @@ networkaddress.cache.negative.ttl=10 # krb5.kdc.bad.policy = tryLess:2,2000 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 diff --git a/src/share/lib/security/java.security-solaris b/src/share/lib/security/java.security-solaris index 204871b144fbd678b5f693e2f54443897976ff7f..a3a796d4ddb39bb0c2e46f2562520a4933e14175 100644 --- a/src/share/lib/security/java.security-solaris +++ b/src/share/lib/security/java.security-solaris @@ -422,6 +422,32 @@ networkaddress.cache.negative.ttl=10 # krb5.kdc.bad.policy = tryLess:2,2000 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 diff --git a/src/share/lib/security/java.security-windows b/src/share/lib/security/java.security-windows index e49d1880aa73d5a5bcf8e11eb590b2bc8357eec1..623d02e4f5d817c871e1ccd08717ae4a14bc2463 100644 --- a/src/share/lib/security/java.security-windows +++ b/src/share/lib/security/java.security-windows @@ -423,6 +423,32 @@ networkaddress.cache.negative.ttl=10 # krb5.kdc.bad.policy = tryLess:2,2000 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 diff --git a/test/sun/security/krb5/auto/KDC.java b/test/sun/security/krb5/auto/KDC.java index 02a363d5d3058c2eb0691fe2cc2d1c21fcc6dbd7..696ae3973df97636b6be73a3a8555b4885032dbc 100644 --- a/test/sun/security/krb5/auto/KDC.java +++ b/test/sun/security/krb5/auto/KDC.java @@ -141,6 +141,14 @@ public class KDC { private TreeMap passwords = new TreeMap<> (String.CASE_INSENSITIVE_ORDER); + // Alias for referrals. + private TreeMap aliasReferrals = new TreeMap<> + (String.CASE_INSENSITIVE_ORDER); + + // Alias for local resolution. + private TreeMap alias2Principals = new TreeMap<> + (String.CASE_INSENSITIVE_ORDER); + // Realm name private String realm; // KDC @@ -492,6 +500,29 @@ public class KDC { 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 /** @@ -680,6 +711,17 @@ public class KDC { 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 { @@ -847,7 +889,8 @@ public class KDC { body.addresses != null // always set caddr ? body.addresses : new HostAddresses( - new InetAddress[]{InetAddress.getLocalHost()}) + new InetAddress[]{InetAddress.getLocalHost()}), + null ); EncryptedData edata = new EncryptedData(ckey, enc_part.asn1Encode(), KeyUsage.KU_ENC_TGS_REP_PART_SESSKEY); TGSRep tgsRep = new TGSRep(null, @@ -890,6 +933,7 @@ public class KDC { */ protected byte[] processAsReq(byte[] in) throws Exception { ASReq asReq = new ASReq(in); + byte[] asReqbytes = asReq.asn1Encode(); int[] eTypes = null; List outPAs = new ArrayList<>(); @@ -909,6 +953,24 @@ public class KDC { eTypes = KDCReqBodyDotEType(body); 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); @@ -1089,20 +1151,33 @@ public class KDC { } PAData[] inPAs = KDCReqDotPAData(asReq); + List 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) { throw new KrbException(Krb5.KDC_ERR_PREAUTH_FAILED); } 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); @@ -1133,7 +1208,8 @@ public class KDC { body.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); ASRep asRep = new ASRep( @@ -1180,8 +1256,10 @@ public class KDC { 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()); } diff --git a/test/sun/security/krb5/auto/ReferralsTest.java b/test/sun/security/krb5/auto/ReferralsTest.java new file mode 100644 index 0000000000000000000000000000000000000000..9fc87b7e064ae16ba6209f71a30cba7b4499f8e4 --- /dev/null +++ b/test/sun/security/krb5/auto/ReferralsTest.java @@ -0,0 +1,168 @@ +/* + * 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 + * @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"); + } + } +}