RUDE

Сбой рукопожатия TLS 1.2 с ошибкой сбоя рукопожатия

Я пытаюсь установить соединение 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 говорит:

  • CN сертификата, сгенерированного HSM, — DirectoryString: телетекстная строка (0).
  • Сгенерированный вручную CN сертификата — утф-8
    CN тот же, формат, показанный 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. сертификаты ФС
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                                                 .
  },
}

121
1

Ответ:

Решено
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, но это не имеет отношения к вашему делу.)