The Evolution of Signatures in TLS
Signature algorithms and schemes in TLS 1.0 - 1.3
This post will take a look at the evolution of signature algorithms and schemes in the TLS protocol since version 1.0. I at first started taking notes for myself but then decided to polish and publish them, hoping that others will benefit as well.
(Let’s ignore client authentication for simplicity.)
Signature algorithms in TLS 1.0 and TLS 1.1
In TLS 1.0 as well as TLS 1.1 there are only two supported signature schemes: RSA with MD5/SHA-1 and DSA with SHA-1. The RSA here stands for the PKCS#1 v1.5 signature scheme, naturally.
An RSA signature signs the concatenation of the MD5 and SHA-1 digest, the DSA signature only the SHA-1 digest. Hashes will be computed as follows:
The ServerParams
are the actual data to be signed, the *Hello.random
values
are prepended to prevent replay attacks. This is the reason TLS 1.3 puts a
downgrade sentinel
at the end of ServerHello.random
for clients to check.
The ServerKeyExchange message containing the signature is sent only when static RSA/DH key exchange is not used, that means we have a DHE_* cipher suite, an RSA_EXPORT_* suite downgraded due to export restrictions, or a DH_anon_* suite where both parties don’t authenticate.
Signature algorithms in TLS 1.2
TLS 1.2 brought bigger changes to
signature algorithms by introducing the signature_algorithms extension.
This is a ClientHello
extension allowing clients to signal supported and
preferred signature algorithms and hash functions.
If a client does not include the signature_algorithms
extension then it is
assumed to support RSA, DSA, or ECDSA (depending on the negotiated cipher suite)
with SHA-1 as the hash function.
Besides adding all SHA-2 family hash functions, TLS 1.2 also introduced ECDSA as a new signature algorithm. Note that the extension does not allow to restrict the curve used for a given scheme, P-521 with SHA-1 is therefore perfectly legal.
A new requirement for RSA signatures is that the hash has to be wrapped in a
DER-encoded DigestInfo
sequence before passing it to the RSA sign function.
This unfortunately led to attacks like Bleichenbacher’06
and BERserk
because it turns out handling ASN.1 correctly is hard. As in TLS 1.1, a
ServerKeyExchange
message is sent only when static RSA/DH key exchange is not
used. The hash computation did not change either:
Signature schemes in TLS 1.3
The signature_algorithms
extension introduced by TLS 1.2 was revamped in
TLS 1.3 and MUST now
be sent if the client offers a single non-PSK cipher suite. The format is
backwards compatible and keeps some old code points.
Instead of SignatureAndHashAlgorithm
, a code point is now called a
SignatureScheme
and tied to a hash function (if applicable) by the
specification. TLS 1.2 algorithm/hash combinations not listed here
are deprecated and MUST NOT be offered or negotiated.
New code points for RSA-PSS schemes, as well as Ed25519 and Ed448-Goldilocks were added. ECDSA schemes are now tied to the curve given by the code point name, to be enforced by implementations. SHA-1 signature schemes SHOULD NOT be offered, if needed for backwards compatibility then only as the lowest priority after all other schemes.
The current draft-13 lists RSASSA-PSS as the only valid signature algorithm allowed to sign handshake messages with an RSA key. The rsa_pkcs1_* values solely refer to signatures which appear in certificates and are not defined for use in signed handshake messages.
To prevent various downgrade attacks like FREAK and Logjam the computation of the hashes to be signed
has changed significantly and covers the complete handshake, up until
CertificateVerify
:
This includes amongst other data the client and server random, key shares, the cipher suite, the certificate, and resumption information to prevent replay and downgrade attacks. With static key exchange algorithms gone the CertificateVerify message is now the one carrying the signature.