如何使用com.sun.net.httpserver.HttpsServer要求客户端证书

我想要求基于Java 1.7的内置HttpsServer的服务器进行客户端证书身份validation。

我似乎无法找到任何方法使服务器失败身份validation。 无论客户端证书是否可信,未知或完全缺席,它都会快乐地向任何旧客户端提供数据。

我对文档的阅读表明,当客户端不受信任时, 设置HttpsParameters.setNeedClientAuth(true)应该导致身份validation失败。 我发现了有类似问题的人的笔记,不同地建议在SSLEngine和SSLParameters中使用相同的标志,但两者都没有改变我的行为。

这是我能够创建的最简单的例子。 看看交易的内容(使用Wireshark或-Djavax.net.debug = all),我看到任何看起来都不像服务器的证书请求…当然,这看起来很明显,因为它在响应时不应该。

我对Java和SSL都比较陌生。 我误解了身份validation过程吗? 我在适当的图书馆使用吗? 我是否忽略了解决此问题的好方法? 谢谢!

编辑1:更新示例代码以正确分离客户端密钥库和信任库。 还有重写问题,以使身份validation问题更清晰。

package authserv; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.URL; import java.security.KeyStore; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManagerFactory; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpsConfigurator; import com.sun.net.httpserver.HttpsExchange; import com.sun.net.httpserver.HttpsParameters; import com.sun.net.httpserver.HttpsServer; public class AuthServer { final static String SERVER_PWD = "aaaaaa"; final static String KST_SERVER = "keys/server.jks"; final static String TST_SERVER = "keys/servertrust.jks"; public static HttpsServer server; public static void main(String[] args) throws Exception { server = makeServer(); server.start(); //System.out.println("Server running, hit enter to stop.\n"); System.in.read(); AuthClient cl = new AuthClient(); cl.testIt(); server.stop(0); } public static HttpsServer makeServer() throws Exception { server = HttpsServer.create(new InetSocketAddress(8888), 0); //server.setHttpsConfigurator(new HttpsConfigurator(SSLContext.getInstance("TLS"))); // Default config with no auth requirement. SSLContext sslCon = createSSLContext(); MyConfigger authconf = new MyConfigger(sslCon); server.setHttpsConfigurator(authconf); server.createContext("/auth", new HelloHandler()); return server; } private static SSLContext createSSLContext() { SSLContext sslContext = null; KeyStore ks; KeyStore ts; try{ sslContext = SSLContext.getInstance("TLS"); ks = KeyStore.getInstance("JKS"); ks.load(new FileInputStream(KST_SERVER), SERVER_PWD.toCharArray()); KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); kmf.init(ks, SERVER_PWD.toCharArray()); ts = KeyStore.getInstance("JKS"); ts.load(new FileInputStream(TST_SERVER), SERVER_PWD.toCharArray()); TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); tmf.init(ts); sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); } catch (Exception e) { e.printStackTrace(); } return sslContext; } } class MyConfigger extends HttpsConfigurator { public MyConfigger(SSLContext sslContext) { super(sslContext); } @Override public void configure(HttpsParameters params) { SSLContext sslContext = getSSLContext(); SSLParameters sslParams = sslContext.getDefaultSSLParameters(); sslParams.setNeedClientAuth(true); params.setNeedClientAuth(true); params.setSSLParameters(sslParams); super.configure(params); /* Other configure options that don't seem to help: SSLEngine engine = sslContext.createSSLEngine (); engine.setNeedClientAuth(true); params.setCipherSuites ( engine.getEnabledCipherSuites () ); params.setProtocols ( engine.getEnabledProtocols () ); */ } } class HelloHandler implements HttpHandler { public void handle(HttpExchange t) throws IOException { HttpsExchange ts = (HttpsExchange) t; SSLSession sess = ts.getSSLSession(); //if( sess.getPeerPrincipal() != null) System.out.println(sess.getPeerPrincipal().toString()); // Principal never populated. t.getResponseHeaders().set("Content-Type", "text/plain"); t.sendResponseHeaders(200,0); String response = "Hello! You seem trustworthy!\n"; OutputStream os = t.getResponseBody(); os.write(response.getBytes()); os.close(); } } class AuthClient{ static String KEYSTORE = ""; static String TRUSTSTORE = "keys/clienttrust.jks"; static String CLIENT_PWD = "aaaaaa"; public static void main(String[] args) throws Exception { KEYSTORE = "keys/unauthclient.jks"; // Doesn't exist in server trust store, should fail authentication. //KEYSTORE = "keys/authclient.jks"; // Exists in server trust store, should pass authentication. AuthClient cl = new AuthClient(); cl.testIt(); } public void testIt(){ try { String https_url = "https://localhost:8888/auth/"; URL url; url = new URL(https_url); HttpsURLConnection conn = (HttpsURLConnection)url.openConnection(); conn.setSSLSocketFactory(getSSLFactory()); conn.setRequestMethod("POST"); conn.setDoOutput(true); conn.setUseCaches(false); // Print response BufferedReader bir = new BufferedReader(new InputStreamReader(conn.getInputStream())); String line = null; while((line = bir.readLine()) != null) { System.out.println(line); } bir.close(); conn.disconnect(); } catch (Exception e) { e.printStackTrace(); } } private static SSLSocketFactory getSSLFactory() throws Exception { // Create key store KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); KeyManager[] kmfs = null; if( KEYSTORE.length() > 0 ) { keyStore.load(new FileInputStream(KEYSTORE), CLIENT_PWD.toCharArray()); KeyManagerFactory kmf = KeyManagerFactory.getInstance( KeyManagerFactory.getDefaultAlgorithm()); kmf.init(keyStore, CLIENT_PWD.toCharArray()); kmfs = kmf.getKeyManagers(); } // create trust store (validates the self-signed server!) KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); trustStore.load(new FileInputStream(TRUSTSTORE), CLIENT_PWD.toCharArray()); TrustManagerFactory trustFactory = TrustManagerFactory.getInstance( TrustManagerFactory.getDefaultAlgorithm()); trustFactory.init(trustStore); SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(kmfs, trustFactory.getTrustManagers(), null); return sslContext.getSocketFactory(); } } 

这是一个用于创建必要证书和密钥库的bash脚本。

 #!/bin/bash LOCALNAME=localhost PASS=aaaaaa function main { gen list } function gen { mkdir -p keys rm -f authclient.cert authclient.jks server.cert server.jks servertrust.jks clienttrust.jks unauthclient.jks # create the keypairs for authclient, unauthclient and for server. keytool -genkey -alias server -keyalg RSA -keystore server.jks -validity 365 -dname "cn=$LOCALNAME, ou=Auth, o=Auth, c=CA" -storepass $PASS -keypass $PASS keytool -genkey -alias authclient -keyalg RSA -keystore authclient.jks -validity 365 -dname "cn=$LOCALNAME, ou=Auth, o=Auth, c=CA" -storepass $PASS -keypass $PASS keytool -genkey -alias unauthclient -keyalg RSA -keystore unauthclient.jks -validity 365 -dname "cn=$LOCALNAME, ou=Auth, o=Auth, c=CA" -storepass $PASS -keypass $PASS keytool -export -file server.cert -keystore server.jks -storepass $PASS -alias server keytool -export -file authclient.cert -keystore authclient.jks -storepass $PASS -alias authclient # Create a bare client truststore with no keypair echo yes | keytool -import -file server.cert -alias server -keystore clienttrust.jks -storepass $PASS # Create a truststore for the server containing ONLY authclient echo yes | keytool -import -file authclient.cert -alias authclient -keystore servertrust.jks -storepass $PASS # Add the server's cert to the client's keystores #echo yes | keytool -import -file server.cert -alias server -keystore authclient.jks -storepass $PASS #echo yes | keytool -import -file server.cert -alias server -keystore unauthclient.jks -storepass $PASS } function list { for x in *.jks; do SER=$(keytool -list -v -keystore $x -storepass aaaaaa | grep Serial) echo $x $SER done } main 

最后,有几个问题。 – 由Gradle设置奇怪合格的JDK引起的API权限问题 – 相同的JDK问题导致SSLparams问题 – 我的初始示例未设置信任库。

有趣的是,我最终改为wantClientAuth(true),并在处理程序中进行了身份validation。

 package authserv; import java.io.FileInputStream; import java.io.IOException; import java.io.OutputStream; import java.net.InetSocketAddress; import java.security.KeyStore; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLSession; import javax.net.ssl.TrustManagerFactory; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpsConfigurator; import com.sun.net.httpserver.HttpsExchange; import com.sun.net.httpserver.HttpsParameters; import com.sun.net.httpserver.HttpsServer; public class AuthServer { final static String SERVER_PWD = "aaaaaa"; final static String KST_SERVER = "keys/server.jks"; final static String TST_SERVER = "keys/servertrust.jks"; public static HttpsServer server; public static void main(String[] args) throws Exception { server = makeServer(); server.start(); System.out.println("Server running, hit enter to stop.\n"); System.in.read(); //AuthClient cl = new AuthClient(); cl.testIt(); server.stop(0); } public static HttpsServer makeServer() throws Exception { server = HttpsServer.create(new InetSocketAddress(8888), 0); //server.setHttpsConfigurator(new HttpsConfigurator(SSLContext.getInstance("TLS"))); // Default config with no auth requirement. SSLContext sslCon = createSSLContext(); MyConfigger authconf = new MyConfigger(sslCon); server.setHttpsConfigurator(authconf); server.createContext("/auth", new HelloHandler()); return server; } private static SSLContext createSSLContext() { SSLContext sslContext = null; KeyStore ks; KeyStore ts; try{ sslContext = SSLContext.getInstance("TLS"); ks = KeyStore.getInstance("JKS"); ks.load(new FileInputStream(KST_SERVER), SERVER_PWD.toCharArray()); KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); kmf.init(ks, SERVER_PWD.toCharArray()); ts = KeyStore.getInstance("JKS"); ts.load(new FileInputStream(TST_SERVER), SERVER_PWD.toCharArray()); TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); tmf.init(ts); sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); } catch (Exception e) { e.printStackTrace(); } return sslContext; } } class MyConfigger extends HttpsConfigurator { public MyConfigger(SSLContext sslContext) { super(sslContext); } @Override public void configure(HttpsParameters params) { SSLContext sslContext = getSSLContext(); SSLParameters sslParams = sslContext.getDefaultSSLParameters(); sslParams.setNeedClientAuth(true); params.setNeedClientAuth(true); params.setSSLParameters(sslParams); } } class HelloHandler implements HttpHandler { public void handle(HttpExchange t) throws IOException { HttpsExchange ts = (HttpsExchange) t; SSLSession sess = ts.getSSLSession(); //if( sess.getPeerPrincipal() != null) System.out.println(sess.getPeerPrincipal().toString()); // Principal never populated. System.out.printf("Responding to host: %s\n",sess.getPeerHost()); t.getResponseHeaders().set("Content-Type", "text/plain"); t.sendResponseHeaders(200,0); String response = "Hello! You seem trustworthy!\n"; OutputStream os = t.getResponseBody(); os.write(response.getBytes()); os.close(); } } 

这是一个展示失败和成功的客户:

 package authserv; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.InputStreamReader; import java.net.SocketException; import java.net.URL; import java.security.KeyStore; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManagerFactory; public class AuthClient{ static String NO_KEYSTORE = ""; static String UNAUTH_KEYSTORE = "keys/unauthclient.jks"; // Doesn't exist in server trust store, should fail authentication. static String AUTH_KEYSTORE = "keys/authclient.jks"; // Exists in server trust store, should pass authentication. static String TRUSTSTORE = "keys/clienttrust.jks"; static String CLIENT_PWD = "aaaaaa"; public static void main(String[] args) throws Exception { AuthClient cl = new AuthClient(); System.out.println("No keystore:"); cl.testIt(NO_KEYSTORE); System.out.println("Unauth keystore:"); cl.testIt(UNAUTH_KEYSTORE); System.out.println("Auth keystore:"); cl.testIt(AUTH_KEYSTORE); } public void testIt(String jksFile){ try { String https_url = "https://localhost:8888/auth/"; URL url; url = new URL(https_url); HttpsURLConnection conn = (HttpsURLConnection)url.openConnection(); conn.setSSLSocketFactory(getSSLFactory(jksFile)); conn.setRequestMethod("POST"); conn.setDoOutput(true); conn.setUseCaches(false); // Print response BufferedReader bir = new BufferedReader(new InputStreamReader(conn.getInputStream())); String line = null; while((line = bir.readLine()) != null) { System.out.println(line); } bir.close(); conn.disconnect(); } catch (SSLHandshakeException|SocketException e) { System.out.println(e.getMessage()); System.out.println(""); } catch (Exception e) { e.printStackTrace(); } } private static SSLSocketFactory getSSLFactory(String jksFile) throws Exception { // Create key store KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); KeyManager[] kmfs = null; if( jksFile.length() > 0 ) { keyStore.load(new FileInputStream(jksFile), CLIENT_PWD.toCharArray()); KeyManagerFactory kmf = KeyManagerFactory.getInstance( KeyManagerFactory.getDefaultAlgorithm()); kmf.init(keyStore, CLIENT_PWD.toCharArray()); kmfs = kmf.getKeyManagers(); } // create trust store (validates the self-signed server!) KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); trustStore.load(new FileInputStream(TRUSTSTORE), CLIENT_PWD.toCharArray()); TrustManagerFactory trustFactory = TrustManagerFactory.getInstance( TrustManagerFactory.getDefaultAlgorithm()); trustFactory.init(trustStore); SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(kmfs, trustFactory.getTrustManagers(), null); return sslContext.getSocketFactory(); } }