From 56c1a353a68f1e7b6e941261b663cd07708103c9 Mon Sep 17 00:00:00 2001 From: Daniel Cohen Hillel Date: Thu, 17 Jun 2021 16:55:47 -0400 Subject: [PATCH] fix off-by-one in update_client_pin_if_verified() The code that validated new PIN length checks if new PIN is between 4 and 63 bytes long, instead of between 4 and 64 as it should. This could allow an attacker to decrypt the 64-th byte of an encrypted message without knowing the shared secret key, assuming he can bypass the HMAC memcmp check (which is not too likely). Reasoning: The authenticator decrypts the given PIN, and a user/attacker can tell if the given decrypted PIN length is correct or not from the returned error, this is a padding oracle. If the check were if the new pin is between was between 4 and 64 this wouln't be a problem, since 64 is a multiple of the block size (16), and since the message is padded to block multiples, all 64-byte long new PINs would be accepted. The next possible length is 80 bytes, and the only way an 80-bytes new PIN message would be accepted is if the last 16 bytes are zero, which is impossibly unlikely (2 ^ -16 random chance), so the padding oracle won't give an attacker anything. Since the actual check is if the PIN is between 4 and 63, if you send a 64 byte new PIN message, it would be accepted only if the last byte was decrypted to zero, and fail otherwise. Since the authenticator uses CBC mode of operation, it is possible the brute- force the last byte of the previous block (i.e. the 48-th byte) until the last byte is zero, then you know the last byte is zero, so the decrypted last byte is the encrypted 48-th byte. --- fido2/ctap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fido2/ctap.c b/fido2/ctap.c index 346c3339..21f88b32 100644 --- a/fido2/ctap.c +++ b/fido2/ctap.c @@ -2023,7 +2023,7 @@ uint8_t ctap_update_pin_if_verified(uint8_t * pinEnc, int len, uint8_t * platfor ret = trailing_zeros(pinEnc, NEW_PIN_ENC_MIN_SIZE - 1); ret = NEW_PIN_ENC_MIN_SIZE - ret; - if (ret < NEW_PIN_MIN_SIZE || ret >= NEW_PIN_MAX_SIZE) + if (ret < NEW_PIN_MIN_SIZE || ret > NEW_PIN_MAX_SIZE) { printf2(TAG_ERR,"new PIN is too short or too long [%d bytes]\n", ret); return CTAP2_ERR_PIN_POLICY_VIOLATION;