java - 无法使用Java keyStore使用PKCS#1私钥进行客户端身份验证

标签 java keystore private-key java-10 pkcs#1

我有公共密钥和私有密钥(PKCS#1:即具有“ BEGIN RSA PRIVATE KEY”)作为单独的String输入。
这些密钥是使用以下命令创建的:

openssl req -new -x509 -keyout ca-key.pem -out ca-cert.pem -days 365 -passout pass:abcd123 -subj "/C=IN/ST=KAD/L=CAT/O=MIN/OU=OPQ/CN=*.xyz.com/emailAddress=xxx.xxxx@xyz.com"
openssl genrsa -out client_private.key 2048
openssl req -new -sha256 -key client_private.key -subj "/C=IN/ST=KAD/L=CAT/O=MIN/OU=OPQ/CN=client.xyx.com" -out client.csr
openssl x509 -req -in client.csr -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial -out client.crt -days 365 -sha256
openssl x509 -in client.crt -out client.pem -outform PEM


其中client.csr的内容作为公共密钥字符串提供
并且client_private.key的内容作为私钥字符串提供

现在,我必须创建Java密钥库,添加这些密钥并实用地导出为JKS文件。
我经历了多个链接,并尝试了有弹性的城堡示例,但出现错误。

javax.net.ssl.SSLProtocolException: Handshake message sequence violation, 2


我正在编写具有SSL和启用客户端身份验证的kafka客户端,并且设置了服务器将所有必需的参数。
创建信任库和密钥库命令行可以正常工作。
但是使用Java程序我无法执行客户端身份验证(SSL可以正常工作)。
下面是代码片段:

public void initClientAuthStore(String certString, String keyString)
        throws GeneralSecurityException, IOException {
    clientAuthStore = KeyStore.getInstance(getType());
    clientAuthStore.load(null, getPassword().toCharArray());

    Certificate clientCert = CertificateHandler.getCertificate(certString);
    Certificate[] certificateChain = {trustStoreCertificate, clientCert};

    log.debug("initClientAuthStore: Certificate is created");
    // keyString.getBytes(StandardCharsets.UTF_8);
    //    clientAuthStore.setKeyEntry(
    //        keyStoreAlias, getPrivateKey(keyString), getPassword().toCharArray(), certificate);
    PrivateKey privateKey = getPrivateKey(keyString);
    clientAuthStore.setKeyEntry(
            keyStoreAlias,
            privateKey,
            getPassword().toCharArray(),
            certificateChain);

    log.debug("initClientAuthStore: setting certificate and private key is success.");
}

public PrivateKey getPrivateKey(String key) throws IOException {
    PemObject privateKeyObject;
    PemReader pemReader =
            new PemReader(
                    new InputStreamReader(
                            new ByteArrayInputStream(key.getBytes(Charset.forName("UTF-8"))),
                            StandardCharsets.UTF_8));
    privateKeyObject = pemReader.readPemObject();
    RSAPrivateCrtKeyParameters privateKeyParameter;
    if (privateKeyObject.getType().endsWith("RSA PRIVATE KEY")) {
        log.info("PRIVATE KEY TYPE: pkcs#1");
        // PKCS#1 key
        RSAPrivateKey rsa = RSAPrivateKey.getInstance(privateKeyObject.getContent());
        privateKeyParameter =
                new RSAPrivateCrtKeyParameters(
                        rsa.getModulus(),
                        rsa.getPublicExponent(),
                        rsa.getPrivateExponent(),
                        rsa.getPrime1(),
                        rsa.getPrime2(),
                        rsa.getExponent1(),
                        rsa.getExponent2(),
                        rsa.getCoefficient());
    } else if (privateKeyObject.getType().endsWith("PRIVATE KEY")) {
        log.info("PRIVATE KEY TYPE: pkcs#8");
        // PKCS#8 key
        privateKeyParameter =
                (RSAPrivateCrtKeyParameters) PrivateKeyFactory.createKey(privateKeyObject.getContent());
    } else {
        throw new RuntimeException("Unsupported key type: " + privateKeyObject.getType());
    }

    JcaPEMKeyConverter jcaPemKeyConverter = new JcaPEMKeyConverter();
    PrivateKey privateKey =
            jcaPemKeyConverter.getPrivateKey(
                    PrivateKeyInfoFactory.createPrivateKeyInfo(privateKeyParameter));
    log.info("PRIVATE KEY generated. {}", privateKey.getFormat());
    return privateKey;
}


出现以下错误:

javax.net.ssl.SSLProtocolException: Handshake message sequence violation, 2


不知道这是怎么回事,如果我缺少任何编码/解码等。
我将PKCS#1私钥作为String输入,并且必须使用充气城堡转换为PKCS#8并生成私钥并添加到存储中。
注意:私钥是client_private.key的内容

最佳答案

(不是答案,但太多的评论。)


  其中client.csr的内容作为公共密钥字符串提供...


CSR不是公钥,而是CSR。但是您清楚显示的代码没有使用它。显然,它确实使用了证书,它也不是公钥,而是一个证书-包含公钥。但是,证书(而不是公钥或CSR)才是用于SSL / TLS身份验证的正确方法。


  我将PKCS#1私钥作为String输入,并且必须使用充气城堡转换为PKCS#8并生成私钥并添加到存储中。


您实际上不需要转换为PKCS8; PEMParser和JcaPEMKeyConvertor可以将PKCS1直接转换为包含可用(RSA)PrivateKey的KeyPair,这非常简单。或者,OpenSSL可以轻松创建标准JCE(没有BouncyCastle)可以读取的PKCS8密钥,或者就此而言,已经是Java密钥库的PKCS12无需任何形式的转换。但是,您所拥有的确实有效。


  现在,我必须创建Java密钥库,添加这些密钥并实用地导出为JKS文件。


撇开:您可能是通过程序而非实用的意思。您的代码不会导出任何密钥库文件,并且不需要;并且您不需要专门使用JKS密钥库,尽管您不显示进行连接和使用密钥库的代码,但任何受支持的内存中密钥库都可以使用。从Java 9(大约两年前)开始,Oracle建议人们停止使用JKS,而改用PKCS12,尽管我怀疑他们实际上是否会很快放弃JKS。


  javax.net.ssl.SSLProtocolException:握手消息序列冲突,2


那是您的实际问题,它不是密钥库或密钥,也不应该是证书。即使您发送的密钥和证书不受欢迎,服务器也应通过拒绝协议内的身份验证(而不违反协议)来做出响应。 2是ServerHello,它仅应在证书发送或检查任一方向之前发生。请至少执行以下一项操作:


standard Java SSL/TLS debugging:使用sysprop javax.net.debug=ssl,handshake运行并捕获/记录输出。 (在Java 11以下,您实际上只能使用ssl,它会自动包含handshake。)这将准确显示系统正在发送和接收的内容,并允许确定错误的原因,这至少应有助于确定原因。
使用wireshark,tcpdump等网络跟踪捕获握手,然后进行查看。即使Java中有严重错误(不应该发生),也可以使用,但是至少很难解码,并且如果您不太谨慎,可能会在同一时间段内包含网络上的其他数据可能是敏感且不可共享的。
如果可能,请尝试使用相同的密钥+证书并使用同一台计算机与其他工具进行连接-这可能是与网络相关的问题,具体取决于您从何处进行连接。由于您具有openssl并且已经具有OpenSSL格式的文件,因此最简单的方法是使用

openssl s_client -connect $host:$port -key keyfile -cert certfile [see text]

s_client也很不常见(唯一的AFAIK),因为您可以控制是否发送SNI(服务器名称指示扩展名),并且对于当今的许多服务器而言,它们很可能会影响服务器的行为(可能触发或抑制错误)。 Java(JSSE)是否发送SNI通常取决于您没有告诉我们的几个因素的组合:始终是Java的版本,通常是进行连接尝试的代码的某些细节,尤其是它是否使用HttpsURLConnection,新的- j11 + HttpClient,Apache或google等第三方代码或自定义代码。对于通过1.1.0发行的OpenSSL版本,默认情况下不发送SNI,您必须添加-servername $host来发送它。对于1.1.1,默认情况下将发送它,但是如果添加-noservername,则不会发送。

在此示例中,我没有指定信任库。 s_client默认情况下将针对其默认信任库进行验证,该信任库对于您的服务器可能是正确的,也可能是正确的,但是将忽略任何错误并继续执行。如果要调试证书信任问题,那么将OpenSSL的信任库与Java所使用的信任库进行匹配非常重要,但并非针对您的问题。
请与服务器操作员联系,以最终解决此问题的方式:他们是否认为您的握手有问题?如果是,该怎么办?它们正在运行什么软件(或更通常是更相关的中间件),以及什么配置或选项?


如果您在Q中添加(部分/足够)此信息,我将尝试将其更新为实际答案。

关于java - 无法使用Java keyStore使用PKCS#1私钥进行客户端身份验证,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56876965/

相关文章:

java - keystore 密码被篡改

php - openssl_pkey_new() 抛出错误——为 php 正确设置 openssl.cnf

java - 私钥分发技术?

android - 不同帐户在 Google Play 商店发布的两个 apk 文件是否可以使用相同的 key / keystore 进行签名?

java - 如何同时获得不可抵赖证书和数字签名证书

ssl - 如何将 .p12 文件更改为 .key 文件

java - AWS java lambda 中的 org.postgresql.util.PSQLException : FATAL: database "<<database_name>>" does not exist,

java - 在Android中生成PDF,无需任何第三方库

java - 找不到类 Apache Tomcat

java - 如何在 Java 中复制 zip 文件