diff --git a/jdk/src/share/classes/com/sun/security/jgss/ExtendedGSSCredential.java b/jdk/src/share/classes/com/sun/security/jgss/ExtendedGSSCredential.java new file mode 100644 index 00000000000..8f09482a7d0 --- /dev/null +++ b/jdk/src/share/classes/com/sun/security/jgss/ExtendedGSSCredential.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2012, 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 com.sun.security.jgss; + +import org.ietf.jgss.*; + +/** + * The extended GSSCredential interface for supporting additional + * functionalities not defined by {@code org.ietf.jgss.GSSCredential}. + * @since 1.8 + */ +public interface ExtendedGSSCredential extends GSSCredential { + /** + * Impersonates a principal. In Kerberos, this can be implemented + * using the Microsoft S4U2self extension. + *

+ * A {@link GSSException#NO_CRED GSSException.NO_CRED} will be thrown if the + * impersonation fails. A {@link GSSException#FAILURE GSSException.FAILURE} + * will be thrown if the impersonation method is not available to this + * credential object. + * @param name the name of the principal to impersonate + * @return a credential for that principal + * @throws GSSException containing the following + * major error codes: + * {@link GSSException#NO_CRED GSSException.NO_CRED} + * {@link GSSException#FAILURE GSSException.FAILURE} + */ + public GSSCredential impersonate(GSSName name) throws GSSException; +} diff --git a/jdk/src/share/classes/sun/security/jgss/GSSCaller.java b/jdk/src/share/classes/sun/security/jgss/GSSCaller.java index bc6eaa2b8ab..278e7b9f485 100644 --- a/jdk/src/share/classes/sun/security/jgss/GSSCaller.java +++ b/jdk/src/share/classes/sun/security/jgss/GSSCaller.java @@ -31,10 +31,19 @@ package sun.security.jgss; * different callers. */ public class GSSCaller { - public static final GSSCaller CALLER_UNKNOWN = new GSSCaller(); - public static final GSSCaller CALLER_INITIATE = new GSSCaller(); - public static final GSSCaller CALLER_ACCEPT = new GSSCaller(); - public static final GSSCaller CALLER_SSL_CLIENT = new GSSCaller(); - public static final GSSCaller CALLER_SSL_SERVER = new GSSCaller(); + public static final GSSCaller CALLER_UNKNOWN = new GSSCaller("UNKNOWN"); + public static final GSSCaller CALLER_INITIATE = new GSSCaller("INITIATE"); + public static final GSSCaller CALLER_ACCEPT = new GSSCaller("ACCEPT"); + public static final GSSCaller CALLER_SSL_CLIENT = new GSSCaller("SSL_CLIENT"); + public static final GSSCaller CALLER_SSL_SERVER = new GSSCaller("SSL_SERVER"); + + private String name; + GSSCaller(String s) { + name = s; + } + @Override + public String toString() { + return "GSSCaller{" + name + '}'; + } } diff --git a/jdk/src/share/classes/sun/security/jgss/GSSCredentialImpl.java b/jdk/src/share/classes/sun/security/jgss/GSSCredentialImpl.java index 36037e12f59..6330f71c1b0 100644 --- a/jdk/src/share/classes/sun/security/jgss/GSSCredentialImpl.java +++ b/jdk/src/share/classes/sun/security/jgss/GSSCredentialImpl.java @@ -28,8 +28,9 @@ package sun.security.jgss; import org.ietf.jgss.*; import sun.security.jgss.spi.*; import java.util.*; +import com.sun.security.jgss.*; -public class GSSCredentialImpl implements GSSCredential { +public class GSSCredentialImpl implements ExtendedGSSCredential { private GSSManagerImpl gssManager = null; private boolean destroyed = false; @@ -122,6 +123,19 @@ public class GSSCredentialImpl implements GSSCredential { } } + public GSSCredential impersonate(GSSName name) throws GSSException { + if (destroyed) { + throw new IllegalStateException("This credential is " + + "no longer valid"); + } + Oid mech = tempCred.getMechanism(); + GSSNameSpi nameElement = (name == null ? null : + ((GSSNameImpl)name).getElement(mech)); + GSSCredentialSpi cred = tempCred.impersonate(nameElement); + return (cred == null ? + null : new GSSCredentialImpl(gssManager, cred)); + } + public GSSName getName() throws GSSException { if (destroyed) { throw new IllegalStateException("This credential is " + diff --git a/jdk/src/share/classes/sun/security/jgss/HttpCaller.java b/jdk/src/share/classes/sun/security/jgss/HttpCaller.java index ae56b2786ac..b4f3e01fd7f 100644 --- a/jdk/src/share/classes/sun/security/jgss/HttpCaller.java +++ b/jdk/src/share/classes/sun/security/jgss/HttpCaller.java @@ -35,6 +35,7 @@ public class HttpCaller extends GSSCaller { final private HttpCallerInfo hci; public HttpCaller(HttpCallerInfo hci) { + super("HTTP_CLIENT"); this.hci = hci; } diff --git a/jdk/src/share/classes/sun/security/jgss/krb5/Krb5AcceptCredential.java b/jdk/src/share/classes/sun/security/jgss/krb5/Krb5AcceptCredential.java index 1df500bc524..0f65137cecf 100644 --- a/jdk/src/share/classes/sun/security/jgss/krb5/Krb5AcceptCredential.java +++ b/jdk/src/share/classes/sun/security/jgss/krb5/Krb5AcceptCredential.java @@ -25,6 +25,7 @@ package sun.security.jgss.krb5; +import java.io.IOException; import org.ietf.jgss.*; import sun.security.jgss.GSSCaller; import sun.security.jgss.spi.*; @@ -177,4 +178,21 @@ public class Krb5AcceptCredential public void destroy() throws DestroyFailedException { screds.destroy(); } + + /** + * Impersonation is only available on the initiator side. The + * service must starts as an initiator to get an initial TGT to complete + * the S4U2self protocol. + */ + @Override + public GSSCredentialSpi impersonate(GSSNameSpi name) throws GSSException { + Credentials cred = screds.getInitCred(); + if (cred != null) { + return Krb5InitCredential.getInstance(this.name, cred) + .impersonate(name); + } else { + throw new GSSException(GSSException.FAILURE, -1, + "Only an initiate credentials can impersonate"); + } + } } diff --git a/jdk/src/share/classes/sun/security/jgss/krb5/Krb5Context.java b/jdk/src/share/classes/sun/security/jgss/krb5/Krb5Context.java index 37c7b98a277..be8074097c9 100644 --- a/jdk/src/share/classes/sun/security/jgss/krb5/Krb5Context.java +++ b/jdk/src/share/classes/sun/security/jgss/krb5/Krb5Context.java @@ -45,6 +45,7 @@ import java.security.PrivilegedActionException; import javax.crypto.Cipher; import javax.security.auth.Subject; import javax.security.auth.kerberos.*; +import sun.security.krb5.internal.Ticket; /** * Implements the mechanism specific context class for the Kerberos v5 @@ -76,7 +77,7 @@ class Krb5Context implements GSSContextSpi { * values. */ - private boolean credDelegState = false; + private boolean credDelegState = false; // now only useful at client private boolean mutualAuthState = true; private boolean replayDetState = true; private boolean sequenceDetState = true; @@ -84,6 +85,8 @@ class Krb5Context implements GSSContextSpi { private boolean integState = true; private boolean delegPolicyState = false; + private boolean isConstrainedDelegationTried = false; + private int mySeqNumber; private int peerSeqNumber; private int keySrc; @@ -113,13 +116,11 @@ class Krb5Context implements GSSContextSpi { private Krb5CredElement myCred; private Krb5CredElement delegatedCred; // Set only on acceptor side - /* DESCipher instance used by the corresponding GSSContext */ - private Cipher desCipher = null; - // XXX See if the required info from these can be extracted and // stored elsewhere private Credentials serviceCreds; private KrbApReq apReq; + Ticket serviceTicket; final private GSSCaller caller; private static final boolean DEBUG = Krb5Util.DEBUG; @@ -248,7 +249,14 @@ class Krb5Context implements GSSContextSpi { * Is credential delegation enabled? */ public final boolean getCredDelegState() { - return credDelegState; + if (isInitiator()) { + return credDelegState; + } else { + // Server side deleg state is not flagged by credDelegState. + // It can use constrained delegation. + tryConstrainedDelegation(); + return delegatedCred != null; + } } /** @@ -498,7 +506,8 @@ class Krb5Context implements GSSContextSpi { * Returns the delegated credential for the context. This * is an optional feature of contexts which not all * mechanisms will support. A context can be requested to - * support credential delegation by using the CRED_DELEG. + * support credential delegation by using the CRED_DELEG, + * or it can request for a constrained delegation. * This is only valid on the acceptor side of the context. * @return GSSCredentialSpi object for the delegated credential * @exception GSSException @@ -507,11 +516,41 @@ class Krb5Context implements GSSContextSpi { public final GSSCredentialSpi getDelegCred() throws GSSException { if (state != STATE_IN_PROCESS && state != STATE_DONE) throw new GSSException(GSSException.NO_CONTEXT); - if (delegatedCred == null) + if (isInitiator()) { throw new GSSException(GSSException.NO_CRED); + } + tryConstrainedDelegation(); + if (delegatedCred == null) { + throw new GSSException(GSSException.NO_CRED); + } return delegatedCred; } + private void tryConstrainedDelegation() { + if (state != STATE_IN_PROCESS && state != STATE_DONE) { + return; + } + // We will only try constrained delegation once (if necessary). + if (!isConstrainedDelegationTried) { + if (delegatedCred == null) { + if (DEBUG) { + System.out.println(">>> Constrained deleg from " + caller); + } + // The constrained delegation part. The acceptor needs to have + // isInitiator=true in order to get a TGT, either earlier at + // logon stage, if useSubjectCredsOnly, or now. + try { + delegatedCred = new Krb5ProxyCredential( + Krb5InitCredential.getInstance( + GSSCaller.CALLER_ACCEPT, myName, lifetime), + peerName, serviceTicket); + } catch (GSSException gsse) { + // OK, delegatedCred is null then + } + } + isConstrainedDelegationTried = true; + } + } /** * Tests if this is the initiator side of the context. * @@ -577,8 +616,15 @@ class Krb5Context implements GSSContextSpi { "No TGT available"); } myName = (Krb5NameElement) myCred.getName(); - Credentials tgt = - ((Krb5InitCredential) myCred).getKrb5Credentials(); + Credentials tgt; + final Krb5ProxyCredential second; + if (myCred instanceof Krb5InitCredential) { + second = null; + tgt = ((Krb5InitCredential) myCred).getKrb5Credentials(); + } else { + second = (Krb5ProxyCredential) myCred; + tgt = second.self.getKrb5Credentials(); + } checkPermission(peerName.getKrb5PrincipalName().getName(), "initiate"); @@ -607,7 +653,9 @@ class Krb5Context implements GSSContextSpi { GSSCaller.CALLER_UNKNOWN, // since it's useSubjectCredsOnly here, // don't worry about the null - myName.getKrb5PrincipalName().getName(), + second == null ? + myName.getKrb5PrincipalName().getName(): + second.getName().getKrb5PrincipalName().getName(), peerName.getKrb5PrincipalName().getName(), acc); }}); @@ -638,9 +686,17 @@ class Krb5Context implements GSSContextSpi { "the subject"); } // Get Service ticket using the Kerberos protocols - serviceCreds = Credentials.acquireServiceCreds( + if (second == null) { + serviceCreds = Credentials.acquireServiceCreds( peerName.getKrb5PrincipalName().getName(), tgt); + } else { + serviceCreds = Credentials.acquireS4U2proxyCreds( + peerName.getKrb5PrincipalName().getName(), + second.tkt, + second.getName().getKrb5PrincipalName(), + tgt); + } if (GSSUtil.useSubjectCredsOnly(caller)) { final Subject subject = AccessController.doPrivileged( @@ -776,6 +832,7 @@ class Krb5Context implements GSSContextSpi { retVal = new AcceptSecContextToken(this, token.getKrbApReq()).encode(); } + serviceTicket = token.getKrbApReq().getCreds().getTicket(); myCred = null; state = STATE_DONE; } else { @@ -802,8 +859,6 @@ class Krb5Context implements GSSContextSpi { return retVal; } - - /** * Queries the context for largest data size to accomodate * the specified protection and be <= maxTokSize. diff --git a/jdk/src/share/classes/sun/security/jgss/krb5/Krb5InitCredential.java b/jdk/src/share/classes/sun/security/jgss/krb5/Krb5InitCredential.java index 1932a950654..fa9a95a6a40 100644 --- a/jdk/src/share/classes/sun/security/jgss/krb5/Krb5InitCredential.java +++ b/jdk/src/share/classes/sun/security/jgss/krb5/Krb5InitCredential.java @@ -309,8 +309,7 @@ public class Krb5InitCredential int initLifetime) throws GSSException { - String realm = null; - final String clientPrincipal, tgsPrincipal = null; + final String clientPrincipal; /* * Find the TGT for the realm that the client is in. If the client @@ -318,20 +317,8 @@ public class Krb5InitCredential */ if (name != null) { clientPrincipal = (name.getKrb5PrincipalName()).getName(); - realm = (name.getKrb5PrincipalName()).getRealmAsString(); } else { clientPrincipal = null; - try { - Config config = Config.getInstance(); - realm = config.getDefaultRealm(); - } catch (KrbException e) { - GSSException ge = - new GSSException(GSSException.NO_CRED, -1, - "Attempt to obtain INITIATE credentials failed!" + - " (" + e.getMessage() + ")"); - ge.initCause(e); - throw ge; - } } final AccessControlContext acc = AccessController.getContext(); @@ -343,9 +330,11 @@ public class Krb5InitCredential return AccessController.doPrivileged( new PrivilegedExceptionAction() { public KerberosTicket run() throws Exception { + // It's OK to use null as serverPrincipal. TGT is almost + // the first ticket for a principal and we use list. return Krb5Util.getTicket( realCaller, - clientPrincipal, tgsPrincipal, acc); + clientPrincipal, null, acc); }}); } catch (PrivilegedActionException e) { GSSException ge = @@ -356,4 +345,20 @@ public class Krb5InitCredential throw ge; } } + + @Override + public GSSCredentialSpi impersonate(GSSNameSpi name) throws GSSException { + try { + Krb5NameElement kname = (Krb5NameElement)name; + Credentials newCred = Credentials.acquireS4U2selfCreds( + kname.getKrb5PrincipalName(), krb5Credentials); + return new Krb5ProxyCredential(this, kname, newCred.getTicket()); + } catch (IOException | KrbException ke) { + GSSException ge = + new GSSException(GSSException.FAILURE, -1, + "Attempt to obtain S4U2self credentials failed!"); + ge.initCause(ke); + throw ge; + } + } } diff --git a/jdk/src/share/classes/sun/security/jgss/krb5/Krb5ProxyCredential.java b/jdk/src/share/classes/sun/security/jgss/krb5/Krb5ProxyCredential.java new file mode 100644 index 00000000000..4c5690b3942 --- /dev/null +++ b/jdk/src/share/classes/sun/security/jgss/krb5/Krb5ProxyCredential.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2012, 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.jgss.krb5; + +import org.ietf.jgss.*; +import sun.security.jgss.spi.*; +import java.util.Date; +import sun.security.krb5.internal.Ticket; + +/** + * Implements the krb5 proxy credential element used in constrained + * delegation. It is used in both impersonation (where there is no Kerberos 5 + * communication between the middle server and the client) and normal + * constrained delegation (where there is, but client has not called + * requestCredDeleg(true)). + * @since 1.8 + */ + +public class Krb5ProxyCredential + implements Krb5CredElement { + + public final Krb5InitCredential self; // the middle server + private final Krb5NameElement client; // the client + + // The ticket with cname=client and sname=self. This can be a normal + // service ticket or an S4U2self ticket. + public final Ticket tkt; + + Krb5ProxyCredential(Krb5InitCredential self, Krb5NameElement client, + Ticket tkt) { + this.self = self; + this.tkt = tkt; + this.client = client; + } + + // The client name behind the proxy + @Override + public final Krb5NameElement getName() throws GSSException { + return client; + } + + @Override + public int getInitLifetime() throws GSSException { + // endTime of tkt is not used by KDC, and it's also not + // available in the case of kerberos constr deleg + return self.getInitLifetime(); + } + + @Override + public int getAcceptLifetime() throws GSSException { + return 0; + } + + @Override + public boolean isInitiatorCredential() throws GSSException { + return true; + } + + @Override + public boolean isAcceptorCredential() throws GSSException { + return false; + } + + @Override + public final Oid getMechanism() { + return Krb5MechFactory.GSS_KRB5_MECH_OID; + } + + @Override + public final java.security.Provider getProvider() { + return Krb5MechFactory.PROVIDER; + } + + @Override + public void dispose() throws GSSException { + try { + self.destroy(); + } catch (javax.security.auth.DestroyFailedException e) { + GSSException gssException = + new GSSException(GSSException.FAILURE, -1, + "Could not destroy credentials - " + e.getMessage()); + gssException.initCause(e); + } + } + + @Override + public GSSCredentialSpi impersonate(GSSNameSpi name) throws GSSException { + // Cannot impersonate multiple levels without the impersonatee's TGT. + throw new GSSException(GSSException.FAILURE, -1, + "Only an initiate credentials can impersonate"); + } +} diff --git a/jdk/src/share/classes/sun/security/jgss/krb5/Krb5Util.java b/jdk/src/share/classes/sun/security/jgss/krb5/Krb5Util.java index 28df9520411..97f1a12447c 100644 --- a/jdk/src/share/classes/sun/security/jgss/krb5/Krb5Util.java +++ b/jdk/src/share/classes/sun/security/jgss/krb5/Krb5Util.java @@ -206,7 +206,7 @@ public class Krb5Util { * identity, which can be: * 1. Some KerberosKeys (generated from password) * 2. A KeyTab (for a typical service) - * 3. A TGT (for a user2user service. Not supported yet) + * 3. A TGT (for S4U2proxy extension) * * Note that some creds can coexist. For example, a user2user service * can use its keytab (or keys) if the client can successfully obtain a @@ -219,7 +219,7 @@ public class Krb5Util { private List ktabs; private List kk; private Subject subj; - //private KerberosTicket tgt; // user2user, not supported yet + private KerberosTicket tgt; private static ServiceCreds getInstance( Subject subj, String serverPrincipal) { @@ -255,6 +255,8 @@ public class Krb5Util { subj, null, null, KeyTab.class); sc.kk = SubjectComber.findMany( subj, serverPrincipal, null, KerberosKey.class); + sc.tgt = SubjectComber.find(subj, null, null, KerberosTicket.class); + if (sc.ktabs.isEmpty() && sc.kk.isEmpty()) { return null; } @@ -310,10 +312,22 @@ public class Krb5Util { return ekeys; } + public Credentials getInitCred() { + if (tgt == null) { + return null; + } + try { + return ticketToCreds(tgt); + } catch (KrbException | IOException e) { + return null; + } + } + public void destroy() { kp = null; ktabs = null; kk = null; + tgt = null; } } /** @@ -357,7 +371,7 @@ public class Krb5Util { }; public static Credentials ticketToCreds(KerberosTicket kerbTicket) - throws KrbException, IOException { + throws KrbException, IOException { return new Credentials( kerbTicket.getEncoded(), kerbTicket.getClient().getName(), diff --git a/jdk/src/share/classes/sun/security/jgss/spi/GSSCredentialSpi.java b/jdk/src/share/classes/sun/security/jgss/spi/GSSCredentialSpi.java index 0c7e6288991..bab8a5dea52 100644 --- a/jdk/src/share/classes/sun/security/jgss/spi/GSSCredentialSpi.java +++ b/jdk/src/share/classes/sun/security/jgss/spi/GSSCredentialSpi.java @@ -96,4 +96,13 @@ public interface GSSCredentialSpi { * @exception GSSException may be thrown */ public Oid getMechanism(); + + /** + * Impersonates another client. + * + * @param name the client to impersonate + * @return the new credential + * @exception GSSException may be thrown + */ + public GSSCredentialSpi impersonate(GSSNameSpi name) throws GSSException; } diff --git a/jdk/src/share/classes/sun/security/jgss/spnego/SpNegoContext.java b/jdk/src/share/classes/sun/security/jgss/spnego/SpNegoContext.java index 849737a98b0..d6bc6d92e6e 100644 --- a/jdk/src/share/classes/sun/security/jgss/spnego/SpNegoContext.java +++ b/jdk/src/share/classes/sun/security/jgss/spnego/SpNegoContext.java @@ -1059,6 +1059,9 @@ public class SpNegoContext implements GSSContextSpi { if (mechContext != null) { GSSCredentialImpl delegCred = (GSSCredentialImpl)mechContext.getDelegCred(); + if (delegCred == null) { + return null; + } // determine delegated cred element usage boolean initiate = false; if (delegCred.getUsage() == GSSCredential.INITIATE_ONLY) { diff --git a/jdk/src/share/classes/sun/security/jgss/spnego/SpNegoCredElement.java b/jdk/src/share/classes/sun/security/jgss/spnego/SpNegoCredElement.java index ae9990085cf..69116f56bd2 100644 --- a/jdk/src/share/classes/sun/security/jgss/spnego/SpNegoCredElement.java +++ b/jdk/src/share/classes/sun/security/jgss/spnego/SpNegoCredElement.java @@ -88,4 +88,9 @@ public class SpNegoCredElement implements GSSCredentialSpi { public Oid getMechanism() { return GSSUtil.GSS_SPNEGO_MECH_OID; } + + @Override + public GSSCredentialSpi impersonate(GSSNameSpi name) throws GSSException { + return cred.impersonate(name); + } } diff --git a/jdk/src/share/classes/sun/security/jgss/wrapper/GSSCredElement.java b/jdk/src/share/classes/sun/security/jgss/wrapper/GSSCredElement.java index 275ef58651e..e38c4c6bd42 100644 --- a/jdk/src/share/classes/sun/security/jgss/wrapper/GSSCredElement.java +++ b/jdk/src/share/classes/sun/security/jgss/wrapper/GSSCredElement.java @@ -28,6 +28,7 @@ import org.ietf.jgss.*; import java.security.Provider; import sun.security.jgss.GSSUtil; import sun.security.jgss.spi.GSSCredentialSpi; +import sun.security.jgss.spi.GSSNameSpi; /** * This class is essentially a wrapper class for the gss_cred_id_t @@ -132,4 +133,10 @@ public class GSSCredElement implements GSSCredentialSpi { protected void finalize() throws Throwable { dispose(); } + + @Override + public GSSCredentialSpi impersonate(GSSNameSpi name) throws GSSException { + throw new GSSException(GSSException.FAILURE, -1, + "Not supported yet"); + } } diff --git a/jdk/src/share/classes/sun/security/krb5/Credentials.java b/jdk/src/share/classes/sun/security/krb5/Credentials.java index bdb8f7c1045..bf4d57bb2b6 100644 --- a/jdk/src/share/classes/sun/security/krb5/Credentials.java +++ b/jdk/src/share/classes/sun/security/krb5/Credentials.java @@ -449,6 +449,18 @@ public class Credentials { return CredentialsUtil.acquireServiceCreds(service, ccreds); } + public static Credentials acquireS4U2selfCreds(PrincipalName user, + Credentials ccreds) throws KrbException, IOException { + return CredentialsUtil.acquireS4U2selfCreds(user, ccreds); + } + + public static Credentials acquireS4U2proxyCreds(String service, + Ticket second, PrincipalName client, Credentials ccreds) + throws KrbException, IOException { + return CredentialsUtil.acquireS4U2proxyCreds( + service, second, client, ccreds); + } + public CredentialsCache getCache() { return cache; } @@ -490,18 +502,19 @@ public class Credentials { public String toString() { StringBuffer buffer = new StringBuffer("Credentials:"); - buffer.append("\nclient=").append(client); - buffer.append("\nserver=").append(server); + buffer.append( "\n client=").append(client); + buffer.append( "\n server=").append(server); if (authTime != null) { - buffer.append("\nauthTime=").append(authTime); + buffer.append("\n authTime=").append(authTime); } if (startTime != null) { - buffer.append("\nstartTime=").append(startTime); + buffer.append("\n startTime=").append(startTime); } - buffer.append("\nendTime=").append(endTime); - buffer.append("\nrenewTill=").append(renewTill); - buffer.append("\nflags: ").append(flags); - buffer.append("\nEType (int): ").append(key.getEType()); + buffer.append( "\n endTime=").append(endTime); + buffer.append( "\n renewTill=").append(renewTill); + buffer.append( "\n flags=").append(flags); + buffer.append( "\nEType (skey)=").append(key.getEType()); + buffer.append( "\n (tkt key)=").append(ticket.encPart.eType); return buffer.toString(); } diff --git a/jdk/src/share/classes/sun/security/krb5/EncryptedData.java b/jdk/src/share/classes/sun/security/krb5/EncryptedData.java index f9907687e30..0849f919b92 100644 --- a/jdk/src/share/classes/sun/security/krb5/EncryptedData.java +++ b/jdk/src/share/classes/sun/security/krb5/EncryptedData.java @@ -160,8 +160,6 @@ public class EncryptedData implements Cloneable { kvno = key.getKeyVersionNumber(); } */ - - // currently destructive on cipher public byte[] decrypt( EncryptionKey key, int usage) throws KdcErrException, KrbApErrException, KrbCryptoException { @@ -175,7 +173,9 @@ public class EncryptedData implements Cloneable { EType etypeEngine = EType.getInstance(eType); plain = etypeEngine.decrypt(cipher, key.getBytes(), usage); - cipher = null; + // The service ticket will be used in S4U2proxy request. Therefore + // the raw ticket is still needed. + //cipher = null; return etypeEngine.decryptedData(plain); } diff --git a/jdk/src/share/classes/sun/security/krb5/KrbApReq.java b/jdk/src/share/classes/sun/security/krb5/KrbApReq.java index 3120490d039..f4e52d35f7d 100644 --- a/jdk/src/share/classes/sun/security/krb5/KrbApReq.java +++ b/jdk/src/share/classes/sun/security/krb5/KrbApReq.java @@ -287,8 +287,9 @@ public class KrbApReq { cusec = authenticator.cusec; authenticator.ctime.setMicroSeconds(authenticator.cusec); - if (!authenticator.cname.equals(enc_ticketPart.cname)) + if (!authenticator.cname.equals(enc_ticketPart.cname)) { throw new KrbApErrException(Krb5.KRB_AP_ERR_BADMATCH); + } KerberosTime currTime = new KerberosTime(KerberosTime.NOW); if (!authenticator.ctime.inClockSkew(currTime)) diff --git a/jdk/src/share/classes/sun/security/krb5/KrbKdcRep.java b/jdk/src/share/classes/sun/security/krb5/KrbKdcRep.java index 1100aadf506..dd0e951028d 100644 --- a/jdk/src/share/classes/sun/security/krb5/KrbKdcRep.java +++ b/jdk/src/share/classes/sun/security/krb5/KrbKdcRep.java @@ -64,7 +64,12 @@ abstract class KrbKdcRep { for (int i = 1; i < 6; i++) { if (req.reqBody.kdcOptions.get(i) != - rep.encKDCRepPart.flags.get(i)) { + rep.encKDCRepPart.flags.get(i)) { + if (Krb5.DEBUG) { + System.out.println("> KrbKdcRep.check: at #" + i + + ". request for " + req.reqBody.kdcOptions.get(i) + + ", received " + rep.encKDCRepPart.flags.get(i)); + } throw new KrbApErrException(Krb5.KRB_AP_ERR_MODIFIED); } } diff --git a/jdk/src/share/classes/sun/security/krb5/KrbTgsRep.java b/jdk/src/share/classes/sun/security/krb5/KrbTgsRep.java index 7ec38701e74..6fc6cebaa70 100644 --- a/jdk/src/share/classes/sun/security/krb5/KrbTgsRep.java +++ b/jdk/src/share/classes/sun/security/krb5/KrbTgsRep.java @@ -87,7 +87,7 @@ public class KrbTgsRep extends KrbKdcRep { check(false, req, rep); this.creds = new Credentials(rep.ticket, - req.reqBody.cname, + rep.cname, rep.ticket.sname, enc_part.key, enc_part.flags, diff --git a/jdk/src/share/classes/sun/security/krb5/KrbTgsReq.java b/jdk/src/share/classes/sun/security/krb5/KrbTgsReq.java index bbdb8a3e018..9cd84c6d9a4 100644 --- a/jdk/src/share/classes/sun/security/krb5/KrbTgsReq.java +++ b/jdk/src/share/classes/sun/security/krb5/KrbTgsReq.java @@ -35,6 +35,7 @@ import sun.security.krb5.internal.*; import sun.security.krb5.internal.crypto.*; import java.io.IOException; import java.net.UnknownHostException; +import java.util.Arrays; /** * This class encapsulates a Kerberos TGS-REQ that is sent from the @@ -55,7 +56,7 @@ public class KrbTgsReq { private byte[] obuf; private byte[] ibuf; - // Used in CredentialsUtil + // Used in CredentialsUtil public KrbTgsReq(Credentials asCreds, PrincipalName sname) throws KrbException, IOException { @@ -72,6 +73,45 @@ public class KrbTgsReq { 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 + } + // Called by Credentials, KrbCred KrbTgsReq( KDCOptions options, @@ -85,14 +125,42 @@ public class KrbTgsReq { AuthorizationData authorizationData, Ticket[] additionalTickets, EncryptionKey subKey) throws KrbException, IOException { + this(options, asCreds, asCreds.getClient(), sname, + from, till, rtime, eTypes, addresses, + authorizationData, additionalTickets, subKey, null); + } - princName = asCreds.client; + private KrbTgsReq( + KDCOptions options, + Credentials asCreds, + PrincipalName cname, + PrincipalName sname, + KerberosTime from, + KerberosTime till, + KerberosTime rtime, + int[] eTypes, + HostAddresses addresses, + AuthorizationData authorizationData, + Ticket[] additionalTickets, + EncryptionKey subKey, + PAData extraPA) throws KrbException, IOException { + + princName = cname; servName = sname; ctime = new KerberosTime(KerberosTime.NOW); // check if they are valid arguments. The optional fields // should be consistent with settings in KDCOptions. + + // TODO: Is this necessary? If the TGT is not FORWARDABLE, + // you can still request for a FORWARDABLE ticket, just the + // KDC will give you a non-FORWARDABLE one. Even if you + // cannot use the ticket expected, it still contains info. + // This means there will be problem later. We already have + // flags check in KrbTgsRep. Of course, sometimes the KDC + // will not issue the ticket at all. + if (options.get(KDCOptions.FORWARDABLE) && (!(asCreds.flags.get(Krb5.TKT_OPTS_FORWARDABLE)))) { throw new KrbException(Krb5.KRB_AP_ERR_REQ_OPTIONS); @@ -130,13 +198,13 @@ public class KrbTgsReq { } else { if (rtime != null) rtime = null; } - if (options.get(KDCOptions.ENC_TKT_IN_SKEY)) { + if (options.get(KDCOptions.ENC_TKT_IN_SKEY) || options.get(KDCOptions.CNAME_IN_ADDL_TKT)) { if (additionalTickets == 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]; + secondTicket = additionalTickets[0]; } else { if (additionalTickets != null) additionalTickets = null; @@ -156,7 +224,8 @@ public class KrbTgsReq { addresses, authorizationData, additionalTickets, - subKey); + subKey, + extraPA); obuf = tgsReqMessg.asn1Encode(); // XXX We need to revisit this to see if can't move it @@ -221,7 +290,8 @@ public class KrbTgsReq { HostAddresses addresses, AuthorizationData authorizationData, Ticket[] additionalTickets, - EncryptionKey subKey) + EncryptionKey subKey, + PAData extraPA) throws Asn1Exception, IOException, KdcErrException, KrbApErrException, UnknownHostException, KrbCryptoException { KerberosTime req_till = null; @@ -318,10 +388,12 @@ public class KrbTgsReq { null, null).getMessage(); - PAData[] tgsPAData = new PAData[1]; - tgsPAData[0] = new PAData(Krb5.PA_TGS_REQ, tgs_ap_req); - - return new TGSReq(tgsPAData, reqBody); + PAData tgsPAData = new PAData(Krb5.PA_TGS_REQ, tgs_ap_req); + return new TGSReq( + extraPA != null ? + new PAData[] {extraPA, tgsPAData } : + new PAData[] {tgsPAData}, + reqBody); } TGSReq getMessage() { diff --git a/jdk/src/share/classes/sun/security/krb5/internal/CredentialsUtil.java b/jdk/src/share/classes/sun/security/krb5/internal/CredentialsUtil.java index f1577c2cba6..e6cd420ca80 100644 --- a/jdk/src/share/classes/sun/security/krb5/internal/CredentialsUtil.java +++ b/jdk/src/share/classes/sun/security/krb5/internal/CredentialsUtil.java @@ -32,17 +32,7 @@ package sun.security.krb5.internal; import sun.security.krb5.*; -import sun.security.krb5.internal.ccache.CredentialsCache; -import java.util.StringTokenizer; -import sun.security.krb5.internal.ktab.*; -import java.io.File; import java.io.IOException; -import java.util.Date; -import java.util.Vector; -import java.io.BufferedReader; -import java.io.InputStreamReader; -import java.io.UnsupportedEncodingException; -import java.net.InetAddress; /** * This class is a utility that contains much of the TGS-Exchange @@ -53,77 +43,158 @@ public class CredentialsUtil { private static boolean DEBUG = sun.security.krb5.internal.Krb5.DEBUG; - /** - * Acquires credentials for a specified service using initial credential. Wh -en the service has a different realm - * from the initial credential, we do cross-realm authentication - first, we - use the current credential to get - * a cross-realm credential from the local KDC, then use that cross-realm cr -edential to request service credential - * from the foreigh KDC. - * - * @param service the name of service principal using format components@real -m - * @param ccreds client's initial credential. - * @exception Exception general exception will be thrown when any error occu -rs. - * @return a Credentials object. - */ + /** + * Used by a middle server to acquire credentials on behalf of a + * client to itself using the S4U2self extension. + * @param client the client to impersonate + * @param ccreds the TGT of the middle service + * @return the new creds (cname=client, sname=middle) + */ + public static Credentials acquireS4U2selfCreds(PrincipalName client, + Credentials ccreds) throws KrbException, IOException { + String uRealm = client.getRealmString(); + String localRealm = ccreds.getClient().getRealmString(); + if (!uRealm.equals(localRealm)) { + // TODO: we do not support kerberos referral now + throw new KrbException("Cross realm impersonation not supported"); + } + KrbTgsReq req = new KrbTgsReq( + ccreds, + ccreds.getClient(), + new PAData(Krb5.PA_FOR_USER, + new PAForUserEnc(client, + ccreds.getSessionKey()).asn1Encode())); + Credentials creds = req.sendAndGetCreds(); + if (!creds.getClient().equals(client)) { + throw new KrbException("S4U2self request not honored by KDC"); + } + return creds; + } + + /** + * 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) + */ + public static Credentials acquireS4U2proxyCreds( + String backend, Ticket second, + PrincipalName client, Credentials ccreds) + throws KrbException, IOException { + KrbTgsReq req = new KrbTgsReq( + ccreds, + second, + new PrincipalName(backend)); + Credentials creds = req.sendAndGetCreds(); + if (!creds.getClient().equals(client)) { + throw new KrbException("S4U2proxy request not honored by KDC"); + } + return creds; + } + + /** + * Acquires credentials for a specified service using initial + * credential. When the service has a different realm from the initial + * credential, we do cross-realm authentication - first, we use the + * current credential to get a cross-realm credential from the local KDC, + * then use that cross-realm credential to request service credential + * from the foreign KDC. + * + * @param service the name of service principal + * @param ccreds client's initial credential + */ public static Credentials acquireServiceCreds( String service, Credentials ccreds) - throws KrbException, IOException { + throws KrbException, IOException { PrincipalName sname = new PrincipalName(service); String serviceRealm = sname.getRealmString(); String localRealm = ccreds.getClient().getRealmString(); - /* - if (!localRealm.equalsIgnoreCase(serviceRealm)) { //do cross-realm auth entication - if (DEBUG) { - System.out.println(">>>DEBUG: Credentails request cross realm ticket for " + "krbtgt/" + serviceRealm + "@" + localRealm); - } - Credentials crossCreds = serviceCreds(new ServiceName("krbtgt/" + serviceRealm + "@" + localRealm), ccreds); - if (DEBUG) { - printDebug(crossCreds); - } - Credentials result = serviceCreds(sname, crossCreds); - if (DEBUG) { - printDebug(result); - } - return result; - } - else return serviceCreds(sname, ccreds); - */ - - if (localRealm.equals(serviceRealm)) - { - if (DEBUG) - System.out.println(">>> Credentials acquireServiceCreds: same realm"); + 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"); + } + + /** + * Gets a TGT to another realm + * @param localRealm this realm + * @param serviceRealm the other realm + * @param ccreds 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) + throws KrbException { // Get a list of realms to traverse String[] realms = Realm.getRealmsList(localRealm, serviceRealm); - boolean okAsDelegate = true; - if (realms == null || realms.length == 0) - { - if (DEBUG) - System.out.println(">>> Credentials acquireServiceCreds: no realms list"); + if (realms == null || realms.length == 0) { + if (DEBUG) { + System.out.println( + ">>> Credentials acquireServiceCreds: no realms list"); + } return null; } int i = 0, k = 0; Credentials cTgt = null, newTgt = null, theTgt = null; PrincipalName tempService = null; - String realm = null, newTgtRealm = null, theTgtRealm = null; + String newTgtRealm = null; - for (cTgt = ccreds, i = 0; i < realms.length;) - { + okAsDelegate[0] = true; + for (cTgt = ccreds, i = 0; i < realms.length;) { tempService = PrincipalName.tgsService(serviceRealm, realms[i]); - if (DEBUG) - { - System.out.println(">>> Credentials acquireServiceCreds: main loop: [" + i +"] tempService=" + tempService); + if (DEBUG) { + System.out.println( + ">>> Credentials acquireServiceCreds: main loop: [" + + i +"] tempService=" + tempService); } try { @@ -132,11 +203,10 @@ rs. newTgt = null; } - if (newTgt == null) - { - if (DEBUG) - { - System.out.println(">>> Credentials acquireServiceCreds: no tgt; searching backwards"); + if (newTgt == null) { + if (DEBUG) { + System.out.println(">>> Credentials acquireServiceCreds: " + + "no tgt; searching backwards"); } /* @@ -144,17 +214,15 @@ rs. * realm as close to the target as possible. * That means traversing the realms list backwards. */ - for (newTgt = null, k = realms.length - 1; - newTgt == null && k > i; k--) - { - + newTgt == null && k > i; k--) { tempService = PrincipalName.tgsService(realms[k], realms[i]); - if (DEBUG) - { - System.out.println(">>> Credentials acquireServiceCreds: inner loop: [" + k +"] tempService=" + tempService); + if (DEBUG) { + System.out.println( + ">>> Credentials acquireServiceCreds: " + + "inner loop: [" + k + + "] tempService=" + tempService); } - try { newTgt = serviceCreds(tempService, cTgt); } catch (Exception exc) { @@ -163,11 +231,10 @@ rs. } } // Ends 'if (newTgt == null)' - if (newTgt == null) - { - if (DEBUG) - { - System.out.println(">>> Credentials acquireServiceCreds: no tgt; cannot get creds"); + if (newTgt == null) { + if (DEBUG) { + System.out.println(">>> Credentials acquireServiceCreds: " + + "no tgt; cannot get creds"); } break; } @@ -176,29 +243,24 @@ rs. * We have a tgt. It may or may not be for the target. * If it's for the target realm, we're done looking for a tgt. */ - newTgtRealm = newTgt.getServer().getInstanceComponent(); - if (okAsDelegate && !newTgt.checkDelegate()) { - if (DEBUG) - { + if (okAsDelegate[0] && !newTgt.checkDelegate()) { + if (DEBUG) { System.out.println(">>> Credentials acquireServiceCreds: " + "global OK-AS-DELEGATE turned off at " + newTgt.getServer()); } - okAsDelegate = false; + okAsDelegate[0] = false; } - if (DEBUG) - { - System.out.println(">>> Credentials acquireServiceCreds: got tgt"); - //printDebug(newTgt); + if (DEBUG) { + System.out.println(">>> Credentials acquireServiceCreds: " + + "got tgt"); } - if (newTgtRealm.equals(serviceRealm)) - { + if (newTgtRealm.equals(serviceRealm)) { /* We got the right tgt */ theTgt = newTgt; - theTgtRealm = newTgtRealm; break; } @@ -207,17 +269,13 @@ rs. * See if the realm of the new tgt is in the list of realms * and continue looking from there. */ - - for (k = i+1; k < realms.length; k++) - { - if (newTgtRealm.equals(realms[k])) - { + for (k = i+1; k < realms.length; k++) { + if (newTgtRealm.equals(realms[k])) { break; } } - if (k < realms.length) - { + if (k < realms.length) { /* * (re)set the counter so we start looking * from the realm we just obtained a tgt for. @@ -225,64 +283,24 @@ rs. i = k; cTgt = newTgt; - if (DEBUG) - { - System.out.println(">>> Credentials acquireServiceCreds: continuing with main loop counter reset to " + i); + if (DEBUG) { + System.out.println(">>> Credentials acquireServiceCreds: " + + "continuing with main loop counter reset to " + i); } - continue; } - else - { + else { /* * The new tgt's realm is not in the heirarchy of realms. * It's probably not safe to get a tgt from * a tgs that is outside the known list of realms. * Give up now. */ - break; } } // Ends outermost/main 'for' loop - Credentials theCreds = null; - - if (theTgt != null) - { - /* We have the right tgt. Let's get the service creds */ - - if (DEBUG) - { - System.out.println(">>> Credentials acquireServiceCreds: got right tgt"); - - //printDebug(theTgt); - - 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) { - theCreds.resetDelegate(); - } - return theCreds; - } - throw new KrbApErrException(Krb5.KRB_AP_ERR_GEN_CRED, - "No service creds"); + return theTgt; } /* diff --git a/jdk/src/share/classes/sun/security/krb5/internal/EncKDCRepPart.java b/jdk/src/share/classes/sun/security/krb5/internal/EncKDCRepPart.java index 943869d60b8..2dd4c841ccc 100644 --- a/jdk/src/share/classes/sun/security/krb5/internal/EncKDCRepPart.java +++ b/jdk/src/share/classes/sun/security/krb5/internal/EncKDCRepPart.java @@ -160,9 +160,10 @@ public class EncKDCRepPart { if (der.getData().available() > 0) { caddr = HostAddresses.parse(der.getData(), (byte) 0x0B, true); } - if (der.getData().available() > 0) { + // We observe extra data from MSAD + /*if (der.getData().available() > 0) { throw new Asn1Exception(Krb5.ASN1_BAD_ID); - } + }*/ } /** diff --git a/jdk/src/share/classes/sun/security/krb5/internal/KDCOptions.java b/jdk/src/share/classes/sun/security/krb5/internal/KDCOptions.java index f154eeb1dc9..a3d93021710 100644 --- a/jdk/src/share/classes/sun/security/krb5/internal/KDCOptions.java +++ b/jdk/src/share/classes/sun/security/krb5/internal/KDCOptions.java @@ -139,13 +139,45 @@ public class KDCOptions extends KerberosFlags { public static final int UNUSED9 = 9; 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 RENEWABLE_OK = 27; public static final int ENC_TKT_IN_SKEY = 28; public static final int RENEW = 30; public static final int VALIDATE = 31; + private static final String[] names = { + "RESERVED", //0 + "FORWARDABLE", //1; + "FORWARDED", //2; + "PROXIABLE", //3; + "PROXY", //4; + "ALLOW_POSTDATE", //5; + "POSTDATED", //6; + "UNUSED7", //7; + "RENEWABLE", //8; + "UNUSED9", //9; + "UNUSED10", //10; + "UNUSED11", //11; + null,null, + "CNAME_IN_ADDL_TKT",//14; + null,null,null,null,null,null,null,null,null,null,null,null, + "RENEWABLE_OK", //27; + "ENC_TKT_IN_SKEY", //28; + null, + "RENEW", //30; + "VALIDATE", //31; + }; + private boolean DEBUG = Krb5.DEBUG; + public static KDCOptions with(int... flags) { + KDCOptions options = new KDCOptions(); + for (int flag: flags) { + options.set(flag, true); + } + return options; + } + public KDCOptions() { super(Krb5.KDC_OPTS_MAX + 1); setDefault(); @@ -238,6 +270,20 @@ public class KDCOptions extends KerberosFlags { return super.get(option); } + @Override public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("KDCOptions: "); + for (int i=0; i + * padata-type ::= PA-FOR-USER + * -- value 129 + * padata-value ::= EncryptedData + * -- PA-FOR-USER-ENC + * PA-FOR-USER-ENC ::= SEQUENCE { + * userName[0] PrincipalName, + * userRealm[1] Realm, + * cksum[2] Checksum, + * auth-package[3] KerberosString + * } + * + * + *

+ * This definition reflects MS-SFU. + */ + +public class PAForUserEnc { + final public PrincipalName name; + final private EncryptionKey key; + final public static String AUTH_PACKAGE = "Kerberos"; + + public PAForUserEnc(PrincipalName name, EncryptionKey key) { + this.name = name; + this.key = key; + } + + /** + * Constructs a PA-FOR-USER object from a DER encoding. + * @param encoding the input object + * @param key the key to verify the checksum inside encoding + * @throws KrbException if the verification fails. + * Note: this method is now only used by test KDC, therefore + * the verification is ignored (at the moment). + */ + public PAForUserEnc(DerValue encoding, EncryptionKey key) + throws Asn1Exception, KrbException, IOException { + DerValue der = null; + this.key = key; + + if (encoding.getTag() != DerValue.tag_Sequence) { + throw new Asn1Exception(Krb5.ASN1_BAD_ID); + } + + // Realm after name? Quite abnormal. + PrincipalName tmpName = null; + der = encoding.getData().getDerValue(); + if ((der.getTag() & 0x1F) == 0x00) { + try { + tmpName = new PrincipalName(der.getData().getDerValue(), + new Realm("PLACEHOLDER")); + } catch (RealmException re) { + // Impossible + } + } else { + throw new Asn1Exception(Krb5.ASN1_BAD_ID); + } + + der = encoding.getData().getDerValue(); + if ((der.getTag() & 0x1F) == 0x01) { + try { + Realm realm = new Realm(der.getData().getDerValue()); + name = new PrincipalName( + tmpName.getNameType(), tmpName.getNameStrings(), realm); + } catch (RealmException re) { + throw new IOException(re); + } + } else { + throw new Asn1Exception(Krb5.ASN1_BAD_ID); + } + + der = encoding.getData().getDerValue(); + if ((der.getTag() & 0x1F) == 0x02) { + // Deal with the checksum + } else { + throw new Asn1Exception(Krb5.ASN1_BAD_ID); + } + + der = encoding.getData().getDerValue(); + if ((der.getTag() & 0x1F) == 0x03) { + String authPackage = new KerberosString(der.getData().getDerValue()).toString(); + if (!authPackage.equalsIgnoreCase(AUTH_PACKAGE)) { + throw new IOException("Incorrect auth-package"); + } + } else { + throw new Asn1Exception(Krb5.ASN1_BAD_ID); + } + if (encoding.getData().available() > 0) + throw new Asn1Exception(Krb5.ASN1_BAD_ID); + } + + public byte[] asn1Encode() throws Asn1Exception, IOException { + DerOutputStream bytes = new DerOutputStream(); + bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte)0x00), name.asn1Encode()); + bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte)0x01), name.getRealm().asn1Encode()); + + try { + Checksum cks = new Checksum( + Checksum.CKSUMTYPE_HMAC_MD5_ARCFOUR, + getS4UByteArray(), + key, + KeyUsage.KU_PA_FOR_USER_ENC_CKSUM); + bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte)0x02), cks.asn1Encode()); + } catch (KrbException ke) { + throw new IOException(ke); + } + + DerOutputStream temp = new DerOutputStream(); + temp.putDerValue(new KerberosString(AUTH_PACKAGE).toDerValue()); + bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte)0x03), temp); + + temp = new DerOutputStream(); + temp.write(DerValue.tag_Sequence, bytes); + return temp.toByteArray(); + } + + /** + * Returns S4UByteArray, the block to calculate checksum inside a + * PA-FOR-USER-ENC data structure. It includes: + * 1. userName.name-type encoded as a 4-byte integer in little endian + * byte order + * 2. all string values in the sequence of strings contained in the + * userName.name-string field + * 3. the string value of the userRealm field + * 4. the string value of auth-package field + */ + public byte[] getS4UByteArray() { + try { + ByteArrayOutputStream ba = new ByteArrayOutputStream(); + ba.write(new byte[4]); + for (String s: name.getNameStrings()) { + ba.write(s.getBytes("UTF-8")); + } + ba.write(name.getRealm().toString().getBytes("UTF-8")); + ba.write(AUTH_PACKAGE.getBytes("UTF-8")); + byte[] output = ba.toByteArray(); + int pnType = name.getNameType(); + output[0] = (byte)(pnType & 0xff); + output[1] = (byte)((pnType>>8) & 0xff); + output[2] = (byte)((pnType>>16) & 0xff); + output[3] = (byte)((pnType>>24) & 0xff); + return output; + } catch (IOException ioe) { + // not possible + throw new AssertionError("Cannot write ByteArrayOutputStream", ioe); + } + } + + public String toString() { + return "PA-FOR-USER: " + name; + } +} diff --git a/jdk/src/share/classes/sun/security/krb5/internal/crypto/KeyUsage.java b/jdk/src/share/classes/sun/security/krb5/internal/crypto/KeyUsage.java index fcc831b0798..69c6fc8d992 100644 --- a/jdk/src/share/classes/sun/security/krb5/internal/crypto/KeyUsage.java +++ b/jdk/src/share/classes/sun/security/krb5/internal/crypto/KeyUsage.java @@ -54,6 +54,7 @@ public class KeyUsage { public static final int KU_ENC_KRB_PRIV_PART = 13; // KrbPriv public static final int KU_ENC_KRB_CRED_PART = 14; // KrbCred 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 boolean isValid(int usage) { diff --git a/jdk/src/share/classes/sun/security/krb5/internal/ktab/KeyTab.java b/jdk/src/share/classes/sun/security/krb5/internal/ktab/KeyTab.java index 2a12a816270..b2a71ce6693 100644 --- a/jdk/src/share/classes/sun/security/krb5/internal/ktab/KeyTab.java +++ b/jdk/src/share/classes/sun/security/krb5/internal/ktab/KeyTab.java @@ -279,6 +279,9 @@ public class KeyTab implements KeyTabConstants { EncryptionKey key; int size = entries.size(); ArrayList keys = new ArrayList<>(size); + if (DEBUG) { + System.out.println("Looking for keys for: " + service); + } for (int i = size-1; i >= 0; i--) { entry = entries.elementAt(i); if (entry.service.match(service)) { diff --git a/jdk/test/sun/security/krb5/auto/Basic.java b/jdk/test/sun/security/krb5/auto/Basic.java index 1048dc037e4..ef7f11db509 100644 --- a/jdk/test/sun/security/krb5/auto/Basic.java +++ b/jdk/test/sun/security/krb5/auto/Basic.java @@ -38,11 +38,13 @@ public class Basic { new OneKDC(null).writeJAASConf(); - Context c, s; + Context c, s, s2, b; c = Context.fromJAAS("client"); s = Context.fromJAAS("server"); + b = Context.fromJAAS("backend"); c.startAsClient(OneKDC.SERVER, GSSUtil.GSS_KRB5_MECH_OID); + c.x().requestCredDeleg(true); s.startAsServer(GSSUtil.GSS_KRB5_MECH_OID); Context.handshake(c, s); @@ -50,7 +52,13 @@ public class Basic { Context.transmit("i say high --", c, s); Context.transmit(" you say low", s, c); + s2 = s.delegated(); s.dispose(); c.dispose(); + + s2.startAsClient(OneKDC.BACKEND, GSSUtil.GSS_KRB5_MECH_OID); + b.startAsServer(GSSUtil.GSS_KRB5_MECH_OID); + + Context.handshake(s2, b); } } diff --git a/jdk/test/sun/security/krb5/auto/Context.java b/jdk/test/sun/security/krb5/auto/Context.java index 64ef5908262..7eef77c61c8 100644 --- a/jdk/test/sun/security/krb5/auto/Context.java +++ b/jdk/test/sun/security/krb5/auto/Context.java @@ -42,9 +42,10 @@ import org.ietf.jgss.Oid; import com.sun.security.jgss.ExtendedGSSContext; import com.sun.security.jgss.InquireType; import com.sun.security.jgss.AuthorizationDataEntry; +import com.sun.security.jgss.ExtendedGSSCredential; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; -import javax.security.auth.kerberos.KeyTab; +import java.security.Principal; /** * Context of a JGSS subject, encapsulating Subject and GSSContext. @@ -90,7 +91,21 @@ public class Context { public Context delegated() throws Exception { Context out = new Context(); out.s = s; - out.cred = x.getDelegCred(); + try { + out.cred = Subject.doAs(s, new PrivilegedExceptionAction() { + @Override + public GSSCredential run() throws Exception { + GSSCredential cred = x.getDelegCred(); + if (cred == null && x.getCredDelegState() || + cred != null && !x.getCredDelegState()) { + throw new Exception("getCredDelegState not match"); + } + return cred; + } + }); + } catch (PrivilegedActionException pae) { + throw pae.getException(); + } out.name = name + " as " + out.cred.getName().toString(); return out; } @@ -212,28 +227,34 @@ public class Context { * @throws java.lang.Exception */ public void startAsServer(final Oid mech) throws Exception { - startAsServer(null, mech); + startAsServer(null, mech, false); } + public void startAsServer(final String name, final Oid mech) throws Exception { + startAsServer(name, mech, false); + } /** * Starts as a server with the specified service name * @param name the service name * @param mech GSS mech * @throws java.lang.Exception */ - public void startAsServer(final String name, final Oid mech) throws Exception { + public void startAsServer(final String name, final Oid mech, final boolean asInitiator) throws Exception { doAs(new Action() { @Override public byte[] run(Context me, byte[] dummy) throws Exception { GSSManager m = GSSManager.getInstance(); - me.x = (ExtendedGSSContext)m.createContext(m.createCredential( + me.cred = m.createCredential( name == null ? null : (name.indexOf('@') < 0 ? m.createName(name, null) : m.createName(name, GSSName.NT_HOSTBASED_SERVICE)), GSSCredential.INDEFINITE_LIFETIME, mech, - GSSCredential.ACCEPT_ONLY)); + asInitiator? + GSSCredential.INITIATE_AND_ACCEPT: + GSSCredential.ACCEPT_ONLY); + me.x = (ExtendedGSSContext)m.createContext(me.cred); return null; } }, null); @@ -331,27 +352,38 @@ public class Context { } catch (Exception e) { ;// Don't care } - System.out.println("====== Private Credentials Set ======"); - for (Object o : s.getPrivateCredentials()) { - System.out.println(" " + o.getClass()); - if (o instanceof KerberosTicket) { - KerberosTicket kt = (KerberosTicket) o; - System.out.println(" " + kt.getServer() + " for " + kt.getClient()); - } else if (o instanceof KerberosKey) { - KerberosKey kk = (KerberosKey) o; - System.out.print(" " + kk.getKeyType() + " " + kk.getVersionNumber() + " " + kk.getAlgorithm() + " "); - for (byte b : kk.getEncoded()) { - System.out.printf("%02X", b & 0xff); - } - System.out.println(); - } else if (o instanceof Map) { - Map map = (Map) o; - for (Object k : map.keySet()) { - System.out.println(" " + k + ": " + map.get(k)); - } - } else { + if (s != null) { + System.out.println("====== START SUBJECT CONTENT ====="); + for (Principal p: s.getPrincipals()) { + System.out.println(" Principal: " + p); + } + for (Object o : s.getPublicCredentials()) { + System.out.println(" " + o.getClass()); System.out.println(" " + o); } + System.out.println("====== Private Credentials Set ======"); + for (Object o : s.getPrivateCredentials()) { + System.out.println(" " + o.getClass()); + if (o instanceof KerberosTicket) { + KerberosTicket kt = (KerberosTicket) o; + System.out.println(" " + kt.getServer() + " for " + kt.getClient()); + } else if (o instanceof KerberosKey) { + KerberosKey kk = (KerberosKey) o; + System.out.print(" " + kk.getKeyType() + " " + kk.getVersionNumber() + " " + kk.getAlgorithm() + " "); + for (byte b : kk.getEncoded()) { + System.out.printf("%02X", b & 0xff); + } + System.out.println(); + } else if (o instanceof Map) { + Map map = (Map) o; + for (Object k : map.keySet()) { + System.out.println(" " + k + ": " + map.get(k)); + } + } else { + System.out.println(" " + o); + } + } + System.out.println("====== END SUBJECT CONTENT ====="); } if (x != null && x instanceof ExtendedGSSContext) { if (x.isEstablished()) { @@ -510,6 +542,29 @@ public class Context { return sb.toString(); } + public Context impersonate(final String someone) throws Exception { + try { + GSSCredential creds = Subject.doAs(s, new PrivilegedExceptionAction() { + @Override + public GSSCredential run() throws Exception { + GSSManager m = GSSManager.getInstance(); + GSSName other = m.createName(someone, GSSName.NT_USER_NAME); + if (Context.this.cred == null) { + Context.this.cred = m.createCredential(GSSCredential.INITIATE_ONLY); + } + return ((ExtendedGSSCredential)Context.this.cred).impersonate(other); + } + }); + Context out = new Context(); + out.s = s; + out.cred = creds; + out.name = name + " as " + out.cred.getName().toString(); + return out; + } catch (PrivilegedActionException pae) { + throw pae.getException(); + } + } + public byte[] take(final byte[] in) throws Exception { return doAs(new Action() { @Override @@ -522,10 +577,11 @@ public class Context { } return null; } else { - System.out.println(name + " call initSecContext"); if (me.x.isInitiator()) { + System.out.println(name + " call initSecContext"); return me.x.initSecContext(input, 0, input.length); } else { + System.out.println(name + " call acceptSecContext"); return me.x.acceptSecContext(input, 0, input.length); } } diff --git a/jdk/test/sun/security/krb5/auto/CrossRealm.java b/jdk/test/sun/security/krb5/auto/CrossRealm.java index 21927b6f8a8..ef04521c66d 100644 --- a/jdk/test/sun/security/krb5/auto/CrossRealm.java +++ b/jdk/test/sun/security/krb5/auto/CrossRealm.java @@ -24,6 +24,7 @@ /* * @test * @bug 6706974 + * @compile -XDignore.symbol.file CrossRealm.java * @run main/othervm CrossRealm * @summary Add krb5 test infrastructure */ diff --git a/jdk/test/sun/security/krb5/auto/KDC.java b/jdk/test/sun/security/krb5/auto/KDC.java index 716e2fa0e07..409f3131fb8 100644 --- a/jdk/test/sun/security/krb5/auto/KDC.java +++ b/jdk/test/sun/security/krb5/auto/KDC.java @@ -178,6 +178,20 @@ public class KDC { * What backend server can be delegated to */ OK_AS_DELEGATE, + /** + * Allow S4U2self, List of middle servers. + * If not set, means KDC does not understand S4U2self at all, therefore + * would ignore any PA-FOR-USER request and send a ticket using the + * cname of teh requestor. If set, it returns FORWARDABLE tickets to + * a server with its name in the list + */ + ALLOW_S4U2SELF, + /** + * Allow S4U2proxy, Map> of middle servers to + * backends. If not set or a backend not in a server's list, + * Krb5.KDC_ERR_POLICY will be send for S4U2proxy request. + */ + ALLOW_S4U2PROXY, }; static { @@ -618,13 +632,18 @@ public class KDC { int e2 = eTypes[0]; // etype for outgoing session key int e3 = eTypes[0]; // etype for outgoing ticket - PAData[] pas = kDCReqDotPAData(tgsReq); + PAData[] pas = KDCReqDotPAData(tgsReq); Ticket tkt = null; EncTicketPart etp = null; + + PrincipalName cname = null; + boolean allowForwardable = true; + if (pas == null || pas.length == 0) { throw new KrbException(Krb5.KDC_ERR_PADATA_TYPE_NOSUPP); } else { + PrincipalName forUserCName = null; for (PAData pa: pas) { if (pa.getType() == Krb5.PA_TGS_REQ) { APReq apReq = new APReq(pa.getValue()); @@ -636,8 +655,31 @@ public class KDC { DerInputStream derIn = new DerInputStream(bb); DerValue der = derIn.getDerValue(); etp = new EncTicketPart(der.toByteArray()); + // Finally, cname will be overwritten by PA-FOR-USER + // if it exists. + cname = etp.cname; + System.out.println(realm + "> 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 + "> presenting a PA_FOR_USER " + + " in the name of " + p4u.name); + } } } + if (forUserCName != null) { + List names = (List)options.get(Option.ALLOW_S4U2SELF); + if (!names.contains(cname.toString())) { + // Mimic the normal KDC behavior. When a server is not + // allowed to send S4U2self, do not send an error. + // Instead, send a ticket which is useless later. + allowForwardable = false; + } + cname = forUserCName; + } if (tkt == null) { throw new KrbException(Krb5.KDC_ERR_PADATA_TYPE_NOSUPP); } @@ -658,7 +700,8 @@ public class KDC { } boolean[] bFlags = new boolean[Krb5.TKT_OPTS_MAX+1]; - if (body.kdcOptions.get(KDCOptions.FORWARDABLE)) { + if (body.kdcOptions.get(KDCOptions.FORWARDABLE) + && allowForwardable) { bFlags[Krb5.TKT_OPTS_FORWARDABLE] = true; } if (body.kdcOptions.get(KDCOptions.FORWARDED) || @@ -678,6 +721,37 @@ public class KDC { if (body.kdcOptions.get(KDCOptions.ALLOW_POSTDATE)) { bFlags[Krb5.TKT_OPTS_MAY_POSTDATE] = true; } + if (body.kdcOptions.get(KDCOptions.CNAME_IN_ADDL_TKT)) { + if (!options.containsKey(Option.ALLOW_S4U2PROXY)) { + // Don't understand CNAME_IN_ADDL_TKT + throw new KrbException(Krb5.KDC_ERR_BADOPTION); + } else { + Map> map = (Map>) + options.get(Option.ALLOW_S4U2PROXY); + Ticket second = KDCReqBodyDotFirstAdditionalTicket(body); + EncryptionKey key2 = keyForUser(second.sname, second.encPart.getEType(), true); + byte[] bb = second.encPart.decrypt(key2, KeyUsage.KU_TICKET); + 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); + } + PrincipalName client = tktEncPart.cname; + System.out.println(realm + "> 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"); + } else { + throw new KrbException(Krb5.KDC_ERR_BADOPTION); + } + } else { + throw new KrbException(Krb5.KDC_ERR_BADOPTION); + } + cname = client; + } + } String okAsDelegate = (String)options.get(Option.OK_AS_DELEGATE); if (okAsDelegate != null && ( @@ -691,7 +765,7 @@ public class KDC { EncTicketPart enc = new EncTicketPart( tFlags, key, - etp.cname, + cname, new TransitedEncoding(1, new byte[0]), // TODO new KerberosTime(new Date()), body.from, @@ -729,7 +803,7 @@ public class KDC { ); EncryptedData edata = new EncryptedData(ckey, enc_part.asn1Encode(), KeyUsage.KU_ENC_TGS_REP_PART_SESSKEY); TGSRep tgsRep = new TGSRep(null, - etp.cname, + cname, t, edata); System.out.println(" Return " + tgsRep.cname @@ -942,7 +1016,7 @@ public class KDC { outPAs.add(new PAData(Krb5.PA_ETYPE_INFO, eid.toByteArray())); } - PAData[] inPAs = kDCReqDotPAData(asReq); + PAData[] inPAs = KDCReqDotPAData(asReq); if (inPAs == null || inPAs.length == 0) { Object preauth = options.get(Option.PREAUTH_REQUIRED); if (preauth == null || preauth.equals(Boolean.TRUE)) { @@ -1252,6 +1326,7 @@ public class KDC { private static final Field getEType; private static final Constructor ctorEncryptedData; private static final Method stringToKey; + private static final Field getAddlTkt; static { try { @@ -1265,6 +1340,8 @@ public class KDC { "stringToKey", char[].class, String.class, byte[].class, Integer.TYPE); stringToKey.setAccessible(true); + getAddlTkt = KDCReqBody.class.getDeclaredField("additionalTickets"); + getAddlTkt.setAccessible(true); } catch (NoSuchFieldException nsfe) { throw new AssertionError(nsfe); } catch (NoSuchMethodException nsme) { @@ -1278,7 +1355,7 @@ public class KDC { throw new AssertionError(e); } } - private static PAData[] kDCReqDotPAData(KDCReq req) { + private static PAData[] KDCReqDotPAData(KDCReq req) { try { return (PAData[])getPADataField.get(req); } catch (Exception e) { @@ -1303,4 +1380,11 @@ public class KDC { throw new AssertionError(e); } } + private static Ticket KDCReqBodyDotFirstAdditionalTicket(KDCReqBody body) { + try { + return ((Ticket[])getAddlTkt.get(body))[0]; + } catch (Exception e) { + throw new AssertionError(e); + } + } } diff --git a/jdk/test/sun/security/krb5/auto/OkAsDelegate.java b/jdk/test/sun/security/krb5/auto/OkAsDelegate.java index 90376341f2d..d66e2421b82 100644 --- a/jdk/test/sun/security/krb5/auto/OkAsDelegate.java +++ b/jdk/test/sun/security/krb5/auto/OkAsDelegate.java @@ -91,7 +91,7 @@ public class OkAsDelegate { Context c, s; c = Context.fromJAAS("client"); - s = Context.fromJAAS("server"); + s = Context.fromJAAS("com.sun.security.jgss.krb5.accept"); Oid mech = GSSUtil.GSS_KRB5_MECH_OID; if (System.getProperty("test.spnego") != null) { diff --git a/jdk/test/sun/security/krb5/auto/S4U2proxy.java b/jdk/test/sun/security/krb5/auto/S4U2proxy.java new file mode 100644 index 00000000000..dd7d50c3892 --- /dev/null +++ b/jdk/test/sun/security/krb5/auto/S4U2proxy.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2012, 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 6355584 + * @summary Introduce constrained Kerberos delegation + * @compile -XDignore.symbol.file S4U2proxy.java + * @run main/othervm S4U2proxy krb5 + * @run main/othervm S4U2proxy spnego + */ + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.ietf.jgss.Oid; +import sun.security.jgss.GSSUtil; + +public class S4U2proxy { + + public static void main(String[] args) throws Exception { + Oid mech; + if (args[0].equals("spnego")) { + mech = GSSUtil.GSS_SPNEGO_MECH_OID; + } else if (args[0].contains("krb5")) { + mech = GSSUtil.GSS_KRB5_MECH_OID; + } else { + throw new Exception("Unknown mech"); + } + + OneKDC kdc = new OneKDC(null); + kdc.writeJAASConf(); + kdc.setOption(KDC.Option.PREAUTH_REQUIRED, false); + Map> map = new HashMap<>(); + map.put(OneKDC.SERVER + "@" + OneKDC.REALM, Arrays.asList( + new String[]{OneKDC.BACKEND + "@" + OneKDC.REALM})); + kdc.setOption(KDC.Option.ALLOW_S4U2PROXY, map); + + Context c, s, b; + c = Context.fromJAAS("client"); + s = Context.fromJAAS("server"); + b = Context.fromJAAS("backend"); + + c.startAsClient(OneKDC.SERVER, mech); + s.startAsServer(null, mech, false); + + Context.handshake(c, s); + Context p = s.delegated(); + + p.startAsClient(OneKDC.BACKEND, mech); + b.startAsServer(mech); + Context.handshake(p, b); + + p.startAsClient(OneKDC.BACKEND, mech); + b.startAsServer(mech); + Context.handshake(p, b); + } +} diff --git a/jdk/test/sun/security/krb5/auto/S4U2proxyGSS.java b/jdk/test/sun/security/krb5/auto/S4U2proxyGSS.java new file mode 100644 index 00000000000..f2f0b305e05 --- /dev/null +++ b/jdk/test/sun/security/krb5/auto/S4U2proxyGSS.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2012, 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 6355584 + * @summary Introduce constrained Kerberos delegation + * @compile -XDignore.symbol.file S4U2proxyGSS.java + * @run main/othervm -Djavax.security.auth.useSubjectCredsOnly=false S4U2proxyGSS krb5 + * @run main/othervm -Djavax.security.auth.useSubjectCredsOnly=false S4U2proxyGSS spnego + */ + +import java.io.File; +import java.io.FileOutputStream; +import java.security.Security; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.ietf.jgss.Oid; +import sun.security.jgss.GSSUtil; + +public class S4U2proxyGSS { + + public static void main(String[] args) throws Exception { + Oid mech; + if (args[0].equals("spnego")) { + mech = GSSUtil.GSS_SPNEGO_MECH_OID; + } else if (args[0].contains("krb5")) { + mech = GSSUtil.GSS_KRB5_MECH_OID; + } else { + throw new Exception("Unknown mech"); + } + + OneKDC kdc = new OneKDC(null); + kdc.writeJAASConf(); + kdc.setOption(KDC.Option.PREAUTH_REQUIRED, false); + Map> map = new HashMap<>(); + map.put(OneKDC.SERVER + "@" + OneKDC.REALM, Arrays.asList( + new String[]{OneKDC.SERVER + "@" + OneKDC.REALM})); + kdc.setOption(KDC.Option.ALLOW_S4U2PROXY, map); + + Context c, s, b; + System.setProperty("javax.security.auth.useSubjectCredsOnly", "false"); + System.setProperty("java.security.auth.login.config", OneKDC.JAAS_CONF); + File f = new File(OneKDC.JAAS_CONF); + FileOutputStream fos = new FileOutputStream(f); + fos.write(( + "com.sun.security.jgss.krb5.initiate {\n" + + " com.sun.security.auth.module.Krb5LoginModule required;\n};\n" + + "com.sun.security.jgss.krb5.accept {\n" + + " com.sun.security.auth.module.Krb5LoginModule required\n" + + " principal=\"" + OneKDC.SERVER + "\"\n" + + " useKeyTab=true\n" + + " storeKey=true;\n};\n" + ).getBytes()); + fos.close(); + Security.setProperty("auth.login.defaultCallbackHandler", "OneKDC$CallbackForClient"); + c = Context.fromThinAir(); + s = Context.fromThinAir(); + b = Context.fromThinAir(); + c.startAsClient(OneKDC.SERVER, mech); + c.x().requestCredDeleg(false); + s.startAsServer(mech); + + Context.handshake(c, s); + Context p = s.delegated(); + p.startAsClient(OneKDC.SERVER, mech); + b.startAsServer(mech); + Context.handshake(p, b); + + String n1 = p.x().getSrcName().toString().split("@")[0]; + String n2 = b.x().getSrcName().toString().split("@")[0]; + if (!n1.equals(OneKDC.USER) || !n2.equals(OneKDC.USER)) { + throw new Exception("Delegation failed"); + } + } +} diff --git a/jdk/test/sun/security/krb5/auto/S4U2self.java b/jdk/test/sun/security/krb5/auto/S4U2self.java new file mode 100644 index 00000000000..a6c4b21c192 --- /dev/null +++ b/jdk/test/sun/security/krb5/auto/S4U2self.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2012, 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 6355584 + * @summary Introduce constrained Kerberos delegation + * @compile -XDignore.symbol.file S4U2self.java + * @run main/othervm -Dsun.security.krb5.debug=false S4U2self krb5 0 + * @run main/othervm/fail -Dsun.security.krb5.debug=false S4U2self krb5 1 + * @run main/othervm/fail -Dsun.security.krb5.debug=false S4U2self krb5 2 + * @run main/othervm/fail -Dsun.security.krb5.debug=false S4U2self krb5 3 + * @run main/othervm/fail -Dsun.security.krb5.debug=false S4U2self krb5 4 + * @run main/othervm/fail -Dsun.security.krb5.debug=false S4U2self krb5 5 + * @run main/othervm -Dsun.security.krb5.debug=false S4U2self spnego + */ + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.ietf.jgss.Oid; +import sun.security.jgss.GSSUtil; + +public class S4U2self { + + public static void main(String[] args) throws Exception { + // Test case, different policy settings in KDC: + // | ALLOW_S4U2SELF on + // | USER USER2 none + // ALLOW_S4U2PORXY |------------------------- + // USER to BACKEND | 0 1 2 + // USER2 to BACKEND | 3 + // USER to SERVER | 4 + // none | 5 + // + // 0 should succeed, all other fail + int test = 0; + Oid mech; + if (args[0].equals("spnego")) { + mech = GSSUtil.GSS_SPNEGO_MECH_OID; + } else if (args[0].contains("krb5")) { + mech = GSSUtil.GSS_KRB5_MECH_OID; + test = Integer.parseInt(args[1]); + } else { + throw new Exception("Unknown mech"); + } + + OneKDC kdc = new OneKDC(null); + kdc.writeJAASConf(); + + switch (test) { + case 1: + kdc.setOption(KDC.Option.ALLOW_S4U2SELF, Arrays.asList( + new String[]{OneKDC.USER2 + "@" + OneKDC.REALM})); + break; + case 2: + // No S4U2self + break; + default: + kdc.setOption(KDC.Option.ALLOW_S4U2SELF, Arrays.asList( + new String[]{OneKDC.USER + "@" + OneKDC.REALM})); + break; + } + + Map> map = new HashMap<>(); + switch (test) { + case 3: + map.put(OneKDC.USER2 + "@" + OneKDC.REALM, Arrays.asList( + new String[]{OneKDC.BACKEND + "@" + OneKDC.REALM})); + kdc.setOption(KDC.Option.ALLOW_S4U2PROXY, map); + break; + case 4: + map.put(OneKDC.USER + "@" + OneKDC.REALM, Arrays.asList( + new String[]{OneKDC.SERVER + "@" + OneKDC.REALM})); + kdc.setOption(KDC.Option.ALLOW_S4U2PROXY, map); + break; + case 5: + // No S4U2proxy set + break; + default: + map.put(OneKDC.USER + "@" + OneKDC.REALM, Arrays.asList( + new String[]{OneKDC.BACKEND + "@" + OneKDC.REALM})); + kdc.setOption(KDC.Option.ALLOW_S4U2PROXY, map); + break; + } + + Context c, s; + c = Context.fromJAAS("client"); + + c = c.impersonate(OneKDC.USER2); + c.status(); + + c.startAsClient(OneKDC.BACKEND, mech); + + s = Context.fromJAAS("backend"); + s.startAsServer(mech); + + Context.handshake(c, s); + + Context.transmit("i say high --", c, s); + Context.transmit(" you say low", s, c); + + c.status(); + s.status(); + + String n1 = c.x().getSrcName().toString().split("@")[0]; + String n2 = s.x().getSrcName().toString().split("@")[0]; + if (!n1.equals(OneKDC.USER2) || !n2.equals(OneKDC.USER2)) { + throw new Exception("Impersonate failed"); + } + + s.dispose(); + c.dispose(); + } +} diff --git a/jdk/test/sun/security/krb5/auto/S4U2selfAsServer.java b/jdk/test/sun/security/krb5/auto/S4U2selfAsServer.java new file mode 100644 index 00000000000..467129c16e3 --- /dev/null +++ b/jdk/test/sun/security/krb5/auto/S4U2selfAsServer.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2012, 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 6355584 + * @summary Introduce constrained Kerberos delegation + * @compile -XDignore.symbol.file S4U2selfAsServer.java + * @run main/othervm S4U2selfAsServer krb5 + * @run main/othervm S4U2selfAsServer spnego + */ + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.ietf.jgss.Oid; +import sun.security.jgss.GSSUtil; + +public class S4U2selfAsServer { + + public static void main(String[] args) throws Exception { + Oid mech; + if (args[0].equals("spnego")) { + mech = GSSUtil.GSS_SPNEGO_MECH_OID; + } else if (args[0].contains("krb5")) { + mech = GSSUtil.GSS_KRB5_MECH_OID; + } else { + throw new Exception("Unknown mech"); + } + + OneKDC kdc = new OneKDC(null); + kdc.writeJAASConf(); + kdc.setOption(KDC.Option.PREAUTH_REQUIRED, false); + Map> map = new HashMap<>(); + map.put(OneKDC.SERVER + "@" + OneKDC.REALM, Arrays.asList( + new String[]{OneKDC.BACKEND + "@" + OneKDC.REALM})); + kdc.setOption(KDC.Option.ALLOW_S4U2PROXY, map); + kdc.setOption(KDC.Option.ALLOW_S4U2SELF, Arrays.asList( + new String[]{OneKDC.SERVER + "@" + OneKDC.REALM})); + + Context s, b; + s = Context.fromJAAS("server"); + b = Context.fromJAAS("backend"); + + s.startAsServer(null, mech, false); + + Context p = s.impersonate(OneKDC.USER); + + p.startAsClient(OneKDC.BACKEND, mech); + b.startAsServer(mech); + Context.handshake(p, b); + + p.startAsClient(OneKDC.BACKEND, mech); + b.startAsServer(mech); + Context.handshake(p, b); + } +} diff --git a/jdk/test/sun/security/krb5/auto/S4U2selfAsServerGSS.java b/jdk/test/sun/security/krb5/auto/S4U2selfAsServerGSS.java new file mode 100644 index 00000000000..0ff2b7bb53d --- /dev/null +++ b/jdk/test/sun/security/krb5/auto/S4U2selfAsServerGSS.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2012, 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 6355584 + * @summary Introduce constrained Kerberos delegation + * @compile -XDignore.symbol.file S4U2selfAsServerGSS.java + * @run main/othervm -Djavax.security.auth.useSubjectCredsOnly=false S4U2selfAsServerGSS krb5 + * @run main/othervm -Djavax.security.auth.useSubjectCredsOnly=false S4U2selfAsServerGSS spnego + */ + +import java.io.File; +import java.io.FileOutputStream; +import java.security.Security; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.ietf.jgss.Oid; +import sun.security.jgss.GSSUtil; + +public class S4U2selfAsServerGSS { + + public static void main(String[] args) throws Exception { + Oid mech; + if (args[0].equals("spnego")) { + mech = GSSUtil.GSS_SPNEGO_MECH_OID; + } else if (args[0].contains("krb5")) { + mech = GSSUtil.GSS_KRB5_MECH_OID; + } else { + throw new Exception("Unknown mech"); + } + + OneKDC kdc = new OneKDC(null); + kdc.writeJAASConf(); + kdc.setOption(KDC.Option.PREAUTH_REQUIRED, false); + Map> map = new HashMap<>(); + map.put(OneKDC.SERVER + "@" + OneKDC.REALM, Arrays.asList( + new String[]{OneKDC.SERVER + "@" + OneKDC.REALM})); + kdc.setOption(KDC.Option.ALLOW_S4U2PROXY, map); + kdc.setOption(KDC.Option.ALLOW_S4U2SELF, Arrays.asList( + new String[]{OneKDC.SERVER + "@" + OneKDC.REALM})); + + Context s, b; + System.setProperty("javax.security.auth.useSubjectCredsOnly", "false"); + System.setProperty("java.security.auth.login.config", OneKDC.JAAS_CONF); + File f = new File(OneKDC.JAAS_CONF); + FileOutputStream fos = new FileOutputStream(f); + fos.write(( + "com.sun.security.jgss.krb5.accept {\n" + + " com.sun.security.auth.module.Krb5LoginModule required\n" + + " principal=\"" + OneKDC.SERVER + "\"\n" + + " useKeyTab=true\n" + + " storeKey=true;\n};\n" + ).getBytes()); + fos.close(); + Security.setProperty("auth.login.defaultCallbackHandler", "OneKDC$CallbackForClient"); + s = Context.fromThinAir(); + b = Context.fromThinAir(); + s.startAsServer(mech); + + Context p = s.impersonate(OneKDC.USER); + + p.startAsClient(OneKDC.SERVER, mech); + b.startAsServer(mech); + Context.handshake(p, b); + + String n1 = p.x().getSrcName().toString().split("@")[0]; + String n2 = b.x().getSrcName().toString().split("@")[0]; + if (!n1.equals(OneKDC.USER) || !n2.equals(OneKDC.USER)) { + throw new Exception("Delegation failed"); + } + } +} diff --git a/jdk/test/sun/security/krb5/auto/S4U2selfGSS.java b/jdk/test/sun/security/krb5/auto/S4U2selfGSS.java new file mode 100644 index 00000000000..f060c787ffb --- /dev/null +++ b/jdk/test/sun/security/krb5/auto/S4U2selfGSS.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2012, 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 6355584 + * @summary Introduce constrained Kerberos delegation + * @compile -XDignore.symbol.file S4U2selfGSS.java + * @run main/othervm -Dsun.security.krb5.debug=false S4U2selfGSS krb5 + * @run main/othervm -Dsun.security.krb5.debug=false S4U2selfGSS spnego + */ + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.ietf.jgss.Oid; +import sun.security.jgss.GSSUtil; + +public class S4U2selfGSS { + + public static void main(String[] args) throws Exception { + Oid mech; + if (args[0].equals("spnego")) { + mech = GSSUtil.GSS_SPNEGO_MECH_OID; + } else if (args[0].contains("krb5")) { + mech = GSSUtil.GSS_KRB5_MECH_OID; + } else { + throw new Exception("Unknown mech"); + } + + OneKDC kdc = new OneKDC(null); + kdc.writeJAASConf(); + kdc.setOption(KDC.Option.ALLOW_S4U2SELF, Arrays.asList( + new String[]{OneKDC.USER + "@" + OneKDC.REALM})); + Map> map = new HashMap<>(); + map.put(OneKDC.USER + "@" + OneKDC.REALM, Arrays.asList( + new String[]{OneKDC.SERVER + "@" + OneKDC.REALM})); + kdc.setOption(KDC.Option.ALLOW_S4U2PROXY, map); + + Context c, s; + System.setProperty("javax.security.auth.useSubjectCredsOnly", "false"); + c = Context.fromThinAir(); + s = Context.fromThinAir(); + + c = c.impersonate(OneKDC.USER2); + + c.startAsClient(OneKDC.SERVER, mech); + s.startAsServer(mech); + + Context.handshake(c, s); + + String n1 = c.x().getSrcName().toString().split("@")[0]; + String n2 = s.x().getSrcName().toString().split("@")[0]; + if (!n1.equals(OneKDC.USER2) || !n2.equals(OneKDC.USER2)) { + throw new Exception("Impersonate failed"); + } + + s.dispose(); + c.dispose(); + } +}