/*
 * Copyright (c) 2003, 2010, 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.ssl.krb5;

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.crypto.SecretKey;
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.GSSCaller;

import sun.security.krb5.EncryptionKey;
import sun.security.krb5.EncryptedData;
import sun.security.krb5.PrincipalName;
import sun.security.krb5.Realm;
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;
import sun.security.krb5.KrbException;
import sun.security.krb5.internal.Krb5;

import sun.security.ssl.Debug;
import sun.security.ssl.HandshakeInStream;
import sun.security.ssl.HandshakeOutStream;
import sun.security.ssl.ProtocolVersion;

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
/** * 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 * */
public final class KerberosClientKeyExchangeImpl extends sun.security.ssl.KerberosClientKeyExchange { private KerberosPreMasterSecret preMaster; private byte[] encodedTicket; private KerberosPrincipal peerPrincipal; private KerberosPrincipal localPrincipal; public KerberosClientKeyExchangeImpl() { }
Creates an instance of KerberosClientKeyExchange consisting of the Kerberos service ticket, authenticator and encrypted premaster secret. Called by client handshaker.
Params:
  • serverName – name of server with which to do handshake; this is used to get the Kerberos service ticket
  • protocolVersion – Maximum version supported by client (i.e, version it requested in client hello)
  • rand – random number generator to use for generating pre-master secret
/** * 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 */
@Override public void init(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.
Params:
  • protocolVersion – current protocol version
  • clientVersion – version requested by client in its ClientHello; used by premaster secret version check
  • rand – random number generator used for generating random premaster secret if ticket and/or premaster verification fails
  • input – inputstream from which to get ASN.1-encoded KerberosWrapper
  • serverKey – server's master secret key
/** * 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 */
@Override public void init(ProtocolVersion protocolVersion, ProtocolVersion clientVersion, SecureRandom rand, HandshakeInStream input, SecretKey[] secretKeys) throws IOException { KerberosKey[] serverKeys = (KerberosKey[])secretKeys; // 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.sname.getRealm(); 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(); 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(); Integer encPartKeyVersion = encPart.getKeyVersionNumber(); KerberosKey dkey = null; try { dkey = findKey(encPartKeyType, encPartKeyVersion, serverKeys); } catch (KrbException ke) { // a kvno mismatch throw new IOException( "Cannot find key matching version number", ke); } 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); 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("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(clientVersion, rand); } } @Override public int messageLength() { return (6 + encodedTicket.length + preMaster.getEncrypted().length); } @Override public void send(HandshakeOutStream s) throws IOException { s.putBytes16(encodedTicket); s.putBytes16(null); // XXX no authenticator s.putBytes16(preMaster.getEncrypted()); } @Override public 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( GSSCaller.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; } } @Override 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.
/** * 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; } }