【问题标题】:Using two private keys (keystore) and two public keys (truststore) in one SSL Socket Connection在一个 SSL 套接字连接中使用两个私钥 (keystore) 和两个公钥 (truststore)
【发布时间】:2012-02-09 17:24:08
【问题描述】:

我需要在一个套接字 ssl 连接中使用密钥对,而无需在客户端中进行任何更改。

为什么?

因为一个客户端使用信任存储中的 CN 属性进行连接握手,而其他客户端使用同一属性中的另一个值以相同的方式处理同一任务。

所以我需要使用 两个密钥库(私有)具有不同 CN 属性以及别名和 共享两个不同的信任库(公共key) 具有不同的 CN 属性和别名。

描述如下:

keyStore1

密钥库类型:JKS

密钥库提供者:SUN

别名:identity1

所有者:CN=app1 ...

发行者:CN=app1 ...

trustStore1

别名:identity1

所有者:CN=app1 ...

发行者:CN=app1 ...

keyStore2

别名:identity2

所有者:CN=app2 ...

发行者:CN=app2 ...

trustStore2

别名:identity2

所有者:CN=app2 ...

发行者:CN=app2 ...

我尝试用这种方式实现这个功能:

    KeyStore KeyStore1;

                try {
                    String keyStoreFile1 = "privatekey1";
                    String keyStoreType1 = "jks";
                    char[] keyStorePwd1 = "password".toCharArray();

                    keyStore1 = KeyStore.getInstance(keyStoreType1);
                    keyStore1.load(new FileInputStream(keyStoreFile1), keyStorePwd1);
                } catch (java.security.GeneralSecurityException thr) {
                    throw new IOException("Cannot load keystore (" + thr + ")");
                }

                KeyStore trustStore1;

                try {
                    String trustStoreFile1 = "publickey1";
                    String trustStoreType1 = "jks";
                    char[] trustStorePwd1 = "password".toCharArray();

                    trustStore1 = KeyStore.getInstance(trustStoreType1);
                    trustStore.load(new FileInputStream(trustStoreFile1), trustStorePwd1);
                } catch (java.security.GeneralSecurityException thr) {
                    throw new IOException("Cannot load truststore (" + thr + ")");
                }


                KeyStore keyStore2;

                try {
                    String keyStoreFile2 = "privatekey2";
                    String keyStoreType2 = "jks";
                    char[] keyStorePwd2 = "anotherpass".toCharArray();

                    keyStore2 = KeyStore.getInstance(key2StoreType);
                    keyStore2.load(new FileInputStream(keyStoreFile2), keyStorePwd2);
                } catch (java.security.GeneralSecurityException thr) {
                    throw new IOException("Cannot load keystore (" + thr + ")");
                }

                KeyStore trustStore2;

                try {
                    String trustStoreFile2 = "publickey2";
                    String trustStoreType2 = "jks";
                    char[] trustStorePwd2 = "anotherpass".toCharArray();

                    trustStore2 = KeyStore.getInstance(trustStoreType2);
                    trustStore2.load(new FileInputStream(trustStoreFile2), trustStorePwd2);
                } catch (java.security.GeneralSecurityException thr) {
                    throw new IOException("Cannot load truststore (" + thr + ")");
                }



                KeyManagerFactory kmfkey1 = KeyManagerFactory
.getInstance(KeyManagerFactory.getkey1Algorithm());

                kmfkey1.init(keyStore1, "password".toCharArray());

                TrustManagerFactory tmfkey1 =
                        TrustManagerFactory.getInstance(TrustManagerFactory.getkey1Algorithm());
                tmfkey1.init(trustStore1);


                SSLContext ctx = SSLContext.getInstance("SSL");
                ctx.init(kmfkey1.getKeyManagers(), tmfkey1.getTrustManagers(), null);


                KeyManagerFactory kmfkey2 =  KeyManagerFactory.
getInstance(KeyManagerFactory.getkey1Algorithm());

                kmfkey2.init(keyStore2, "password".toCharArray());

                TrustManagerFactory tmfkey2 =  TrustManagerFactory
.getInstance(TrustManagerFactory.getkey1Algorithm());

                tmfkey2.init(trustStore2);


                SSLContext ctxkey2 = SSLContext.getInstance("SSL");
                ctxkey2.init(kmfkey2.getKeyManagers(), tmfkey2.getTrustManagers(), null);

                SSLServerSocketFactory sslSrvSockFact = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();

                serverSocket = sslSrvSockFact.createServerSocket(port);

