8025123: SNI support in Kerberos cipher suites
Reviewed-by: weijun, xuelei
Contributed-by: Artem Smotrakov <artem.smotrakov@oracle.com>
--- 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...");