是否可以使用javax.net.debug以PEM格式记录服务器证书



我正在用命令行上的-Djavax.net.debug来解决一些SSL/TLS问题,如果服务器证书以可以解析和读取的格式记录,那么进行一些日志记录将非常有帮助。

我尝试了以下调试设置:

-Djavax.net.debug=ssl:record:plaintext
-Djavax.net.debug=ssl:handshake:verbose:keymanager:trustmanager
-Djavax.net.debug=ssl:handshake:verbose

我来的最后一条声明记录了以下内容:

10/11/2021 10:27:36    "version"            : "v3",
10/11/2021 10:27:36    "serial number"      : "I8 00 00 00 00 D2 91 BH 88 A4 10 58 00 00 02 00 04 9E 4B",
10/11/2021 10:27:36    "signature algorithm": "SHA256withRSA",
10/11/2021 10:27:36    "issuer"             : "CN=test, DC=test, DC=test, DC=com",
10/11/2021 10:27:36    "not before"         : "2021-07-23 17:38:30.000 UTC",
10/11/2021 10:27:36    "not  after"         : "2026-07-22 17:38:30.000 UTC",
10/11/2021 10:27:36    "subject"            : "CN=CNTest, OU=TIS, O="ACME Inc", L=France, ST=Paris, C=EU",
10/11/2021 10:27:36    "subject public key" : "RSA",
10/11/2021 10:27:36    "extensions"         : [
...
]

这已经很有用了,但以可读的格式提供服务器证书将大大有助于进一步解决问题。将我们收到的证书与服务器上的实际证书进行比较会有所帮助。

我已经尝试过使用openssl工具来打印证书但是Java应用程序也在使用队列,这些队列似乎使用了与我提供的证书不同的证书,并且使用openssl工具提取队列证书并不容易

上述假设是错误的。我发现了问题,我们在clientHello上只提供了1个密码套件。服务器不支持的一个密码套件,这就是握手失败的原因:

11/9/2021 3:24:03 PMSession ID:  {}
11/9/2021 3:24:03 PMCipher Suites: [TLS_DHE_DSS_WITH_AES_256_CBC_SHA256]
11/9/2021 3:24:03 PMCompression Methods:  { 0 }
...
READ: TLSv1.2 Alert, length = 2
RECV TLSv1.2 ALERT:  fatal, handshake_failure
called closeSocket()
handling exception: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure

但最初的问题仍然存在,对调试很有用。

来源:

  • https://colinpaice.blog/2020/04/05/using-java-djavax-net-debug-to-examine-data-flows-including-tls/
  • https://access.redhat.com/solutions/973783
  • 使用openssl从服务器获取证书
  • https://docs.oracle.com/javase/7/docs/technotes/guides/security/jsse/ReadDebug.html

对于所有情况下的旧堆栈(低于8u261或11(,以及当使用(协商(的协议为TLS1.2或更低时的新堆栈,javax.net.debug=ssl:handshake:packet将为每个传出记录显示一个"原始写入",其中(所有(数据为十六进制和ASCII,每个传入记录至少显示两个"原始读取"(一个用于5字节的标头,一个或多个用于数据(,同上。进入传出记录的字段显示在记录之前,而从传入记录解码的字段则显示在记录之后。例如,在(构建和(写入ClientHello和读取(解码(ServerHello之后连接到https://example.com时,我会为证书消息/记录获取此粘贴框(否则会超过堆栈大小限制(。

获取主体数据(仅限(,在Unix或WSL上,通过cut -c9-58 <hex | xxd -r -p >bin(对于旧堆栈-c7-56(运行它。文件bin现在包含与接收到的完全相同的Certificate消息:第一个字节是0B,接下来的三个字节是消息主体的(bigendian(长度,接下来的四个字节是证书列表的长度,接下来三个字节则是第一个证书的长度。要分离出第一个证书,请执行tail -c+11 bin | head -c$((0x$(xxd -p -s7 -l3 bin))) >cert1。现在,您可以使用openssl x509 -inform d <cert1将证书转换为PEM,或者使用任何其他x509选项进行检查,或者使用keytool -printcert -file cert1或任何其他合适的工具进行检查。如果你想要链中的其他证书(全部或部分(,那就有点复杂了。

仅在新堆栈中的TLS1.3略有不同。它对证书消息进行加密,可能有一个hello重试周期,可能有早期CCS,并且在证书之前会有一个EncryptedExtensions消息。将:plaintext添加到sysprop中,对于每个传入的记录,您将获得clear中标头(5个字节(的原始读取、cipher中正文的原始读取和正文的解密,就像证书消息/记录一样。此消息与以前的协议版本略有不同,因为消息长度和证书列表长度之间有一个字节00,因此更改为:tail -c+12 bin | head -c$((0x$(xxd -p -s8 -l3 bin)))

请注意,有些服务器可能会将消息组合在一个握手记录(或加密握手记录(中,而不是像example.com那样为每个握手记录使用一个单独的记录,这将需要稍微的变化。如果你有这样的例子(可访问(,我会调整。

然而,我不知道SSL/TLS连接中的"队列"是什么意思,也不知道为什么更简单的openssl s_client不可用。如果您的意思是虚拟主机,服务器使用SNI在多个证书中进行选择,请参阅-servername x选项的手册页描述。

使用javax.net.debug不可能获得作为pem的服务器证书。您可以尝试以下片段,我也将其用于自己的项目:

Map<String, List<String>> certificates = CertificateUtils.getCertificateAsPem(
"https://github.com/", 
"https://stackoverflow.com/");

它将以pem格式提取给定url的所有证书。它在我自己的库中可用,在这里公开:GitHub-SSLContext Kickstart

或者你也可以用CLI做同样的事情,请参阅这里:GitHub-Certificate Ripper下面是上面片段的一个例子:

crip print -u=https://github.com -u=https://stackoverflow.com -f=pem

请让我知道,如果这是什么东西会为你做

最新更新