... 但是我收到了这个错误信息

...

java.security.KeyStoreException:未初始化的密钥库 在 java.security.KeyStore.aliases(KeyStore.java:941) 在 com.sun.net.ssl.internal.ssl.SunX509KeyManagerImpl.(SunX509KeyManagerImpl.java:106) 在 com.sun.net.ssl.internal.ssl.KeyManagerFactoryImpl$SunX509.engineInit(KeyManagerFactoryImpl.java:41) 在 javax.net.ssl.KeyManagerFactory.init(KeyManagerFactory.java:192)

...

所以我真的很想知道是否可以在一个套接字连接中使用密钥对,或者以我看不到或无法处理的不同方式解决这个问题。

编辑 1

Bruno,你能给我一个完整或完整的例子吗?

因为我不清楚....

我尝试了两件事:

一种解决方案是按照先前的建议将两个密钥放入私有密钥库中......但不起作用,我收到以下消息:

线程“main”中的异常 java.lang.NoSuchMethodError: javax.net.ssl.SSLContext.setDefault(Ljavax/net/ssl/SSLContext;) 在 ...initialiseManager(499)

你是对的......我仍然需要选择一个上下文或

javax.net.ssl.SSLException: 没有可用的证书或密钥对应于启用的 SSL 密码套件。 尝试向服务器发送消息时...

第二次我听从了你的建议……但是 SSL Socket 连接没有启动

我在许多其他人的帮助下实现了这一点,他们展示了他们的 本站代码...谢谢大家

