[spring-projects/spring-boot]使用除 secp384r1 之外的椭圆曲线 https 会失败

2024-06-26 510 views
4
无法在 Spring boot 第一个环境详细信息中使用椭圆曲线自签名证书

JDK 17 Spring Boot 3.0.2 Spring Web Tomcat(内置)Mac OS 13.x

第二环境细节

JDK 17 Spring Boot 3.0.2 Spring Webflux Netty(内置)Mac OS 13.x

第三环境详情

JDK 17 Spring Boot 2.7.8 Spring Web Tomcat(内置)Mac OS 13.x

对于 tomcat 服务器,我收到以下错误:-
java.io.IOException: EOF during handshake.
    at org.apache.tomcat.util.net.SecureNioChannel.handshakeUnwrap(SecureNioChannel.java:470) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
    at org.apache.tomcat.util.net.SecureNioChannel.handshake(SecureNioChannel.java:213) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1709) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
    at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
    at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
    at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]

2023-02-19T16:25:08.523+05:30 DEBUG 43160 --- [nio-8443-exec-5] o.apache.coyote.http11.Http11Processor   : Socket: [org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper@5e2b7fa0:org.apache.tomcat.util.net.SecureNioChannel@27f27fb1:java.nio.channels.SocketChannel[connected local=/[0:0:0:0:0:0:0:1]:8443 remote=/[0:0:0:0:0:0:0:1]:55280]], Status in: [CONNECT_FAIL], State out: [CLOSED]
2023-02-19T16:25:08.523+05:30 DEBUG 43160 --- [nio-8443-exec-5] o.apache.tomcat.util.threads.LimitLatch  : Counting down[https-jsse-nio-8443-exec-5] latch=1
2023-02-19T16:25:08.523+05:30 DEBUG 43160 --- [nio-8443-exec-5] org.apache.tomcat.util.net.NioEndpoint   : Calling [org.apache.tomcat.util.net.NioEndpoint@3f6fedae].closeSocket([org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper@5e2b7fa0:org.apache.tomcat.util.net.SecureNioChannel@27f27fb1:java.nio.channels.SocketChannel[connected local=/[0:0:0:0:0:0:0:1]:8443 remote=/[0:0:0:0:0:0:0:1]:55280]])
2023-02-19T16:25:08.524+05:30 ERROR 43160 --- [nio-8443-exec-5] org.apache.tomcat.util.net.NioEndpoint   : Failed to close channel

java.io.IOException: Invalid close state, will not send network data.
    at org.apache.tomcat.util.net.SecureNioChannel.close(SecureNioChannel.java:540) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
    at org.apache.tomcat.util.net.SecureNioChannel.close(SecureNioChannel.java:555) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
    at org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper.doClose(NioEndpoint.java:1208) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
    at org.apache.tomcat.util.net.SocketWrapperBase.close(SocketWrapperBase.java:425) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1741) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
    at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
    at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
    at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]
对于 netty 来说略有不同,但几乎相同
ava.nio.channels.ClosedChannelException: null
    at io.netty.handler.ssl.SslHandler.channelInactive(SslHandler.java:1065) ~[netty-handler-4.1.87.Final.jar:4.1.87.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelInactive(AbstractChannelHandlerContext.java:305) ~[netty-transport-4.1.87.Final.jar:4.1.87.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelInactive(AbstractChannelHandlerContext.java:281) ~[netty-transport-4.1.87.Final.jar:4.1.87.Final]
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelInactive(AbstractChannelHandlerContext.java:274) ~[netty-transport-4.1.87.Final.jar:4.1.87.Final]
    at io.netty.channel.DefaultChannelPipeline$HeadContext.channelInactive(DefaultChannelPipeline.java:1405) ~[netty-transport-4.1.87.Final.jar:4.1.87.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelInactive(AbstractChannelHandlerContext.java:301) ~[netty-transport-4.1.87.Final.jar:4.1.87.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelInactive(AbstractChannelHandlerContext.java:281) ~[netty-transport-4.1.87.Final.jar:4.1.87.Final]
    at io.netty.channel.DefaultChannelPipeline.fireChannelInactive(DefaultChannelPipeline.java:901) ~[netty-transport-4.1.87.Final.jar:4.1.87.Final]
    at io.netty.channel.AbstractChannel$AbstractUnsafe$7.run(AbstractChannel.java:813) ~[netty-transport-4.1.87.Final.jar:4.1.87.Final]
    at io.netty.util.concurrent.AbstractEventExecutor.runTask(AbstractEventExecutor.java:174) ~[netty-common-4.1.87.Final.jar:4.1.87.Final]
    at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:167) ~[netty-common-4.1.87.Final.jar:4.1.87.Final]
    at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:470) ~[netty-common-4.1.87.Final.jar:4.1.87.Final]
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:566) ~[netty-transport-4.1.87.Final.jar:4.1.87.Final]
    at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997) ~[netty-common-4.1.87.Final.jar:4.1.87.Final]
    at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.87.Final.jar:4.1.87.Final]
    at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.87.Final.jar:4.1.87.Final]
    at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]

