| /* |
| * 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 |
| * |
| */ |
| 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. |
| * |
| * @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. |
| * |
| * @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.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(); |
| 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("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(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. |
| */ |
| 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; |
| } |
| } |