private KeyManager[] getKeyManagers() throws IOException, GeneralSecurityException {

        // First, get the default KeyManagerFactory.
        String alg = KeyManagerFactory.getDefaultAlgorithm();
        KeyManagerFactory kmFact = KeyManagerFactory.getInstance(alg);

        // Next, set up the KeyStore to use. We need to load the file into
        // a KeyStore instance.

        File keyFile = new File("privatekey1");

        FileInputStream fis = new FileInputStream(keyFile);
        LogManager.log("Loaded keystore privatekey1 " + keyFile.getAbsolutePath(),
LogManager.LOG_LOWEST_LEVEL);
        KeyStore ks = KeyStore.getInstance("jks");
        String keyStorePassword = "password";
        ks.load(fis, keyStorePassword.toCharArray());
        fis.close();
        // Now we initialise the KeyManagerFactory with this KeyStore
        kmFact.init(ks, keyStorePassword.toCharArray());


        KeyManagerFactory dkmFact = KeyManagerFactory.getInstance(alg);

        File keyFileTwo = new File("privatekey2");

        FileInputStream fisTwo = new FileInputStream(keyFileTwo);
        LogManager.log("Loaded keystore privatekey2 " + keyFileTwo.getAbsolutePath(), LogManager.LOG_LOWEST_LEVEL);
        KeyStore ksTwo = KeyStore.getInstance("jks");
        String keyStorePasswordTwo = "password";
        ksTwo.load(fisTwo, keyStorePasswordTwo.toCharArray());
        fisTwo.close();
        // Now we initialise the KeyManagerFactory with this KeyStore
        dkmFact.init(ksTwo, keyStorePasswordTwo.toCharArray());


        // default
        //KeyManagerFactory dkmFact = KeyManagerFactory.getInstance(alg);
        //dkmFact.init(null, null);


        // Get the first X509KeyManager in the list
        X509KeyManager customX509KeyManager = getX509KeyManager(alg, kmFact);
        X509KeyManager jvmX509KeyManager = getX509KeyManager(alg, dkmFact);

        KeyManager[] km = {new MultiKeyStoreManager(jvmX509KeyManager, customX509KeyManager)};
        LogManager.log("Number of key managers registered:" + km.length, LogManager.LOG_LOWEST_LEVEL);
        return km;
    }

    /**
     * Find a X509 Key Manager compatible with a particular algorithm
     * @param algorithm
     * @param kmFact
     * @return
     * @throws NoSuchAlgorithmException
     */
    private X509KeyManager getX509KeyManager(String algorithm, KeyManagerFactory kmFact)
            throws NoSuchAlgorithmException {
        KeyManager[] keyManagers = kmFact.getKeyManagers();

        if (keyManagers == null || keyManagers.length == 0) {
            throw new NoSuchAlgorithmException("The default algorithm :" + algorithm + " produced no key managers");
        }

        X509KeyManager x509KeyManager = null;

        for (int i = 0; i < keyManagers.length; i++) {
            if (keyManagers[i] instanceof X509KeyManager) {
                x509KeyManager = (X509KeyManager) keyManagers[i];
                break;
            }
        }

        if (x509KeyManager == null) {
            throw new NoSuchAlgorithmException("The default algorithm :" + algorithm + " did not produce a X509 Key manager");
        }
        return x509KeyManager;
    }

    private void initialiseManager(int iPort) throws IOException, GeneralSecurityException {
        // Next construct and initialise a SSLContext with the KeyStore and
        // the TrustStore. We use the default SecureRandom.

        // load your key store as a stream and initialize a KeyStore
        File trustFile = new File("publicKey");
        LogManager.log("Trust File Loaded " + trustFile.getAbsolutePath(), LogManager.LOG_LOWEST_LEVEL);

        InputStream trustStream = new FileInputStream(trustFile);
        KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());

        // if your store is password protected then declare it (it can be null however)
        char[] trustPassword = "password".toCharArray();

        // load the stream to your store
        trustStore.load(trustStream, trustPassword);

        File trustFileTwo = new File("publicKeyTwo");
        LogManager.log("Trust File Loaded " + trustFileTwo.getAbsolutePath(), LogManager.LOG_LOWEST_LEVEL);

        InputStream trustStreamTwo = new FileInputStream(trustFileTwo);
        KeyStore trustStoreTwo = KeyStore.getInstance(KeyStore.getDefaultType());

        // if your store is password protected then declare it (it can be null however)
        char[] trustPasswordTwo = "password".toCharArray();

        // load the stream to your store
        trustStoreTwo.load(trustStreamTwo, trustPasswordTwo);


        // initialize a trust manager factory with the trusted store
        TrustManagerFactory trustFactory =
                TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        trustFactory.init(trustStore);
        trustFactory.init(trustStoreTwo);

        // get the trust managers from the factory
        TrustManager[] managers = trustFactory.getTrustManagers();


        SSLContext context = SSLContext.getInstance("SSL");
        context.init(getKeyManagers(), managers, new SecureRandom());
        SSLContext.setDefault(context);

        SSLServerSocketFactory sslSrvFact = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();

        serverSocket = sslSrvFact.createServerSocket(iPort);
        // this method didn't create a Socket Connection correctly 

    }

    class MultiKeyStoreManager implements X509KeyManager {

        private final X509KeyManager jvmKeyManager;
        private final X509KeyManager customKeyManager;

        public MultiKeyStoreManager(X509KeyManager jvmKeyManager, X509KeyManager customKeyManager) {
            this.jvmKeyManager = jvmKeyManager;
            this.customKeyManager = customKeyManager;
        }

        @Override
        public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
            // try the first key manager
            String alias = customKeyManager.chooseClientAlias(keyType, issuers, socket);
            if (alias == null) {
                alias = jvmKeyManager.chooseClientAlias(keyType, issuers, socket);
                LogManager.log("Reverting to JVM CLIENT alias : " + alias, LogManager.LOG_LOWEST_LEVEL);
            }

            return alias;

        }

        @Override
        public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
            // try the first key manager
            String alias = customKeyManager.chooseServerAlias(keyType, issuers, socket);
            if (alias == null) {
                alias = jvmKeyManager.chooseServerAlias(keyType, issuers, socket);
                LogManager.log("Reverting to JVM Server alias : " + alias ,LogManager.LOG_LOWEST_LEVEL);
            }
            return alias;
        }

        @Override
        public String[] getClientAliases(String keyType, Principal[] issuers) {
            String[] cAliases = customKeyManager.getClientAliases(keyType, issuers);
            String[] jAliases = jvmKeyManager.getClientAliases(keyType, issuers);
            LogManager.log("Supported Client Aliases Custom: " + cAliases.length + " JVM : " + jAliases.length,
                    LogManager.LOG_LOWEST_LEVEL);
            return (String[]) ArrayUtils.addAll(cAliases, jAliases);
        }

        @Override
        public PrivateKey getPrivateKey(String alias) {
            PrivateKey key = customKeyManager.getPrivateKey(alias);
            if (key == null) {
                System.out.println("Reverting to JVM Key : " + alias);
                return jvmKeyManager.getPrivateKey(alias);
            } else {
                return key;
            }
        }

        @Override
        public String[] getServerAliases(String keyType, Principal[] issuers) {
            String[] cAliases = customKeyManager.getServerAliases(keyType, issuers);
            String[] jAliases = jvmKeyManager.getServerAliases(keyType, issuers);
            LogManager.log("Supported Server Aliases Custom: " + cAliases.length + " JVM : " + jAliases.length,
                    LogManager.LOG_LOWEST_LEVEL);
            return (String[]) ArrayUtils.addAll(cAliases, jAliases);
        }

        @Override
        public java.security.cert.X509Certificate[] getCertificateChain(String string) {
            java.security.cert.X509Certificate[] chain = customKeyManager.getCertificateChain("alias_key1");
            if (chain == null || chain.length == 0) {
                LogManager.log("Reverting to JVM Chain : " + string, LogManager.LOG_LOWEST_LEVEL);
                return jvmKeyManager.getCertificateChain("alias_key2");
            } else {
                return chain;
            }
        }
    }