2023-02-19T18:17:29.840+05:30 DEBUG 45870 --- [ctor-http-nio-3] r.netty.transport.ServerTransport        : [2dbfdbf8, L:/[0:0:0:0:0:0:0:1]:8443 ! R:/[0:0:0:0:0:0:0:1]:56166] onUncaughtException(SimpleConnection{channel=[id: 0x2dbfdbf8, L:/[0:0:0:0:0:0:0:1]:8443 ! R:/[0:0:0:0:0:0:0:1]:56166]})

java.nio.channels.ClosedChannelException: null
    at io.netty.handler.ssl.SslHandler.channelInactive(SslHandler.java:1065) ~[netty-handler-4.1.87.Final.jar:4.1.87.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelInactive(AbstractChannelHandlerContext.java:305) ~[netty-transport-4.1.87.Final.jar:4.1.87.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelInactive(AbstractChannelHandlerContext.java:281) ~[netty-transport-4.1.87.Final.jar:4.1.87.Final]
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelInactive(AbstractChannelHandlerContext.java:274) ~[netty-transport-4.1.87.Final.jar:4.1.87.Final]
    at io.netty.channel.DefaultChannelPipeline$HeadContext.channelInactive(DefaultChannelPipeline.java:1405) ~[netty-transport-4.1.87.Final.jar:4.1.87.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelInactive(AbstractChannelHandlerContext.java:301) ~[netty-transport-4.1.87.Final.jar:4.1.87.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelInactive(AbstractChannelHandlerContext.java:281) ~[netty-transport-4.1.87.Final.jar:4.1.87.Final]
    at io.netty.channel.DefaultChannelPipeline.fireChannelInactive(DefaultChannelPipeline.java:901) ~[netty-transport-4.1.87.Final.jar:4.1.87.Final]
    at io.netty.channel.AbstractChannel$AbstractUnsafe$7.run(AbstractChannel.java:813) ~[netty-transport-4.1.87.Final.jar:4.1.87.Final]
    at io.netty.util.concurrent.AbstractEventExecutor.runTask(AbstractEventExecutor.java:174) ~[netty-common-4.1.87.Final.jar:4.1.87.Final]
    at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:167) ~[netty-common-4.1.87.Final.jar:4.1.87.Final]
    at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:470) ~[netty-common-4.1.87.Final.jar:4.1.87.Final]
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:566) ~[netty-transport-4.1.87.Final.jar:4.1.87.Final]
    at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997) ~[netty-common-4.1.87.Final.jar:4.1.87.Final]
    at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.87.Final.jar:4.1.87.Final]
    at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.87.Final.jar:4.1.87.Final]
    at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]
Spring Boot 应用程序属性
server.ssl.enabled=true
server.port=8443
server.ssl.certificate=/path-to/cert.pem
server.ssl.certificate-private-key=/path-to/private-key.pem
生成自签名证书的步骤如下:-
openssl ecparam -name prime256v1 -genkey -noout -out private-key.pem

openssl ec -in private-key.pem -pubout -out public-key.pem

