2016-04-24 225 views
1

我想需要客戶端證書身份驗證與基於Java 1.7的服務器的內置HttpsServer.如何要求客戶端證書與com.sun.net.httpserver.HttpsServer

我似乎無法找到任何方式使服務器無法通過身份驗證。無論客戶證書是可信的,未知的還是完全不存在的,它都會愉快地爲任何老客戶提供數據。

我對文檔的閱讀表明setting HttpsParameters.setNeedClientAuth(true)應該導致身份驗證失敗,當客戶端不受信任時。我發現了類似問題的人們的註釋,各種建議在SSLEngine和SSLParameters中使用了相同的標誌,但都沒有改變我的行爲。

這是我創建的最簡單的例子。看看交易的內容(使用Wireshark或-Djavax.net.debug = all),我看不出任何看起來很像服務器的證書請求......當然,這看起來很明顯,因爲它在響應時不應該。

我對Java和SSL都比較陌生。我誤解了認證過程嗎?我是否在適當的庫中使用?我忽略瞭解決這個問題的好方法嗎?謝謝!

編輯1:更新示例代碼以正確分離客戶機密鑰庫和信任庫。還重新提出了問題以使認證問題更加清晰。

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 
+1

您的客戶端不與配置的密鑰存儲運行,因此它不能發送證書,無論是要求一個或不。 – EJP

+0

使用「unauthclient.jks」(具有無法識別的客戶端證書的正確密鑰庫)運行客戶端具有完全相同的行爲。但無論如何,服務器肯定會拒絕來自無法進行身份驗證的客戶端的連接? –

+1

您正在使用信任存儲而不是密鑰存儲來運行客戶端。它無處獲得一個私鑰或相應的證書。 – EJP

回答

0

最後,出現了幾個問題。 - 由Gradle設置奇怪合格的JDK導致的API權限問題 - 同一個JDK問題導致SSLparams出現問題 - 我的初始示例未設置信任庫。

感興趣的是,我最終更改爲wantClientAuth(true),並在處理程序中進行身份驗證。

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(); 
    } 
} 
+0

你是如何實現WantClientAuth的?我總是得到錯誤的SSL密鑰無法驗證... –