and this gave me this status

*2012.02.09 18:47:00 激活 SSL 连接

2012.02.09 18:47:00 [... ::运行]

2012.02.09 18:47:00 信任文件加载公钥

2012.02.09 18:47:00 信任文件加载 publicKeyTwo

2012.02.09 18:47:00 已加载密钥库 privateKey privateKey

2012.02.09 18:47:00 已加载密钥库 privateKey2 privateKeyTwo

2012.02.09 18:47:00 注册的密钥管理器数量:1*

但是当我尝试向服务器发送消息时没有任何反应......

很难找到在这种情况下真正有效的示例。

编辑 2

你好布鲁诺

你真的对这个主题、java 和 ssh 有深入的了解,我很感激你 帮助。所有这些信息都有助于我更好地理解这个问题,同时为我指明方向......非常感谢

你又是对的......我在 Java 5 JRE 上使用 Java 6 的代码

但是再次按照你的食谱我得到了这个代码:

            // Load the key store: change store type if needed
        KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
        FileInputStream fis = new FileInputStream("keyStore1");
        char[] keyPass = "passw".toCharArray();
        try {
            ks.load(fis, keyPass);
        } finally {
            if (fis != null) {
                fis.close();
            }
        }

        // Get the default Key Manager
        KeyManagerFactory kmf = KeyManagerFactory.getInstance(
                KeyManagerFactory.getDefaultAlgorithm());
        kmf.init(ks, keyPass);

        final X509KeyManager origKm = (X509KeyManager) kmf.getKeyManagers()[0];
        X509KeyManager km = new X509KeyManager() {

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

                InetAddress remoteAddress = socket.getInetAddress();

                if (remoteAddress.getHostAddress().equalsIgnoreCase("11.111.111.11")) {
                    alias = "alias1";
                    LogManager.log("Reverting to JVM CLIENT alias : " + alias, LogManager.LOG_LOWEST_LEVEL);
                } else {
                    alias = "alias2";
                    LogManager.log("Reverting to JVM CLIENT alias : " + alias, LogManager.LOG_LOWEST_LEVEL);
                }
                return alias;
            }

            public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
                // try this key manager
                String alias = origKm.chooseClientAlias(keyType, issuers, socket);
                LogManager.log("Reverting to JVM CLIENT alias : " + alias, LogManager.LOG_LOWEST_LEVEL);
                return alias;
            }

            public String[] getClientAliases(String keyType, Principal[] issues) {

                String[] cAliases = origKm.getClientAliases(keyType, issues);
                LogManager.log("Supported Client Aliases : " + cAliases.length, LogManager.LOG_LOWEST_LEVEL);
                return cAliases;

            }

            public String[] getServerAliases(String keyType, Principal[] issues) {

                String[] sAliases = origKm.getServerAliases(keyType, issues);
                LogManager.log("Supported Server Aliases: " + sAliases.length, LogManager.LOG_LOWEST_LEVEL);
                return sAliases;

            }

            public String chooseServerAlias(String keyType, Principal[] issues, Socket socket) {

                // try this key manager
                String alias = origKm.chooseServerAlias(keyType, issues, socket);
                LogManager.log("Reverting to JVM Server alias : " + alias, LogManager.LOG_LOWEST_LEVEL);
                return alias;
            }

            public X509Certificate[] getCertificateChain(String keyType) {

                // here I could specify my other keystore, keystore2 how I could do this?

                // I'm thinking in the righ way when I implemented this code to get the correct private key? 

                X509Certificate[] chain = origKm.getCertificateChain("alias1");
                if (chain == null || chain.length == 0) {
                    LogManager.log("Reverting to JVM Chain : " + keyType, LogManager.LOG_LOWEST_LEVEL);

                    return origKm.getCertificateChain("alias2");
                } else {
                    return chain;
                }
            }

            public PrivateKey getPrivateKey(String alias) {

                PrivateKey key = origKm.getPrivateKey(alias);

                // here I could get my other keystore according the alias, for example 
                // keystore2 how I could do this?

                LogManager.log("Reverting to JVM Key : " + alias, LogManager.LOG_LOWEST_LEVEL);
                return key;
            }
        };

        SSLContext sslContext = SSLContext.getInstance("SSL");
        sslContext.init(new KeyManager[]{km}, null, null);

        SSLServerSocketFactory sslSrvFact = sslContext.getServerSocketFactory();
        objServerSocket = sslSrvFact.createServerSocket(iPort);

