Spring 引导导致使用自签名证书的 SSL 对等握手失败

Spring Boot causes SSL peer handshake failure with self-signed certs

我创建了一个 GitHub project that perfectly reproduces 这里解释的一切。


我正在构建一个 Spring 引导应用程序(带有嵌入式 Jetty Web 容器),并试图让它在本地 运行 时提供自签名的 OpenSSL 证书(当 运行在暂存或生产环境中,应用程序将提供根 CA 签名证书)。

因此,为了创建 SSL 功能,我首先创建了 public key/CSR,方法是:

openssl req -x509 -newkey rsa:4096 -keyout myapp-key.pem -out myapp-csr.pem -days 3650

然后我创建了 JKS 并像这样导入了我的 public 密钥:

keytool -importcert -trustcacerts -file myapp-csr.pem -alias myorg -keystore myapp.jks

然后我用 SSL 属性更新了我的 application.yml

ssl:
  key-store: 'myapp.jks'
  key-store-password: '123456'
  key-password: '123456' 

然后我 运行 我的 Spring 启动应用程序:

./gradlew build && java -Dspring.config=. -jar build/libs/spring-boot-troubleshooting.jar

到目前为止一切顺利 -- 启动时没有 errors/exceptions/warnings。然后我打开一个新终端和 运行:

curl -k -H "Content-Type: application/json" -X GET https://localhost:9200/health
curl: (35) SSL peer handshake failed, the server most likely requires a client certificate to connect

如果我切换回应用 运行ning 所在的终端,我现在会在控制台输出中看到:

06:14:00.149 [qtp1706099897-15] WARN  org.eclipse.jetty.http.HttpParser - Illegal character 0x16 in state=START for buffer HeapByteBuffer@5b876b3[p=1,l=175,c=8192,r=174]={\x16<<<\x03\x01\x00\xAa\x01\x00\x00\xA6\x03\x03ZL\xBa\xF8T\x86r...\x00\x05\x01\x00\x00\x00\x00\x00\x12\x00\x00\x00\x17\x00\x00>>>-Type: applicatio...\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00}

-k 是有意忽略 SSL 因为这是一个自签名证书。我试过使用和不使用 -k,结果是一样的。

我看到了 evidence that this could be a MacOS issue for some reason,但坦率地说,我什至不确定从哪里开始寻找。我创建 public/private 密钥对的方式有问题吗?我将 public key/CSR 导入 JKS 文件的方式有问题吗?我的配置还有其他问题吗?

None 这个说得有道理。

  • 您根本不需要使用 openssl
  • CSR 不是证书,更不是 public 密钥。
  • 将 CSR 导入 KeyStore 的结果不是有效的 KeyStore,因为没有私钥。

你只需要用到keytool,别的不说,如下:

  1. keytool -genkey ...
  2. keytool -selfcert ...

    两次使用相同的别名。

  3. keytool -export ...

  4. keytool -import ... 进入客户的 truststore 文件。

要执行 SSL 握手,服务器密钥库必须同时包含 public 证书和私钥。最简单的方法是用私钥和证书创建一个 p12 包,然后使用 importkeystore 命令一次导入它们。

openssl req -x509 -newkey rsa:4096 -keyout myapp-key.pem -out myapp-csr.pem -days 3650 -subj '/CN=localhost'
openssl pkcs12 -export -in myapp-csr.pem -inkey myapp-key.pem -out keystore.p12
keytool -importkeystore -deststorepass 123456 -destkeystore keystore.jks -srckeystore keystore.p12 -srcstoretype PKCS12

现在您可以使用 curl 查询您的服务器了。

curl -k -H "Content-Type: application/json" -X GET https://localhost:9200/health
curl --cacert myapp-csr.pem -H "Content-Type: application/json" https://localhost:9200/health

所以基本上你的配置是正确的,除了在密钥库中缺少私钥。

@EJP 很好地解释了根本原因。您的 myapp.jks 文件中没有私钥。

我可以把解决方法说的很详细。有3种解决方案。我用你的代码和 java 8.

测试了它们
  1. 只有 keytool - 最快的

    删除现有 myapp.jks 并使用这些参数通过 keytool 重新生成。

    keytool -genkeypair -dname "cn=Name Surname, ou=MyUnit, o=MyOrg, c=US" -alias myorg -keypass 123456 -storepass 123456 -validity 365 -keyalg RSA -keystore myapp.jks

  2. 仅 openssl

    通过名为"myapp.p12".

    的openssl创建pkcs文件

    openssl pkcs12 -inkey myapp-key.pem -in myapp-csr.pem -export -out myapp.p12;

    将 application.yml 密钥库:'myapp.jks' 更改为密钥库:'myapp.p12'

  3. 它们的组合(当前方法)。

    通过名为 "myapp.p12" 的 openssl 创建 pkcs 文件。

    openssl pkcs12 -inkey myapp-key.pem -in myapp-csr.pem -export -out myapp.p12;

    导入jks文件

    keytool -importkeystore -srckeystore myapp.p12 -srcstoretype pkcs12 -destkeystore myapp.jks -deststoretype JKS