openssl req -new -x509 -key private-key.pem -out cert.pem -days 360

示例证书和密钥文件如下:-

證書:-

-----BEGIN CERTIFICATE-----
MIIB5zCCAYwCCQDIZlhtOQc80jAKBggqhkjOPQQDAjB7MQswCQYDVQQGEwJJTjEL
MAkGA1UECAwCUEIxDzANBgNVBAcMBk1PSEFMSTENMAsGA1UECgwETmV1dzENMAsG
A1UECwwETmV1dzESMBAGA1UEAwwJbG9jYWxob3N0MRwwGgYJKoZIhvcNAQkBFg10
ZXN0QHRlc3QuY29tMB4XDTIzMDIxOTA5MjM0M1oXDTI0MDIxOTA5MjM0M1owezEL
MAkGA1UEBhMCSU4xCzAJBgNVBAgMAlBCMQ8wDQYDVQQHDAZNT0hBTEkxDTALBgNV
BAoMBE5ldXcxDTALBgNVBAsMBE5ldXcxEjAQBgNVBAMMCWxvY2FsaG9zdDEcMBoG
CSqGSIb3DQEJARYNdGVzdEB0ZXN0LmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEH
A0IABITJYdvCvlkdu+IyVISe+KqoplP1A8f26hpKecqxkS5U2VDd8JaqhsPWlC/Y
bLbBAg6DibTEWMTK8Ab5+aE/ZgIwCgYIKoZIzj0EAwIDSQAwRgIhANS1nxKGXevL
i9jkaXZiFh5IZ2DMTDwQ5ETduj7X/ZMaAiEAntNWKb+SF299sxA5Hv6i0oYK1Gl6
nh82yO5SqCvjECk=
-----END CERTIFICATE-----

钥匙:-

-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIDsXD9BbsGGOnFjbMCDIXjGPI4rqDKGqlRUH4oiWxOjboAoGCCqGSM49
AwEHoUQDQgAEhMlh28K+WR274jJUhJ74qqimU/UDx/bqGkp5yrGRLlTZN3wlqqG
w9aUL9hstsECDoOJtMRYxMrwBvn5oT9mAg==
-----END EC PRIVATE KEY-----

另一个奇怪的部分是 - 相同的证书适用于 nodejs - expressJS

如果这是一个错误,那么是否存在与 SSL 相关的缺少配置或 JDK 支持问题?

回答

9

如果您希望我们花一些时间进行调查,请花时间提供一个完整的最小样本(我们可以解压缩或 git 克隆、构建和部署的样本),以重现该问题。

7

只需添加一些额外的信息@mhalbritter - RSA类型证书/密钥即可一次性完成

另一方面,椭圆曲线要么有一些 JDK 配置要求或密码内容,要么是我们不知道的东西

0

这看起来像是我们的一个错误PrivateKeyParser,我们在中对 secp384r1 曲线进行了硬编码org.springframework.boot.web.server.PrivateKeyParser#EC_PARAMETERS。您的私钥使用 prime256v1 曲线,解析PrivateKey使用 secp384r1 曲线。

这与 curl 给我的错误消息相符:

curl: (35) error:0A00017A:SSL routines::wrong curve

使用这个作品:

openssl ecparam -name secp384r1 -genkey -noout -out private-key.pem

openssl ec -in private-key.pem -pubout -out public-key.pem

openssl req -new -x509 -key private-key.pem -out cert.pem -days 360
0

所有曲线都支持哪些?

3

曲线secp384r1起作用了。

5

PrivateKeyParserspring boot 2.7.x 和 3.0.x 有共同点吗?

9

抱歉,我无意中点击了关闭和评论,按钮的位置确实有点不对

0

是的,2.7.x 和 3.0.x 中的 EC 解析代码是相同的,这就是我将其标记为 2.7.x 错误的原因。

2

抱歉再次打扰您,您能分享一下仅支持曲线的规范定义的地方吗!

9

JDK 默认支持大量曲线。从 Java 8 开始使用以下代码:

String attribute = Security.getProviders("AlgorithmParameters.EC")[0]
        .getService("AlgorithmParameters", "EC").getAttribute("SupportedCurves");
for (String curve : attribute.split("\\|")) {
    System.out.println(curve.substring(1, curve.length() - 1));
}
secp112r1,1.3.132.0.6
secp112r2,1.3.132.0.7
secp128r1,1.3.132.0.28
secp128r2,1.3.132.0.29
secp160k1,1.3.132.0.9
secp160r1,1.3.132.0.8
secp160r2,1.3.132.0.30
secp192k1,1.3.132.0.31
secp192r1,NIST P-192,X9.62 prime192v1,1.2.840.10045.3.1.1
secp224k1,1.3.132.0.32
secp224r1,NIST P-224,1.3.132.0.33
secp256k1,1.3.132.0.10
secp256r1,NIST P-256,X9.62 prime256v1,1.2.840.10045.3.1.7
secp384r1,NIST P-384,1.3.132.0.34
secp521r1,NIST P-521,1.3.132.0.35
X9.62 prime192v2,1.2.840.10045.3.1.2
X9.62 prime192v3,1.2.840.10045.3.1.3
X9.62 prime239v1,1.2.840.10045.3.1.4
X9.62 prime239v2,1.2.840.10045.3.1.5
X9.62 prime239v3,1.2.840.10045.3.1.6
sect113r1,1.3.132.0.4
sect113r2,1.3.132.0.5
sect131r1,1.3.132.0.22
sect131r2,1.3.132.0.23
sect163k1,NIST K-163,1.3.132.0.1
sect163r1,1.3.132.0.2
sect163r2,NIST B-163,1.3.132.0.15
sect193r1,1.3.132.0.24
sect193r2,1.3.132.0.25
sect233k1,NIST K-233,1.3.132.0.26
sect233r1,NIST B-233,1.3.132.0.27
sect239k1,1.3.132.0.3
sect283k1,NIST K-283,1.3.132.0.16
sect283r1,NIST B-283,1.3.132.0.17
sect409k1,NIST K-409,1.3.132.0.36
sect409r1,NIST B-409,1.3.132.0.37
sect571k1,NIST K-571,1.3.132.0.38
sect571r1,NIST B-571,1.3.132.0.39
X9.62 c2tnb191v1,1.2.840.10045.3.0.5
X9.62 c2tnb191v2,1.2.840.10045.3.0.6
X9.62 c2tnb191v3,1.2.840.10045.3.0.7
X9.62 c2tnb239v1,1.2.840.10045.3.0.11
X9.62 c2tnb239v2,1.2.840.10045.3.0.12
X9.62 c2tnb239v3,1.2.840.10045.3.0.13
X9.62 c2tnb359v1,1.2.840.10045.3.0.18
X9.62 c2tnb431r1,1.2.840.10045.3.0.20
brainpoolP160r1,1.3.36.3.3.2.8.1.1.1
brainpoolP192r1,1.3.36.3.3.2.8.1.1.3
brainpoolP224r1,1.3.36.3.3.2.8.1.1.5
brainpoolP256r1,1.3.36.3.3.2.8.1.1.7
brainpoolP320r1,1.3.36.3.3.2.8.1.1.9
brainpoolP384r1,1.3.36.3.3.2.8.1.1.11
brainpoolP512r1,1.3.36.3.3.2.8.1.1.13
6

带有标头的私钥的-----BEGIN EC PRIVATE KEY-----格式与 PKCS#1 类似,但结构略有不同(此处对差异进行了很好的解释)。PKCS#8 EC 私钥具有标头-----BEGIN PRIVATE KEY-----

Spring Boot 支持具有所有曲线的 PKCS#8 EC 私钥,如PrivateKeyParser 的单元测试所示。Spring Boot 对 PKCS#1 和 EC 模拟的支持有限,secp384r1目前仅支持曲线。

在非 PKCS#8 私钥中支持额外的 EC 曲线需要 Boot 对密钥内容进行更多的解析和解码。我认为这是一种增强,而不是错误,而且鉴于 PKCS#8 是广受欢迎的格式,我不确定这样做是否值得。

