8025123: SNI support in Kerberos cipher suites
authorxuelei
Tue, 01 Oct 2013 20:25:44 -0700
changeset 20499 4aa3d51ec41b
parent 20498 6b1da6741f95
child 20500 44c2cb8a99bb
8025123: SNI support in Kerberos cipher suites Reviewed-by: weijun, xuelei Contributed-by: Artem Smotrakov <artem.smotrakov@oracle.com>
jdk/src/share/classes/sun/security/ssl/ClientHandshaker.java
jdk/src/share/classes/sun/security/ssl/Handshaker.java
jdk/src/share/classes/sun/security/ssl/KerberosClientKeyExchange.java
jdk/src/share/classes/sun/security/ssl/krb5/KerberosClientKeyExchangeImpl.java
jdk/test/sun/security/krb5/auto/SSL.java
--- a/jdk/src/share/classes/sun/security/ssl/ClientHandshaker.java	Fri Sep 27 13:32:32 2013 -0400
+++ b/jdk/src/share/classes/sun/security/ssl/ClientHandshaker.java	Tue Oct 01 20:25:44 2013 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1996, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 2013, 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
@@ -92,6 +92,8 @@
     private List<SNIServerName> requestedServerNames =
             Collections.<SNIServerName>emptyList();
 
+    private boolean serverNamesAccepted = false;
+
     /*
      * Constructors
      */