这正是我实现目标所需要的吗?

编辑 3

使用这种方法,使用第二种方法在客户端和服务器之间进行握手 在客户端使用 public trust store2 的带有 alias2 的密钥库

...但是当我尝试在客户端中使用 trust store1 时仍然出错

...收到来自 [addr=/11.111.111.11] 的消息 恢复为 JVM 服务器别名:alias2 恢复到 JVM 密钥:alias2 检索错误: javax.net.ssl.SSLHandshakeException:收到致命警报:certificate_unknown

当客户端使用带有证书别名1的公共信任库1时,我的代码不会更改服务器以使用带有别名1的私钥1 ...

还需要做些什么来解决这个问题...我相信已经接近最终解决方案...

再次感谢!

编辑 4

Bruno,我按照您的建议更改了 getCertificateChain 方法,如下所示

public X509Certificate[] getCertificateChain(String alias) {
      X509Certificate[] chain = origKm.getCertificateChain(alias);
      return chain;
}

以及方法

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

我也删除了重复的方法...

而且上瘾的是使用旧信任存储的客户端不验证主机名

private static final HostnameVerifier DO_NOT_VERIFY = new HostnameVerifier() {

    public boolean verify(String hostname, SSLSession session) {
        return true;
    }
};

但是对于需要使用第二个信任存储的客户端进行此验证,这是一个原因,因为我需要处理密钥对证书...

EDIT5

我想知道如何实现服务器套接字以使其能够识别和使用 根据客户正在使用的证书进行正确的证书以继续 与服务器握手通信。

解释得更好,在服务器端是:

AppServerSideSocket.jar

  • 私钥库:privateKeyApp(JKS 类型,使用 keytool 生成)
  • 公钥库:publicKeyApp(JKS 类型,与所有客户端共享)

在客户端...

AppClientSideSocket.jar - 公共密钥库:publicKeyApp

AppServerSideSocket.jar 监听客户端请求并一旦收到进程 客户发送的信息

AppClientSideSocket.jar 使用 SSL 与服务器连接,使用 publicKeyApp 验证服务器主机名,并在握手后为 AppServerSideSocket 应用程序发送信息。

现在我有另一个客户端应用程序,AppClientSideSocketNEW.jar,这将验证服务器主机名 与服务器通信。在这种情况下,客户端的公共证书中使用的 CN 必须与 AppServerSideSocket.jar 所在的主机名匹配。

原来连接是在服务器端这样配置的:

if (usingSSLConn) {
    System.setProperty("javax.net.ssl.keyStore", "privateKeyApp");
    System.setProperty("javax.net.ssl.keyStorePassword", "privateKeyAppPassword");

    SSLServerSocketFactory sslServerSocketFactory = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
    ServerSocket serverSocketApp = sslServerSocketFactory.createServerSocket(Port);

} else
    serverSocketApp = new ServerSocket(Port);
}

所有客户端都收到相同的 publicKeyApp 并与服务器连接而无需验证主机名,所以没关系 如果服务器套接字应用程序 (AppServerSideSocket.jar) 安装在主机名的服务器中的服务器是 badServer1.com 和 privateKeyApp 和 publicKeyApp 中 key 的 CN 设置为 goodServer1.com,因为所有的 客户端不验证主机名或密钥的 CN 属性。

