[Crypto] SSL/TLS, part 4: Toy TLS 1.2 client under ~2k SLOC of Python.

Previous parts: 1, 2, 3.

Now with ECC (elliptic curve cryptography) support.

First, the TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 ciphersuite: ECDHE instead of DHE. This is the Diffie-Hellman algorithm somewhat 'ported' to EC. Thanks to cryptography.io Python module, ECDH(E) support is simple. See: generate_premaster_and_client_key_exchange_ECDHE(). So far, we don't want to delve into ECC math, because it's a bit complicated. At this point we can just think about ECDHE as 'upgraded' version of DHE. 'Points' are exchanged between server and client and shared secret is generated.

Only one curve is supported here: SECP256R1, but others can be added as well.

Also, these ciphersuites are added: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256.

ECDHE shared secret will be signed with ECDSA algorithm instead of RSA. That implies that the server has ECC public key instead of RSA public key.

Interestingly, some servers supports two certificates. Let's see at google.com, a report made by testssl.sh:

% ./testssl.sh --color 0 google.com
...
  Server Certificate #1
   Signature Algorithm          SHA256 with RSA
   Server key size              RSA 2048 bits (exponent is 65537)
   Server key usage             Digital Signature, Key Encipherment
...
  Server Certificate #2
   Signature Algorithm          SHA256 with RSA
   Server key size              EC 256 bits (curve P-256)
   Server key usage             Digital Signature

I added a feature to my ToyTLS so that it saves received certificates as is, in binary form. Which is, in fact, DER.

Let's connect as if client supports only RSA signatures:

% ./toytls_v4.py -save_certs -host google.com -cipher TLS_RSA_WITH_AES_128_CBC_SHA

...

Connecting to google.com:443
google.com_TLS_RSA_WITH_AES_128_CBC_SHA_cert_1.der written
Warning: mask in wildcard certs is not handled yet: *.google.com, but cert is checked anyway
google.com_TLS_RSA_WITH_AES_128_CBC_SHA_cert_2.der written
google.com_TLS_RSA_WITH_AES_128_CBC_SHA_cert_3.der written

...

% openssl x509 -inform der -noout -text -in google.com_TLS_RSA_WITH_AES_128_CBC_SHA_cert_1.der
Certificate:
    Data:
...
        Subject: CN = *.google.com
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:cc:4d:22:f1:14:c2:12:e6:1c:2d:97:93:c7:3f:
                    0c:4c:44:b3:b5:86:e8:d3:64:d7:b5:1b:87:b5:53:
                    f5:81:98:21:72:03:15:d4:35:1a:21:e1:63:9f:bb:
                    5b:c7:4e:3f:3f:d6:d6:27:df:cf:bd:0e:b2:b5:78:
...

Now what if my client pretend it only supports ECC?

% ./toytls_v4.py -save_certs -host google.com -cipher TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA

% openssl x509 -inform der -noout -text -in google.com_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA_cert_1.der
Certificate:
...
        Subject: CN = *.google.com
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (256 bit)
                pub:
                    04:e0:26:2a:c5:37:13:59:81:1c:e4:4b:eb:97:03:
                    0d:59:d2:51:ea:a5:09:0f:50:91:d1:66:0f:4b:a0:
                    43:1f:92:3e:9f:89:1f:15:38:c2:4a:57:30:ea:bb:
                    52:3d:21:a2:9a:a8:bb:fe:0d:c9:7b:2b:d7:a1:4d:
                    9c:7e:dc:f5:1e
                ASN1 OID: prime256v1
                NIST CURVE: P-256
...

Indeed, client sends ciphersuites it supports in ClientHello message and server can decide what to do. In our case, google.com has two certificates which are sent depending on what client supports.

By the way, second and third certificates in chain are RSA certificates, always the same. Second is used to sign first certificate, either RSA or ECC. (Third is used to sign second.)

Thanks to cryptography.io module, certificate chain can be verified using EC functions, see chk_cert_2().

And ECDHE information signature is verified in unpack_SSL3_MT_SERVER_KEY_EXCHANGE_chk_signature().

But again, a client may not do this. In fact, early versions of ToyTLS didn't check any signatures, but it could download a HTML page successfully. This is bad.

In other words, significant burden of all security lays on client side. Client must check signatures, in certificate chain and signature of ECDHE info.

Bottom line: this toy(-ish) TLS client can connect successfully to all ~1k top/popular/busiest websites, as in May 2024. It's under ~2k SLOC of Python, but relies on cryptography.io module heavily.

The files and the patched libressl-3.8.1 I used (with additional debugging/dumping printf statements (AKA "debugging breadcrumbs").

(the post first published at 20240518.)


List of my other blog posts.

Subscribe to my news feed,

Yes, I know about these lousy Disqus ads. Please use adblocker. I would consider to subscribe to 'pro' version of Disqus if the signal/noise ratio in comments would be good enough.