This posts describes how to forge public-key signatures computed using mbedTLS’s implementation of RSA-PSS (the RSA-based standard signature scheme). Forging a signature means determining a valid signature of some message without knowing the secret key, but possibly know valid signatures of other messages. A signature scheme—or implementation thereof—is considered insecure if such forgeries are practical.
I reported the alleged bug to ARM more than three months ago. ARM did acknowledge receiving the report but did not follow up, and thus neither confirmed nor denied the vulnerability.
Here’s the thing:
mbedTLS offers the
mbedtls_pk_sign() function, which takes arguments including:
const unsigned char *hash, the message to be signed, which can be a hash of a message or a message of arbitrary size. The data in
hashis actually hashed in
mbedtls_rsa_rsassa_pss_sign(), the function internally called by
mbedtls_pk_sign()to compute an RSA-PSS signature.
size_t hash_len, the byte length of
mbedtls_md_type_t md_alg, the type of hash that was used to create the hash value
hash, set to
MBEDTLS_MD_NONEif the data isn’t a hash but an arbitrary message.
Note here the confusing naming of variables, where
*hash can either be a message or an arbitrary size, not only a hash (said message being hashed prior to computing the actual signature when
mbedtls_pk_sign() is called).
Now observe that the function computing an RSA-PSS signature within
mbedtls_rsa_rsassa_pss_sign(), which receives
hash_len 64-bit length argument as… an
unsigned int, long of 32 bits. An integer overflow thus occurs when the message is larger than 4GB.
A forgery attack is thus trivial, if you have access to a system computing PSS signatures using mbedTLS:
- Request the signature of some message M1 long of
L > 0xffffffffbytes
- Receive a signature that is a valid signature of the message M2 of length
Lmod 232 consisting of the first
Lmod 232 bytes of the long message signed at the first step.
Thus you know a valid signature of M2, even though what you received is an (alleged) signature of M1, a different message. mbedTLS’s signature verification will accept the forged signature as a valid signature of M2.
Conversely, a valid signature of a message of length
L < 4GB will get you a signature that mbedTLS will consider as valid for the message of length
L + 4GB sharing the same
L-byte prefix as the original message.
The fix is easy: use
size_t -typed lengths everywhere.
NOTE: This support of arbitrary length input through the
MBEDTLS_MD_NONE option is not compliant with the PKCS standard (see RFC 3447), and will likely not be interoperable with other implementations of PSS signatures. Thanks to Jethro Beekman for pointing out this non-standard behavior of mbedTLS.