--- 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");
+ }
+ }
+}