8272162: S4U2Self ticket without forwardable flag

Reviewed-by: valeriep
This commit is contained in:
Weijun Wang 2021-12-01 00:48:28 +00:00
parent dd73e3cea2
commit ab867f6c7c
17 changed files with 375 additions and 195 deletions

@ -1365,3 +1365,29 @@ jdk.tls.alpnCharset=ISO_8859_1
# The default pattern value allows any object factory class specified by the reference
# instance to recreate the referenced object.
#jdk.jndi.object.factoriesFilter=*
#
# Policy for non-forwardable service ticket in a S4U2proxy request
#
# The Service for User to Proxy (S4U2proxy) Kerberos extension enables a middle service
# to obtain a service ticket to another service on behalf of a user. It requires that
# the user's service ticket to the first service has the forwardable flag set [1].
# However, some KDC implementations ignore this requirement and accept service tickets
# with the flag unset.
#
# If this security property is set to "true", then
#
# 1) The user service ticket, when obtained by the middle service after a S4U2self
# impersonation, is not required to have the forwardable flag set; and,
#
# 2) If a S4U2proxy request receives a KRB_ERROR of the KDC_ERR_BADOPTION error code
# and the ticket to the middle service is not forwardable, OpenJDK will try the same
# request with another KDC instead of treating it as a fatal failure.
#
# The default value is "false".
#
# If a system property of the same name is also specified, it supersedes the
# security property value defined here.
#
# [1] https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-sfu/bde93b0e-f3c9-4ddf-9f44-e1453be7af5a
#jdk.security.krb5.s4u2proxy.acceptNonForwardableServiceTicket=false

