Я пытаюсь установить соединение TLSV1.2 с брокером MQTT на основе Java (Active MQ). Мой клиент имеет закрытый ключ в модуле HSM и, следовательно, недоступен для меня. Сертификат x509 доступен. ЦС является самоподписанным.
Детали сертификата: Алгоритм подписи: ecdsa-с-SHA256 Алгоритм открытого ключа: id-ecPublicKey (256 бит) изгиб: премьер256v1
Наблюдаемое поведение:
Сервер выдает следующую ошибку:
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
Что я пробовал до сих пор:
Я попытался установить соединение через curl с тем же брокером, используя сертификат x509, подписанный тем же CA. Это принимается. Следовательно, я пришел к выводу, что это неплохая проблема с сертификатом.
Примечание: Я использую --insecure в команде curl, меня это устраивает, поскольку аутентификация на стороне клиента меня не беспокоит, учитывая, что сервер выдает ошибку.
Наблюдения/предположения:
Сбой рукопожатия исходит от сервера после:
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)
Из этого я делаю вывод, что проблем с совместимостью набора шифров/протокола нет.
Единственная разница, которую я вижу между сертификатом X509, созданным HSM, и сертификатом, созданным мной для проверки, заключается в общем формате имени. Wireshark говорит:
Я никогда не рассматривал рукопожатие так подробно и знаю только основы рукопожатия TLS.
Сервер представляет собой управляемое развертывание Kubernetes, и поэтому в настоящее время очень сложно связать журналы с клиентами.
Но, исходя из наблюдения, я вижу это сообщение (activemq-netty-threads)","message":"AMQ222208: SSL handshake failed for client. java.io.IOException: Sequence tag error."
Думаю, это соответствующие логи брокера. Любой путь вперед/мнения приветствуются.
Результаты парсинга сертификатов x509
CN=A_00000066,OU=AA,O=Organisation Limited,L=Place,ST=State,C=C
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$4.run(SingleThreadEventExecutor.java:989)
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at org.apache.activemq.artemis.utils.ActiveMQThreadFactory$1.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
Обновление 2:
Подписание передано в библиотеку rustls. Существует пользовательская функция, которая использует закрытый ключ HSM для подписи дайджеста по мере необходимости.
Пример подписи: 0349003046022100838bde8a902f9ebb18cdd9bc5af263dc978a670d95770c11e2e8d29e3c7b2c28022100d345fa7245fb34c8cf710958da80a638c508d9d724e2cbd
Фактическое зашифрованное сообщение рукопожатия клиента, после которого происходит сбой:
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 .
},
}
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
Предполагая, что вы отображаете шестнадцатеричный код для данных, которые на самом деле являются двоичными, это правильно почти. В расшифрованном виде это фактически действительный (или, по крайней мере, правильно отформатированный) Экдса-Сиг для 256-битной кривой, такой как P-256, встроенный в (ASN.1) BITSTRING. В частности, 03 49 00 — это тег и длина для BITSTRING, содержащего 72 байта без неиспользуемых/лишних битов. Эти 72 байта состоят из 30 46 02 21 (33 байта) 02 21 (33 байта), что является правильной кодировкой: сконструированной ПОСЛЕДОВАТЕЛЬНОСТИ (тег 0x10 плюс 0x20) с длиной значения 70 байт, содержащей два примитивных элемента INTEGER (тег 0x02), каждый с длина значения 33 байта.
Какой бы код ни создавал сообщение CertificateVerify, он должен удалить первые 3 байта и использовать остальные. Он должен обрабатывать переменную длину; в самый сигнатура DER будет иметь размер 72 байта, как в этом примере, но часто вместо этого будет 71 или 70 байтов, а иногда, но редко, даже меньше. Сравните openssl rust crate: размер подписи ECDSA не 64 байта? и (как указано там) как указать длину подписи для метода подписи java.security.Signature также (мой) Java BouncyCastle не всегда проверяет подпись OpenSSL ECDSA .
(PS: сообщение ClientKeyExchange здесь не имеет значения. Подпись клиента всегда находится в сообщении CertVerify. Для сервер ниже TLS1.3 подпись передается в ServerKeyExchange, но это не имеет отношения к вашему делу.)