2009-11-24 82 views
12

我有一個用Java編寫的內部HTTP服務器;完整的源代碼在我的處置。 HTTP服務器可以配置任意數量的網站,其中的每一個將與創建一個單獨的監聽套接字:如何爲Java服務器獲得多個SSL證書

skt=SSLServerSocketFactory.getDefault().createServerSocket(prt,bcklog,adr); 

使用與Java密鑰工具創建標準密鑰存儲,我不能爲我的生活工作如何獲取與不同偵聽套接字關聯的不同證書,以便每個配置的網站都擁有自己的證書。

我現在在這個時候捏,所以一些代碼示例說明將非常感激。但是,儘可能多地感謝JSSE在這方面如何掛在一起的良好概述(我搜索了Sun的JSSE doco,直到我的大腦受傷(從字面上看,儘管它可能與咖啡因一樣多)。

編輯

是否有使用別名的服務器證書與偵聽套接字密鑰存儲相關聯沒有簡單的方法?因此:

  • 客戶有一個密鑰存儲管理所有證書和
  • 沒有必要反覆折騰多個密鑰存儲等

我得到的印象(今天下午早些時候)我可以寫一個簡單的KeyManager,只有chooseServerAlias(...)返回非null,這是我想要的別名的名稱 - 任何人都有任何想法推理?

我使用的解決方案,從slyvarking的回答是內置創建臨時密鑰存儲,並使用從單數外部密鑰存儲提取的期望的鍵/證書填充它。代碼如下任何誰是興趣(svrctfals是我的「服務器證書別名」值):

SSLServerSocketFactory    ssf;         // server socket factory 
    SSLServerSocket      skt;         // server socket 

    // LOAD EXTERNAL KEY STORE 
    KeyStore mstkst; 
    try { 
     String kstfil=GlobalSettings.getString("javax.net.ssl.keyStore"  ,System.getProperty("javax.net.ssl.keyStore"  ,"")); 
     String ksttyp=GlobalSettings.getString("javax.net.ssl.keyStoreType" ,System.getProperty("javax.net.ssl.keyStoreType" ,"jks")); 
     char[] kstpwd=GlobalSettings.getString("javax.net.ssl.keyStorePassword",System.getProperty("javax.net.ssl.keyStorePassword","")).toCharArray(); 

     mstkst=KeyStore.getInstance(ksttyp); 
     mstkst.load(new FileInputStream(kstfil),kstpwd); 
     } 
    catch(java.security.GeneralSecurityException thr) { 
     throw new IOException("Cannot load keystore ("+thr+")"); 
     } 

    // CREATE EPHEMERAL KEYSTORE FOR THIS SOCKET USING DESIRED CERTIFICATE 
    try { 
     SSLContext  ctx=SSLContext.getInstance("TLS"); 
     KeyManagerFactory kmf=KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); 
     KeyStore   sktkst; 
     char[]   blkpwd=new char[0]; 

     sktkst=KeyStore.getInstance("jks"); 
     sktkst.load(null,blkpwd); 
     sktkst.setKeyEntry(svrctfals,mstkst.getKey(svrctfals,blkpwd),blkpwd,mstkst.getCertificateChain(svrctfals)); 
     kmf.init(sktkst,blkpwd); 
     ctx.init(kmf.getKeyManagers(),null,null); 
     ssf=ctx.getServerSocketFactory(); 
     } 
    catch(java.security.GeneralSecurityException thr) { 
     throw new IOException("Cannot create secure socket ("+thr+")"); 
     } 

    // CREATE AND INITIALIZE SERVER SOCKET 
    skt=(SSLServerSocket)ssf.createServerSocket(prt,bcklog,adr); 
    ... 
    return skt; 
+0

你可以很容易地實現自己的KeyManager,但是你的建築在內存中使用別名新的臨時密鑰庫進入「主」鑰匙店的想法是好的。 – erickson 2009-11-24 06:43:12

回答

8

最簡單的方法是對所有域名使用單一證書。將所有其他網站名稱放入SAN(主題備用名稱)中。

如果您更喜歡每個域名的一個證書,您可以編寫自己的密鑰管理器並使用別名來標識域,以便您可以使用單個密鑰庫。在我們的系統中,我們制定了一個約定,即密鑰庫別名始終等於證書中的CN。因此,我們可以做這樣的事情,

