0N/A/*
1753N/A * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
0N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0N/A *
0N/A * This code is free software; you can redistribute it and/or modify it
0N/A * under the terms of the GNU General Public License version 2 only, as
0N/A * published by the Free Software Foundation. Oracle designates this
0N/A * particular file as subject to the "Classpath" exception as provided
0N/A * by Oracle in the LICENSE file that accompanied this code.
0N/A *
0N/A * This code is distributed in the hope that it will be useful, but WITHOUT
0N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0N/A * version 2 for more details (a copy is included in the LICENSE file that
0N/A * accompanied this code).
0N/A *
0N/A * You should have received a copy of the GNU General Public License version
0N/A * 2 along with this work; if not, write to the Free Software Foundation,
1472N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
1472N/A *
1472N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
0N/A * or visit www.oracle.com if you need additional information or have any
0N/A * questions.
0N/A */
1879N/A
1879N/Apackage sun.security.ssl.krb5;
1879N/A
1879N/Aimport java.io.IOException;
4141N/Aimport java.io.PrintStream;
1879N/Aimport java.security.AccessController;
1879N/Aimport java.security.AccessControlContext;
1879N/Aimport java.security.PrivilegedExceptionAction;
0N/Aimport java.security.PrivilegedActionException;
0N/Aimport java.security.SecureRandom;
0N/Aimport java.net.InetAddress;
0N/A
0N/Aimport javax.crypto.SecretKey;
0N/Aimport javax.security.auth.kerberos.KerberosTicket;
0N/Aimport javax.security.auth.kerberos.KerberosKey;
0N/Aimport javax.security.auth.kerberos.KerberosPrincipal;
0N/Aimport javax.security.auth.kerberos.ServicePermission;
0N/Aimport sun.security.jgss.GSSCaller;
0N/A
0N/Aimport sun.security.krb5.EncryptionKey;
0N/Aimport sun.security.krb5.EncryptedData;
0N/Aimport sun.security.krb5.PrincipalName;
0N/Aimport sun.security.krb5.Realm;
0N/Aimport sun.security.krb5.internal.Ticket;
1753N/Aimport sun.security.krb5.internal.EncTicketPart;
0N/Aimport sun.security.krb5.internal.crypto.KeyUsage;
0N/A
0N/Aimport sun.security.jgss.krb5.Krb5Util;
2941N/Aimport sun.security.krb5.KrbException;
2941N/Aimport sun.security.krb5.internal.Krb5;
2941N/A
2941N/Aimport sun.security.ssl.Debug;
2941N/Aimport sun.security.ssl.HandshakeInStream;
2941N/Aimport sun.security.ssl.HandshakeOutStream;
2941N/Aimport sun.security.ssl.ProtocolVersion;
2941N/A
2941N/A/**
2941N/A * This is Kerberos option in the client key exchange message
2941N/A * (CLIENT -> SERVER). It holds the Kerberos ticket and the encrypted
2941N/A * premaster secret encrypted with the session key sealed in the ticket.
2941N/A * From RFC 2712:
2941N/A * struct
2941N/A * {
2941N/A * opaque Ticket;
2941N/A * opaque authenticator; // optional
2941N/A * opaque EncryptedPreMasterSecret; // encrypted with the session key
2941N/A * // which is sealed in the ticket
2941N/A * } KerberosWrapper;
2941N/A *
2941N/A *
2941N/A * Ticket and authenticator are encrypted as per RFC 1510 (in ASN.1)
2941N/A * Encrypted pre-master secret has the same structure as it does for RSA
2941N/A * except for Kerberos, the encryption key is the session key instead of
2941N/A * the RSA public key.
2941N/A *
2941N/A * XXX authenticator currently ignored
2941N/A *
2941N/A */
2941N/Apublic final class KerberosClientKeyExchangeImpl
2941N/A extends sun.security.ssl.KerberosClientKeyExchange {
2941N/A
2941N/A private KerberosPreMasterSecret preMaster;
2941N/A private byte[] encodedTicket;
2941N/A private KerberosPrincipal peerPrincipal;
2941N/A private KerberosPrincipal localPrincipal;
2941N/A
2941N/A public KerberosClientKeyExchangeImpl() {
2941N/A }
2941N/A
2941N/A /**
2941N/A * Creates an instance of KerberosClientKeyExchange consisting of the
2941N/A * Kerberos service ticket, authenticator and encrypted premaster secret.
2941N/A * Called by client handshaker.
2941N/A *
2941N/A * @param serverName name of server with which to do handshake;
2941N/A * this is used to get the Kerberos service ticket
2941N/A * @param protocolVersion Maximum version supported by client (i.e,
2941N/A * version it requested in client hello)
2941N/A * @param rand random number generator to use for generating pre-master
2941N/A * secret
2941N/A */
2941N/A @Override
2941N/A public void init(String serverName, boolean isLoopback,
2941N/A AccessControlContext acc, ProtocolVersion protocolVersion,
0N/A SecureRandom rand) throws IOException {
0N/A
0N/A // Get service ticket
342N/A KerberosTicket ticket = getServiceTicket(serverName, isLoopback, acc);
342N/A encodedTicket = ticket.getEncoded();
342N/A
0N/A // Record the Kerberos principals
0N/A peerPrincipal = ticket.getServer();
0N/A localPrincipal = ticket.getClient();
0N/A
0N/A // Optional authenticator, encrypted using session key,
0N/A // currently ignored
0N/A
0N/A // Generate premaster secret and encrypt it using session key
0N/A EncryptionKey sessionKey = new EncryptionKey(
0N/A ticket.getSessionKeyType(),
0N/A ticket.getSessionKey().getEncoded());
0N/A
0N/A preMaster = new KerberosPreMasterSecret(protocolVersion,
0N/A rand, sessionKey);
0N/A }
0N/A
0N/A /**
0N/A * Creates an instance of KerberosClientKeyExchange from its ASN.1 encoding.
0N/A * Used by ServerHandshaker to verify and obtain premaster secret.
0N/A *
0N/A * @param protocolVersion current protocol version
0N/A * @param clientVersion version requested by client in its ClientHello;
0N/A * used by premaster secret version check
0N/A * @param rand random number generator used for generating random
0N/A * premaster secret if ticket and/or premaster verification fails
0N/A * @param input inputstream from which to get ASN.1-encoded KerberosWrapper
0N/A * @param serverKey server's master secret key
1753N/A */
0N/A @Override
0N/A public void init(ProtocolVersion protocolVersion,
0N/A ProtocolVersion clientVersion,
0N/A SecureRandom rand, HandshakeInStream input, SecretKey[] secretKeys)
0N/A throws IOException {
342N/A
342N/A KerberosKey[] serverKeys = (KerberosKey[])secretKeys;
342N/A
342N/A // Read ticket
342N/A encodedTicket = input.getBytes16();
342N/A
342N/A if (debug != null && Debug.isOn("verbose")) {
342N/A Debug.println(System.out,
0N/A "encoded Kerberos service ticket", encodedTicket);
0N/A }
0N/A
0N/A EncryptionKey sessionKey = null;
0N/A
0N/A try {
2941N/A Ticket t = new Ticket(encodedTicket);
0N/A
0N/A EncryptedData encPart = t.encPart;
0N/A PrincipalName ticketSname = t.sname;
0N/A Realm ticketRealm = t.realm;
0N/A
0N/A String serverPrincipal = serverKeys[0].getPrincipal().getName();
0N/A
0N/A /*
0N/A * permission to access and use the secret key of the Kerberized
0N/A * "host" service is done in ServerHandshaker.getKerberosKeys()
0N/A * to ensure server has the permission to use the secret key
0N/A * before promising the client
0N/A */
0N/A
0N/A // Check that ticket Sname matches serverPrincipal
0N/A String ticketPrinc = ticketSname.toString().concat("@" +
0N/A ticketRealm.toString());
0N/A if (!ticketPrinc.equals(serverPrincipal)) {
0N/A if (debug != null && Debug.isOn("handshake"))
0N/A System.out.println("Service principal in Ticket does not"
0N/A + " match associated principal in KerberosKey");
0N/A throw new IOException("Server principal is " +
0N/A serverPrincipal + " but ticket is for " +
0N/A ticketPrinc);
0N/A }
0N/A
0N/A // See if we have the right key to decrypt the ticket to get
0N/A // the session key.
0N/A int encPartKeyType = encPart.getEType();
0N/A Integer encPartKeyVersion = encPart.getKeyVersionNumber();
0N/A KerberosKey dkey = null;
0N/A try {
0N/A dkey = findKey(encPartKeyType, encPartKeyVersion, serverKeys);
0N/A } catch (KrbException ke) { // a kvno mismatch
0N/A throw new IOException(
0N/A "Cannot find key matching version number", ke);
0N/A }
0N/A if (dkey == null) {
0N/A // %%% Should print string repr of etype
0N/A throw new IOException(
0N/A "Cannot find key of appropriate type to decrypt ticket - need etype " +
0N/A encPartKeyType);
0N/A }
0N/A
0N/A EncryptionKey secretKey = new EncryptionKey(
0N/A encPartKeyType,
0N/A dkey.getEncoded());
0N/A
0N/A // Decrypt encPart using server's secret key
0N/A byte[] bytes = encPart.decrypt(secretKey, KeyUsage.KU_TICKET);
0N/A
0N/A // Reset data stream after decryption, remove redundant bytes
0N/A byte[] temp = encPart.reset(bytes);
0N/A EncTicketPart encTicketPart = new EncTicketPart(temp);
0N/A
0N/A // Record the Kerberos Principals
0N/A peerPrincipal =
0N/A new KerberosPrincipal(encTicketPart.cname.getName());
0N/A localPrincipal = new KerberosPrincipal(ticketSname.getName());
0N/A
0N/A sessionKey = encTicketPart.key;
0N/A
0N/A if (debug != null && Debug.isOn("handshake")) {
0N/A System.out.println("server principal: " + serverPrincipal);
0N/A System.out.println("realm: " + encTicketPart.crealm.toString());
0N/A System.out.println("cname: " + encTicketPart.cname.toString());
989N/A }
0N/A } catch (IOException e) {
989N/A throw e;
0N/A } catch (Exception e) {
0N/A if (debug != null && Debug.isOn("handshake")) {
989N/A System.out.println("KerberosWrapper error getting session key,"
989N/A + " generating random secret (" + e.getMessage() + ")");
989N/A }
989N/A sessionKey = null;
989N/A }
989N/A
989N/A input.getBytes16(); // XXX Read and ignore authenticator
989N/A
989N/A if (sessionKey != null) {
989N/A preMaster = new KerberosPreMasterSecret(protocolVersion,
0N/A clientVersion, rand, input, sessionKey);
0N/A } else {
0N/A // Generate bogus premaster secret
0N/A preMaster = new KerberosPreMasterSecret(clientVersion, rand);
2390N/A }
2390N/A }
0N/A
0N/A @Override
1753N/A public int messageLength() {
0N/A return (6 + encodedTicket.length + preMaster.getEncrypted().length);
0N/A }
0N/A
2390N/A @Override
2390N/A public void send(HandshakeOutStream s) throws IOException {
0N/A s.putBytes16(encodedTicket);
0N/A s.putBytes16(null); // XXX no authenticator
0N/A s.putBytes16(preMaster.getEncrypted());
0N/A }
0N/A
0N/A @Override
0N/A public void print(PrintStream s) throws IOException {
989N/A s.println("*** ClientKeyExchange, Kerberos");
989N/A
0N/A if (debug != null && Debug.isOn("verbose")) {
0N/A Debug.println(s, "Kerberos service ticket", encodedTicket);
989N/A Debug.println(s, "Random Secret", preMaster.getUnencrypted());
0N/A Debug.println(s, "Encrypted random Secret",
0N/A preMaster.getEncrypted());
0N/A }
0N/A }
0N/A
0N/A // Similar to sun.security.jgss.krb5.Krb5InitCredenetial/Krb5Context
989N/A private static KerberosTicket getServiceTicket(String srvName,
0N/A boolean isLoopback, final AccessControlContext acc) throws IOException {
0N/A
0N/A // get the local hostname if srvName is loopback address
0N/A String serverName = srvName;
0N/A if (isLoopback) {
0N/A String localHost = java.security.AccessController.doPrivileged(
342N/A new java.security.PrivilegedAction<String>() {
0N/A public String run() {
0N/A String hostname;
0N/A try {
0N/A hostname = InetAddress.getLocalHost().getHostName();
0N/A } catch (java.net.UnknownHostException e) {
2941N/A hostname = "localhost";
2941N/A }
3008N/A return hostname;
2941N/A }
2941N/A });
2941N/A serverName = localHost;
2941N/A }
0N/A
0N/A // Resolve serverName (possibly in IP addr form) to Kerberos principal
0N/A // name for service with hostname
0N/A String serviceName = "host/" + serverName;
0N/A PrincipalName principal;
0N/A try {
0N/A principal = new PrincipalName(serviceName,
0N/A PrincipalName.KRB_NT_SRV_HST);
0N/A } catch (SecurityException se) {
0N/A throw se;
0N/A } catch (Exception e) {
0N/A IOException ioe = new IOException("Invalid service principal" +
0N/A " name: " + serviceName);
0N/A ioe.initCause(e);
4141N/A throw ioe;
4141N/A }
4141N/A String realm = principal.getRealmAsString();
4141N/A
4141N/A final String serverPrincipal = principal.toString();
0N/A final String tgsPrincipal = "krbtgt/" + realm + "@" + realm;
0N/A final String clientPrincipal = null; // use default
0N/A
0N/A
0N/A // check permission to obtain a service ticket to initiate a
0N/A // context with the "host" service
0N/A SecurityManager sm = System.getSecurityManager();
0N/A if (sm != null) {
0N/A sm.checkPermission(new ServicePermission(serverPrincipal,
0N/A "initiate"), acc);
0N/A }
0N/A
0N/A try {
0N/A KerberosTicket ticket = AccessController.doPrivileged(
0N/A new PrivilegedExceptionAction<KerberosTicket>() {
0N/A public KerberosTicket run() throws Exception {
0N/A return Krb5Util.getTicketFromSubjectAndTgs(
0N/A GSSCaller.CALLER_SSL_CLIENT,
0N/A clientPrincipal, serverPrincipal,
0N/A tgsPrincipal, acc);
0N/A }});
0N/A
0N/A if (ticket == null) {
0N/A throw new IOException("Failed to find any kerberos service" +
0N/A " ticket for " + serverPrincipal);
0N/A }
0N/A return ticket;
0N/A } catch (PrivilegedActionException e) {
0N/A IOException ioe = new IOException(
0N/A "Attempt to obtain kerberos service ticket for " +
0N/A serverPrincipal + " failed!");
342N/A ioe.initCause(e);
342N/A throw ioe;
0N/A }
0N/A }
0N/A
1879N/A @Override
1879N/A public byte[] getUnencryptedPreMasterSecret() {
return preMaster.getUnencrypted();
}
@Override
public KerberosPrincipal getPeerPrincipal() {
return peerPrincipal;
}
@Override
public KerberosPrincipal getLocalPrincipal() {
return localPrincipal;
}
/**
* Determines if a kvno matches another kvno. Used in the method
* findKey(etype, version, keys). Always returns true if either input
* is null or zero, in case any side does not have kvno info available.
*
* Note: zero is included because N/A is not a legal value for kvno
* in javax.security.auth.kerberos.KerberosKey. Therefore, the info
* that the kvno is N/A might be lost when converting between
* EncryptionKey and KerberosKey.
*/
private static boolean versionMatches(Integer v1, int v2) {
if (v1 == null || v1 == 0 || v2 == 0) {
return true;
}
return v1.equals(v2);
}
private static KerberosKey findKey(int etype, Integer version,
KerberosKey[] keys) throws KrbException {
int ktype;
boolean etypeFound = false;
for (int i = 0; i < keys.length; i++) {
ktype = keys[i].getKeyType();
if (etype == ktype) {
etypeFound = true;
if (versionMatches(version, keys[i].getVersionNumber())) {
return keys[i];
}
}
}
// Key not found.
// %%% kludge to allow DES keys to be used for diff etypes
if ((etype == EncryptedData.ETYPE_DES_CBC_CRC ||
etype == EncryptedData.ETYPE_DES_CBC_MD5)) {
for (int i = 0; i < keys.length; i++) {
ktype = keys[i].getKeyType();
if (ktype == EncryptedData.ETYPE_DES_CBC_CRC ||
ktype == EncryptedData.ETYPE_DES_CBC_MD5) {
etypeFound = true;
if (versionMatches(version, keys[i].getVersionNumber())) {
return new KerberosKey(keys[i].getPrincipal(),
keys[i].getEncoded(),
etype,
keys[i].getVersionNumber());
}
}
}
}
if (etypeFound) {
throw new KrbException(Krb5.KRB_AP_ERR_BADKEYVER);
}
return null;
}
}