-
Notifications
You must be signed in to change notification settings - Fork 2.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fixes for pkcs12 with NULL and/or zero length password #5155
Fixes for pkcs12 with NULL and/or zero length password #5155
Conversation
Previously passing a NULL or zero length password into either mbedtls_pkcs12_pbe() or mbedtls_pkcs12_derive() could cause an infinate loop, and it was also possible to pass a NULL password, with a non-zero length, which would cause memory corruption. I have fixed these errors, and improved the documentation to reflect the changes and further explain what is expected of the inputs. Signed-off-by: Paul Elliott <paul.elliott@arm.com>
include/mbedtls/pkcs12.h
Outdated
* \param pwd password to use (may be NULL if no password is used) | ||
* \param datalen length of buffer to fill | ||
* \param pwd Null terminated BMPString password to use (may be NULL if | ||
* no password is used, but not if pwdlen is non-zero) | ||
* \param pwdlen length of the password (may be 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, looking at our implementation, I don't think this function is correct when pwdlen==0
or saltlen==0
. This is unrelated to pointer nullability. Our code does:
if( hlen <= 32 )
v = 64;
else
v = 128;
…
pkcs12_fill_buffer( salt_block, v, salt, saltlen );
pkcs12_fill_buffer( pwd_block, v, pwd, pwdlen );
…
if( ( ret = mbedtls_md_update( &md_ctx, salt_block, v ) ) != 0 )
goto exit;
if( ( ret = mbedtls_md_update( &md_ctx, pwd_block, v ) ) != 0 )
goto exit;
But PKCS#12 §B.2 steps 2 and 3 specify that if the salt or password is empty, what gets hashed is an empty string, not v
bytes.
mbedtls_pkcs12_derivation
already errors out when pwdlen > 64 || saltlen > 64
, both cases where the calculation quoted above would be incorrect because it would need to hash multiple blocks of v
bytes. (64 could be raised to 128 when the hash is one with 128-byte blocks.) We could simply make it error out when pwdlen == 0 || saltlen == 0
for the same reason.
Per RFC 128, pwdlen
is at least 2 bytes (a UCS2 null byte). saltlen
is an arbitrary byte string, and could in theory be empty. A salt is necessary for security when the password is actually a password, but not when using an empty password (pwdlen==2
).
Erroring out would break backward compability, but it would eliminate a non-compliance with the standard. So I'm not sure what to do. At least in LTS branches, we should maintain backward compatibility, but document what's going on. In non-LTS branches, I would prefer to either error out or fix the functional bug. Fixing the invalid pointer dereference is urgent, fixing the functional bug isn't, so we don't need to fix the functional bug now, but if we decide not to make it an error, we should at least document the current behavior, either as something we guarantee or something we may fix in the future.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, ok. I took this documentation to mean that the password and salt buffers still had to be v
bytes long, but if the password or salt was empty, then so would the buffer be (hence the memset to zero)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hang on, though. How are we supposed to do step 6C from the documentation above if the password and salt blocks are not of length v
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Step 6 doesn't use the password and salt directly, only I. I is always a concatenation of v-bit blocks. The number of blocks can be 0; this only happens if both the salt and the password are empty. If the number of blocks is 0, step 6C doesn't actually modify I. This might hurt for the security analysis of the algorithm, but that's not really a concern when both the administrator and the user decided to have no effective security (0-length salt and empty password).
Signed-off-by: Paul Elliott <paul.elliott@arm.com>
Signed-off-by: Paul Elliott <paul.elliott@arm.com>
Remove surplus _test suffix. Change labeling from Pcks12 to PCKS#12 as it should be. Signed-off-by: Paul Elliott <paul.elliott@arm.com>
Also ensure they are used in test data rather than values Signed-off-by: Paul Elliott <paul.elliott@arm.com>
Signed-off-by: Paul Elliott <paul.elliott@arm.com>
Signed-off-by: Paul Elliott <paul.elliott@arm.com>
Incorrect interpretation of 'empty' Signed-off-by: Paul Elliott <paul.elliott@arm.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please amend the commit message of "Add expected output for tests":
- To accurately reflect the fact that for (pwd="", salt=""), the output is what our code generated, not from openssl implementation.
- To include the C code you used to generate the test vectors with openssl. (Just paste the C code into the commit message, it's simple enough that it doesn't need a makefile or anything.)
I'm not completely sure what the intended output is when the password and the salt are both empty according to RFC 7292. Our code doesn't have a special case and we're validating that it doesn't do anything bad, and that's good enough for me. We could reject this special case on the basis that it's invalid, but I'm not sure it's supposed to be invalid, although OpenSSL seems to think it is.
My remark about the use variables is not a blocker.
use_password = ( pwd && pwdlen != 0 ); | ||
use_salt = ( salt && saltlen != 0 ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Specification point of view: the conditions that use use_password
below are actually “iterate n times where n is the number of password blocks rounded up” (first if) and “if the password is between 1 and block_size bytes long” (second if). Since passwords longer than block_size bytes are rejected, the condition simplifies to pwdlen != 0
.
Implementation point of view: if pwdlen != 0
then pwd
can't be null at this point, so use_password
could be simplified to pwdlen != 0
.
So the indirection via use_password
isn't really useful, we could just write if( pwdlen != 0 )
below to match the specification more closely.
Same for the salt.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agreed, we either have pwdlen == 0
(and password not to be used) or pwdlen != 0 && pwd != NULL
. So this is equivalent to use_password = (pwdlen != 0)
and if( use_password != 0 )
is equivalent to if( pwd_len != 0 )
.
same for the salt.
if it's worth changing this, there are a couple of minor style changes I've mentioned. however, if it's not worth changing, the whole thing is fine
c22580d
to
7fa7383
Compare
Expected output generated by OpenSSL (see below) apart from the case where both password and salt are either NULL or zero length, as OpenSSL does not support this. For these test cases we have had to use our own output as that which is expected. Code to generate test cases is as follows: #include <openssl/pkcs12.h> #include <openssl/evp.h> #include <string.h> int Keygen_Uni( const char * test_name, unsigned char *pass, int passlen, unsigned char *salt, int saltlen, int id, int iter, int n, unsigned char *out, const EVP_MD *md_type ) { size_t index; printf( "%s\n", test_name ); int ret = PKCS12_key_gen_uni( pass, passlen, salt, saltlen, id, iter, n, out, md_type ); if( ret != 1 ) { printf( "Key generation returned %d\n", ret ); } else { for( index = 0; index < n; ++index ) { printf( "%02x", out[index] ); } printf( "\n" ); } printf( "\n" ); } int main(void) { unsigned char out_buf[48]; unsigned char pass[64]; int pass_len; unsigned char salt[64]; int salt_len; /* If ID=1, then the pseudorandom bits being produced are to be used as key material for performing encryption or decryption. If ID=2, then the pseudorandom bits being produced are to be used as an IV (Initial Value) for encryption or decryption. If ID=3, then the pseudorandom bits being produced are to be used as an integrity key for MACing. */ int id = 1; int iter = 3; memset( out_buf, 0, sizeof( out_buf ) ); memset( pass, 0, sizeof( pass ) ); memset( salt, 0, sizeof( salt ) ); Keygen_Uni( "Zero length pass and salt", pass, 0, salt, 0, id, iter, sizeof(out_buf), out_buf, EVP_md5( ) ); memset( out_buf, 0, sizeof( out_buf ) ); Keygen_Uni( "NULL pass and salt", NULL, 0, NULL, 0, id, iter, sizeof(out_buf), out_buf, EVP_md5( ) ); memset( out_buf, 0, sizeof( out_buf ) ); salt[0] = 0x01; salt[1] = 0x23; salt[2] = 0x45; salt[3] = 0x67; salt[4] = 0x89; salt[5] = 0xab; salt[6] = 0xcd; salt[7] = 0xef; Keygen_Uni( "Zero length pass", pass, 0, salt, 8, id, iter, sizeof(out_buf), out_buf, EVP_md5( ) ); memset( out_buf, 0, sizeof( out_buf ) ); Keygen_Uni( "NULL pass", NULL, 0, salt, 8, id, iter, sizeof(out_buf), out_buf, EVP_md5( ) ); memset( out_buf, 0, sizeof( out_buf ) ); memset( salt, 0, sizeof( salt ) ); pass[0] = 0x01; pass[1] = 0x23; pass[2] = 0x45; pass[3] = 0x67; pass[4] = 0x89; pass[5] = 0xab; pass[6] = 0xcd; pass[7] = 0xef; Keygen_Uni( "Zero length salt", pass, 8, salt, 0, id, iter, sizeof(out_buf), out_buf, EVP_md5( ) ); memset( out_buf, 0, sizeof( out_buf ) ); Keygen_Uni( "NULL salt", pass, 8, NULL, 0, id, iter, sizeof(out_buf), out_buf, EVP_md5( ) ); memset( out_buf, 0, sizeof( out_buf ) ); salt[0] = 0x01; salt[1] = 0x23; salt[2] = 0x45; salt[3] = 0x67; salt[4] = 0x89; salt[5] = 0xab; salt[6] = 0xcd; salt[7] = 0xef; Keygen_Uni( "Valid pass and salt", pass, 8, salt, 8, id, iter, sizeof(out_buf), out_buf, EVP_md5( ) ); return 0; } Signed-off-by: Paul Elliott <paul.elliott@arm.com>
7fa7383
to
6e7deb1
Compare
pkcs12_fill_buffer( pwd_block, v, pwd, pwdlen ); | ||
if( use_salt != 0 ) | ||
{ | ||
pkcs12_fill_buffer( salt_block, v, salt, saltlen ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note: these two single-statement blocks within if
s use braces, unlike the surrounding code. however, probably not worth while changing at this point
data_t* expected_output, int expected_status ) | ||
|
||
{ | ||
int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note: this appears to be indented by 3 spaces, not the usual 4. again, probably not worthwhile going through another review cycle to fix
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
The CVE for this issue still lists affected versions as "all versions before 3.1.0" but this ticket indicated they were backported. Does 2.28.1 have the fix for this? |
@borrrden Yes, this was backported to 2.28.1 (and 2.16.12). |
Description
Previously passing a NULL or zero length password into either
mbedtls_pkcs12_pbe()
ormbedtls_pkcs12_derive()
could cause an infinate loop, and it was also possible to pass a NULL password, with a non-zero length, which would cause memory corruption. Unfortunately the documentation said that either was acceptable.I have fixed these issues, and improved the documentation to reflect the changes and further explain what is expected of the inputs.
This closes #5136 and #4928.
Status
READY
Requires Backporting
Yes
2.x (#5310)
2.16 (#5311)
(issue exists in all branches)
Migrations
NO
Todos
Steps to test or reproduce
Call either
mbedtls_pkcs12_pbe()
ormbedtls_pkcs12_derive()
with a null password, the library should now handle this.