TLS 1.2 握手失败并出现握手失败错误

TLS 1.2 handhsake failing with Handshake failure error

我正在尝试与基于 Java 的 MQTT 代理(Active MQ)建立 TLSV1.2 连接。
我的客户在 HSM 模块中有私钥,因此我无法访问。
x509 证书可用。
CA 是自签名的。

证书详情:
签名算法: ecdsa-with-SHA256
Public密钥算法:id-ecPublic密钥(256位)
曲线: prime256v1

观察到的行为:

服务器抛出以下错误:

43 2022-04-01 23:22:26.772084 serverip clientip TLSv1.2 73 Alert (Level: Fatal, Description: Handshake Failure)

这是在客户端密钥交换之后发生的

41 2022-04-01 23:22:26.739193 clientip serverip TLSv1.2 303 Client Key Exchange, Certificate Verify, Change Cipher Spec, Encrypted Handshake Message

到目前为止我已经尝试过:

我尝试使用由同一 CA 签名的 x509 证书通过 curl 与同一代理建立连接。这正在被接受。因此我得出结论,这不是一个糟糕的证书问题。

注意: 我在 curl 命令中使用 --insecure,我对此没意见,因为客户端身份验证是我不关心的事情,因为服务器正在抛出一个错误。

Observations/Assumptions:

握手失败来自服务器:

Client Hello  
Server Hello  
Server Hello, Certificate, Server Key Exchange, Certificate Request, ServerHelloDone
Certificate
Client Key Exchange, Certificate Verify, Change Cipher Spec, Encrypted Handshake Message  
Alert (Level: Fatal, Description: Handshake Failure) ( from server)

由此我得出结论,密码套件/协议兼容性没有问题。

我看到 HSM 生成的 X509 证书与我为验证生成的证书之间的唯一区别是通用名称格式。
Wireshark 说:

我从未如此深入地研究过握手,只知道 TLS 握手的基础知识。

服务器是托管的 Kubernetes 部署,因此目前很难将日志与客户端相关联。

但是,根据观察,我看到了这条消息 (activemq-netty-threads)","message":"AMQ222208: SSL handshake failed for client. java.io.IOException: Sequence tag error."

我认为这是相应的代理日志。
任何前进的方向/意见表示赞赏。

解析 x509 证书的结果

  1. HSM 证书 CN=A_00000066,OU=AA,O=Organisation Limited,L=Place,ST=State,C=C
  2. FS 证书
1.2.840.113549.1.9.1=#160a726f6f74406174686572,CN=A_00000066,OU=AA,O=O,L=Place,ST=State,C=C

添加相关服务器日志

服务器上的 SSL 日志:

"throwable" : {
  java.security.SignatureException: Invalid encoding for signature
    at java.base/sun.security.util.ECUtil.decodeSignature(ECUtil.java:279)
    at jdk.crypto.ec/sun.security.ec.ECDSASignature.engineVerify(ECDSASignature.java:477)
    at java.base/java.security.Signature$Delegate.engineVerify(Signature.java:1247)
    at java.base/java.security.Signature.verify(Signature.java:675)
    at java.base/sun.security.ssl.CertificateVerify$T12CertificateVerifyMessage.<init>(CertificateVerify.java:651)
    at java.base/sun.security.ssl.CertificateVerify$T12CertificateVerifyConsumer.consume(CertificateVerify.java:771)
    at java.base/sun.security.ssl.SSLHandshake.consume(SSLHandshake.java:392)
    at java.base/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:443)
    at java.base/sun.security.ssl.SSLEngineImpl$DelegatedTask$DelegatedAction.run(SSLEngineImpl.java:1074)
    at java.base/sun.security.ssl.SSLEngineImpl$DelegatedTask$DelegatedAction.run(SSLEngineImpl.java:1061)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:689)
    at java.base/sun.security.ssl.SSLEngineImpl$DelegatedTask.run(SSLEngineImpl.java:1008)
    at io.netty.handler.ssl.SslHandler.runAllDelegatedTasks(SslHandler.java:1542)
    at io.netty.handler.ssl.SslHandler.runDelegatedTasks(SslHandler.java:1556)
    at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1440)
    at io.netty.handler.ssl.SslHandler.decodeJdkCompatible(SslHandler.java:1267)
    at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1314)
    at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:501)
    at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:440)
    at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:276)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
    at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
    at io.netty.channel.epoll.AbstractEpollStreamChannel$EpollStreamUnsafe.epollInReady(AbstractEpollStreamChannel.java:792)
    at io.netty.channel.epoll.EpollEventLoop.processReady(EpollEventLoop.java:475)
    at io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:378)
    at io.netty.util.concurrent.SingleThreadEventExecutor.run(SingleThreadEventExecutor.java:989)
    at io.netty.util.internal.ThreadExecutorMap.run(ThreadExecutorMap.java:74)
    at org.apache.activemq.artemis.utils.ActiveMQThreadFactory.run(ActiveMQThreadFactory.java:118)
  Caused by: java.io.IOException: Sequence tag error
    at java.base/sun.security.util.DerInputStream.getSequence(DerInputStream.java:336)
    at java.base/sun.security.util.ECUtil.decodeSignature(ECUtil.java:255)
    ... 32 more}

)
javax.net.ssl|WARNING|32|Thread-4 (activemq-netty-threads)|2022-04-06 10:01:32.559 UTC|SSLEngineOutputRecord.java:168|outbound has closed, ignore outbound application data

更新二:

