mirror of
https://github.com/haproxy/haproxy.git
synced 2026-06-08 16:23:24 -04:00
BUG/MEDIUM: jwe: substitute random CEK on RSA1_5 decryption failure per RFC 7516 #11.5
do_decrypt_cek_rsa() calls EVP_PKEY_decrypt with RSA_PKCS1_PADDING for RSA1_5 and returns failure (goto end) on decrypt error. This creates a measurable timing difference between "padding invalid" (fast exit before content decryption) and "padding valid + AEAD tag fail" (full AES-GCM/CBC decryption path), exposing the RSA private key to a Bleichenbacher-style adaptive attack requiring ~10^4-10^6 queries. Fix: On RSA_PKCS1_PADDING failure, fill decrypted_cek with random bytes of the buffer size and return success (retval=0). This forces execution into decrypt_ciphertext() regardless of padding validity, so the attacker cannot distinguish valid from invalid padding via timing. The AEAD tag check in decrypt_ciphertext() will still reject the wrong CEK, but the timing profile is identical for both branches. RSA-OAEP variants are not affected (mathematically infeasible to craft valid ciphertext without the private key). Introduced by RSA1_5 path lacking constant-time fallback.
This commit is contained in:
parent
4e7518ed21
commit
1a5a33396d
1 changed files with 32 additions and 4 deletions
36
src/jwe.c
36
src/jwe.c
|
|
@ -584,8 +584,13 @@ static int decrypt_ciphertext(jwe_enc enc, struct jwt_item items[JWE_ELT_MAX],
|
||||||
goto end;
|
goto end;
|
||||||
|
|
||||||
/* Only use the second part of the decrypted key for actual
|
/* Only use the second part of the decrypted key for actual
|
||||||
* content decryption. */
|
* content decryption.
|
||||||
if (b_data(decrypted_cek) != key_size * 2)
|
* Because of the RSAES-PKCS1-V1_5 algorithm, we might have a
|
||||||
|
* bigger than expected decrypted_cek (if it was filled with
|
||||||
|
* random bytes in do_decrypt_cek_rsa) and still want to call
|
||||||
|
* aes_process on the ciphertext in order to avoid timing
|
||||||
|
* attacks. */
|
||||||
|
if (b_data(decrypted_cek) < key_size * 2)
|
||||||
goto end;
|
goto end;
|
||||||
chunk_memcpy(aes_key, decrypted_cek->area + key_size, key_size);
|
chunk_memcpy(aes_key, decrypted_cek->area + key_size, key_size);
|
||||||
}
|
}
|
||||||
|
|
@ -819,8 +824,31 @@ static int do_decrypt_cek_rsa(struct buffer *cek, struct buffer *decrypted_cek,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (EVP_PKEY_decrypt(ctx, (unsigned char*)b_orig(decrypted_cek), &outl,
|
if (EVP_PKEY_decrypt(ctx, (unsigned char*)b_orig(decrypted_cek), &outl,
|
||||||
(unsigned char*)b_orig(cek), b_data(cek)) <= 0)
|
(unsigned char*)b_orig(cek), b_data(cek)) <= 0) {
|
||||||
goto end;
|
/* Per RFC 7516 #11.5, on RSAES-PKCS1-V1_5 decryption failure,
|
||||||
|
* substitute a random CEK and continue into content decryption.
|
||||||
|
* This prevents the Bleichenbacher timing oracle: without this
|
||||||
|
* guard, "padding invalid" (fast exit) is distinguishable from
|
||||||
|
* "padding valid + AEAD tag fail" (full decrypt path).
|
||||||
|
* We will build the biggest decrypted_cek necessary rather than
|
||||||
|
* filling the entire buffer, which would be a key for the
|
||||||
|
* A256CBC_HS512 encrypting algorithm for which the decrypted
|
||||||
|
* cek contains the actual key as well as the tag.
|
||||||
|
*/
|
||||||
|
if (pad == RSA_PKCS1_PADDING) {
|
||||||
|
#define MAX_DECRYPTED_CEK_LEN (32 * 2) /* See https://datatracker.ietf.org/doc/html/rfc7518#section-5.2.2.1 */
|
||||||
|
int i;
|
||||||
|
unsigned char *p = (unsigned char *)b_orig(decrypted_cek);
|
||||||
|
|
||||||
|
for (i = 0; i < MAX_DECRYPTED_CEK_LEN; i++) {
|
||||||
|
uint64_t r = ha_random64();
|
||||||
|
memcpy(p, &r, 8);
|
||||||
|
p+=8;
|
||||||
|
}
|
||||||
|
outl = MAX_DECRYPTED_CEK_LEN;
|
||||||
|
} else
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
decrypted_cek->data = outl;
|
decrypted_cek->data = outl;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue