jdk/src/java.security.jgss/share/classes/sun/security/ssl/krb5/KerberosClientKeyExchangeImpl.java
changeset 33985 6a01dc9458f7
parent 33984 2333676816eb
parent 31008 5b500c93ce48
child 33986 5cbe9cd17789
equal deleted inserted replaced
33984:2333676816eb 33985:6a01dc9458f7
     1 /*
       
     2  * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.  Oracle designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Oracle in the LICENSE file that accompanied this code.
       
    10  *
       
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    14  * version 2 for more details (a copy is included in the LICENSE file that
       
    15  * accompanied this code).
       
    16  *
       
    17  * You should have received a copy of the GNU General Public License version
       
    18  * 2 along with this work; if not, write to the Free Software Foundation,
       
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    20  *
       
    21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    22  * or visit www.oracle.com if you need additional information or have any
       
    23  * questions.
       
    24  */
       
    25 
       
    26 package sun.security.ssl.krb5;
       
    27 
       
    28 import java.io.IOException;
       
    29 import java.io.PrintStream;
       
    30 import java.security.AccessController;
       
    31 import java.security.AccessControlContext;
       
    32 import java.security.PrivilegedExceptionAction;
       
    33 import java.security.PrivilegedActionException;
       
    34 import java.security.SecureRandom;
       
    35 import java.net.InetAddress;
       
    36 import java.security.PrivilegedAction;
       
    37 
       
    38 import javax.security.auth.kerberos.KerberosTicket;
       
    39 import javax.security.auth.kerberos.KerberosKey;
       
    40 import javax.security.auth.kerberos.KerberosPrincipal;
       
    41 import javax.security.auth.kerberos.ServicePermission;
       
    42 import sun.security.jgss.GSSCaller;
       
    43 
       
    44 import sun.security.krb5.EncryptionKey;
       
    45 import sun.security.krb5.EncryptedData;
       
    46 import sun.security.krb5.PrincipalName;
       
    47 import sun.security.krb5.internal.Ticket;
       
    48 import sun.security.krb5.internal.EncTicketPart;
       
    49 import sun.security.krb5.internal.crypto.KeyUsage;
       
    50 
       
    51 import sun.security.jgss.krb5.Krb5Util;
       
    52 import sun.security.jgss.krb5.ServiceCreds;
       
    53 import sun.security.krb5.KrbException;
       
    54 import sun.security.krb5.internal.Krb5;
       
    55 
       
    56 import sun.security.ssl.Debug;
       
    57 import sun.security.ssl.HandshakeInStream;
       
    58 import sun.security.ssl.HandshakeOutStream;
       
    59 import sun.security.ssl.Krb5Helper;
       
    60 import sun.security.ssl.ProtocolVersion;
       
    61 
       
    62 /**
       
    63  * This is Kerberos option in the client key exchange message
       
    64  * (CLIENT -> SERVER). It holds the Kerberos ticket and the encrypted
       
    65  * premaster secret encrypted with the session key sealed in the ticket.
       
    66  * From RFC 2712:
       
    67  *  struct
       
    68  *  {
       
    69  *    opaque Ticket;
       
    70  *    opaque authenticator;            // optional
       
    71  *    opaque EncryptedPreMasterSecret; // encrypted with the session key
       
    72  *                                     // which is sealed in the ticket
       
    73  *  } KerberosWrapper;
       
    74  *
       
    75  *
       
    76  * Ticket and authenticator are encrypted as per RFC 1510 (in ASN.1)
       
    77  * Encrypted pre-master secret has the same structure as it does for RSA
       
    78  * except for Kerberos, the encryption key is the session key instead of
       
    79  * the RSA public key.
       
    80  *
       
    81  * XXX authenticator currently ignored
       
    82  *
       
    83  */
       
    84 public final class KerberosClientKeyExchangeImpl
       
    85     extends sun.security.ssl.KerberosClientKeyExchange {
       
    86 
       
    87     private KerberosPreMasterSecret preMaster;
       
    88     private byte[] encodedTicket;
       
    89     private KerberosPrincipal peerPrincipal;
       
    90     private KerberosPrincipal localPrincipal;
       
    91 
       
    92     public KerberosClientKeyExchangeImpl() {
       
    93     }
       
    94 
       
    95     /**
       
    96      * Creates an instance of KerberosClientKeyExchange consisting of the
       
    97      * Kerberos service ticket, authenticator and encrypted premaster secret.
       
    98      * Called by client handshaker.
       
    99      *
       
   100      * @param serverName name of server with which to do handshake;
       
   101      *             this is used to get the Kerberos service ticket
       
   102      * @param protocolVersion Maximum version supported by client (i.e,
       
   103      *          version it requested in client hello)
       
   104      * @param rand random number generator to use for generating pre-master
       
   105      *          secret
       
   106      */
       
   107     @Override
       
   108     public void init(String serverName,
       
   109         AccessControlContext acc, ProtocolVersion protocolVersion,
       
   110         SecureRandom rand) throws IOException {
       
   111 
       
   112          // Get service ticket
       
   113          KerberosTicket ticket = getServiceTicket(serverName, acc);
       
   114          encodedTicket = ticket.getEncoded();
       
   115 
       
   116          // Record the Kerberos principals
       
   117          peerPrincipal = ticket.getServer();
       
   118          localPrincipal = ticket.getClient();
       
   119 
       
   120          // Optional authenticator, encrypted using session key,
       
   121          // currently ignored
       
   122 
       
   123          // Generate premaster secret and encrypt it using session key
       
   124          EncryptionKey sessionKey = new EncryptionKey(
       
   125                                         ticket.getSessionKeyType(),
       
   126                                         ticket.getSessionKey().getEncoded());
       
   127 
       
   128          preMaster = new KerberosPreMasterSecret(protocolVersion,
       
   129              rand, sessionKey);
       
   130     }
       
   131 
       
   132     /**
       
   133      * Creates an instance of KerberosClientKeyExchange from its ASN.1 encoding.
       
   134      * Used by ServerHandshaker to verify and obtain premaster secret.
       
   135      *
       
   136      * @param protocolVersion current protocol version
       
   137      * @param clientVersion version requested by client in its ClientHello;
       
   138      *          used by premaster secret version check
       
   139      * @param rand random number generator used for generating random
       
   140      *          premaster secret if ticket and/or premaster verification fails
       
   141      * @param input inputstream from which to get ASN.1-encoded KerberosWrapper
       
   142      * @param acc the AccessControlContext of the handshaker
       
   143      * @param serviceCreds server's creds
       
   144      */
       
   145     @Override
       
   146     public void init(ProtocolVersion protocolVersion,
       
   147         ProtocolVersion clientVersion,
       
   148         SecureRandom rand, HandshakeInStream input, AccessControlContext acc, Object serviceCreds)
       
   149         throws IOException {
       
   150 
       
   151         // Read ticket
       
   152         encodedTicket = input.getBytes16();
       
   153 
       
   154         if (debug != null && Debug.isOn("verbose")) {
       
   155             Debug.println(System.out,
       
   156                 "encoded Kerberos service ticket", encodedTicket);
       
   157         }
       
   158 
       
   159         EncryptionKey sessionKey = null;
       
   160 
       
   161         try {
       
   162             Ticket t = new Ticket(encodedTicket);
       
   163 
       
   164             EncryptedData encPart = t.encPart;
       
   165             PrincipalName ticketSname = t.sname;
       
   166 
       
   167             final ServiceCreds creds = (ServiceCreds)serviceCreds;
       
   168             final KerberosPrincipal princ =
       
   169                     new KerberosPrincipal(ticketSname.toString());
       
   170 
       
   171             // For bound service, permission already checked at setup
       
   172             if (creds.getName() == null) {
       
   173                 SecurityManager sm = System.getSecurityManager();
       
   174                 try {
       
   175                     if (sm != null) {
       
   176                         // Eliminate dependency on ServicePermission
       
   177                         sm.checkPermission(Krb5Helper.getServicePermission(
       
   178                                 ticketSname.toString(), "accept"), acc);
       
   179                     }
       
   180                 } catch (SecurityException se) {
       
   181                     serviceCreds = null;
       
   182                     // Do not destroy keys. Will affect Subject
       
   183                     if (debug != null && Debug.isOn("handshake")) {
       
   184                         System.out.println("Permission to access Kerberos"
       
   185                                 + " secret key denied");
       
   186                     }
       
   187                     throw new IOException("Kerberos service not allowedy");
       
   188                 }
       
   189             }
       
   190             KerberosKey[] serverKeys = AccessController.doPrivileged(
       
   191                     new PrivilegedAction<KerberosKey[]>() {
       
   192                         @Override
       
   193                         public KerberosKey[] run() {
       
   194                             return creds.getKKeys(princ);
       
   195                         }
       
   196                     });
       
   197             if (serverKeys.length == 0) {
       
   198                 throw new IOException("Found no key for " + princ +
       
   199                         (creds.getName() == null ? "" :
       
   200                         (", this keytab is for " + creds.getName() + " only")));
       
   201             }
       
   202 
       
   203             /*
       
   204              * permission to access and use the secret key of the Kerberized
       
   205              * "host" service is done in ServerHandshaker.getKerberosKeys()
       
   206              * to ensure server has the permission to use the secret key
       
   207              * before promising the client
       
   208              */
       
   209 
       
   210             // See if we have the right key to decrypt the ticket to get
       
   211             // the session key.
       
   212             int encPartKeyType = encPart.getEType();
       
   213             Integer encPartKeyVersion = encPart.getKeyVersionNumber();
       
   214             KerberosKey dkey = null;
       
   215             try {
       
   216                 dkey = findKey(encPartKeyType, encPartKeyVersion, serverKeys);
       
   217             } catch (KrbException ke) { // a kvno mismatch
       
   218                 throw new IOException(
       
   219                         "Cannot find key matching version number", ke);
       
   220             }
       
   221             if (dkey == null) {
       
   222                 // %%% Should print string repr of etype
       
   223                 throw new IOException("Cannot find key of appropriate type" +
       
   224                         " to decrypt ticket - need etype " + encPartKeyType);
       
   225             }
       
   226 
       
   227             EncryptionKey secretKey = new EncryptionKey(
       
   228                 encPartKeyType,
       
   229                 dkey.getEncoded());
       
   230 
       
   231             // Decrypt encPart using server's secret key
       
   232             byte[] bytes = encPart.decrypt(secretKey, KeyUsage.KU_TICKET);
       
   233 
       
   234             // Reset data stream after decryption, remove redundant bytes
       
   235             byte[] temp = encPart.reset(bytes);
       
   236             EncTicketPart encTicketPart = new EncTicketPart(temp);
       
   237 
       
   238             // Record the Kerberos Principals
       
   239             peerPrincipal =
       
   240                 new KerberosPrincipal(encTicketPart.cname.getName());
       
   241             localPrincipal = new KerberosPrincipal(ticketSname.getName());
       
   242 
       
   243             sessionKey = encTicketPart.key;
       
   244 
       
   245             if (debug != null && Debug.isOn("handshake")) {
       
   246                 System.out.println("server principal: " + ticketSname);
       
   247                 System.out.println("cname: " + encTicketPart.cname.toString());
       
   248             }
       
   249         } catch (IOException e) {
       
   250             throw e;
       
   251         } catch (Exception e) {
       
   252             if (debug != null && Debug.isOn("handshake")) {
       
   253                 System.out.println("KerberosWrapper error getting session key,"
       
   254                         + " generating random secret (" + e.getMessage() + ")");
       
   255             }
       
   256             sessionKey = null;
       
   257         }
       
   258 
       
   259         input.getBytes16();   // XXX Read and ignore authenticator
       
   260 
       
   261         if (sessionKey != null) {
       
   262             preMaster = new KerberosPreMasterSecret(protocolVersion,
       
   263                 clientVersion, rand, input, sessionKey);
       
   264         } else {
       
   265             // Generate bogus premaster secret
       
   266             preMaster = new KerberosPreMasterSecret(clientVersion, rand);
       
   267         }
       
   268     }
       
   269 
       
   270     @Override
       
   271     public int messageLength() {
       
   272         return (6 + encodedTicket.length + preMaster.getEncrypted().length);
       
   273     }
       
   274 
       
   275     @Override
       
   276     public void send(HandshakeOutStream s) throws IOException {
       
   277         s.putBytes16(encodedTicket);
       
   278         s.putBytes16(null); // XXX no authenticator
       
   279         s.putBytes16(preMaster.getEncrypted());
       
   280     }
       
   281 
       
   282     @Override
       
   283     public void print(PrintStream s) throws IOException {
       
   284         s.println("*** ClientKeyExchange, Kerberos");
       
   285 
       
   286         if (debug != null && Debug.isOn("verbose")) {
       
   287             Debug.println(s, "Kerberos service ticket", encodedTicket);
       
   288             Debug.println(s, "Random Secret", preMaster.getUnencrypted());
       
   289             Debug.println(s, "Encrypted random Secret",
       
   290                 preMaster.getEncrypted());
       
   291         }
       
   292     }
       
   293 
       
   294     // Similar to sun.security.jgss.krb5.Krb5InitCredenetial/Krb5Context
       
   295     private static KerberosTicket getServiceTicket(String serverName,
       
   296         final AccessControlContext acc) throws IOException {
       
   297 
       
   298         if ("localhost".equals(serverName) ||
       
   299                 "localhost.localdomain".equals(serverName)) {
       
   300 
       
   301             if (debug != null && Debug.isOn("handshake")) {
       
   302                 System.out.println("Get the local hostname");
       
   303             }
       
   304             String localHost = java.security.AccessController.doPrivileged(
       
   305                 new java.security.PrivilegedAction<String>() {
       
   306                 public String run() {
       
   307                     try {
       
   308                         return InetAddress.getLocalHost().getHostName();
       
   309                     } catch (java.net.UnknownHostException e) {
       
   310                         if (debug != null && Debug.isOn("handshake")) {
       
   311                             System.out.println("Warning,"
       
   312                                 + " cannot get the local hostname: "
       
   313                                 + e.getMessage());
       
   314                         }
       
   315                         return null;
       
   316                     }
       
   317                 }
       
   318             });
       
   319             if (localHost != null) {
       
   320                 serverName = localHost;
       
   321             }
       
   322         }
       
   323 
       
   324         // Resolve serverName (possibly in IP addr form) to Kerberos principal
       
   325         // name for service with hostname
       
   326         String serviceName = "host/" + serverName;
       
   327         PrincipalName principal;
       
   328         try {
       
   329             principal = new PrincipalName(serviceName,
       
   330                                 PrincipalName.KRB_NT_SRV_HST);
       
   331         } catch (SecurityException se) {
       
   332             throw se;
       
   333         } catch (Exception e) {
       
   334             IOException ioe = new IOException("Invalid service principal" +
       
   335                                 " name: " + serviceName);
       
   336             ioe.initCause(e);
       
   337             throw ioe;
       
   338         }
       
   339         String realm = principal.getRealmAsString();
       
   340 
       
   341         final String serverPrincipal = principal.toString();
       
   342         final String tgsPrincipal = "krbtgt/" + realm + "@" + realm;
       
   343         final String clientPrincipal = null;  // use default
       
   344 
       
   345 
       
   346         // check permission to obtain a service ticket to initiate a
       
   347         // context with the "host" service
       
   348         SecurityManager sm = System.getSecurityManager();
       
   349         if (sm != null) {
       
   350            sm.checkPermission(new ServicePermission(serverPrincipal,
       
   351                                 "initiate"), acc);
       
   352         }
       
   353 
       
   354         try {
       
   355             KerberosTicket ticket = AccessController.doPrivileged(
       
   356                 new PrivilegedExceptionAction<KerberosTicket>() {
       
   357                 public KerberosTicket run() throws Exception {
       
   358                     return Krb5Util.getTicketFromSubjectAndTgs(
       
   359                         GSSCaller.CALLER_SSL_CLIENT,
       
   360                         clientPrincipal, serverPrincipal,
       
   361                         tgsPrincipal, acc);
       
   362                         }});
       
   363 
       
   364             if (ticket == null) {
       
   365                 throw new IOException("Failed to find any kerberos service" +
       
   366                         " ticket for " + serverPrincipal);
       
   367             }
       
   368             return ticket;
       
   369         } catch (PrivilegedActionException e) {
       
   370             IOException ioe = new IOException(
       
   371                 "Attempt to obtain kerberos service ticket for " +
       
   372                         serverPrincipal + " failed!");
       
   373             ioe.initCause(e);
       
   374             throw ioe;
       
   375         }
       
   376     }
       
   377 
       
   378     @Override
       
   379     public byte[] getUnencryptedPreMasterSecret() {
       
   380         return preMaster.getUnencrypted();
       
   381     }
       
   382 
       
   383     @Override
       
   384     public KerberosPrincipal getPeerPrincipal() {
       
   385         return peerPrincipal;
       
   386     }
       
   387 
       
   388     @Override
       
   389     public KerberosPrincipal getLocalPrincipal() {
       
   390         return localPrincipal;
       
   391     }
       
   392 
       
   393     /**
       
   394      * Determines if a kvno matches another kvno. Used in the method
       
   395      * findKey(etype, version, keys). Always returns true if either input
       
   396      * is null or zero, in case any side does not have kvno info available.
       
   397      *
       
   398      * Note: zero is included because N/A is not a legal value for kvno
       
   399      * in javax.security.auth.kerberos.KerberosKey. Therefore, the info
       
   400      * that the kvno is N/A might be lost when converting between
       
   401      * EncryptionKey and KerberosKey.
       
   402      */
       
   403     private static boolean versionMatches(Integer v1, int v2) {
       
   404         if (v1 == null || v1 == 0 || v2 == 0) {
       
   405             return true;
       
   406         }
       
   407         return v1.equals(v2);
       
   408     }
       
   409 
       
   410     private static KerberosKey findKey(int etype, Integer version,
       
   411             KerberosKey[] keys) throws KrbException {
       
   412         int ktype;
       
   413         boolean etypeFound = false;
       
   414 
       
   415         // When no matched kvno is found, returns tke key of the same
       
   416         // etype with the highest kvno
       
   417         int kvno_found = 0;
       
   418         KerberosKey key_found = null;
       
   419 
       
   420         for (int i = 0; i < keys.length; i++) {
       
   421             ktype = keys[i].getKeyType();
       
   422             if (etype == ktype) {
       
   423                 int kv = keys[i].getVersionNumber();
       
   424                 etypeFound = true;
       
   425                 if (versionMatches(version, kv)) {
       
   426                     return keys[i];
       
   427                 } else if (kv > kvno_found) {
       
   428                     key_found = keys[i];
       
   429                     kvno_found = kv;
       
   430                 }
       
   431             }
       
   432         }
       
   433         // Key not found.
       
   434         // %%% kludge to allow DES keys to be used for diff etypes
       
   435         if ((etype == EncryptedData.ETYPE_DES_CBC_CRC ||
       
   436             etype == EncryptedData.ETYPE_DES_CBC_MD5)) {
       
   437             for (int i = 0; i < keys.length; i++) {
       
   438                 ktype = keys[i].getKeyType();
       
   439                 if (ktype == EncryptedData.ETYPE_DES_CBC_CRC ||
       
   440                         ktype == EncryptedData.ETYPE_DES_CBC_MD5) {
       
   441                     int kv = keys[i].getVersionNumber();
       
   442                     etypeFound = true;
       
   443                     if (versionMatches(version, kv)) {
       
   444                         return new KerberosKey(keys[i].getPrincipal(),
       
   445                             keys[i].getEncoded(),
       
   446                             etype,
       
   447                             kv);
       
   448                     } else if (kv > kvno_found) {
       
   449                         key_found = new KerberosKey(keys[i].getPrincipal(),
       
   450                                 keys[i].getEncoded(),
       
   451                                 etype,
       
   452                                 kv);
       
   453                         kvno_found = kv;
       
   454                     }
       
   455                 }
       
   456             }
       
   457         }
       
   458         if (etypeFound) {
       
   459             return key_found;
       
   460         }
       
   461         return null;
       
   462     }
       
   463 }