From 5edf65616a8dcbf5988bbabe0493827d9e125fc3 Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Mon, 12 Jul 2021 11:40:50 +0200 Subject: [PATCH] crypt32: Implement CNG_RSA_PUBLIC_KEY_BLOB encoding/decoding. Add support for the OID CNG_RSA_PUBLIC_KEY_BLOB to CryptEncodeObjectEx and CryptDecodeObjectEx. This OID decodes to / encodes from memory consisting of a BCRYPT_RSAKEY_BLOB, followed in memory by the exponent and modulus in big-endian format. Signed-off-by: Aaron Hill Signed-off-by: Hans Leidekker Signed-off-by: Alexandre Julliard --- dlls/crypt32/crypt32_private.h | 5 ++ dlls/crypt32/decode.c | 114 ++++++++++++++++++++++++++--- dlls/crypt32/encode.c | 53 ++++++++++++++ dlls/crypt32/tests/encode.c | 130 +++++++++++++++++++++++++++++++++ include/wincrypt.h | 1 + 5 files changed, 291 insertions(+), 12 deletions(-) diff --git a/dlls/crypt32/crypt32_private.h b/dlls/crypt32/crypt32_private.h index 9f8f43e7632..dbd19e6c914 100644 --- a/dlls/crypt32/crypt32_private.h +++ b/dlls/crypt32/crypt32_private.h @@ -42,6 +42,11 @@ BOOL CNG_ImportPubKey(CERT_PUBLIC_KEY_INFO *pubKeyInfo, BCRYPT_KEY_HANDLE *key) #define ASN_UNIVERSALSTRING (ASN_UNIVERSAL | ASN_PRIMITIVE | 0x1c) #define ASN_BMPSTRING (ASN_UNIVERSAL | ASN_PRIMITIVE | 0x1e) +/* Copies `len` bytes from `src` to `dst`, + * reversing the order of the bytes + */ +void CRYPT_CopyReversed(BYTE *dst, const BYTE *src, size_t len); + BOOL CRYPT_EncodeLen(DWORD len, BYTE *pbEncoded, DWORD *pcbEncoded) DECLSPEC_HIDDEN; typedef BOOL (WINAPI *CryptEncodeObjectExFunc)(DWORD, LPCSTR, const void *, diff --git a/dlls/crypt32/decode.c b/dlls/crypt32/decode.c index a0e78d62b4d..08b07efdbe6 100644 --- a/dlls/crypt32/decode.c +++ b/dlls/crypt32/decode.c @@ -55,6 +55,14 @@ WINE_DEFAULT_DEBUG_CHANNEL(cryptasn); WINE_DECLARE_DEBUG_CHANNEL(crypt); +void CRYPT_CopyReversed(BYTE *dst, const BYTE *src, size_t len) +{ + DWORD i; + for (i = 0; i < len; i++) { + dst[len - i - 1] = src[i]; + } +} + typedef BOOL (WINAPI *CryptDecodeObjectFunc)(DWORD, LPCSTR, const BYTE *, DWORD, DWORD, void *, DWORD *); typedef BOOL (WINAPI *CryptDecodeObjectExFunc)(DWORD, LPCSTR, const BYTE *, @@ -3870,6 +3878,93 @@ struct DECODED_RSA_PUB_KEY CRYPT_INTEGER_BLOB modulus; }; +/* Helper function to decode an ASN.1 DER encoded RSA public key, writing the decoded + * key into 'decodedKey', and the length into 'size'. The memory + * for 'decodedKey' is allocated with 'CRYPT_DECODE_ALLOC_FLAG' + */ +static BOOL CRYPT_raw_decode_rsa_pub_key(struct DECODED_RSA_PUB_KEY **decodedKey, + DWORD *size, const BYTE *pbEncoded, DWORD cbEncoded) +{ + BOOL ret; + + struct AsnDecodeSequenceItem items[] = { + { ASN_INTEGER, offsetof(struct DECODED_RSA_PUB_KEY, modulus), + CRYPT_AsnDecodeUnsignedIntegerInternal, sizeof(CRYPT_INTEGER_BLOB), + FALSE, TRUE, offsetof(struct DECODED_RSA_PUB_KEY, modulus.pbData), + 0 }, + { ASN_INTEGER, offsetof(struct DECODED_RSA_PUB_KEY, pubexp), + CRYPT_AsnDecodeIntInternal, sizeof(DWORD), FALSE, FALSE, 0, 0 }, + }; + + ret = CRYPT_AsnDecodeSequence(items, ARRAY_SIZE(items), + pbEncoded, cbEncoded, CRYPT_DECODE_ALLOC_FLAG, NULL, decodedKey, + size, NULL, NULL); + return ret; +} + +static BOOL WINAPI CRYPT_AsnDecodeRsaPubKey_Bcrypt(DWORD dwCertEncodingType, + LPCSTR lpszStructType, const BYTE *pbEncoded, DWORD cbEncoded, DWORD dwFlags, + PCRYPT_DECODE_PARA pDecodePara, void *pvStructInfo, DWORD *pcbStructInfo) +{ + BOOL ret; + + __TRY + { + struct DECODED_RSA_PUB_KEY *decodedKey = NULL; + DWORD size = 0; + + TRACE("%p, %d, %08x, %p, %d\n", pbEncoded, cbEncoded, dwFlags, + pvStructInfo, *pcbStructInfo); + + ret = CRYPT_raw_decode_rsa_pub_key(&decodedKey, &size, pbEncoded, cbEncoded); + if (ret) + { + /* Header, exponent, and modulus */ + DWORD bytesNeeded = sizeof(BCRYPT_RSAKEY_BLOB) + sizeof(DWORD) + + decodedKey->modulus.cbData; + + if (!pvStructInfo) + { + *pcbStructInfo = bytesNeeded; + ret = TRUE; + } + else if ((ret = CRYPT_DecodeEnsureSpace(dwFlags, pDecodePara, + pvStructInfo, pcbStructInfo, bytesNeeded))) + { + BCRYPT_RSAKEY_BLOB *hdr; + + if (dwFlags & CRYPT_DECODE_ALLOC_FLAG) + pvStructInfo = *(BYTE **)pvStructInfo; + + hdr = pvStructInfo; + hdr->Magic = BCRYPT_RSAPUBLIC_MAGIC; + hdr->BitLength = decodedKey->modulus.cbData * 8; + hdr->cbPublicExp = sizeof(DWORD); + hdr->cbModulus = decodedKey->modulus.cbData; + hdr->cbPrime1 = 0; + hdr->cbPrime2 = 0; + /* CNG_RSA_PUBLIC_KEY_BLOB always stores the exponent and modulus + * in big-endian format, so we need to convert from little-endian + */ + CRYPT_CopyReversed((BYTE *)pvStructInfo + sizeof(BCRYPT_RSAKEY_BLOB), + (BYTE *)&decodedKey->pubexp, sizeof(DWORD)); + CRYPT_CopyReversed((BYTE *)pvStructInfo + sizeof(BCRYPT_RSAKEY_BLOB) + + sizeof(DWORD), decodedKey->modulus.pbData, + decodedKey->modulus.cbData); + } + LocalFree(decodedKey); + } + } + __EXCEPT_PAGE_FAULT + { + SetLastError(STATUS_ACCESS_VIOLATION); + ret = FALSE; + } + __ENDTRY + return ret; +} + + static BOOL WINAPI CRYPT_AsnDecodeRsaPubKey(DWORD dwCertEncodingType, LPCSTR lpszStructType, const BYTE *pbEncoded, DWORD cbEncoded, DWORD dwFlags, PCRYPT_DECODE_PARA pDecodePara, void *pvStructInfo, DWORD *pcbStructInfo) @@ -3878,20 +3973,9 @@ static BOOL WINAPI CRYPT_AsnDecodeRsaPubKey(DWORD dwCertEncodingType, __TRY { - struct AsnDecodeSequenceItem items[] = { - { ASN_INTEGER, offsetof(struct DECODED_RSA_PUB_KEY, modulus), - CRYPT_AsnDecodeUnsignedIntegerInternal, sizeof(CRYPT_INTEGER_BLOB), - FALSE, TRUE, offsetof(struct DECODED_RSA_PUB_KEY, modulus.pbData), - 0 }, - { ASN_INTEGER, offsetof(struct DECODED_RSA_PUB_KEY, pubexp), - CRYPT_AsnDecodeIntInternal, sizeof(DWORD), FALSE, FALSE, 0, 0 }, - }; struct DECODED_RSA_PUB_KEY *decodedKey = NULL; DWORD size = 0; - - ret = CRYPT_AsnDecodeSequence(items, ARRAY_SIZE(items), - pbEncoded, cbEncoded, CRYPT_DECODE_ALLOC_FLAG, NULL, &decodedKey, - &size, NULL, NULL); + ret = CRYPT_raw_decode_rsa_pub_key(&decodedKey, &size, pbEncoded, cbEncoded); if (ret) { DWORD bytesNeeded = sizeof(BLOBHEADER) + sizeof(RSAPUBKEY) + @@ -6207,7 +6291,11 @@ static CryptDecodeObjectExFunc CRYPT_GetBuiltinDecoder(DWORD dwCertEncodingType, break; case LOWORD(X509_ECC_SIGNATURE): decodeFunc = CRYPT_AsnDecodeEccSignature; + case LOWORD(CNG_RSA_PUBLIC_KEY_BLOB): + decodeFunc = CRYPT_AsnDecodeRsaPubKey_Bcrypt; break; + default: + FIXME("Unimplemented decoder for lpszStructType OID %d\n", LOWORD(lpszStructType)); } } else if (!strcmp(lpszStructType, szOID_CERT_EXTENSIONS)) @@ -6264,6 +6352,8 @@ static CryptDecodeObjectExFunc CRYPT_GetBuiltinDecoder(DWORD dwCertEncodingType, decodeFunc = CRYPT_AsnDecodeCTL; else if (!strcmp(lpszStructType, szOID_ECC_PUBLIC_KEY)) decodeFunc = CRYPT_AsnDecodeObjectIdentifier; + else + FIXME("Unsupported decoder for lpszStructType %s\n", lpszStructType); return decodeFunc; } diff --git a/dlls/crypt32/encode.c b/dlls/crypt32/encode.c index 02b235696ce..af0dd2cf743 100644 --- a/dlls/crypt32/encode.c +++ b/dlls/crypt32/encode.c @@ -3130,6 +3130,52 @@ static BOOL WINAPI CRYPT_AsnEncodeCertPolicyConstraints( return ret; } +static BOOL WINAPI CRYPT_AsnEncodeRsaPubKey_Bcrypt(DWORD dwCertEncodingType, + LPCSTR lpszStructType, const void *pvStructInfo, DWORD dwFlags, + PCRYPT_ENCODE_PARA pEncodePara, BYTE *pbEncoded, DWORD *pcbEncoded) +{ + BOOL ret; + + __TRY + { + const BCRYPT_RSAKEY_BLOB *hdr = pvStructInfo; + + BYTE *pubexp = (BYTE*)pvStructInfo + sizeof(BCRYPT_RSAKEY_BLOB); + BYTE *modulus = (BYTE*)pvStructInfo + sizeof(BCRYPT_RSAKEY_BLOB) + hdr->cbPublicExp; + BYTE *pubexp_be = CryptMemAlloc(hdr->cbPublicExp); + BYTE *modulus_be = CryptMemAlloc(hdr->cbModulus); + CRYPT_INTEGER_BLOB pubexp_int = { hdr->cbPublicExp, pubexp_be }; + CRYPT_INTEGER_BLOB modulus_int = { hdr->cbModulus, modulus_be}; + + struct AsnEncodeSequenceItem items[] = { + { &modulus_int, CRYPT_AsnEncodeUnsignedInteger, 0 }, + { &pubexp_int, CRYPT_AsnEncodeInteger, 0 }, + }; + + /* CNG_RSA_PUBLIC_KEY_BLOB stores the exponent and modulus + * in big-endian format, so we need to convert them + * to little-endian format before encoding + */ + CRYPT_CopyReversed(pubexp_be, pubexp, hdr->cbPublicExp); + CRYPT_CopyReversed(modulus_be, modulus, hdr->cbModulus); + + ret = CRYPT_AsnEncodeSequence(dwCertEncodingType, items, + ARRAY_SIZE(items), dwFlags, pEncodePara, pbEncoded, pcbEncoded); + + CryptMemFree(pubexp_be); + CryptMemFree(modulus_be); + } + __EXCEPT_PAGE_FAULT + { + SetLastError(STATUS_ACCESS_VIOLATION); + ret = FALSE; + } + __ENDTRY + return ret; + +} + + static BOOL WINAPI CRYPT_AsnEncodeRsaPubKey(DWORD dwCertEncodingType, LPCSTR lpszStructType, const void *pvStructInfo, DWORD dwFlags, PCRYPT_ENCODE_PARA pEncodePara, BYTE *pbEncoded, DWORD *pcbEncoded) @@ -4563,6 +4609,11 @@ static CryptEncodeObjectExFunc CRYPT_GetBuiltinEncoder(DWORD dwCertEncodingType, case LOWORD(CMS_SIGNER_INFO): encodeFunc = CRYPT_AsnEncodeCMSSignerInfo; break; + case LOWORD(CNG_RSA_PUBLIC_KEY_BLOB): + encodeFunc = CRYPT_AsnEncodeRsaPubKey_Bcrypt; + break; + default: + FIXME("Unimplemented encoder for lpszStructType OID %d\n", LOWORD(lpszStructType)); } } else if (!strcmp(lpszStructType, szOID_CERT_EXTENSIONS)) @@ -4617,6 +4668,8 @@ static CryptEncodeObjectExFunc CRYPT_GetBuiltinEncoder(DWORD dwCertEncodingType, encodeFunc = CRYPT_AsnEncodePolicyQualifierUserNotice; else if (!strcmp(lpszStructType, szOID_CTL)) encodeFunc = CRYPT_AsnEncodeCTL; + else + FIXME("Unsupported encoder for lpszStructType %s\n", lpszStructType); return encodeFunc; } diff --git a/dlls/crypt32/tests/encode.c b/dlls/crypt32/tests/encode.c index 9597f1dfec1..9353bd201a7 100644 --- a/dlls/crypt32/tests/encode.c +++ b/dlls/crypt32/tests/encode.c @@ -31,6 +31,14 @@ static BOOL (WINAPI *pCryptDecodeObjectEx)(DWORD,LPCSTR,const BYTE*,DWORD,DWORD,PCRYPT_DECODE_PARA,void*,DWORD*); static BOOL (WINAPI *pCryptEncodeObjectEx)(DWORD,LPCSTR,const void*,DWORD,PCRYPT_ENCODE_PARA,void*,DWORD*); +void CRYPT_CopyReversed(BYTE *dst, const BYTE *src, size_t len) +{ + DWORD i; + for (i = 0; i < len; i++) { + dst[len - i - 1] = src[i]; + } +} + struct encodedInt { int val; @@ -2572,6 +2580,126 @@ static void test_decodeRsaPublicKey(DWORD dwEncoding) } } +static void test_encodeRsaPublicKey_Bcrypt(DWORD dwEncoding) +{ + BYTE toEncode[sizeof(BCRYPT_RSAKEY_BLOB) + sizeof(DWORD) + sizeof(modulus1)]; + BCRYPT_RSAKEY_BLOB *hdr = (BCRYPT_RSAKEY_BLOB *)toEncode; + BOOL ret; + BYTE *buf = NULL; + DWORD bufSize = 0, i; + BYTE pubexp[] = {0x01,0x00,0x01,0x00}; /* 65537 */ + + /* Verify that the Magic value doesn't matter */ + hdr->Magic = 1; + hdr->BitLength = sizeof(modulus1) * 8; + hdr->cbPublicExp = sizeof(pubexp); + hdr->cbModulus = sizeof(modulus1); + hdr->cbPrime1 = 0; + hdr->cbPrime2 = 0; + + /* CNG_RSA_PUBLIC_KEY_BLOB stores the exponent and modulus + * in big-endian format, so we need convert them + * from little-endian format before encoding + */ + CRYPT_CopyReversed(toEncode + sizeof(BCRYPT_RSAKEY_BLOB), pubexp, sizeof(pubexp)); + CRYPT_CopyReversed(toEncode + sizeof(BCRYPT_RSAKEY_BLOB) + sizeof(pubexp), modulus1, sizeof(modulus1)); + + ret = pCryptEncodeObjectEx(dwEncoding, CNG_RSA_PUBLIC_KEY_BLOB, + toEncode, CRYPT_ENCODE_ALLOC_FLAG, NULL, &buf, &bufSize); + ok(ret, "CryptEncodeObjectEx failed: %08x\n", GetLastError()); + + /* Finally, all valid */ + hdr->Magic = BCRYPT_RSAPUBLIC_MAGIC; + for (i = 0; i < ARRAY_SIZE(rsaPubKeys); i++) + { + hdr->BitLength = rsaPubKeys[i].modulusLen * 8; + hdr->cbModulus = rsaPubKeys[i].modulusLen; + + CRYPT_CopyReversed(toEncode + sizeof(BCRYPT_RSAKEY_BLOB) + sizeof(DWORD), + rsaPubKeys[i].modulus, rsaPubKeys[i].modulusLen); + + ret = pCryptEncodeObjectEx(dwEncoding, CNG_RSA_PUBLIC_KEY_BLOB, + toEncode, CRYPT_ENCODE_ALLOC_FLAG, NULL, &buf, &bufSize); + ok(ret, "CryptEncodeObjectEx failed: %08x\n", GetLastError()); + if (ret) + { + ok(bufSize == rsaPubKeys[i].encoded[1] + 2, + "Expected size %d, got %d\n", rsaPubKeys[i].encoded[1] + 2, + bufSize); + ok(!memcmp(buf, rsaPubKeys[i].encoded, bufSize), + "Unexpected value\n"); + LocalFree(buf); + } + } +} + +static void test_decodeRsaPublicKey_Bcrypt(DWORD dwEncoding) +{ + DWORD i; + LPBYTE buf = NULL; + LPBYTE leModulus = NULL; + DWORD bufSize = 0; + BOOL ret; + + /* Try with a bad length */ + ret = pCryptDecodeObjectEx(dwEncoding, CNG_RSA_PUBLIC_KEY_BLOB, + rsaPubKeys[0].encoded, rsaPubKeys[0].encoded[1], + CRYPT_DECODE_ALLOC_FLAG, NULL, &buf, &bufSize); + ok(!ret && (GetLastError() == CRYPT_E_ASN1_EOD || + GetLastError() == OSS_MORE_INPUT /* Win9x/NT4 */), + "Expected CRYPT_E_ASN1_EOD or OSS_MORE_INPUT, got %08x\n", + GetLastError()); + /* Now try success cases */ + for (i = 0; i < ARRAY_SIZE(rsaPubKeys); i++) + { + bufSize = 0; + ret = pCryptDecodeObjectEx(dwEncoding, CNG_RSA_PUBLIC_KEY_BLOB, + rsaPubKeys[i].encoded, rsaPubKeys[i].encoded[1] + 2, + CRYPT_DECODE_ALLOC_FLAG, NULL, &buf, &bufSize); + ok(ret, "CryptDecodeObjectEx failed: %08x\n", GetLastError()); + if (ret) + { + BCRYPT_RSAKEY_BLOB *hdr = (BCRYPT_RSAKEY_BLOB *)buf; + BYTE pubexp[] = {0xff,0xff,0xff,0xff}, pubexp_expected[] = {0x01,0x00,0x01}; + /* CNG_RSA_PUBLIC_KEY_BLOB stores the exponent + * in big-endian format, so we need to convert it to little-endian + */ + CRYPT_CopyReversed((BYTE *)&pubexp, buf + sizeof(BCRYPT_RSAKEY_BLOB), hdr->cbPublicExp); + ok(bufSize >= sizeof(BCRYPT_RSAKEY_BLOB) + + rsaPubKeys[i].decodedModulusLen, + "Wrong size %d\n", bufSize); + ok(hdr->Magic == BCRYPT_RSAPUBLIC_MAGIC, + "Expected magic BCRYPT_RSAPUBLIC_MAGIC (%d), got %d\n", BCRYPT_RSAPUBLIC_MAGIC, + hdr->Magic); + ok(hdr->BitLength == rsaPubKeys[i].decodedModulusLen * 8, + "Wrong bit len %d\n", hdr->BitLength); + /* Windows decodes the exponent to 3 bytes, since it will fit. + * Our implementation currently unconditionally decodes to a DWORD (4 bytes) + */ + todo_wine ok(hdr->cbPublicExp == 3, "Expected cbPublicExp 3, got %d\n", hdr->cbPublicExp); + ok(hdr->cbModulus == rsaPubKeys[i].decodedModulusLen, + "Wrong modulus len %d\n", hdr->cbModulus); + ok(hdr->cbPrime1 == 0,"Wrong cbPrime1 %d\n", hdr->cbPrime1); + ok(hdr->cbPrime2 == 0,"Wrong cbPrime2 %d\n", hdr->cbPrime2); + ok(!memcmp(pubexp, pubexp_expected, sizeof(pubexp_expected)), "Wrong exponent\n"); + todo_wine ok(pubexp[3] == 0xff, "Got %02x\n", pubexp[3]); + + leModulus = HeapAlloc(GetProcessHeap(), 0, hdr->cbModulus); + /* + * CNG_RSA_PUBLIC_KEY_BLOB stores the modulus in big-endian format, + * so we need to convert it to little-endian + */ + CRYPT_CopyReversed(leModulus, buf + sizeof(BCRYPT_RSAKEY_BLOB) + hdr->cbPublicExp, + hdr->cbModulus); + ok(!memcmp(leModulus, + rsaPubKeys[i].modulus, rsaPubKeys[i].decodedModulusLen), + "Unexpected modulus\n"); + LocalFree(buf); + LocalFree(leModulus); + } + } +} + static const BYTE intSequence[] = { 0x30, 0x1b, 0x02, 0x01, 0x01, 0x02, 0x01, 0x7f, 0x02, 0x02, 0x00, 0x80, 0x02, 0x02, 0x01, 0x00, 0x02, 0x01, 0x80, 0x02, 0x02, 0xff, 0x7f, 0x02, 0x04, 0xba, 0xdd, 0xf0, 0x0d }; @@ -8547,6 +8675,8 @@ START_TEST(encode) test_decodeBasicConstraints(encodings[i]); test_encodeRsaPublicKey(encodings[i]); test_decodeRsaPublicKey(encodings[i]); + test_encodeRsaPublicKey_Bcrypt(encodings[i]); + test_decodeRsaPublicKey_Bcrypt(encodings[i]); test_encodeSequenceOfAny(encodings[i]); test_decodeSequenceOfAny(encodings[i]); test_encodeExtensions(encodings[i]); diff --git a/include/wincrypt.h b/include/wincrypt.h index 28bebf8dc4b..456f2f14446 100644 --- a/include/wincrypt.h +++ b/include/wincrypt.h @@ -3179,6 +3179,7 @@ typedef struct _CTL_FIND_SUBJECT_PARA #define CMC_ADD_EXTENSIONS ((LPCSTR)62) #define CMC_ADD_ATTRIBUTES ((LPCSTR)63) #define X509_CERTIFICATE_TEMPLATE ((LPCSTR)64) +#define CNG_RSA_PUBLIC_KEY_BLOB ((LPCSTR)72) #define X509_OBJECT_IDENTIFIER ((LPCSTR)73) #define PKCS7_SIGNER_INFO ((LPCSTR)500) #define CMS_SIGNER_INFO ((LPCSTR)501)