Search code examples
javassltls1.2tls1.3

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


I'm troubleshooting some SSL/TLS problems, with -Djavax.net.debug on the command line and it would help immensely to have some logging where the server certificate is logged in a format which can be parsed and read.

I tried following debug settings:

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

The closed I came was with the last statement which logs the following:

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"         : [
...
]

Which is already useful but it would help immensely to have the server certificate in a readable format to further troubleshoot the problem. It would help to compare the certificate we received with the actual that is on the server.

I already tried to use the openssl tooling to print the certificates. But the Java application is also using queues which seems to use different certificates than I was supplied and isn't easy to extract the queue certificates with the openssl tooling.

The above assumption was an incorrect assumption. I found the problem, we only supplied 1 ciphersuite on clientHello. One ciphersuite the server didn't support so that's why the handshaking failed:

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

But still the original question stands and would be useful for debugging.

sources:


Solution

  • For the old stack (below 8u261 or 11) in all cases, and the new stack when the protocol used (negotiated) is TLS1.2 or below, javax.net.debug=ssl:handshake:packet will show you a 'raw write' for each outgoing record with (all) the data in hex and ASCII, and at least two 'raw read' for each incoming record (one for the header of 5 bytes, and one or more for the data) ditto. The fields going into an outgoing record are shown before the record, while the fields decoded from an incoming record are shown after it. For example on a connection to https://example.com after (build and) write the ClientHello and read (and decode) the ServerHello, I get for the Certificate message/record this pastebin (otherwise exceeds Stack size limit).

    Take the body data (only) and on Unix, or WSL, run it through cut -c9-58 <hex | xxd -r -p >bin (for old stack -c7-56). The file bin now contains the Certificate message exactly as received: the first byte is 0B, the next three bytes are the (bigendian) length of the body of the message, the next three bytes are the length of the certificate list, and the next three are the length of the first certificate. To separate out that first certificate, do tail -c+11 bin | head -c$((0x$(xxd -p -s7 -l3 bin))) >cert1. You can now convert the cert to PEM with openssl x509 -inform d <cert1 or examine it with any other x509 options, or examine it with keytool -printcert -file cert1, or any other suitable tool. If you want (all or some of) the other cert(s) in the chain, it's a little more complicated.

    TLS1.3, in new stack only, is slightly different. It encrypts the Certificate message and may have a hello-retry cycle, may have an early CCS, and will have an EncryptedExtensions message before the Certificate. Add :plaintext to the sysprop and for each incoming record you get a raw read of the header (5 bytes) in clear, a raw read of the body in cipher, and a decryption of the body, like this for the Certificate message/record. This message differs slightly from the previous protocol version by having one byte of 00 between the message length and the certificate-list length, so change to: tail -c+12 bin | head -c$((0x$(xxd -p -s8 -l3 bin))).

    Note some servers may combine messages in one handshake record (or encrypted handshake record), rather than using a separate record for each as example.com did, which will require a slight variation. If you have an example (accessible) of such, I will adjust.

    However, I have no idea what you mean by 'queues' in an SSL/TLS connection, and why the much easier openssl s_client isn't usable. If you mean virtual hosting with the server choosing among multiple certificates using SNI, see the man page description of the -servername x option.