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

Is it possible to log the server certifcate in PEM format with javax.net.debug?

我正在解决一些 SSL/TLS 问题,在命令行上使用 -Djavax.net.debug 并且在服务器证书以可以解析的格式记录日志的地方进行一些日志记录会非常有帮助阅读。

我尝试了以下调试设置:

-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

但原来的问题仍然成立,对调试很有用。

来源:

无法使用 javax.net.debug 获取服务器证书作为 pem 您可以尝试以下代码片段,我也将其用于我自己的项目:

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

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

或者您也可以使用 CLI 执行相同的操作,请参阅此处:GitHub - Certificate Ripper 下面是上述代码段的示例:

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

如果这对您有用,请告诉我

对于所有情况下的旧堆栈(低于 8u261 或 11),以及使用(协商)协议为 TLS1.2 或更低时的新堆栈,javax.net.debug=ssl:handshake:packet 将显示 'raw write' 对于每个传出记录(全部)十六进制和 ASCII 数据,每个传入记录至少两个 'raw read'(一个用于 5 字节的 header,一个或多个用于数据)同上。进入传出记录的字段显示在记录之前,而从传入记录解码的字段显示在记录之后。例如,在(构建和)写入 ClientHello 并读取(和解码)ServerHello 之后连接到 https://example.com,我得到 for the Certificate message/record this pastebin(否则超过堆栈大小限制)。

获取 body 数据(仅),在 Unix 或 WSL 上,运行 通过 cut -c9-58 <hex | xxd -r -p >bin(旧堆栈 -c7-56)。文件 bin 现在包含与收到的完全相同的证书消息:第一个字节是 0B,接下来的三个字节是消息 body 的(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,仅在新堆栈中,略有不同。它加密 Certificate 消息并且可能有一个 hello-retry 周期,可能有一个早期的 CCS,并且在 Certificate 之前会有一个 EncryptedExtensions 消息。将 :plaintext 添加到 sysprop,对于每条传入记录,您将以明文形式原始读取 header(5 个字节),以密码形式原始读取 body,并解密body,就像 this for the Certificate message/record。 此消息与以前的协议版本略有不同,消息长度和 certificate-list 长度之间有一个字节 00,因此更改为:tail -c+12 bin | head -c$((0x$(xxd -p -s8 -l3 bin))).

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

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