@@ -567,7 +569,9 @@
         // check extensions
         for (HelloExtension ext : mesg.extensions.list()) {
             ExtensionType type = ext.type;
-            if ((type != ExtensionType.EXT_ELLIPTIC_CURVES)
+            if (type == ExtensionType.EXT_SERVER_NAME) {
+                serverNamesAccepted = true;
+            } else if ((type != ExtensionType.EXT_ELLIPTIC_CURVES)
                     && (type != ExtensionType.EXT_EC_POINT_FORMATS)
                     && (type != ExtensionType.EXT_SERVER_NAME)
                     && (type != ExtensionType.EXT_RENEGOTIATION_INFO)) {
@@ -864,15 +868,47 @@
             break;
         case K_KRB5:
         case K_KRB5_EXPORT:
-            String hostname = getHostSE();
-            if (hostname == null) {
-                throw new IOException("Hostname is required" +
-                                " to use Kerberos cipher suites");
+            String sniHostname = null;
+            for (SNIServerName serverName : requestedServerNames) {
+                if (serverName instanceof SNIHostName) {
+                    sniHostname = ((SNIHostName) serverName).getAsciiName();
+                    break;
+                }
             }
-            KerberosClientKeyExchange kerberosMsg =
-                new KerberosClientKeyExchange(
-                    hostname, isLoopbackSE(), getAccSE(), protocolVersion,
-                sslContext.getSecureRandom());
+
+            KerberosClientKeyExchange kerberosMsg = null;
+            if (sniHostname != null) {
+                // use first requested SNI hostname
+                try {
+                    kerberosMsg = new KerberosClientKeyExchange(
+                        sniHostname, getAccSE(), protocolVersion,
+                        sslContext.getSecureRandom());
+                } catch(IOException e) {
+                    if (serverNamesAccepted) {
+                        // server accepted requested SNI hostname,
+                        // so it must be used
+                        throw e;
+                    }
+                    // fallback to using hostname
+                    if (debug != null && Debug.isOn("handshake")) {
+                        System.out.println(
+                            "Warning, cannot use Server Name Indication: "
+                                + e.getMessage());
+                    }
+                }
+            }
+
+            if (kerberosMsg == null) {
+                String hostname = getHostSE();
+                if (hostname == null) {
+                    throw new IOException("Hostname is required" +
+                        " to use Kerberos cipher suites");
+                }
+                kerberosMsg = new KerberosClientKeyExchange(
+                     hostname, getAccSE(), protocolVersion,
+                     sslContext.getSecureRandom());
+            }
+
             // Record the principals involved in exchange
             session.setPeerPrincipal(kerberosMsg.getPeerPrincipal());
             session.setLocalPrincipal(kerberosMsg.getLocalPrincipal());
--- a/jdk/src/share/classes/sun/security/ssl/Handshaker.java	Fri Sep 27 13:32:32 2013 -0400
+++ b/jdk/src/share/classes/sun/security/ssl/Handshaker.java	Tue Oct 01 20:25:44 2013 -0700
@@ -335,14 +335,6 @@
         }
     }
 
-    boolean isLoopbackSE() {
-        if (conn != null) {
-            return conn.getInetAddress().isLoopbackAddress();
-        } else {
-            return false;
-        }
-    }
-
     int getPortSE() {
         if (conn != null) {
             return conn.getPort();
--- a/jdk/src/share/classes/sun/security/ssl/KerberosClientKeyExchange.java	Fri Sep 27 13:32:32 2013 -0400
+++ b/jdk/src/share/classes/sun/security/ssl/KerberosClientKeyExchange.java	Tue Oct 01 20:25:44 2013 -0700
@@ -77,12 +77,12 @@
         // please won't check the value of impl variable
     }
 
-    public KerberosClientKeyExchange(String serverName, boolean isLoopback,
+    public KerberosClientKeyExchange(String serverName,
         AccessControlContext acc, ProtocolVersion protocolVersion,
         SecureRandom rand) throws IOException {
 
         if (impl != null) {
-            init(serverName, isLoopback, acc, protocolVersion, rand);
+            init(serverName, acc, protocolVersion, rand);
         } else {
             throw new IllegalStateException("Kerberos is unavailable");
         }
@@ -120,12 +120,12 @@
         impl.print(p);
     }
 
-    public void init(String serverName, boolean isLoopback,
+    public void init(String serverName,
         AccessControlContext acc, ProtocolVersion protocolVersion,
         SecureRandom rand) throws IOException {
 
         if (impl != null) {
-            impl.init(serverName, isLoopback, acc, protocolVersion, rand);
+            impl.init(serverName, acc, protocolVersion, rand);
         }
     }
 
--- a/jdk/src/share/classes/sun/security/ssl/krb5/KerberosClientKeyExchangeImpl.java	Fri Sep 27 13:32:32 2013 -0400
+++ b/jdk/src/share/classes/sun/security/ssl/krb5/KerberosClientKeyExchangeImpl.java	Tue Oct 01 20:25:44 2013 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2003, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2003, 2013, 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
@@ -105,12 +105,12 @@
      *          secret
      */
     @Override
-    public void init(String serverName, boolean isLoopback,
+    public void init(String serverName,
         AccessControlContext acc, ProtocolVersion protocolVersion,
         SecureRandom rand) throws IOException {
 
          // Get service ticket
-         KerberosTicket ticket = getServiceTicket(serverName, isLoopback, acc);
+         KerberosTicket ticket = getServiceTicket(serverName, acc);
          encodedTicket = ticket.getEncoded();
 
          // Record the Kerberos principals
@@ -292,25 +292,33 @@
     }
 
     // Similar to sun.security.jgss.krb5.Krb5InitCredenetial/Krb5Context
-    private static KerberosTicket getServiceTicket(String srvName,
-        boolean isLoopback, final AccessControlContext acc) throws IOException {
+    private static KerberosTicket getServiceTicket(String serverName,
+        final AccessControlContext acc) throws IOException {
 
-        // get the local hostname if srvName is loopback address
-        String serverName = srvName;
-        if (isLoopback) {
+        if ("localhost".equals(serverName) ||
+                "localhost.localdomain".equals(serverName)) {
+
+            if (debug != null && Debug.isOn("handshake")) {
+                System.out.println("Get the local hostname");
+            }
             String localHost = java.security.AccessController.doPrivileged(
                 new java.security.PrivilegedAction<String>() {
                 public String run() {
-                    String hostname;
                     try {
-                        hostname = InetAddress.getLocalHost().getHostName();
+                        return InetAddress.getLocalHost().getHostName();
                     } catch (java.net.UnknownHostException e) {
-                        hostname = "localhost";
+                        if (debug != null && Debug.isOn("handshake")) {
+                            System.out.println("Warning,"
+                                + " cannot get the local hostname: "
+                                + e.getMessage());
+                        }
+                        return null;
                     }
-                    return hostname;
                 }
             });
-          serverName = localHost;
+            if (localHost != null) {
+                serverName = localHost;
+            }
         }
 
         // Resolve serverName (possibly in IP addr form) to Kerberos principal
--- a/jdk/test/sun/security/krb5/auto/SSL.java	Fri Sep 27 13:32:32 2013 -0400
+++ b/jdk/test/sun/security/krb5/auto/SSL.java	Tue Oct 01 20:25:44 2013 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2009, 2013, 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
@@ -23,11 +23,12 @@
 
 /*
  * @test
- * @bug 6894643 6913636 8005523
+ * @bug 6894643 6913636 8005523 8025123
  * @summary Test JSSE Kerberos ciphersuite
 
  * @run main/othervm SSL TLS_KRB5_WITH_RC4_128_SHA
  * @run main/othervm SSL TLS_KRB5_WITH_RC4_128_SHA unbound
+ * @run main/othervm SSL TLS_KRB5_WITH_RC4_128_SHA unbound sni
  * @run main/othervm SSL TLS_KRB5_WITH_3DES_EDE_CBC_SHA
  * @run main/othervm SSL TLS_KRB5_WITH_3DES_EDE_CBC_MD5
  * @run main/othervm SSL TLS_KRB5_WITH_DES_CBC_SHA
@@ -44,6 +45,9 @@
 import javax.net.ssl.*;
 import java.security.Principal;
 import java.util.Date;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Locale;
 import javax.security.auth.kerberos.ServicePermission;
 import sun.security.jgss.GSSUtil;
 import sun.security.krb5.PrincipalName;
@@ -56,6 +60,8 @@
     private static int loopCount = 0;
     private static volatile String server;
     private static volatile int port;
+    private static String sniHostname = null;
+    private static String sniMatcherPattern = null;
 
     private static String permChecks = "";
 
@@ -84,11 +90,11 @@
         System.setSecurityManager(new SSL());
 
         KDC kdc = KDC.create(OneKDC.REALM);
-        // Run this after KDC, so our own DNS service can be started
-        try {
-            server = InetAddress.getLocalHost().getHostName().toLowerCase();
-        } catch (java.net.UnknownHostException e) {
-            server = "localhost";
+        server = "host." + OneKDC.REALM.toLowerCase(Locale.US);
+
+        if (args.length > 2) {
+            sniHostname = "test." + server;
+            sniMatcherPattern = ".*";
         }
 
         kdc.addPrincipal(OneKDC.USER, OneKDC.PASS);
@@ -98,15 +104,21 @@
 
         // Add 3 versions of keys into keytab
         KeyTab ktab = KeyTab.create(OneKDC.KTAB);
+        String serviceName = null;
+        if (sniHostname != null) {
+            serviceName = "host/" + sniHostname;
+        } else {
+            serviceName = "host/" + server;
+        }
         PrincipalName service = new PrincipalName(
-                "host/" + server, PrincipalName.KRB_NT_SRV_HST);
+            serviceName, PrincipalName.KRB_NT_SRV_HST);
         ktab.addEntry(service, "pass1".toCharArray(), 1, true);
         ktab.addEntry(service, "pass2".toCharArray(), 2, true);
         ktab.addEntry(service, "pass3".toCharArray(), 3, true);
         ktab.save();
 
         // and use the middle one as the real key
-        kdc.addPrincipal("host/" + server, "pass2".toCharArray());
+        kdc.addPrincipal(serviceName, "pass2".toCharArray());
 
 
         // JAAS config entry name ssl
@@ -118,7 +130,7 @@
                 "    com.sun.security.auth.module.Krb5LoginModule required\n" +
                 (unbound ?
                     "    principal=*\n" :
-                    "    principal=\"host/" + server + "\"\n") +
+                    "    principal=\"" + serviceName + "\"\n") +
                 "    useKeyTab=true\n" +
                 "    keyTab=" + OneKDC.KTAB + "\n" +
                 "    isInitiator=false\n" +
@@ -153,7 +165,7 @@
         }
 
         c = Context.fromUserPass(OneKDC.USER, OneKDC.PASS, false);
-        c.startAsClient("host/" + server, GSSUtil.GSS_KRB5_MECH_OID);
+        c.startAsClient(serviceName, GSSUtil.GSS_KRB5_MECH_OID);
         c.doAs(new JsseClientAction(), null);
 
         // Add another version of key, make sure it can be loaded
@@ -161,10 +173,10 @@
         ktab = KeyTab.getInstance(OneKDC.KTAB);
         ktab.addEntry(service, "pass4".toCharArray(), 4, true);
         ktab.save();
-        kdc.addPrincipal("host/" + server, "pass4".toCharArray());
+        kdc.addPrincipal(serviceName, "pass4".toCharArray());
 
         c = Context.fromUserPass(OneKDC.USER, OneKDC.PASS, false);
-        c.startAsClient("host/" + server, GSSUtil.GSS_KRB5_MECH_OID);
+        c.startAsClient(serviceName, GSSUtil.GSS_KRB5_MECH_OID);
         c.doAs(new JsseClientAction(), null);
 
         // Permission checking check. Please note this is highly
@@ -199,6 +211,14 @@
             sslSocket.setEnabledCipherSuites(enabledSuites);
             // Should check for exception if enabledSuites is not supported
 
+            if (sniHostname != null) {
+                List<SNIServerName> serverNames = new ArrayList<>();
+                serverNames.add(new SNIHostName(sniHostname));
+                SSLParameters params = sslSocket.getSSLParameters();
+                params.setServerNames(serverNames);
+                sslSocket.setSSLParameters(params);
+            }
+
             BufferedReader in = new BufferedReader(new InputStreamReader(
                 sslSocket.getInputStream()));
             BufferedWriter out = new BufferedWriter(new OutputStreamWriter(
@@ -242,6 +262,14 @@
             sslServerSocket.setEnabledCipherSuites(enabledSuites);
             // Should check for exception if enabledSuites is not supported
 
+            if (sniMatcherPattern != null) {
+                List<SNIMatcher> matchers = new ArrayList<>();
+                matchers.add(SNIHostName.createSNIMatcher(sniMatcherPattern));
+                SSLParameters params = sslServerSocket.getSSLParameters();
+                params.setSNIMatchers(matchers);
+                sslServerSocket.setSSLParameters(params);
+            }
+
             while (loopCount++ < LOOP_LIMIT) {
                 System.out.println("Waiting for incoming connection...");