可以修改原始报告中显示的创建私钥和证书的步骤以创建 PKCS#8 密钥,如以下示例所示:

openssl ecparam -name prime256v1 -genkey -noout -out private-key-ec.pem

openssl pkcs8 -topk8 -in private-key-ec.pem -nocrypt -out private-key.pem

openssl ec -in private-key.pem -pubout -out public-key.pem

openssl req -new -x509 -key private-key.pem -out cert.pem -days 360
4

我们今天讨论了这个问题,并认为这应该成为一个文档问题,指导人们使用 PKCS8。剩下的最后一件事是决定如何处理我们对BEGIN EC PRIVATE KEY密钥的有限支持。我们可以保留它并记录它的局限性,或者我们可能要考虑在未来弃用它并删除它。我们认为不值得尝试支持BEGIN EC PRIVATE KEY具有所有可能曲线的密钥。

9

我今天对此进行了更深入的研究,发现从数据中提取参数并不需要太多额外的代码。鉴于我们根据用户的要求添加了 EC PRIVATE KEY 支持(#32646),我认为如果可以的话,我们应该尝试暂时保留它。

我已经打开#37170 来改进我们的文档。

7

你会考虑实现Bouncy Castle 库吗?它们在MIT 许可证下运行。

这可以解决您的 PEM 解析问题:DOCs - PEMParser

所有受支持的椭圆曲线的列表
Iterator<String> it = ECNamedCurveTable.getNames().asIterator();
while (it.hasNext()) {
    System.out.println(it.next());
}
c2pnb272w1
c2tnb191v3
c2pnb208w1
c2tnb191v2
c2tnb191v1
prime192v3
c2tnb359v1
prime192v2
prime192v1
c2tnb239v3
c2pnb163v3
c2tnb239v2
c2pnb163v2
c2tnb239v1
c2pnb163v1
c2pnb176w1
prime256v1
c2pnb304w1
c2pnb368w1
c2tnb431r1
prime239v3
prime239v2
prime239v1
sect283r1
sect283k1
sect163r2
secp256k1
secp160k1
secp160r1
secp112r2
secp112r1
sect113r2
sect113r1
sect239k1
secp128r2
sect163r1
secp128r1
sect233r1
sect163k1
sect233k1
sect193r2
sect193r1
sect131r2
sect131r1
secp256r1
sect571r1
sect571k1
secp192r1
sect409r1
sect409k1
secp521r1
secp384r1
secp224r1
secp224k1
secp192k1
secp160r2
B-163
P-521
P-256
K-163
B-233
P-224
P-384
K-233
B-409
B-283
B-571
K-409
K-283
P-192
K-571
brainpoolP224t1
brainpoolP512t1
brainpoolP224r1
brainpoolP512r1
brainpoolP192t1
brainpoolP384t1
brainpoolP192r1
brainpoolP384r1
brainpoolP160t1
brainpoolP320t1
brainpoolP160r1
brainpoolP320r1
brainpoolP256t1
brainpoolP256r1
FRP256v1
Tc26-Gost-3410-12-256-paramSetA
Tc26-Gost-3410-12-512-paramSetC
GostR3410-2001-CryptoPro-C
Tc26-Gost-3410-12-512-paramSetB
GostR3410-2001-CryptoPro-B
Tc26-Gost-3410-12-512-paramSetA
GostR3410-2001-CryptoPro-A
GostR3410-2001-CryptoPro-XchB
GostR3410-2001-CryptoPro-XchA
wapip192v1
sm2p256v1
9

我非常喜欢您的文档,因此对此主题提出最后一个建议:您可以将支持的曲线列表添加到您的文档中。这将为任何开发人员节省一些时间。也许更好的是直接链接到Java DOC - 支持的椭圆曲线名称。使用这种方法,您可以将问题完全转移给 Java,而不必担心任何事情。但在这里要小心,因为我不知道 OpenJDK 是否使用与 Oracle 相同的实现。链接是 Oracles 文档。遗憾的是,OpenJDK 没有任何可比性。我刚刚找到了这个概述