下面显示了这种连接的一个片段

 private static final HostnameVerifier DO_NOT_VERIFY = new HostnameVerifier() {

        public boolean verify(String hostname, SSLSession session) {
            return true;
        }
 };

System.setProperty("javax.net.ssl.trustStore", publicKey1);
        System.getProperties().setProperty("java.protocol.handler.pkgs", "javax.net.ssl.internal.www.protocol"); 
     HttpsURLConnection.setDefaultHostnameVerifier(DO_NOT_VERIFY);
     ...
     SOAPConnectionFactory soapConn = SOAPConnectionFactory.newInstance();
     SOAPConnection connection = soapConn.createConnection();
     ...     
     URL endpoint = new URL(hostname + ":" + port);

但是新客户端(AppClientSideSocketNEW.jar)必须做这个验证,现在需要提供一个新的证书 对于此客户端,其 CN 属性的新值反映了服务器套接字所在的正确主机名 CN。

我无权访问第二个客户端,我确信它会进行主机名验证。

所以我创建了两个新的密钥对证书(privateKeyAppNew 和 publicKeyAppNew),显然通信发生了 在使用这个新的密钥对的服务器和使用这个新的公共 publicKeyAppNew 密钥的新客户端之间成功,当然在将服务器配置为使用这个新的密钥对之后。

但我需要继续为老客户使用旧密钥对。我想知道我该如何处理。

使用密钥管理器让我能够在客户端尝试连接时验证服务器应用程序上的客户端证书,并且 选择合适的并使用正确的证书进行握手?

或者我需要在不同端口中为哪种客户端使用不同的 ssl 套接字连接?

【问题讨论】:

  • 您是说要根据连接的客户端提供不同的证书吗?您是否也尝试使用客户端证书?
  • 我认为您确实需要改进您的问题。开始很乱,但并没有变得更好。请指定您的错误消息以及您在哪一侧(客户端或服务器)获得它们。不要尝试额外的事情,这些事情必然会导致额外的不相关问题(例如,“我在 Java 5 JRE 上使用 Java 6 的代码”)。也许使用更有意义的日志消息(您的“恢复到...”消息并不真正意味着它们所在的任何地方,因为您没有“恢复”到那里的任何东西),记录 remoteAddress.getHostAddress() 以确保它是你的d期望,以及它需要哪个分支。
  • 布鲁诺,我正在努力解决这个问题,这个解决方案可以在本周拯救我的皮肤 :-D 如果你需要更多信息,我会提供更多细节......但我试图解释按照你问我的方式解决这个问题。

标签: java sockets ssl keystore


【解决方案1】:

“明显”的问题是您实际上并没有使用您创建的SSLContexts:

