I recently encountered a similar situation. I have my own built-in Java web server that can host any number of websites. Each website has its own domain name. Each website / domain is assigned a unique IP address on the server. A socket listener is created for each IP address on port 80.
For sites that have SSL certificates, I imported the keys and certificates into one KeyStore. I have assigned a certificate alias for each domain SSL certificate so that it matches the domain name. Each domain / website that has an SSL certificate is assigned a new socket listener on port 443.
By default, the standard Java X509KeyManager and the SunX509 implementation will select the first aliases that it finds, for which there is a private key and a key of the desired type for the selected cipher suite (usually RSA) . Unfortunately, the selected alias does not necessarily correspond to the requested domain, therefore you receive certificate errors.
To get around this, I used the ZZ Coder suggestion and implemented a custom X509KeyManager. Actually for my server I need X509ExtendedKeyManager, which has an extra selectEngineServerAlias () method.
My custom KeyManager relies on a hash file of hostnames and their corresponding IP addresses. When a new SSL request is made, it checks the incoming IP address and finds the corresponding host name. He then tries to find an alias in the keystore that matches the host name.
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."); } }
The custom KeyManager is used to initialize the SSLContext. The best part is that you only need to initialize one 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);