/* * Copyright 2003-2007 Sun Microsystems, Inc. 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. Sun designates this * particular file as subject to the "Classpath" exception as provided * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. */ package sun.security.ssl; import java.io.IOException; import java.io.PrintStream; import java.security.AccessController; import java.security.AccessControlContext; import java.security.PrivilegedExceptionAction; import java.security.PrivilegedActionException; import java.security.SecureRandom; import java.net.InetAddress; import javax.net.ssl.SSLException; import javax.security.auth.kerberos.KerberosTicket; import javax.security.auth.kerberos.KerberosKey; import javax.security.auth.kerberos.KerberosPrincipal; import javax.security.auth.kerberos.ServicePermission; import sun.security.jgss.GSSUtil; import sun.security.krb5.Config; import sun.security.krb5.EncryptionKey; import sun.security.krb5.EncryptedData; import sun.security.krb5.PrincipalName; import sun.security.krb5.Realm; import sun.security.krb5.KrbException; import sun.security.krb5.internal.Ticket; import sun.security.krb5.internal.EncTicketPart; import sun.security.krb5.internal.crypto.KeyUsage; import sun.security.jgss.krb5.Krb5Util; /** * This is Kerberos option in the client key exchange message * (CLIENT -> SERVER). It holds the Kerberos ticket and the encrypted * premaster secret encrypted with the session key sealed in the ticket. * From RFC 2712: * struct * { * opaque Ticket; * opaque authenticator; // optional * opaque EncryptedPreMasterSecret; // encrypted with the session key * // which is sealed in the ticket * } KerberosWrapper; * * * Ticket and authenticator are encrypted as per RFC 1510 (in ASN.1) * Encrypted pre-master secret has the same structure as it does for RSA * except for Kerberos, the encryption key is the session key instead of * the RSA public key. * * XXX authenticator currently ignored * */ final class KerberosClientKeyExchange extends HandshakeMessage { private KerberosPreMasterSecret preMaster; private byte[] encodedTicket; private KerberosPrincipal peerPrincipal; private KerberosPrincipal localPrincipal; /** * Creates an instance of KerberosClientKeyExchange consisting of the * Kerberos service ticket, authenticator and encrypted premaster secret. * Called by client handshaker. * * @param serverName name of server with which to do handshake; * this is used to get the Kerberos service ticket * @param protocolVersion Maximum version supported by client (i.e, * version it requested in client hello) * @param rand random number generator to use for generating pre-master * secret */ KerberosClientKeyExchange(String serverName, boolean isLoopback, AccessControlContext acc, ProtocolVersion protocolVersion, SecureRandom rand) throws IOException { // Get service ticket KerberosTicket ticket = getServiceTicket(serverName, isLoopback, acc); encodedTicket = ticket.getEncoded(); // Record the Kerberos principals peerPrincipal = ticket.getServer(); localPrincipal = ticket.getClient(); // Optional authenticator, encrypted using session key, // currently ignored // Generate premaster secret and encrypt it using session key EncryptionKey sessionKey = new EncryptionKey( ticket.getSessionKeyType(), ticket.getSessionKey().getEncoded()); preMaster = new KerberosPreMasterSecret(protocolVersion, rand, sessionKey); } /** * Creates an instance of KerberosClientKeyExchange from its ASN.1 encoding. * Used by ServerHandshaker to verify and obtain premaster secret. * * @param protocolVersion current protocol version * @param clientVersion version requested by client in its ClientHello; * used by premaster secret version check * @param rand random number generator used for generating random * premaster secret if ticket and/or premaster verification fails * @param input inputstream from which to get ASN.1-encoded KerberosWrapper * @param serverKey server's master secret key */ KerberosClientKeyExchange(ProtocolVersion protocolVersion, ProtocolVersion clientVersion, SecureRandom rand, HandshakeInStream input, KerberosKey[] serverKeys) throws IOException { // Read ticket encodedTicket = input.getBytes16(); if (debug != null && Debug.isOn("verbose")) { Debug.println(System.out, "encoded Kerberos service ticket", encodedTicket); } EncryptionKey sessionKey = null; try { Ticket t = new Ticket(encodedTicket); EncryptedData encPart = t.encPart; PrincipalName ticketSname = t.sname; Realm ticketRealm = t.realm; String serverPrincipal = serverKeys[0].getPrincipal().getName(); /* * permission to access and use the secret key of the Kerberized * "host" service is done in ServerHandshaker.getKerberosKeys() * to ensure server has the permission to use the secret key * before promising the client */ // Check that ticket Sname matches serverPrincipal String ticketPrinc = ticketSname.toString().concat("@" + ticketRealm.toString()); if (!ticketPrinc.equals(serverPrincipal)) { if (debug != null && Debug.isOn("handshake")) System.out.println("Service principal in Ticket does not" + " match associated principal in KerberosKey"); throw new IOException("Server principal is " + serverPrincipal + " but ticket is for " + ticketPrinc); } // See if we have the right key to decrypt the ticket to get // the session key. int encPartKeyType = encPart.getEType(); KerberosKey dkey = findKey(encPartKeyType, serverKeys); if (dkey == null) { // %%% Should print string repr of etype throw new IOException( "Cannot find key of appropriate type to decrypt ticket - need etype " + encPartKeyType); } EncryptionKey secretKey = new EncryptionKey( encPartKeyType, dkey.getEncoded()); // Decrypt encPart using server's secret key byte[] bytes = encPart.decrypt(secretKey, KeyUsage.KU_TICKET); // Reset data stream after decryption, remove redundant bytes byte[] temp = encPart.reset(bytes, true); EncTicketPart encTicketPart = new EncTicketPart(temp); // Record the Kerberos Principals peerPrincipal = new KerberosPrincipal(encTicketPart.cname.getName()); localPrincipal = new KerberosPrincipal(ticketSname.getName()); sessionKey = encTicketPart.key; if (debug != null && Debug.isOn("handshake")) { System.out.println("server principal: " + serverPrincipal); System.out.println("realm: " + encTicketPart.crealm.toString()); System.out.println("cname: " + encTicketPart.cname.toString()); } } catch (IOException e) { throw e; } catch (Exception e) { if (debug != null && Debug.isOn("handshake")) { System.out.println("KerberosWrapper error getting session key," + " generating random secret (" + e.getMessage() + ")"); } sessionKey = null; } input.getBytes16(); // XXX Read and ignore authenticator if (sessionKey != null) { preMaster = new KerberosPreMasterSecret(protocolVersion, clientVersion, rand, input, sessionKey); } else { // Generate bogus premaster secret preMaster = new KerberosPreMasterSecret(protocolVersion, rand); } } int messageType() { return ht_client_key_exchange; } int messageLength() { return (6 + encodedTicket.length + preMaster.getEncrypted().length); } void send(HandshakeOutStream s) throws IOException { s.putBytes16(encodedTicket); s.putBytes16(null); // XXX no authenticator s.putBytes16(preMaster.getEncrypted()); } void print(PrintStream s) throws IOException { s.println("*** ClientKeyExchange, Kerberos"); if (debug != null && Debug.isOn("verbose")) { Debug.println(s, "Kerberos service ticket", encodedTicket); Debug.println(s, "Random Secret", preMaster.getUnencrypted()); Debug.println(s, "Encrypted random Secret", preMaster.getEncrypted()); } } // Similar to sun.security.jgss.krb5.Krb5InitCredenetial/Krb5Context private static KerberosTicket getServiceTicket(String srvName, boolean isLoopback, final AccessControlContext acc) throws IOException { // get the local hostname if srvName is loopback address String serverName = srvName; if (isLoopback) { String localHost = java.security.AccessController.doPrivileged( new java.security.PrivilegedAction<String>() { public String run() { String hostname; try { hostname = InetAddress.getLocalHost().getHostName(); } catch (java.net.UnknownHostException e) { hostname = "localhost"; } return hostname; } }); serverName = localHost; } // Resolve serverName (possibly in IP addr form) to Kerberos principal // name for service with hostname String serviceName = "host/" + serverName; PrincipalName principal; try { principal = new PrincipalName(serviceName, PrincipalName.KRB_NT_SRV_HST); } catch (SecurityException se) { throw se; } catch (Exception e) { IOException ioe = new IOException("Invalid service principal" + " name: " + serviceName); ioe.initCause(e); throw ioe; } String realm = principal.getRealmAsString(); final String serverPrincipal = principal.toString(); final String tgsPrincipal = "krbtgt/" + realm + "@" + realm; final String clientPrincipal = null; // use default // check permission to obtain a service ticket to initiate a // context with the "host" service SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(new ServicePermission(serverPrincipal, "initiate"), acc); } try { KerberosTicket ticket = AccessController.doPrivileged( new PrivilegedExceptionAction<KerberosTicket>() { public KerberosTicket run() throws Exception { return Krb5Util.getTicketFromSubjectAndTgs( GSSUtil.CALLER_SSL_CLIENT, clientPrincipal, serverPrincipal, tgsPrincipal, acc); }}); if (ticket == null) { throw new IOException("Failed to find any kerberos service" + " ticket for " + serverPrincipal); } return ticket; } catch (PrivilegedActionException e) { IOException ioe = new IOException( "Attempt to obtain kerberos service ticket for " + serverPrincipal + " failed!"); ioe.initCause(e); throw ioe; } } KerberosPreMasterSecret getPreMasterSecret() { return preMaster; } KerberosPrincipal getPeerPrincipal() { return peerPrincipal; } KerberosPrincipal getLocalPrincipal() { return localPrincipal; } private static KerberosKey findKey(int etype, KerberosKey[] keys) { int ktype; for (int i = 0; i < keys.length; i++) { ktype = keys[i].getKeyType(); if (etype == ktype) { 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) { return new KerberosKey(keys[i].getPrincipal(), keys[i].getEncoded(), etype, keys[i].getVersionNumber()); } } } return null; } }