SSLServerSocketFactory sslSrvSockFact =
    (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();

这至少应该是:

SSLServerSocketFactory sslSrvSockFact =
    (SSLServerSocketFactory) ctx.getServerSocketFactory();

问题是你必须在一个上下文或另一个上下文之间做出选择......

您的问题的解决方案在the answer I gave to your other similar question a couple of days ago:您需要实现自己的X509KeyManager 才能选择要使用的密钥。

您是想使用单个密钥库还是从两个密钥库加载密钥/证书并不重要:如果您真的想要,您当然可以实现 getPrivateKeygetCertificateChain 以便它们加载密钥/certs 来自两个不同的密钥库,具体取决于别名。不过,这将是不必要的复杂。无论如何,您仍然需要根据别名选择做一些事情,因此您不妨使用不同的别名从单个密钥存储中加载两个密钥/证书。

从服务器的角度来看,选择一个别名(以及因此密钥/证书对)的唯一方法是使用套接字中可用的内容(如果您使用的是X509ExtendedKeyManager,则使用引擎)。由于 Java 7 不支持服务器名称指示(这将让客户端在此选择过程之前告诉它正在请求哪个主机名),您可能必须根据客户端 IP 地址或您的服务器 IP 地址执行此操作正在使用(如果您有多个)。

使用两个私钥(keystore)和两个公钥(truststore)

您似乎对密钥库和信任库是什么感到困惑。除非您打算使用客户端证书身份验证,否则您可以忽略服务器上的信任存储设置。您可以使用默认值 (null) 作为SSLContext.init(...) 的第二个参数。你的“密钥库(keystore)”是本地方(在这种情况下是你的服务器)使用的信息,“信任库(keystore)”用于确定要信任哪个远程方。

您要提供给客户的公钥(或者准确地说是证书)也在您的密钥库中,与您的私钥相关联,而不是在信任库中。

编辑:

线程“主”java.lang.NoSuchMethodError 中的异常: javax.net.ssl.SSLContext.setDefault(Ljavax/net/ssl/SSLContext;) 在 ...初始化管理器(499)

NoSuchMethodError:

如果应用程序尝试调用类的指定方法时抛出 (静态或实例),并且该类不再有定义 那个方法。

通常,编译器会捕获此错误;这个错误只能 如果类的定义不兼容,则在运行时发生 改变了。

这与您的 SSL 设置无关。不确定您在这里做了什么,但看起来您可能正在 Java 5 JRE 上使用 Java 6 的代码(Java 6 在 SSLContext 上没有 setDefault)。更重要的是,您在这里使用 Java 的一般方式似乎有问题。

javax.net.ssl.SSLException:

没有可用的证书或密钥对应于 SSL 密码套件 已启用。

这可以很好地解释为您似乎根本没有使用您初始化的SSLContexts...

  • 如果您的密钥库中有两对私钥/证书。

我的回答 here 仍然有效。我会尽量让它更明确一点。我在这里假设您的一个证书/私钥正在使用alias1 和另一个alias2。如果您不确定,请使用keyool -list 查找。您可以自行选择和设置它们。

// Load the key store: change store type if needed
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
FileInputStream fis = new FileInputStream("/path/to/keystore");
try {
    ks.load(fis, keystorePassword);
} finally {
    if (fis != null) { fis.close(); }
}

// Get the default Key Manager
KeyManagerFactory kmf = KeyManagerFactory.getInstance(
   KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, keyPassword);

final X509KeyManager origKm = (X509KeyManager)kmf.getKeyManagers()[0];
X509KeyManager km = new X509KeyManager() {
    public String chooseServerAlias(String keyType, 
                                    Principal[] issuers, Socket socket) {
        InetAddress remoteAddress = socket.getInetAddress();
        if (/* remoteAddress has some conditions you need to define yourself */ {
            return "alias1";
        } else {
            return "alias2";
        }
    }

    public String chooseClientAlias(String[] keyType, 
                                    Principal[] issuers, Socket socket) {
       // Delegate this other methods to origKm.
        origKm.chooseClientAlias(keyType, issuers, socket);
    }

    // Delegate this other methods to origKm, in the same way as 
    // it was done for chooseClientAlias.
}

SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(new KeyManager[] { km }, null, null);

SSLSocketFactory sslSocketFactory = sslContext.getSSLSocketFactory();
  • 如果您确实需要两个不同的密钥库。

做同样的事情,除此之外,在getCertificateChain(String alias) 中,根据别名选择要使用的两个密钥库中的哪一个,并使用它来获取证书链。 getPrivateKey(...) 也一样。

【讨论】:

  • 我可以使用客户端IP地址来选择正确的证书Bruno,例如...不是更容易做两个SSL/Socket连接吗?
  • @Bera,在我的例子中有一个错字,chooseServerAlias 显然(或者可能没有)覆盖了定义的方法(第一个参数是 String),没有用 @ 定义一个新的重载方法987654346@ 作为第一个参数。另外,避免getCertificateChain中的复杂逻辑:把原来的密钥管理器包装起来,用alias(最后没关系,但是你使用了错误的参数名称,可能会导致更多的混乱) .
  • 除此之外,您还应该知道客户端提供的证书中的主机名(在主题替代名称中,如果没有 SAN,则在 CN 中),必须与客户。
  • @Bera,如果您确实说明了您在问题中看到的确切错误消息,您将获得更多帮助。有太多的编辑,很难看到。在“EDIT 5”上,不清楚你得到什么样的错误......
【解决方案2】:

最简单的方法是将所有内容放在一个密钥库中 - 所有密钥和所有信任证书。这将消除你的问题。

【讨论】:

  • 这并不能真正解决选择使用哪个密钥/证书的问题。
猜你喜欢
  • 1970-01-01
  • 2018-07-19
  • 2012-02-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多