Certificate Transparency: manually verify SCT with openssl

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.

Certificate Transparency - SCT via TLS Extension

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.

The following two tabs change content below.
Italian, born in 1980, I started working in the IT/telecommunications industry in the late '90s; I'm now a system and network engineer with a deep knowledge of the global Internet and its core architectures, and a strong focus on network automation.

Latest posts by Pier Carlo Chiodi (see all)

4 Comments

  1. Nico says:

    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

    • AfroThundr says:

      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.

  2. Theo says:

    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

  3. Dinesh Kavuru says:

    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.

Leave a Reply to Dinesh Kavuru Cancel reply