The recent OpenSSL 1.0.2 version added support for Certificate Transparency (CT) RFC6962 by implementing one of the methods that allow TLS clients to receive and verify Signed Certificate Timestamp during the TLS handshake, that is the OCSP response extension. My goal here is to show how to use another method, the signed_certificate_timestamp TLS extension, to gain the same result.
Of course, what I’ll show here is quite clunky, but it’s a funny way to see how CT SCTs work. I used OpenSSL 1.0.2a, if your distro doesn’t provide a package for it you can download it and compile yourself.
Using OpenSSL to receive SCTs via TLS extension
The new version of OpenSSL added an option to the s_client program which allows to send empty ClientHello TLS Extensions of any type, -serverinfo, that is exactly what RFC6962 mandates about the usage of the CT TLS Extension:
Clients that support the extension SHOULD send a ClientHello extension with the appropriate type and empty “extension_data”.
The aforementioned “appropriate type” is the value 18 (decimal), assigned by IANA to the signed_certificate_timestamp extension type.
This is what is received when this option is used to connect to sni.velox.ch, a sample site that delivers SCTs using the TLS extension:
$ openssl s_client -serverinfo 18 \ -connect sni.velox.ch:443 -servername sni.velox.ch </dev/null -----BEGIN SERVERINFO FOR EXTENSION 18----- ABIBawFpAHYApLkJkLQYWBSHuxOizGdwCjw1mAT5G9+443fNDsgN3BAAAAFFnfl9 nAAABAMARzBFAiAQ25+7pSeXWLd8avDiR62sj5b/FbjbwNKBwWGTQUE70wIhANax d2n3oLC9KVlukDagBhBBIbL8AvWkoQapyzc7ivF+AHcAaPaY+B9kgr46jO65KB1M /HFRXWeT1ETRCmesu09P+8QAAAFFjo2ioQAABAMASDBGAiEA6AeMpGTdNTLvoXbo xQr3Ob6uimQ1E2AdWVGLkIcfZzkCIQD4XWyFOOa5M3vymG/v2DMAHjIL8iefQgmb PwCXtppgeQB2AFYUBpov18Ls0/XhvUSyPsdGdrm8mRFcwO+UmFXWidDdAAABTCa0 vMMAAAQDAEcwRQIgAgzYsJsvN98eIjpGx2aI2mmxlzdTEOMBmBqRJacwCkgCIQC1 U7+4y3KnUwTqYJcQfyqXWn5gJxz1qPNt4Nr3lhz73Q== -----END SERVERINFO FOR EXTENSION 18----- [...]
It is the PEM formatted SignedCertificateTimestampList structure defined in RFC6962, encapsulated in the signed_certificate_timestamp extension. Save it into sni.velox.ch.serverinfo18.pem, it will be used to extract SCTs data:
$ openssl s_client -serverinfo 18 \ -connect sni.velox.ch:443 -servername sni.velox.ch </dev/null 2>/dev/null | \ sed -n -e "/BEGIN SERVERINFO/,/END SERVERINFO/p" >sni.velox.ch.serverinfo18.pem
Parse the SCTs
Now, decode it and view it using hexdump:
$ grep -v "\---" sni.velox.ch.serverinfo18.pem \ | base64 -d > sni.velox.ch.serverinfo18.der $ hd sni.velox.ch.serverinfo18.der 00000000 00 12 01 6b 01 69 00 76 00 a4 b9 09 90 b4 18 58 |...k.i.v.......X| 00000010 14 87 bb 13 a2 cc 67 70 0a 3c 35 98 04 f9 1b df |......gp.<5.....| 00000020 b8 e3 77 cd 0e c8 0d dc 10 00 00 01 45 9d f9 7d |..w.........E..}| 00000030 9c 00 00 04 03 00 47 30 45 02 20 10 db 9f bb a5 |......G0E. .....| 00000040 27 97 58 b7 7c 6a f0 e2 47 ad ac 8f 96 ff 15 b8 |'.X.|j..G.......| 00000050 db c0 d2 81 c1 61 93 41 41 3b d3 02 21 00 d6 b1 |.....a.AA;..!...| 00000060 77 69 f7 a0 b0 bd 29 59 6e 90 36 a0 06 10 41 21 |wi....)Yn.6...A!| 00000070 b2 fc 02 f5 a4 a1 06 a9 cb 37 3b 8a f1 7e 00 77 |.........7;..~.w| 00000080 00 68 f6 98 f8 1f 64 82 be 3a 8c ee b9 28 1d 4c |.h....d..:...(.L| 00000090 fc 71 51 5d 67 93 d4 44 d1 0a 67 ac bb 4f 4f fb |.qQ]g..D..g..OO.| 000000a0 c4 00 00 01 45 8e 8d a2 a1 00 00 04 03 00 48 30 |....E.........H0| 000000b0 46 02 21 00 e8 07 8c a4 64 dd 35 32 ef a1 76 e8 |F.!.....d.52..v.| 000000c0 c5 0a f7 39 be ae 8a 64 35 13 60 1d 59 51 8b 90 |...9...d5.`.YQ..| 000000d0 87 1f 67 39 02 21 00 f8 5d 6c 85 38 e6 b9 33 7b |..g9.!..]l.8..3{| 000000e0 f2 98 6f ef d8 33 00 1e 32 0b f2 27 9f 42 09 9b |..o..3..2..'.B..| 000000f0 3f 00 97 b6 9a 60 79 00 76 00 56 14 06 9a 2f d7 |?....`y.v.V.../.| 00000100 c2 ec d3 f5 e1 bd 44 b2 3e c7 46 76 b9 bc 99 11 |......D.>.Fv....| 00000110 5c c0 ef 94 98 55 d6 89 d0 dd 00 00 01 4c 26 b4 |\....U.......L&.| 00000120 bc c3 00 00 04 03 00 47 30 45 02 20 02 0c d8 b0 |.......G0E. ....| 00000130 9b 2f 37 df 1e 22 3a 46 c7 66 88 da 69 b1 97 37 |./7..":F.f..i..7| 00000140 53 10 e3 01 98 1a 91 25 a7 30 0a 48 02 21 00 b5 |S......%.0.H.!..| 00000150 53 bf b8 cb 72 a7 53 04 ea 60 97 10 7f 2a 97 5a |S...r.S..`...*.Z| 00000160 7e 60 27 1c f5 a8 f3 6d e0 da f7 96 1c fb dd |~`'....m.......|
The 2 initial words define the TLS extension type (0x12 = 18 decimal = signed_certificate_timestamp) and length (0x016b = 363 decimal), then 0x0169 (361 decimal) is the length of the SignedCertificateTimestampList structure; at this point some serialized SCTs start:
- 00 76 is the length of the SCT that follows (SignedCertificateTimestamp structure);
- 00 is the version of the protocol (v1 = 0 decimal for RFC6962, v2 = 1 decimal for draft-ietf-trans-rfc6962-bis);
- a4 b9 … dc 10 is the LogID, that is the SHA-256 hash of the DER-encoded public key of the CT log;
- 00 00 01 45 9d f9 7d 9c is the timestamp (milliseconds since epoch) produced by the log when the certificate is submitted;
- 00 00 is the length of the extensions, not present;
- 04 03 are two bytes that describe the signature’s algorithms used for hashing (0x04 = SHA-256) and signing (0x03 = ECDSA);
- 00 47 is the length of the signature (= 71 decimal);
- 30 45 … f1 7e is the signature.
Then another SCT structure starts and another again for a total of 3 SCTs.
LogID
The LogID value allows to understand which CT log signed the timestamp: a list of known CT logs is maintained in the official Certificate Transparency web site, where for each log the LogID and its public key are also given:
- ct.googleapis.com/aviator
Base64 Log ID: aPaY+B9kgr46jO65KB1M/HFRXWeT1ETRCmesu09P+8Q=
Operator: Google - ct.googleapis.com/pilot
Base64 Log ID: pLkJkLQYWBSHuxOizGdwCjw1mAT5G9+443fNDsgN3BA=
Operator: Google - ct1.digicert-ct.com/log
Base64 Log ID: VhQGmi/XwuzT9eG9RLI+x0Z2ubyZEVzA75SYVdaJ0N0=
Operator: DigiCert
For example, the “Pilot” log’s public key can be found in Issue 389511: Certificate Transparency: Inclusion of Google’s “Pilot” log; here it is, save it in pilot_key.pem:
$ cat > pilot_key.pem << EOF -----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfahLEimAoz2t01p3uMziiLOl/fHT DM0YDOhBRuiBARsV4UvxG2LdNgoIGLrtCzWE0J5APC2em4JlvR8EEEFMoA== -----END PUBLIC KEY----- EOF
Given the list of known CT logs public keys, their LogID can be calculated, both in Base64 and binary format:
$ openssl ec -in pilot_key.pem -outform der -pubin 2>/dev/null | \ openssl dgst -sha256 -binary | \ base64 pLkJkLQYWBSHuxOizGdwCjw1mAT5G9+443fNDsgN3BA= $ openssl ec -in pilot_key.pem -outform der -pubin 2>/dev/null | \ openssl dgst -sha256 -binary | \ hd 00000000 a4 b9 09 90 b4 18 58 14 87 bb 13 a2 cc 67 70 0a |......X......gp.| 00000010 3c 35 98 04 f9 1b df b8 e3 77 cd 0e c8 0d dc 10 |<5.......w......| 00000020
So, by comparing the LogID value in the SCT with those we know we can state that the first SCT has been signed by "Pilot" CT log.
Signature verification
Once a TLS client knows which log signed a SCT it can "validate the SCT by computing the signature input from the SCT data as well as the certificate and verifying the signature, using the corresponding log's public key" (RFC6962 Section 5.2); extract the signature from the SCT and save it into signature.bin:
$ dd if=sni.velox.ch.serverinfo18.der bs=1 skip=55 count=71 of=signature.bin $ hd signature.bin 00000000 30 45 02 20 10 db 9f bb a5 27 97 58 b7 7c 6a f0 |0E. .....'.X.|j.| 00000010 e2 47 ad ac 8f 96 ff 15 b8 db c0 d2 81 c1 61 93 |.G............a.| 00000020 41 41 3b d3 02 21 00 d6 b1 77 69 f7 a0 b0 bd 29 |AA;..!...wi....)| 00000030 59 6e 90 36 a0 06 10 41 21 b2 fc 02 f5 a4 a1 06 |Yn.6...A!.......| 00000040 a9 cb 37 3b 8a f1 7e |..7;..~| 00000047
The signature is computed over the components of the SCT structure and the end-entity certificate whom the SCT refers to: the RFC6962 SignedCertificateTimestamp structure defines the order of these elements:
digitally-signed struct { Version sct_version; SignatureType signature_type = certificate_timestamp; uint64 timestamp; LogEntryType entry_type; select(entry_type) { case x509_entry: ASN.1Cert; case precert_entry: PreCert; } signed_entry; CtExtensions extensions; };
Particular attention must be paid to signed_entry, that in this case is an ASN.1Cert field, that is made of two parts: the DER-encoded end-entity certificate preceded by 3 bytes representing its length.
Download the end-entity certificate for sni.velox.ch and save it into sni.velox.ch.ee.der...
$ openssl s_client -connect sni.velox.ch:443 -servername sni.velox.ch \ -showcerts </dev/null 2>/dev/null | \ openssl x509 -outform der -out sni.velox.ch.ee.der 2>/dev/null
... then build the data that has to be verified against the log's signature...
$ echo -n -e \\x00 > data.bin # sct_version = v1 = 0x00, 1 byte $ echo -n -e \\x00 >> data.bin # signature_type = certificate_timestamp = 0x00, 1 byte $ echo -n -e \\x00\\x00\\x01\\x45\ \\x9d\\xf9\\x7d\\x9c >> data.bin # timestamp = 0x000001459df97d9c, 8 bytes $ echo -n -e \\x00\\x00 >> data.bin # entry_type = x509_entry = 0x0000, 2 bytes $ # show length of DER end-entity certificate $ echo "obase=16; `stat -c '%s' sni.velox.ch.ee.der`" | bc 4E2 $ echo -n -e \\x00\\x04\\xE2 >> data.bin # length of DER-encoded EE cert, 3 bytes $ cat sni.velox.ch.ee.der >> data.bin # DER-encoded EE cert $ echo -n -e \\x00\\x00 >> data.bin # extensions, none, 0-length, 2 bytes
... and finally verify it:
$ openssl dgst -sha256 -verify pilot_key.pem -signature signature.bin data.bin Verified OK
Script
I wrote a small python script that summarizes these steps: https://github.com/pierky/sct-verify.
Using OpenSSL to receive SCTs via OCSP extension
For the sake of completeness, this is the builtin method provided by OpenSSL to obtain SCTs via OCSP; a response from the sslanalyzer.comodoca.com OCSP responder follows:
$ openssl s_client -connect sslanalyzer.comodoca.com:443 -status </dev/null OCSP response: ====================================== OCSP Response Data: OCSP Response Status: successful (0x0) [...] Response Single Extensions: CT Certificate SCTs: Signed Certificate Timestamp: Version : v1(0) Log ID : 68:F6:98:F8:1F:64:82:BE:3A:8C:EE:B9:28:1D:4C:FC: 71:51:5D:67:93:D4:44:D1:0A:67:AC:BB:4F:4F:FB:C4 Timestamp : Apr 25 11:35:28.002 2014 GMT Extensions: none Signature : ecdsa-with-SHA256 30:44:02:20:19:AA:26:AF:C0:2C:92:B1:DD:71:75:1E: AE:16:0C:9B:4E:8A:23:90:E4:75:A1:90:3C:E5:69:EF: EE:9B:AD:2D:02:20:20:FB:14:DB:1E:3E:09:09:51:74: 1A:97:68:38:0E:64:18:2A:FA:F6:5F:2A:5C:77:EB:73: 3B:0D:D6:4D:CF:BB Signed Certificate Timestamp: Version : v1(0) Log ID : A4:B9:09:90:B4:18:58:14:87:BB:13:A2:CC:67:70:0A: 3C:35:98:04:F9:1B:DF:B8:E3:77:CD:0E:C8:0D:DC:10 Timestamp : Apr 23 21:08:37.767 2014 GMT Extensions: none Signature : ecdsa-with-SHA256 30:45:02:20:16:C2:50:36:17:32:AC:AC:B5:74:50:2B: 02:76:39:94:18:70:8A:7C:2C:0D:04:81:2A:09:C0:2F: FE:26:20:71:02:21:00:F3:ED:1C:92:D2:A6:AC:3C:C5: B3:54:DD:FE:4C:D1:DE:95:60:58:43:73:03:5E:6C:06: 12:D0:8E:EF:9A:F2:3D
Further reading
Tom Ritter blogged a nice post where he shows how to use Apache to send SCTs via TLS Extensions and also how to patch Chrome to require CT.
Latest posts by Pier Carlo Chiodi (see all)
- Good MANRS for IXPs route servers made easier - 11 December 2020
- Route server feature-rich and automatic configuration - 13 February 2017
- Large BGP Communities playground - 15 September 2016
Nice post!
Chrome shows 3 fields named: embedded_scts, scts_from_ocsp_response, scts_from_tls_extension.
With OpenSSL, I can get second and third but how do you get the first one (base64 encoded) with OpenSSL or another tool?
Thank you
Nico
The ’embedded_scts’ field refers to SCTs embedded in the certificate directly as an X.509v3 extension. They can be viewed using ‘openssl x509 -text -noout -in myCert.pem’ under the ‘CT Precertificate SCTs’ field.
Thank You Pier for this very good explanations. They helped me to write the python script `verify-scts` (https://github.com/theno/ctutlz) which uses the C-API of OpenSSL via PyOpenSSL and cffi to receive the SCTs via (pre-) cert, via TLS extension, and via OCSP extension.
Theo
Quick Question: If the SCTs are Embedded in the X.509 certificate, then what would I include in the data.bin ? I am sure it wont be the full certificate that already has SCTs. Any help is appreciated.