SSLContext sctx1 = SSLContext.getInstance("SSLv3"); 
sctx1.init(new X509KeyManager[] { 
    new MyKeyManager("/config/master.jks","changeme".toCharArray(),"site1.example.com") 
    },null, null); 
SSLServerSocketFactory ssf = (SSLServerSocketFactory) sctx1.getServerSocketFactory(); 
ServerSocket ss1 = ssf.createServerSocket(1234); 

... 

SSLContext sctx2 = SSLContext.getInstance("SSLv3"); 
sctx2.init(new X509KeyManager[] { 
    new MyKeyManager("/config/master.jks","changeme".toCharArray(),"site2.example.com") 
    },null, null); 
ssf = (SSLServerSocketFactory) sctx2.getServerSocketFactory(); 
ServerSocket ss2 = ssf.createServerSocket(5678); 

...

public static class MyKeyManager implements X509KeyManager { 
    private KeyStore keyStore; 
    private String alias; 
    private char[] password; 

    MyKeyManager(String keyStoreFile, char[] password, String alias) 
     throws IOException, GeneralSecurityException 
    { 
     this.alias = alias; 
     this.password = password; 
     InputStream stream = new FileInputStream(keyStoreFile); 
     keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); 
     keyStore.load(stream, password); 
    } 

    public PrivateKey getPrivateKey(String alias) { 
     try { 
      return (PrivateKey) keyStore.getKey(alias, password); 
     } catch (Exception e) { 
      return null; 
     } 
    } 

    public X509Certificate[] getCertificateChain(String alias) { 
     try { 
      java.security.cert.Certificate[] certs = keyStore.getCertificateChain(alias); 
      if (certs == null || certs.length == 0) 
       return null; 
      X509Certificate[] x509 = new X509Certificate[certs.length]; 
      for (int i = 0; i < certs.length; i++) 
       x509[i] = (X509Certificate)certs[i]; 
      return x509; 
     } catch (Exception e) { 
      return null; 
     }   
    } 

    public String chooseServerAlias(String keyType, Principal[] issuers, 
            Socket socket) { 
     return alias; 
    } 

    public String[] getClientAliases(String parm1, Principal[] parm2) { 
     throw new UnsupportedOperationException("Method getClientAliases() not yet implemented."); 
    } 

    public String chooseClientAlias(String keyTypes[], Principal[] issuers, Socket socket) { 
     throw new UnsupportedOperationException("Method chooseClientAlias() not yet implemented."); 
    } 

    public String[] getServerAliases(String parm1, Principal[] parm2) { 
     return new String[] { alias }; 
    } 

    public String chooseServerAlias(String parm1, Principal[] parm2) { 
     return alias; 
    } 
} 
+0

啊。這正是我懷疑可以在昨天完成的事情......我會認爲這是我的解決方案,因爲它在邏輯上更清潔。 – 2009-11-25 02:00:26

+0

我看到的工具都沒有設置替代主題名稱的方法 - 您有任何建議嗎? – 2009-11-25 02:02:07

+0

OpenSSL可以使用SAN創建CSR。您不能使用命令行。您需要在配置文件的[alt_names]部分中添加SAN。請參閱http://therowes.net/~greg/2008/01/08/creating-a-certificate-with-multiple-hostnames/ – 2009-11-25 04:26:09

5

您將無法使用默認SSLServerSocketFactory

相反,initialize不同SSLContext爲每個站點,每個使用KeyManagerFactoryconfigured含有與正確的服務器證書的密鑰條目的密鑰存儲。 (初始化KeyManagerFactory後,通過它key managersSSLContextinit方法。)

SSLContext是initalized後,get its SSLServerSocketFactory,並用它來創建你的聽衆。

KeyStore identity = KeyStore.getInstance(KeyStore.getDefaultType()); 
/* Load the keystore (a different one for each site). */ 
... 
SSLContext ctx = SSLContext.getInstance("TLS"); 
KeyManagerFactory kmf = 
    KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); 
kmf.init(identity, password); 
ctx.init(kmf.getKeyManagers(), null, null); 
SSLServerSocketFactory factory = ctx.getServerSocketFactory(); 
ServerSocket server = factory.createSocket(port); 
+1

如何獲得與偵聽套接字關聯的不同SSLContext? – 2009-11-24 05:44:34

+0

哪些對象需要提供自定義實現,哪些會使用由JSSE提供的庫存對象? – 2009-11-24 05:52:52

