2009-11-24 5 views
12

Ich habe einen internen HTTP-Server in Java geschrieben; vollständiger Quellcode zu meiner Verfügung. Der HTTP-Server kann eine beliebige Anzahl von Web-Sites konfigurieren, müssen von denen jede eine separate Buchse mit erstellt hören:Wie kann ich mehrere SSL-Zertifikate für einen Java-Server haben

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

ein Standardschlüsselspeicher mit dem keytool Java erstellt verwenden, kann ich nicht für das Leben von mir heraus wie man verschiedene Zertifikate erhält, die mit verschiedenen Listen Sockets verbunden sind, so dass jede konfigurierte Website ihr eigenes Zertifikat hat.

Ich bin in einer Zeit Pinch für diese jetzt, so einige Codebeispiele, die veranschaulichen würde am meisten geschätzt werden. Aber so viel würde ich jeden guten Überblick darüber schätzen, wie JSSE in dieser Hinsicht zusammenhängt (ich habe Sun JSSE doco gesucht, bis mein Gehirn weh tut (wörtlich; obwohl es genauso viel Koffeinentzug sein könnte)).

bearbeiten

Gibt es keine einfache Möglichkeit, die Aliasnamen zu verwenden, um die Server-Zertifikate in einem Schlüsselspeicher mit den hören Steckdosen zu verbinden? So dass:

  • Der Kunde einen Schlüsselspeicher hat für alle Zertifikate zu verwalten, und
  • Es gibt keine Notwendigkeit, mehrere Schlüssel speichert ist herumzudaddeln mit etc.

Ich war immer den Eindruck, (früher heute Nachmittag), dass ich einen einfachen KeyManager schreiben könnte, mit nur chooseServerAlias(...) Rückgabe nicht-null, das ist der Name des Alias, den ich wollte - hat irgendjemand irgendwelche Gedanken zu dieser Argumentationslinie?

Lösung

Die Lösung, die ich verwenden, von slyvarking ‚s Antwort gebaut war ein temporären Schlüsselspeicher und füllen Sie es mit dem gewünschten Schlüssel/cert aus den Einzahl externen Schlüsselspeichern extrahierte zu erstellen. Code folgt für alle, die interessiert sind (svrctfals ist mein „Serverzertifikat alias“ Wert):

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

Sie können Ihren eigenen KeyManager ziemlich einfach implementieren, aber Ihre Idee, einen neuen, temporären KeyStore im Speicher unter Verwendung eines Alias ​​im "Master" -Schlüsselspeicher zu erstellen, ist ein guter. – erickson

Antwort

8

Der einfachste Weg, dies zu tun ist, ein einziges Zertifikat für alle Ihre Domain-Namen zu verwenden. Setzen Sie alle anderen Site-Namen in SAN (Alternativer Antragstellername).

Wenn Sie für jeden Domänennamen ein Zertifikat bevorzugen, können Sie Ihren eigenen Schlüsselmanager schreiben und Alias ​​verwenden, um die Domäne zu identifizieren, sodass Sie einen einzelnen Schlüsselspeicher verwenden können. In unserem System treffen wir eine Konvention, bei der der Keystore-Alias ​​immer dem CN im Zertifikat entspricht.So können wir etwas tun,

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

Ahh. Dies ist genau das, was ich gestern für möglich gehalten habe ... Ich hätte das meiner Lösung vorgezogen, da es logischerweise sauberer ist, denke ich. –

+0

Keines der Tools, die ich gesehen habe, hatte eine Möglichkeit, alternative Subjektnamen festzulegen - haben Sie Empfehlungen? –

+0

OpenSSL kann CSR mit SANs erstellen. Sie können die Befehlszeile nicht verwenden.Sie müssen SANs im Abschnitt [alt_names] in der Konfigurationsdatei hinzufügen. Siehe http://therowes.net/~greg/2008/01/08/creating-a-certificate-with-multiple-hostnames/ –

5

Sie werden nicht in der Lage sein, den Standard SSLServerSocketFactory zu verwenden.

Stattdessen initialize eine anderes SSLContext für jede Website, die jeweils ein KeyManagerFactoryconfigured mit einem Schlüsselspeicher mit einem Schlüssel-Eintrag mit korrektem Server-Zertifikat enthält. (Nach den KeyManagerFactory Initialisierung passiert seine key managers zur init Methode der SSLContext.)

Nachdem die SSLContext initialisiert wird, get its SSLServerSocketFactory, und verwenden Sie, dass Ihre Zuhörer zu erstellen.

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

Und wie bekommt man einen anderen SSLContext mit dem Listened-Socket? –

+0

Für welche dieser Objekte muss ich eine benutzerdefinierte Implementierung bereitstellen und welche von JSSE bereitgestellten Bestandsobjekte verwenden? –

+0

Sie können alle auf Lager JSSE sein. Ihre Hauptaufgabe besteht darin, für jede Site einen eigenen Schlüsselspeicher zu erstellen. Siehe mein Update für ein Beispiel. Ich habe keine IDE verwendet, daher sind einige der Methodennamen möglicherweise deaktiviert. – erickson

2

ich vor kurzem in einer ähnlichen Situation lief. Ich habe einen benutzerdefinierten eingebetteten Java-Webserver, der eine beliebige Anzahl von Websites hosten kann. Jede Website hat ihren eigenen Domainnamen. Jede Website/Domain erhält auf dem Server eine eindeutige IP-Adresse. Für jede IP-Adresse an Port 80 wird ein Socket-Listener erstellt.

Für Websites mit SSL-Zertifikaten habe ich die Schlüssel und Zertifikate in einen einzigen KeyStore importiert. Ich habe für die SSL-Zertifikate jeder Domäne einen Zertifikatalias zugewiesen, der dem Domänennamen entspricht. Jede Domäne/Website, die über ein SSL-Zertifikat verfügt, erhält an Port 443 einen neuen Socket-Listener.

Standardmäßig ist der standardmäßige Java X509KeyManager und die SunX509-Implementierung 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). Leider entspricht der ausgewählte Alias ​​nicht unbedingt der angeforderten Domäne, sodass Sie mit Zertifikatsfehlern konfrontiert werden.

Um dieses Problem zu umgehen, habe ich verwendet und einen benutzerdefinierten X509KeyManager implementiert. Für meinen Server brauchte ich einen X509ExtendedKeyManager, der eine extra chosenEngineServerAlias ​​() -Methode hat.

Mein benutzerdefinierter KeyManager basiert auf einer hashmap von Hostnamen und ihren entsprechenden IP-Adressen. Wenn eine neue SSL-Anfrage gemacht wird, prüft sie die eingehende IP-Adresse und findet den entsprechenden Hostnamen. Anschließend wird versucht, im Keystore einen Aliasnamen zu finden, der dem Hostnamen entspricht.

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."); 
    }   
} 

Mit dem benutzerdefinierten KeyManager wird ein SSLContext initialisiert. Die coole Sache ist, dass Sie nur einen SSLContext initialisieren müssen.

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); 
Verwandte Themen