@ -45,7 +45,6 @@ import javax.security.auth.kerberos.ServicePermission;
import javax.security.auth.kerberos.KerberosCredMessage;
import javax.security.auth.kerberos.KerberosPrincipal;
import javax.security.auth.kerberos.KerberosTicket;
import sun.security.krb5.internal.Ticket;
import sun.security.krb5.internal.AuthorizationData;
/**
@ -120,9 +119,12 @@ class Krb5Context implements GSSContextSpi {
// XXX See if the required info from these can be extracted and
// stored elsewhere
private Credentials tgt;
// On the Initiator side, contains the final TGS to a service on both
// delegation and no-delegation scenarios.
// On the Acceptor side, contains a user TGS usable for delegation.
private Credentials serviceCreds;
private KrbApReq apReq;
Ticket serviceTicket;
private final GSSCaller caller;
private static final boolean DEBUG = Krb5Util.DEBUG;
@ -548,7 +550,7 @@ class Krb5Context implements GSSContextSpi {
delegatedCred = new Krb5ProxyCredential(
Krb5InitCredential.getInstance(
GSSCaller.CALLER_ACCEPT, myName, lifetime),
peerName, serviceTicket);
peerName, serviceCreds);
} catch (GSSException gsse) {
// OK, delegatedCred is null then
}
@ -623,13 +625,13 @@ class Krb5Context implements GSSContextSpi {
"No TGT available");
}
myName = (Krb5NameElement) myCred.getName();
final Krb5ProxyCredential second;
final Krb5ProxyCredential proxyCreds;
if (myCred instanceof Krb5InitCredential) {
second = null;
proxyCreds = null;
tgt = ((Krb5InitCredential) myCred).getKrb5Credentials();
} else {
second = (Krb5ProxyCredential) myCred;
tgt = second.self.getKrb5Credentials();
proxyCreds = (Krb5ProxyCredential) myCred;
tgt = proxyCreds.self.getKrb5Credentials();
}
checkPermission(peerName.getKrb5PrincipalName().getName(),
@ -657,9 +659,9 @@ class Krb5Context implements GSSContextSpi {
GSSCaller.CALLER_UNKNOWN,
// since it's useSubjectCredsOnly here,
// don't worry about the null
second == null ?
proxyCreds == null ?
myName.getKrb5PrincipalName().getName():
second.getName().getKrb5PrincipalName().getName(),
proxyCreds.getName().getKrb5PrincipalName().getName(),
peerName.getKrb5PrincipalName().getName());
}});
kerbTicket = tmp;
@ -690,15 +692,15 @@ class Krb5Context implements GSSContextSpi {
"the subject");
}
// Get Service ticket using the Kerberos protocols
if (second == null) {
if (proxyCreds == null) {
serviceCreds = Credentials.acquireServiceCreds(
peerName.getKrb5PrincipalName().getName(),
tgt);
} else {
serviceCreds = Credentials.acquireS4U2proxyCreds(
peerName.getKrb5PrincipalName().getName(),
second.tkt,
second.getName().getKrb5PrincipalName(),
proxyCreds.userCreds,
proxyCreds.getName().getKrb5PrincipalName(),
tgt);
}
if (GSSUtil.useSubjectCredsOnly(caller)) {
@ -844,7 +846,7 @@ class Krb5Context implements GSSContextSpi {
retVal = new AcceptSecContextToken(this,
token.getKrbApReq()).encode();
}
serviceTicket = token.getKrbApReq().getCreds().getTicket();
serviceCreds = token.getKrbApReq().getCreds();
myCred = null;
state = STATE_DONE;
} else {

@ -392,7 +392,7 @@ public class Krb5InitCredential
Krb5NameElement kname = (Krb5NameElement)name;
Credentials newCred = Credentials.acquireS4U2selfCreds(
kname.getKrb5PrincipalName(), krb5Credentials);
return new Krb5ProxyCredential(this, kname, newCred.getTicket());
return new Krb5ProxyCredential(this, kname, newCred);
} catch (IOException | KrbException ke) {
GSSException ge =
new GSSException(GSSException.FAILURE, -1,

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2021, 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,7 +33,6 @@ import java.io.IOException;
import sun.security.krb5.Credentials;
import sun.security.krb5.KrbException;
import sun.security.krb5.internal.Ticket;
import javax.security.auth.kerberos.KerberosTicket;
@ -50,23 +49,23 @@ public class Krb5ProxyCredential
implements Krb5CredElement {
public final Krb5InitCredential self; // the middle server
private final Krb5NameElement client; // the client
private final Krb5NameElement user; // the user
// The ticket with cname=client and sname=self. This can be a normal
// service ticket or an S4U2self ticket.
public final Ticket tkt;
// The creds with cname=user and sname=self. The ticket inside can
// be either a normal service ticket or an S4U2self ticket.
public final Credentials userCreds;
Krb5ProxyCredential(Krb5InitCredential self, Krb5NameElement client,
Ticket tkt) {
Krb5ProxyCredential(Krb5InitCredential self, Krb5NameElement user,
Credentials userCreds) {
this.self = self;
this.tkt = tkt;
this.client = client;
this.userCreds = userCreds;
this.user = user;
}
// The client name behind the proxy
// The user name behind the proxy
@Override
public final Krb5NameElement getName() throws GSSException {
return client;
return user;
}
@Override
@ -130,7 +129,7 @@ public class Krb5ProxyCredential
Credentials proxyCreds = Krb5Util.ticketToCreds(proxy);
return new Krb5ProxyCredential(initiator,
Krb5NameElement.getInstance(proxyCreds.getClient()),
proxyCreds.getTicket());
proxyCreds);
} else {
return initiator;
}

@ -35,6 +35,8 @@ import sun.security.action.GetPropertyAction;
import sun.security.krb5.internal.*;
import sun.security.krb5.internal.ccache.CredentialsCache;
import sun.security.krb5.internal.crypto.EType;
import sun.security.util.SecurityProperties;
import java.io.IOException;
import java.util.Date;
import java.util.Locale;
@ -64,6 +66,10 @@ public class Credentials {
static boolean alreadyLoaded = false;
private static boolean alreadyTried = false;
public static final boolean S4U2PROXY_ACCEPT_NON_FORWARDABLE
= "true".equalsIgnoreCase(SecurityProperties.privilegedGetOverridable(
"jdk.security.krb5.s4u2proxy.acceptNonForwardableServiceTicket"));
private Credentials proxy = null;
public Credentials getProxy() {
@ -97,7 +103,7 @@ public class Credentials {
this.authzData = authzData;
}
// Warning: called by NativeCreds.c and nativeccache.c
// Warning: also called by NativeCreds.c and nativeccache.c
public Credentials(Ticket new_ticket,
PrincipalName new_client,
PrincipalName new_client_alias,
@ -478,7 +484,7 @@ public class Credentials {
*
* @param service the name of service principal using format
* components@realm
* @param ccreds client's initial credential.
* @param initCreds client's initial credential.
* @exception IOException if an error occurs in reading the credentials
* cache
* @exception KrbException if an error occurs specific to Kerberos
@ -486,21 +492,21 @@ public class Credentials {
*/
public static Credentials acquireServiceCreds(String service,
Credentials ccreds)
Credentials initCreds)
throws KrbException, IOException {
return CredentialsUtil.acquireServiceCreds(service, ccreds);
return CredentialsUtil.acquireServiceCreds(service, initCreds);
}
public static Credentials acquireS4U2selfCreds(PrincipalName user,
Credentials ccreds) throws KrbException, IOException {
return CredentialsUtil.acquireS4U2selfCreds(user, ccreds);
Credentials middleTGT) throws KrbException, IOException {
return CredentialsUtil.acquireS4U2selfCreds(user, middleTGT);
}
public static Credentials acquireS4U2proxyCreds(String service,
Ticket second, PrincipalName client, Credentials ccreds)
Credentials userCreds, PrincipalName client, Credentials middleTGT)
throws KrbException, IOException {
return CredentialsUtil.acquireS4U2proxyCreds(
service, second, client, ccreds);
service, userCreds, client, middleTGT);
}
/*

@ -188,21 +188,22 @@ public final class KdcComm {
this.realm = realm;
}
public byte[] send(byte[] obuf)
public byte[] send(KrbKdcReq req)
throws IOException, KrbException {
int udpPrefLimit = getRealmSpecificValue(
realm, "udp_preference_limit", defaultUdpPrefLimit);
byte[] obuf = req.encoding();
boolean useTCP = (udpPrefLimit > 0 &&
(obuf != null && obuf.length > udpPrefLimit));
return send(obuf, useTCP);
return send(req, useTCP);
}
private byte[] send(byte[] obuf, boolean useTCP)
private byte[] send(KrbKdcReq req, boolean useTCP)
throws IOException, KrbException {
if (obuf == null)
if (req == null)
return null;
Config cfg = Config.getInstance();
@ -225,12 +226,12 @@ public final class KdcComm {
}
byte[] ibuf = null;
try {
ibuf = sendIfPossible(obuf, tempKdc.next(), useTCP);
ibuf = sendIfPossible(req, tempKdc.next(), useTCP);
} catch(Exception first) {
boolean ok = false;
while(tempKdc.hasNext()) {
try {
ibuf = sendIfPossible(obuf, tempKdc.next(), useTCP);
ibuf = sendIfPossible(req, tempKdc.next(), useTCP);
ok = true;
break;
} catch(Exception ignore) {}
@ -243,13 +244,13 @@ public final class KdcComm {
return ibuf;
}
// send the AS Request to the specified KDC
// send the KDC Request to the specified KDC
// failover to using TCP if useTCP is not set and response is too big
private byte[] sendIfPossible(byte[] obuf, String tempKdc, boolean useTCP)
private byte[] sendIfPossible(KrbKdcReq req, String tempKdc, boolean useTCP)
throws IOException, KrbException {
try {
byte[] ibuf = send(obuf, tempKdc, useTCP);
byte[] ibuf = send(req, tempKdc, useTCP);
KRBError ke = null;
try {
ke = new KRBError(ibuf);
@ -259,10 +260,17 @@ public final class KdcComm {
if (ke != null) {
if (ke.getErrorCode() ==
Krb5.KRB_ERR_RESPONSE_TOO_BIG) {
ibuf = send(obuf, tempKdc, true);
ibuf = send(req, tempKdc, true);
} else if (ke.getErrorCode() ==
Krb5.KDC_ERR_SVC_UNAVAILABLE) {
throw new KrbException("A service is not available");
} else if (ke.getErrorCode() == Krb5.KDC_ERR_BADOPTION
&& Credentials.S4U2PROXY_ACCEPT_NON_FORWARDABLE
&& req instanceof KrbTgsReq tgsReq) {
Credentials extra = tgsReq.getAdditionalCreds();
if (extra != null && !extra.isForwardable()) {
throw new KrbException("S4U2Proxy with non-forwardable ticket");
}
}
}
KdcAccessibility.removeBad(tempKdc);
@ -278,12 +286,12 @@ public final class KdcComm {
}
}
// send the AS Request to the specified KDC
// send the KDC Request to the specified KDC
private byte[] send(byte[] obuf, String tempKdc, boolean useTCP)
private byte[] send(KrbKdcReq req, String tempKdc, boolean useTCP)
throws IOException, KrbException {
if (obuf == null)
if (req == null)
return null;
int port = Krb5.KDC_INET_DEFAULT_PORT;
@ -336,6 +344,7 @@ public final class KdcComm {
port = tempPort;
}
byte[] obuf = req.encoding();
if (DEBUG) {
System.out.println(">>> KrbKdcReq send: kdc=" + kdc
+ (useTCP ? " TCP:":" UDP:")

@ -1,5 +1,5 @@
/*
* Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2000, 2021, 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
@ -42,7 +42,7 @@ import java.util.Arrays;
* This class encapsulates the KRB-AS-REQ message that the client
* sends to the KDC.
*/
public class KrbAsReq {
public class KrbAsReq extends KrbKdcReq {
private ASReq asReqMessg;
private boolean DEBUG = Krb5.DEBUG;
@ -165,10 +165,7 @@ public class KrbAsReq {
asReqMessg = new ASReq(
paData,
kdc_req_body);
}
byte[] encoding() throws IOException, Asn1Exception {
return asReqMessg.asn1Encode();
obuf = asReqMessg.asn1Encode();
}
// Used by KrbAsRep to validate AS-REP

@ -1,5 +1,5 @@
/*
* Copyright (c) 2010, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2010, 2021, 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
@ -342,7 +342,7 @@ public final class KrbAsReqBuilder {
}
try {
req = build(pakey, referralsState);
rep = new KrbAsRep(comm.send(req.encoding()));
rep = new KrbAsRep(comm.send(req));
return this;
} catch (KrbException ke) {
if (!preAuthFailedOnce && (

@ -0,0 +1,38 @@
/*
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.security.krb5;
/**
* Parent class for KrbAsReq and KrbTgsReq.
*/
abstract class KrbKdcReq {
protected byte[] obuf;
public byte[] encoding() {
return obuf;
}
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2000, 2021, 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
@ -43,7 +43,7 @@ import java.io.IOException;
public class KrbTgsRep extends KrbKdcRep {
private TGSRep rep;
private Credentials creds;
private Ticket secondTicket;
private Credentials additionalCreds;
KrbTgsRep(byte[] ibuf, KrbTgsReq tgsReq)
throws KrbException, IOException {
@ -115,7 +115,7 @@ public class KrbTgsRep extends KrbKdcRep {
enc_part.caddr
);
this.rep = rep;
this.secondTicket = tgsReq.getSecondTicket();
this.additionalCreds = tgsReq.getAdditionalCreds();
}
/**
@ -126,7 +126,8 @@ public class KrbTgsRep extends KrbKdcRep {
}
sun.security.krb5.internal.ccache.Credentials setCredentials() {
return new sun.security.krb5.internal.ccache.Credentials(rep, secondTicket);
return new sun.security.krb5.internal.ccache.Credentials(
rep, additionalCreds == null ? null : additionalCreds.ticket);
}
private static boolean isReferralSname(PrincipalName sname) {

@ -42,7 +42,7 @@ import java.util.Arrays;
* This class encapsulates a Kerberos TGS-REQ that is sent from the
* client to the KDC.
*/
public class KrbTgsReq {
public class KrbTgsReq extends KrbKdcReq {
private PrincipalName princName;
private PrincipalName clientAlias;
@ -50,34 +50,31 @@ public class KrbTgsReq {
private PrincipalName serverAlias;
private TGSReq tgsReqMessg;
private KerberosTime ctime;
private Ticket secondTicket = null;
private Credentials additionalCreds = null;
private boolean useSubkey = false;
EncryptionKey tgsReqKey;
private byte[] obuf;
private byte[] ibuf;
// Used in CredentialsUtil
public KrbTgsReq(KDCOptions options, Credentials asCreds,
PrincipalName cname, PrincipalName clientAlias,
PrincipalName sname, PrincipalName serverAlias,
Ticket[] additionalTickets, PAData[] extraPAs)
throws KrbException, IOException {
Credentials additionalCreds, PAData[] extraPAs)
throws KrbException, IOException {
this(options,
asCreds,
cname,
clientAlias,
sname,
serverAlias,
null, // KerberosTime from
null, // KerberosTime till
null, // KerberosTime rtime
null, // int[] eTypes
null, // HostAddresses addresses
null, // AuthorizationData authorizationData
additionalTickets,
null, // EncryptionKey subKey
extraPAs);
asCreds,
cname,
clientAlias,
sname,
serverAlias,
null, // KerberosTime from
null, // KerberosTime till
null, // KerberosTime rtime
null, // int[] eTypes
null, // HostAddresses addresses
null, // AuthorizationData authorizationData
additionalCreds,
null, // EncryptionKey subKey
extraPAs);
}
// Called by Credentials, KrbCred
@ -92,11 +89,11 @@ public class KrbTgsReq {
int[] eTypes,
HostAddresses addresses,
AuthorizationData authorizationData,
Ticket[] additionalTickets,
Credentials additionalCreds,
EncryptionKey subKey) throws KrbException, IOException {
this(options, asCreds, asCreds.getClient(), asCreds.getClientAlias(),
sname, serverAlias, from, till, rtime, eTypes,
addresses, authorizationData, additionalTickets, subKey, null);
addresses, authorizationData, additionalCreds, subKey, null);
}
private KrbTgsReq(
@ -112,7 +109,7 @@ public class KrbTgsReq {
int[] eTypes,
HostAddresses addresses,
AuthorizationData authorizationData,
Ticket[] additionalTickets,
Credentials additionalCreds,
EncryptionKey subKey,
PAData[] extraPAs) throws KrbException, IOException {
@ -154,24 +151,24 @@ public class KrbTgsReq {
if (!(asCreds.flags.get(KDCOptions.POSTDATED)))
throw new KrbException(Krb5.KRB_AP_ERR_REQ_OPTIONS);
} else {
if (from != null) from = null;
if (from != null) from = null;
}
if (options.get(KDCOptions.RENEWABLE)) {
if (!(asCreds.flags.get(KDCOptions.RENEWABLE)))
throw new KrbException(Krb5.KRB_AP_ERR_REQ_OPTIONS);
} else {
if (rtime != null) rtime = null;
if (rtime != null) rtime = null;
}
if (options.get(KDCOptions.ENC_TKT_IN_SKEY) || options.get(KDCOptions.CNAME_IN_ADDL_TKT)) {
if (additionalTickets == null)
if (additionalCreds == null)
throw new KrbException(Krb5.KRB_AP_ERR_REQ_OPTIONS);
// in TGS_REQ there could be more than one additional
// tickets, but in file-based credential cache,
// there is only one additional ticket field.
secondTicket = additionalTickets[0];
this.additionalCreds = additionalCreds;
} else {
if (additionalTickets != null)
additionalTickets = null;
if (additionalCreds != null)
additionalCreds = null;
}
tgsReqMessg = createRequest(
@ -187,7 +184,7 @@ public class KrbTgsReq {
eTypes,
addresses,
authorizationData,
additionalTickets,
additionalCreds,
subKey,
extraPAs);
obuf = tgsReqMessg.asn1Encode();
@ -206,34 +203,16 @@ public class KrbTgsReq {
}
/**
* Sends a TGS request to the realm of the target.
* @throws KrbException
* @throws IOException
*/
public void send() throws IOException, KrbException {
String realmStr = null;
if (servName != null)
realmStr = servName.getRealmString();
KdcComm comm = new KdcComm(realmStr);
ibuf = comm.send(obuf);
}
public KrbTgsRep getReply()
throws KrbException, IOException {
return new KrbTgsRep(ibuf, this);
}
/**
* Sends the request, waits for a reply, and returns the Credentials.
* Used in Credentials, KrbCred, and internal/CredentialsUtil.
*/
public Credentials sendAndGetCreds() throws IOException, KrbException {
KrbTgsRep tgs_rep = null;
String kdc = null;
send();
tgs_rep = getReply();
return tgs_rep.getCreds();
String realmStr = servName != null
? servName.getRealmString()
: null;
KdcComm comm = new KdcComm(realmStr);
return new KrbTgsRep(comm.send(this), this).getCreds();
}
KerberosTime getCtime() {
@ -253,7 +232,7 @@ public class KrbTgsReq {
int[] eTypes,
HostAddresses addresses,
AuthorizationData authorizationData,
Ticket[] additionalTickets,
Credentials additionalCreds,
EncryptionKey subKey,
PAData[] extraPAs)
throws IOException, KrbException, UnknownHostException {
@ -302,6 +281,8 @@ public class KrbTgsReq {
KeyUsage.KU_TGS_REQ_AUTH_DATA_SESSKEY);
}
Ticket[] additionalTickets = additionalCreds == null ? null
: new Ticket[] { additionalCreds.getTicket() };
KDCReqBody reqBody = new KDCReqBody(
kdc_options,
cname,
@ -347,8 +328,8 @@ public class KrbTgsReq {
return tgsReqMessg;
}
Ticket getSecondTicket() {
return secondTicket;
Credentials getAdditionalCreds() {
return additionalCreds;
}
PrincipalName getClientAlias() {

@ -32,7 +32,6 @@
package sun.security.krb5.internal;
import sun.security.krb5.*;
import sun.security.util.DerValue;
import java.io.IOException;
import java.util.LinkedList;
@ -55,17 +54,14 @@ public class CredentialsUtil {
* Used by a middle server to acquire credentials on behalf of a
* user to itself using the S4U2self extension.
* @param user the user to impersonate
* @param ccreds the TGT of the middle service
* @param middleTGT the TGT of the middle service
* @return the new creds (cname=user, sname=middle)
*/
public static Credentials acquireS4U2selfCreds(PrincipalName user,
Credentials ccreds) throws KrbException, IOException {
if (!ccreds.isForwardable()) {
throw new KrbException("S4U2self needs a FORWARDABLE ticket");
}
PrincipalName sname = ccreds.getClient();
Credentials middleTGT) throws KrbException, IOException {
PrincipalName sname = middleTGT.getClient();
String uRealm = user.getRealmString();
String localRealm = ccreds.getClient().getRealmString();
String localRealm = sname.getRealmString();
if (!uRealm.equals(localRealm)) {
// Referrals will be required because the middle service
// and the user impersonated are on different realms.
@ -73,25 +69,25 @@ public class CredentialsUtil {
throw new KrbException("Cross-realm S4U2Self request not" +
" possible when referrals are disabled.");
}
if (ccreds.getClientAlias() != null) {
if (middleTGT.getClientAlias() != null) {
// If the name was canonicalized, the user pick
// has preference. This gives the possibility of
// using FQDNs that KDCs may use to return referrals.
// I.e.: a SVC/host.realm-2.com@REALM-1.COM name
// may be used by REALM-1.COM KDC to return a
// referral to REALM-2.COM.
sname = ccreds.getClientAlias();
sname = middleTGT.getClientAlias();
}
sname = new PrincipalName(sname.getNameType(),
sname.getNameStrings(), new Realm(uRealm));
}
Credentials creds = serviceCreds(
KDCOptions.with(KDCOptions.FORWARDABLE),
ccreds, ccreds.getClient(), sname, user,
middleTGT, middleTGT.getClient(), sname, user,
null, new PAData[] {
new PAData(Krb5.PA_FOR_USER,
new PAForUserEnc(user,
ccreds.getSessionKey()).asn1Encode()),
middleTGT.getSessionKey()).asn1Encode()),
new PAData(Krb5.PA_PAC_OPTIONS,
new PaPacOptions()
.setResourceBasedConstrainedDelegation(true)
@ -101,7 +97,7 @@ public class CredentialsUtil {
if (!creds.getClient().equals(user)) {
throw new KrbException("S4U2self request not honored by KDC");
}
if (!creds.isForwardable()) {
if (!creds.isForwardable() && !Credentials.S4U2PROXY_ACCEPT_NON_FORWARDABLE) {
throw new KrbException("S4U2self ticket must be FORWARDABLE");
}
return creds;
@ -111,17 +107,17 @@ public class CredentialsUtil {
* Used by a middle server to acquire a service ticket to a backend
* server using the S4U2proxy extension.
* @param backend the name of the backend service
* @param second the client's service ticket to the middle server
* @param ccreds the TGT of the middle server
* @return the creds (cname=client, sname=backend)
* @param userCreds containing the user's service ticket to the middle server
* @param middleTGT the TGT of the middle server
* @return the creds (cname=user, sname=backend)
*/
public static Credentials acquireS4U2proxyCreds(
String backend, Ticket second,
PrincipalName client, Credentials ccreds)
String backend, Credentials userCreds,
PrincipalName client, Credentials middleTGT)
throws KrbException, IOException {
PrincipalName backendPrincipal = new PrincipalName(backend);
String backendRealm = backendPrincipal.getRealmString();
String localRealm = ccreds.getClient().getRealmString();
String localRealm = middleTGT.getClient().getRealmString();
if (!backendRealm.equals(localRealm)) {
// The middle service and the backend service are on
// different realms, so referrals will be required.
@ -136,8 +132,8 @@ public class CredentialsUtil {
}
Credentials creds = serviceCreds(KDCOptions.with(
KDCOptions.CNAME_IN_ADDL_TKT, KDCOptions.FORWARDABLE),
ccreds, ccreds.getClient(), backendPrincipal, null,
new Ticket[] {second}, new PAData[] {
middleTGT, middleTGT.getClient(), backendPrincipal, null,
userCreds, new PAData[] {
new PAData(Krb5.PA_PAC_OPTIONS,
new PaPacOptions()
.setResourceBasedConstrainedDelegation(true)
@ -159,28 +155,30 @@ public class CredentialsUtil {
* from the foreign KDC.
*
* @param service the name of service principal
* @param ccreds client's initial credential
* @param initCreds client's initial credential
*/
public static Credentials acquireServiceCreds(
String service, Credentials ccreds)
String service, Credentials initCreds)
throws KrbException, IOException {
PrincipalName sname = new PrincipalName(service,
PrincipalName.KRB_NT_UNKNOWN);
return serviceCreds(sname, ccreds);
return serviceCreds(new KDCOptions(), initCreds,
initCreds.getClient(),
new PrincipalName(service, PrincipalName.KRB_NT_UNKNOWN),
null, null,
null, S4U2Type.NONE);
}
/**
* Gets a TGT to another realm
* @param localRealm this realm
* @param serviceRealm the other realm, cannot equals to localRealm
* @param ccreds TGT in this realm
* @param localTGT TGT in this realm
* @param okAsDelegate an [out] argument to receive the okAsDelegate
* property. True only if all realms allow delegation.
* @return the TGT for the other realm, null if cannot find a path
* @throws KrbException if something goes wrong
*/
private static Credentials getTGTforRealm(String localRealm,
String serviceRealm, Credentials ccreds, boolean[] okAsDelegate)
String serviceRealm, Credentials localTGT, boolean[] okAsDelegate)
throws KrbException {
// Get a list of realms to traverse
@ -192,7 +190,7 @@ public class CredentialsUtil {
String newTgtRealm = null;
okAsDelegate[0] = true;
for (cTgt = ccreds, i = 0; i < realms.length;) {
for (cTgt = localTGT, i = 0; i < realms.length;) {
tempService = PrincipalName.tgsService(serviceRealm, realms[i]);
if (DEBUG) {
@ -309,10 +307,10 @@ public class CredentialsUtil {
* This method does the real job to request the service credential.
*/
private static Credentials serviceCreds(
PrincipalName service, Credentials ccreds)
PrincipalName service, Credentials initCreds)
throws KrbException, IOException {
return serviceCreds(new KDCOptions(), ccreds,
ccreds.getClient(), service, null, null,
return serviceCreds(new KDCOptions(), initCreds,
initCreds.getClient(), service, null, null,
null, S4U2Type.NONE);
}
@ -325,13 +323,13 @@ public class CredentialsUtil {
private static Credentials serviceCreds(
KDCOptions options, Credentials asCreds,
PrincipalName cname, PrincipalName sname,
PrincipalName user, Ticket[] additionalTickets,
PrincipalName user, Credentials additionalCreds,
PAData[] extraPAs, S4U2Type s4u2Type)
throws KrbException, IOException {
if (!Config.DISABLE_REFERRALS) {
try {
return serviceCredsReferrals(options, asCreds, cname, sname,
s4u2Type, user, additionalTickets, extraPAs);
s4u2Type, user, additionalCreds, extraPAs);
} catch (KrbException e) {
// Server may raise an error if CANONICALIZE is true.
// Try CANONICALIZE false.
@ -339,7 +337,7 @@ public class CredentialsUtil {
}
return serviceCredsSingle(options, asCreds, cname,
asCreds.getClientAlias(), sname, sname, s4u2Type,
user, additionalTickets, extraPAs);
user, additionalCreds, extraPAs);
}
/*
@ -350,7 +348,7 @@ public class CredentialsUtil {
KDCOptions options, Credentials asCreds,
PrincipalName cname, PrincipalName sname,
S4U2Type s4u2Type, PrincipalName user,
Ticket[] additionalTickets, PAData[] extraPAs)
Credentials additionalCreds, PAData[] extraPAs)
throws KrbException, IOException {
options = new KDCOptions(options.toBooleanArray());
options.set(KDCOptions.CANONICALIZE, true);
@ -363,12 +361,12 @@ public class CredentialsUtil {
while (referrals.size() <= Config.MAX_REFERRALS) {
ReferralsCache.ReferralCacheEntry ref =
ReferralsCache.get(cname, sname, user,
additionalTickets, refSname.getRealmString());
additionalCreds, refSname.getRealmString());
String toRealm = null;
if (ref == null) {
creds = serviceCredsSingle(options, asCreds, cname,
clientAlias, refSname, cSname, s4u2Type,
user, additionalTickets, extraPAs);
user, additionalCreds, extraPAs);
PrincipalName server = creds.getServer();
if (!refSname.equals(server)) {
String[] serverNameStrings = server.getNameStrings();
@ -380,7 +378,7 @@ public class CredentialsUtil {
// Server Name (sname) has the following format:
// krbtgt/TO-REALM.COM@FROM-REALM.COM
ReferralsCache.put(cname, sname, user,
additionalTickets, server.getRealmString(),
additionalCreds, server.getRealmString(),
serverNameStrings[1], creds);
toRealm = serverNameStrings[1];
isReferral = true;
@ -398,13 +396,11 @@ public class CredentialsUtil {
toRealm = handleS4U2ProxyReferral(asCreds,
credsInOut, sname);
creds = credsInOut[0];
if (additionalTickets == null ||
additionalTickets.length == 0 ||
credsInOut[1] == null) {
if (additionalCreds == null || credsInOut[1] == null) {
throw new KrbException("Additional tickets expected" +
" for S4U2Proxy.");
}
additionalTickets[0] = credsInOut[1].getTicket();
additionalCreds = credsInOut[1];
} else if (s4u2Type == S4U2Type.SELF) {
handleS4U2SelfReferral(extraPAs, user, creds);
}
@ -436,7 +432,7 @@ public class CredentialsUtil {
PrincipalName cname, PrincipalName clientAlias,
PrincipalName refSname, PrincipalName sname,
S4U2Type s4u2Type, PrincipalName user,
Ticket[] additionalTickets, PAData[] extraPAs)
Credentials additionalCreds, PAData[] extraPAs)
throws KrbException, IOException {
Credentials theCreds = null;
boolean[] okAsDelegate = new boolean[]{true};
@ -473,7 +469,7 @@ public class CredentialsUtil {
" same realm");
}
KrbTgsReq req = new KrbTgsReq(options, asCreds, cname, clientAlias,
refSname, sname, additionalTickets, extraPAs);
refSname, sname, additionalCreds, extraPAs);
theCreds = req.sendAndGetCreds();
if (theCreds != null) {
if (DEBUG) {

@ -31,7 +31,6 @@
package sun.security.krb5.internal;
import sun.security.krb5.*;
import java.util.Vector;
import sun.security.util.*;
import java.io.IOException;
import java.math.BigInteger;
@ -192,8 +191,4 @@ public class KDCReq {
true, (byte) msgType), bytes);
return out.toByteArray();
}
public byte[] asn1EncodeReqBody() throws Asn1Exception, IOException {
return reqBody.asn1Encode(msgType);
}
}

@ -112,10 +112,10 @@ final class ReferralsCache {
* REALM-1.COM -> REALM-2.COM referral entry is removed from the cache.
*/
static synchronized void put(PrincipalName cname, PrincipalName service,
PrincipalName user, Ticket[] userSvcTickets, String fromRealm,
PrincipalName user, Credentials additionalCreds, String fromRealm,
String toRealm, Credentials creds) {
Ticket userSvcTicket = (userSvcTickets != null ?
userSvcTickets[0] : null);
Ticket userSvcTicket = (additionalCreds != null ?
additionalCreds.getTicket() : null);
ReferralCacheKey k = new ReferralCacheKey(cname, service,
user, userSvcTicket);
pruneExpired(k);
@ -152,9 +152,9 @@ final class ReferralsCache {
*/
static synchronized ReferralCacheEntry get(PrincipalName cname,
PrincipalName service, PrincipalName user,
Ticket[] userSvcTickets, String fromRealm) {
Ticket userSvcTicket = (userSvcTickets != null ?
userSvcTickets[0] : null);
Credentials additionalCreds, String fromRealm) {
Ticket userSvcTicket = (additionalCreds != null ?
additionalCreds.getTicket() : null);
ReferralCacheKey k = new ReferralCacheKey(cname, service,
user, userSvcTicket);
pruneExpired(k);

@ -85,12 +85,12 @@ public class Ticket implements Cloneable {
// Warning: called by NativeCreds.c and nativeccache.c
public Ticket(byte[] data) throws Asn1Exception,
RealmException, KrbApErrException, IOException {
RealmException, KrbApErrException, IOException {
init(new DerValue(data));
}
public Ticket(DerValue encoding) throws Asn1Exception,
RealmException, KrbApErrException, IOException {
RealmException, KrbApErrException, IOException {
init(encoding);
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2008, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2008, 2021, 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
@ -29,8 +29,11 @@ import java.lang.reflect.InvocationTargetException;
import java.net.*;
import java.io.*;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;
@ -249,6 +252,14 @@ public class KDC {
* If true, will check if TGS-REQ contains a non-null addresses field.
*/
CHECK_ADDRESSES,
/**
* If true, S4U2self ticket is not set forwardable.
*/
S4U2SELF_NOT_FORWARDABLE,
/**
* If true, allow S4U2self ticket not forwardable.
*/
S4U2SELF_ALLOW_NOT_FORWARDABLE,
};
/**
@ -441,12 +452,12 @@ public class KDC {
}
/**
* Adds a new principal to this realm with a random password
* Adds a new principal to this realm with a generated password
* @param user the principal's name. For a service principal, use the
* form of host/f.q.d.n
*/
public void addPrincipalRandKey(String user) {
addPrincipal(user, randomPassword());
addPrincipal(user, randomPassword(user + "@" + realm));
}
/**
@ -607,14 +618,29 @@ public class KDC {
startServer(port, asDaemon);
}
/**
* Generates a 32-char random password
* Generates a 32-char password
* @param user the string to generate from, random is null
* @return the password
*/
private static char[] randomPassword() {
char[] pass = new char[32];
Random r = new Random();
for (int i=0; i<31; i++)
pass[i] = (char)('a' + r.nextInt(26));
private static char[] randomPassword(String user) {
char[] pass;
if (user == null) {
pass = new char[32];
Random r = new Random();
for (int i = 0; i < 31; i++) {
pass[i] = (char) ('a' + r.nextInt(26));
}
} else {
try {
pass = Base64.getEncoder().encodeToString(
MessageDigest.getInstance("SHA-256").digest((user)
.getBytes(StandardCharsets.UTF_8)))
.substring(0, 32)
.toCharArray();
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
}
}
// The last char cannot be a number, otherwise, keyForUser()
// believes it's a sign of kvno
pass[31] = 'Z';
@ -629,7 +655,7 @@ public class KDC {
*/
private static EncryptionKey generateRandomKey(int eType)
throws KrbException {
return genKey0(randomPassword(), "NOTHING", null, eType, null);
return genKey0(randomPassword(null), "NOTHING", null, eType, null);
}
/**
@ -790,7 +816,7 @@ public class KDC {
service.getNameStrings(), service.getRealm());
}
try {
System.out.println(realm + "> " + tgsReq.reqBody.cname +
log(tgsReq.reqBody.cname +
" sends TGS-REQ for " +
service + ", " + tgsReq.reqBody.kdcOptions);
KDCReqBody body = tgsReq.reqBody;
@ -810,7 +836,7 @@ public class KDC {
boolean allowForwardable = true;
boolean isReferral = false;
if (body.kdcOptions.get(KDCOptions.CANONICALIZE)) {
System.out.println(realm + "> verifying referral for " +
log("verifying referral for " +
body.sname.getNameString());
KDC referral = aliasReferrals.get(body.sname.getNameString());
if (referral != null) {
@ -819,7 +845,7 @@ public class KDC {
PrincipalName.NAME_COMPONENT_SEPARATOR_STR +
referral.getRealm(), PrincipalName.KRB_NT_SRV_INST,
this.getRealm());
System.out.println(realm + "> referral to " +
log("referral to " +
referral.getRealm());
isReferral = true;
}
@ -842,14 +868,14 @@ public class KDC {
// Finally, cname will be overwritten by PA-FOR-USER
// if it exists.
cname = etp.cname;
System.out.println(realm + "> presenting a ticket of "
log("presenting a ticket of "
+ etp.cname + " to " + tkt.sname);
} else if (pa.getType() == Krb5.PA_FOR_USER) {
if (options.containsKey(Option.ALLOW_S4U2SELF)) {
PAForUserEnc p4u = new PAForUserEnc(
new DerValue(pa.getValue()), null);
forUserCName = p4u.name;
System.out.println(realm + "> See PA_FOR_USER "
log("See PA_FOR_USER "
+ " in the name of " + p4u.name);
}
}
@ -862,6 +888,9 @@ public class KDC {
// allowed to send S4U2self, do not send an error.
// Instead, send a ticket which is useless later.
allowForwardable = false;
} else if (options.get(Option.S4U2SELF_NOT_FORWARDABLE) == Boolean.TRUE) {
// Requsted not forwardable
allowForwardable = false;
}
cname = forUserCName;
}
@ -936,15 +965,16 @@ public class KDC {
DerInputStream derIn = new DerInputStream(bb);
DerValue der = derIn.getDerValue();
EncTicketPart tktEncPart = new EncTicketPart(der.toByteArray());
if (!tktEncPart.flags.get(Krb5.TKT_OPTS_FORWARDABLE)) {
//throw new KrbException(Krb5.KDC_ERR_BADOPTION);
if (!tktEncPart.flags.get(Krb5.TKT_OPTS_FORWARDABLE)
&& options.get(Option.S4U2SELF_ALLOW_NOT_FORWARDABLE) != Boolean.TRUE) {
throw new KrbException(Krb5.KDC_ERR_BADOPTION);
}
PrincipalName client = tktEncPart.cname;
System.out.println(realm + "> and an additional ticket of "
log("and an additional ticket of "
+ client + " to " + second.sname);
if (map.containsKey(cname.toString())) {
if (map.get(cname.toString()).contains(service.toString())) {
System.out.println(realm + "> S4U2proxy OK");
log("S4U2proxy OK");
} else {
throw new KrbException(Krb5.KDC_ERR_BADOPTION);
}
@ -1066,7 +1096,7 @@ public class KDC {
Realm.getDefault());
}
try {
System.out.println(realm + "> " + asReq.reqBody.cname +
log(asReq.reqBody.cname +
" sends AS-REQ for " +
service + ", " + asReq.reqBody.kdcOptions);
@ -1601,6 +1631,10 @@ public class KDC {
return udpConsumerReady && tcpConsumerReady && dispatcherReady;
}
void log(String s) {
System.out.println(realm + ":" + port + "> " + s);
}
public void terminate() {
if (nativeKdc != null) {
System.out.println("Killing kdc...");

@ -0,0 +1,96 @@
/*
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @bug 8272162
* @summary S4U2Self ticket without forwardable flag
* @library /test/lib
* @compile -XDignore.symbol.file S4U2selfNotF.java
* @run main jdk.test.lib.FileInstaller TestHosts TestHosts
* @run main/othervm -Djdk.net.hosts.file=TestHosts
* -Djdk.security.krb5.s4u2proxy.acceptNonForwardableServiceTicket=true
* S4U2selfNotF
*/
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import jdk.test.lib.Asserts;
import sun.security.jgss.GSSUtil;
import sun.security.krb5.Config;
public class S4U2selfNotF {
public static void main(String[] args) throws Exception {
// Create 2 KDCs that has almost the same settings
OneKDC[] kdcs = new OneKDC[2];
boolean[] touched = new boolean[2];
for (int i = 0; i < 2; i++) {
final int pos = i;
kdcs[i] = new OneKDC(null) {
protected byte[] processTgsReq(byte[] in) throws Exception {
touched[pos] = true;
return super.processTgsReq(in);
}
};
kdcs[i].setOption(KDC.Option.ALLOW_S4U2SELF,
List.of(OneKDC.USER + "@" + OneKDC.REALM));
kdcs[i].setOption(KDC.Option.ALLOW_S4U2PROXY, Map.of(
OneKDC.USER + "@" + OneKDC.REALM,
List.of(OneKDC.BACKEND + "@" + OneKDC.REALM)));
}
kdcs[0].writeJAASConf();
// except that the 1st issues a non-forwardable S4U2self
// ticket and only the 2nd accepts it
kdcs[0].setOption(KDC.Option.S4U2SELF_NOT_FORWARDABLE, true);
kdcs[1].setOption(KDC.Option.S4U2SELF_ALLOW_NOT_FORWARDABLE, true);
Files.write(Path.of(OneKDC.KRB5_CONF), String.format("""
[libdefaults]
default_realm = RABBIT.HOLE
forwardable = true
default_keytab_name = localkdc.ktab
[realms]
RABBIT.HOLE = {
kdc = kdc.rabbit.hole:%d kdc.rabbit.hole:%d
}
""", kdcs[0].getPort(), kdcs[1].getPort())
.getBytes(StandardCharsets.UTF_8));
Config.refresh();
Context c = Context.fromJAAS("client");
c = c.impersonate(OneKDC.USER2);
c.startAsClient(OneKDC.BACKEND, GSSUtil.GSS_KRB5_MECH_OID);
c.take(new byte[0]);
Asserts.assertTrue(touched[0]); // get S4U2self from 1st one
Asserts.assertTrue(touched[1]); // get S4U2proxy from 2nd one
}
}