+0

他們都可以股票JSSE。您的主要任務是爲每個網站構建一個獨特的密鑰存儲。查看我的更新以獲取示例。我沒有使用IDE,因此某些方法名稱可能關閉。 – erickson 2009-11-24 05:55:23

2

我最近碰到了類似的情況。我有一個可以託管任意數量網站的定製嵌入式Java Web服務器。每個網站都有自己的域名。每個網站/域在服務器上分配一個唯一的IP地址。爲端口80上的每個IP地址創建一個套接字偵聽器。

對於具有SSL證書的站點,我將密鑰和證書導入到一個KeyStore中。我爲每個域的SSL證書分配了一個證書別名來匹配域名。每個擁有SSL證書的域/網站都會在端口443上分配一個新的套接字偵聽器。

默認情況下,標準Java X509KeyManager和SunX509實現will pick the first aliases it finds for which there is a private key and a key of the right type for the chosen cipher suite (typically RSA)。不幸的是,所選的別名不一定對應於所請求的域名,因此最終會出現證書錯誤。

爲了規避這個問題,我使用了ZZ Coder's suggestion並實現了一個自定義的X509KeyManager。實際上,對於我的服務器,我需要一個X509ExtendedKeyManager,它有一個額外的chooseEngineServerAlias()方法。

我的自定義KeyManager依賴於主機名及其相應IP地址的散列映射。當發出新的SSL請求時,它會檢查傳入的IP地址並找到相應的主機名。然後,它會嘗試在對應於主機名的密鑰庫中查找別名。

private class MyKeyManager extends X509ExtendedKeyManager implements X509KeyManager { 
    private KeyStore keyStore; 
    private char[] password; 
    private java.util.HashMap<InetAddress, String> hosts; 

    public MyKeyManager(KeyStore keystore, char[] password, java.util.HashMap<InetAddress, String> hosts) 
    throws IOException, GeneralSecurityException { 
     this.keyStore = keystore; 
     this.password = password; 
     this.hosts = hosts; 
    } 

    public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine) { 
     try{ 
      return hosts.get(InetAddress.getByName(engine.getPeerHost())); 
     } 
     catch(Exception e){ 
      return null; 
     } 
    } 

    public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) { 
     return hosts.get(socket.getLocalAddress()); 
    } 

    public PrivateKey getPrivateKey(String alias) { 
     try { 
      return (PrivateKey) keyStore.getKey(alias, password); 
     } 
     catch (Exception e) { 
      return null; 
     } 
    } 

    public X509Certificate[] getCertificateChain(String alias) { 
     try { 
      java.security.cert.Certificate[] certs = keyStore.getCertificateChain(alias); 
      if (certs == null || certs.length == 0) return null; 
      X509Certificate[] x509 = new X509Certificate[certs.length]; 
      for (int i = 0; i < certs.length; i++){ 
       x509[i] = (X509Certificate)certs[i]; 
      } 
      return x509; 
     } 
     catch (Exception e) { 
      e.printStackTrace(); 
      return null; 
     }   
    } 

    public String[] getServerAliases(String keyType, Principal[] issuers) { 
     throw new UnsupportedOperationException("Method getServerAliases() not yet implemented."); 
    } 

    public String[] getClientAliases(String keyType, Principal[] issuers) { 
     throw new UnsupportedOperationException("Method getClientAliases() not yet implemented."); 
    } 

    public String chooseClientAlias(String keyTypes[], Principal[] issuers, Socket socket) { 
     throw new UnsupportedOperationException("Method chooseClientAlias() not yet implemented."); 
    } 

    public String chooseEngineClientAlias(String[] strings, Principal[] prncpls, SSLEngine ssle) { 
     throw new UnsupportedOperationException("Method chooseEngineClientAlias() not yet implemented."); 
    }   
} 

自定義KeyManager用於初始化SSLContext。很酷的是你只需要初始化一個SSLContext。

javax.net.ssl.KeyManager[] kms = new javax.net.ssl.KeyManager[]{ 
    new MyKeyManager(keystore, keypass, hosts) 
}; 
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); 
tmf.init(keystore); 
javax.net.ssl.TrustManager[] tms = tmf.getTrustManagers(); 
SSLContext sslContext = SSLContext.getInstance("TLS"); 
sslContext.init(kms, tms, null);