签名交给rustls库。 有一个自定义函数使用 HSM 私钥根据需要对摘要进行签名。

签名示例: 0349003046022100838bde8a902f9ebb18cdd9bc5af263dc978a670d95770c11e2e8d29e3c7b2c28022100d345fa7245fb34c8cf710958da80a638c598c44e2cbd724571dfd9e9ade95008

发生失败后的实际客户端加密握手消息:

Consuming ECDHE ClientKeyExchange handshake message (
"ECDH ClientKeyExchange": {
  "ecdh public": {
    0000: 04 EB B8 76 96 C5 E0 C6   20 73 F0 4C AB 93 F1 A6  ...v.... s.L....
    0010: E9 6C 64 B0 BB 72 64 A4   74 75 26 4B E2 79 C0 26  .ld..rd.tu&K.y.&
    0020: 42 C8 C8 8F D4 C5 CA EC   22 DA B5 3B 03 E8 E8 19  B......."..;....
    0030: 28 28 EF C6 9D EE 80 3A   CD A1 60 2B 62 83 52 8F  ((.....:..`+b.R.
    0040: 23 B4 5B 46 1F 76 86 00   0D DF F3 1E 6B 86 01 A4  #.[F.v......k...
    0050: 64 09 C9 80 0A 03 C6 EE   A4 AA 36 05 F4 45 7A 91  d.........6..Ez.
    0060: A5                                                 .
  },
}

更新 2: 已更新堆栈跟踪

javax.net.ssl|ERROR|41|Thread-16 (activemq-netty-threads)|2022-04-11 10:39:02.983 UTC|TransportContext.java:312|Fatal (HANDSHAKE_FAILURE): Invalid CertificateVerify signature (
"throwable" : {
  javax.net.ssl.SSLHandshakeException: Invalid CertificateVerify signature
      at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:131)
      at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:117)
      at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:307)
      at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:263)
      at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:254)
      at java.base/sun.security.ssl.CertificateVerify$T13CertificateVerifyMessage.<init>(CertificateVerify.java:978)
      at java.base/sun.security.ssl.CertificateVerify$T13CertificateVerifyConsumer.consume(CertificateVerify.java:1125)
      at java.base/sun.security.ssl.SSLHandshake.consume(SSLHandshake.java:392)
      at java.base/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:443)
      at java.base/sun.security.ssl.SSLEngineImpl$DelegatedTask$DelegatedAction.run(SSLEngineImpl.java:1074)
      at java.base/sun.security.ssl.SSLEngineImpl$DelegatedTask$DelegatedAction.run(SSLEngineImpl.java:1061)
      at java.base/java.security.AccessController.doPrivileged(AccessController.java:689)
      at java.base/sun.security.ssl.SSLEngineImpl$DelegatedTask.run(SSLEngineImpl.java:1008)
      at io.netty.handler.ssl.SslHandler.runAllDelegatedTasks(SslHandler.java:1542)
      at io.netty.handler.ssl.SslHandler.runDelegatedTasks(SslHandler.java:1556)
      at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1440)
      at io.netty.handler.ssl.SslHandler.decodeJdkCompatible(SslHandler.java:1267)
      at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1314)
      at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:501)
      at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:440)
      at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:276)
      at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
      at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
      at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
      at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
      at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
      at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
      at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
      at io.netty.channel.epoll.AbstractEpollStreamChannel$EpollStreamUnsafe.epollInReady(AbstractEpollStreamChannel.java:792)
      at io.netty.channel.epoll.EpollEventLoop.processReady(EpollEventLoop.java:475)
      at io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:378)
      at io.netty.util.concurrent.SingleThreadEventExecutor.run(SingleThreadEventExecutor.java:989)
      at io.netty.util.internal.ThreadExecutorMap.run(ThreadExecutorMap.java:74)
      at org.apache.activemq.artemis.utils.ActiveMQThreadFactory.run(ActiveMQThreadFactory.java:118)}

The signing is handed over to the rustls library. There is a custom function that uses the HSM private key to sign the digest as required.
example signature: 0349003046022100838bde8a902f9ebb18cdd9bc5af263dc978a670d95770c11e2e8d29e3c7b2c28022100d345fa7245fb34c8cf710958da80a638c598c44e2cbd724571dfd9e9ade95008

假设您为实际上是二进制的数据显示十六进制,即 几乎 正确。 Dehexed 它实际上是一个有效的(或至少有效格式化)Ecdsa-Sig 对于 256 位曲线,例如 P-256, 嵌入在 (ASN.1) BITSTRING 中。 具体来说,03 49 00 是 BITSTRING 的标记和长度,其中包含 72 个字节,没有 unused/extra 位。这 72 个字节由 30 46 02 21 (33bytes) 02 21 (33bytes) 组成,它是以下内容的正确编码:一个构造的 SEQUENCE(标签 0x10 加 0x20),值长度为 70 字节,包含两个原始 INTEGER 项目(标签 0x02),每个项目具有值长度 33 个字节。

无论使用什么代码构建 CertificateVerify 消息,都应删除前 3 个字节并使用其余字节。 它需要处理不同的长度;在 most 中,DER 签名将像本例中那样为 72 字节,但通常为 71 或 70 字节,偶尔但很少会更少。 比较 and (as linked there) also (my) .

(PS:ClientKeyExchange消息在这里无关紧要。客户端签名总是在CertVerify消息中。对于TLS1.3以下的server,签名确实进入ServerKeyExchange,但这与您的情况无关。)