//+------------------------------------------------------------------------- // Microsoft Windows // // Copyright (C) Microsoft Corporation, 2001 - 2001 // // File: verhash.cpp // // Contents: Minimal Cryptographic functions to verify ASN.1 encoded // signed hashes. Signed hashes are used in X.509 certificates // and PKCS #7 signed data. // // Also contains md5 or sha1 memory hash function. // // // Functions: MinCryptDecodeHashAlgorithmIdentifier // MinCryptHashMemory // MinCryptVerifySignedHash // // History: 17-Jan-01 philh created //-------------------------------------------------------------------------- #include "global.hxx" #include #include #include #include #define MAX_RSA_PUB_KEY_BIT_LEN 4096 #define MAX_RSA_PUB_KEY_BYTE_LEN (MAX_RSA_PUB_KEY_BIT_LEN / 8 ) #define MAX_BSAFE_PUB_KEY_MODULUS_BYTE_LEN \ (MAX_RSA_PUB_KEY_BYTE_LEN + sizeof(DWORD) * 4) typedef struct _BSAFE_PUB_KEY_CONTENT { BSAFE_PUB_KEY Header; BYTE rgbModulus[MAX_BSAFE_PUB_KEY_MODULUS_BYTE_LEN]; } BSAFE_PUB_KEY_CONTENT, *PBSAFE_PUB_KEY_CONTENT; #ifndef RSA1 #define RSA1 ((DWORD)'R'+((DWORD)'S'<<8)+((DWORD)'A'<<16)+((DWORD)'1'<<24)) #endif // from \nt\ds\win32\ntcrypto\scp\nt_sign.c // // Reverse ASN.1 Encodings of possible hash identifiers. The leading byte is // the length of the remaining byte string. // static const BYTE *md2Encodings[] // 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 = { (CONST BYTE *)"\x12\x10\x04\x00\x05\x02\x02\x0d\xf7\x86\x48\x86\x2a\x08\x06\x0c\x30\x20\x30", (CONST BYTE *)"\x10\x10\x04\x02\x02\x0d\xf7\x86\x48\x86\x2a\x08\x06\x0a\x30\x1e\x30", (CONST BYTE *)"\x00" }, *md5Encodings[] = { (CONST BYTE *)"\x12\x10\x04\x00\x05\x05\x02\x0d\xf7\x86\x48\x86\x2a\x08\x06\x0c\x30\x20\x30", (CONST BYTE *)"\x10\x10\x04\x05\x02\x0d\xf7\x86\x48\x86\x2a\x08\x06\x0a\x30\x1e\x30", (CONST BYTE *)"\x00" }, *shaEncodings[] = { (CONST BYTE *)"\x0f\x14\x04\x00\x05\x1a\x02\x03\x0e\x2b\x05\x06\x09\x30\x21\x30", (CONST BYTE *)"\x0d\x14\x04\x1a\x02\x03\x0e\x2b\x05\x06\x07\x30\x1f\x30", (CONST BYTE *)"\x00"}; typedef struct _ENCODED_OID_INFO { DWORD cbEncodedOID; const BYTE *pbEncodedOID; ALG_ID AlgId; } ENCODED_OID_INFO, *PENCODED_OID_INFO; // // SHA1/MD5/MD2 HASH OIDS // // #define szOID_OIWSEC_sha1 "1.3.14.3.2.26" const BYTE rgbOIWSEC_sha1[] = {0x2B, 0x0E, 0x03, 0x02, 0x1A}; // #define szOID_OIWSEC_sha "1.3.14.3.2.18" const BYTE rgbOID_OIWSEC_sha[] = {0x2B, 0x0E, 0x03, 0x02, 0x12}; // #define szOID_RSA_MD5 "1.2.840.113549.2.5" const BYTE rgbOID_RSA_MD5[] = {0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x05}; // #define szOID_RSA_MD2 "1.2.840.113549.2.2" const BYTE rgbOID_RSA_MD2[] = {0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x02}; // // RSA SHA1/MD5/MD2 SIGNATURE OIDS // // #define szOID_RSA_SHA1RSA "1.2.840.113549.1.1.5" const BYTE rgbOID_RSA_SHA1RSA[] = {0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x05}; // #define szOID_RSA_MD5RSA "1.2.840.113549.1.1.4" const BYTE rgbOID_RSA_MD5RSA[] = {0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x04}; // #define szOID_OIWSEC_sha1RSASign "1.3.14.3.2.29" const BYTE rgbOID_OIWSEC_sha1RSASign[] = {0x2B, 0x0E, 0x03, 0x02, 0x1D}; // #define szOID_OIWSEC_shaRSA "1.3.14.3.2.15" const BYTE rgbOID_OIWSEC_shaRSA[] = {0x2B, 0x0E, 0x03, 0x02, 0x0F}; // #define szOID_OIWSEC_md5RSA "1.3.14.3.2.3" const BYTE rgbOID_OIWSEC_md5RSA[] = {0x2B, 0x0E, 0x03, 0x02, 0x03}; // #define szOID_RSA_MD2RSA "1.2.840.113549.1.1.2" const BYTE rgbOID_RSA_MD2RSA[] = {0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x02}; // #define szOID_OIWDIR_md2RSA "1.3.14.7.2.3.1" const BYTE rgbOID_OIWDIR_md2RSA[] = {0x2B, 0x0E, 0x07, 0x02, 0x03, 0x01}; const ENCODED_OID_INFO HashAlgTable[] = { // Hash OIDs sizeof(rgbOIWSEC_sha1), rgbOIWSEC_sha1, CALG_SHA1, sizeof(rgbOID_OIWSEC_sha), rgbOID_OIWSEC_sha, CALG_SHA1, sizeof(rgbOID_RSA_MD5), rgbOID_RSA_MD5, CALG_MD5, sizeof(rgbOID_RSA_MD2), rgbOID_RSA_MD2, CALG_MD2, // Signature OIDs sizeof(rgbOID_RSA_SHA1RSA), rgbOID_RSA_SHA1RSA, CALG_SHA1, sizeof(rgbOID_RSA_MD5RSA), rgbOID_RSA_MD5RSA, CALG_MD5, sizeof(rgbOID_OIWSEC_sha1RSASign), rgbOID_OIWSEC_sha1RSASign, CALG_SHA1, sizeof(rgbOID_OIWSEC_shaRSA), rgbOID_OIWSEC_shaRSA, CALG_SHA1, sizeof(rgbOID_OIWSEC_md5RSA), rgbOID_OIWSEC_md5RSA, CALG_MD5, sizeof(rgbOID_RSA_MD2RSA), rgbOID_RSA_MD2RSA, CALG_MD2, sizeof(rgbOID_OIWDIR_md2RSA), rgbOID_OIWDIR_md2RSA, CALG_MD2, }; #define HASH_ALG_CNT (sizeof(HashAlgTable) / sizeof(HashAlgTable[0])) //+------------------------------------------------------------------------- // Decodes an ASN.1 encoded Algorithm Identifier and converts to // a CAPI Hash AlgID, such as, CALG_SHA1 or CALG_MD5. // // Returns 0 if there isn't a CAPI AlgId corresponding to the Algorithm // Identifier. // // Only CALG_SHA1, CALG_MD5 and CALG_MD2 are supported. //-------------------------------------------------------------------------- ALG_ID WINAPI MinCryptDecodeHashAlgorithmIdentifier( IN PCRYPT_DER_BLOB pAlgIdValueBlob ) { ALG_ID HashAlgId = 0; LONG lSkipped; CRYPT_DER_BLOB rgAlgIdBlob[MINASN1_ALGID_BLOB_CNT]; DWORD cbEncodedOID; const BYTE *pbEncodedOID; DWORD i; lSkipped = MinAsn1ParseAlgorithmIdentifier( pAlgIdValueBlob, rgAlgIdBlob ); if (0 >= lSkipped) goto CommonReturn; cbEncodedOID = rgAlgIdBlob[MINASN1_ALGID_OID_IDX].cbData; pbEncodedOID = rgAlgIdBlob[MINASN1_ALGID_OID_IDX].pbData; for (i = 0; i < HASH_ALG_CNT; i++) { if (cbEncodedOID == HashAlgTable[i].cbEncodedOID && 0 == memcmp(pbEncodedOID, HashAlgTable[i].pbEncodedOID, cbEncodedOID)) { HashAlgId = HashAlgTable[i].AlgId; break; } } CommonReturn: return HashAlgId; } #pragma warning (push) // local variable 'Md5Ctx' may be used without having been initialized #pragma warning (disable: 4701) //+------------------------------------------------------------------------- // Hashes one or more memory blobs according to the Hash ALG_ID. // // rgbHash is updated with the resultant hash. *pcbHash is updated with // the length associated with the hash algorithm. // // If the function succeeds, the return value is ERROR_SUCCESS. Otherwise, // a nonzero error code is returned. // // Only CALG_SHA1, CALG_MD5 and CALG_MD2 are supported. //-------------------------------------------------------------------------- LONG WINAPI MinCryptHashMemory( IN ALG_ID HashAlgId, IN DWORD cBlob, IN PCRYPT_DER_BLOB rgBlob, OUT BYTE rgbHash[MINCRYPT_MAX_HASH_LEN], OUT DWORD *pcbHash ) { A_SHA_CTX ShaCtx; MD5_CTX Md5Ctx; MD2_CTX Md2Ctx; DWORD iBlob; switch (HashAlgId) { case CALG_MD2: memset(&Md2Ctx, 0, sizeof(Md2Ctx)); *pcbHash = MINCRYPT_MD2_HASH_LEN; break; case CALG_MD5: MD5Init(&Md5Ctx); *pcbHash = MINCRYPT_MD5_HASH_LEN; break; case CALG_SHA1: A_SHAInit(&ShaCtx); *pcbHash = MINCRYPT_SHA1_HASH_LEN; break; default: *pcbHash = 0; return NTE_BAD_ALGID; } for (iBlob = 0; iBlob < cBlob; iBlob++) { BYTE *pb = rgBlob[iBlob].pbData; DWORD cb = rgBlob[iBlob].cbData; if (0 == cb) continue; switch (HashAlgId) { case CALG_MD2: MD2Update(&Md2Ctx, pb, cb); break; case CALG_MD5: MD5Update(&Md5Ctx, pb, cb); break; case CALG_SHA1: A_SHAUpdate(&ShaCtx, pb, cb); break; } } switch (HashAlgId) { case CALG_MD2: MD2Final(&Md2Ctx); memcpy(rgbHash, Md2Ctx.state, MINCRYPT_MD2_HASH_LEN); break; case CALG_MD5: MD5Final(&Md5Ctx); assert(MD5DIGESTLEN == MINCRYPT_MD5_HASH_LEN); memcpy(rgbHash, Md5Ctx.digest, MD5DIGESTLEN); break; case CALG_SHA1: A_SHAFinal(&ShaCtx, rgbHash); break; } return ERROR_SUCCESS; } #pragma warning (pop) //+========================================================================= // MinCryptVerifySignedHash Support Functions //-========================================================================= VOID WINAPI I_ReverseAndCopyBytes( OUT BYTE *pbDst, IN const BYTE *pbSrc, IN DWORD cb ) { if (0 == cb) return; for (pbDst += cb - 1; cb > 0; cb--) *pbDst-- = *pbSrc++; } // The basis for much of the code in this function can be found in // \nt\ds\win32\ntcrypto\scp\nt_key.c LONG WINAPI I_ConvertParsedRSAPubKeyToBSafePubKey( IN CRYPT_DER_BLOB rgRSAPubKeyBlob[MINASN1_RSA_PUBKEY_BLOB_CNT], OUT PBSAFE_PUB_KEY_CONTENT pBSafePubKeyContent ) { LONG lErr; DWORD cbModulus; const BYTE *pbAsn1Modulus; DWORD cbExp; const BYTE *pbAsn1Exp; DWORD cbTmpLen; LPBSAFE_PUB_KEY pBSafePubKey; // Get the ASN.1 public key modulus (BIG ENDIAN). The modulus length // is the public key byte length. cbModulus = rgRSAPubKeyBlob[MINASN1_RSA_PUBKEY_MODULUS_IDX].cbData; pbAsn1Modulus = rgRSAPubKeyBlob[MINASN1_RSA_PUBKEY_MODULUS_IDX].pbData; // Strip off a leading 0 byte. Its there in the decoded ASN // integer for an unsigned integer with the leading bit set. if (cbModulus > 1 && *pbAsn1Modulus == 0) { pbAsn1Modulus++; cbModulus--; } if (MAX_RSA_PUB_KEY_BYTE_LEN < cbModulus) goto ExceededMaxPubKeyModulusLen; // Get the ASN.1 public exponent (BIG ENDIAN). cbExp = rgRSAPubKeyBlob[MINASN1_RSA_PUBKEY_EXPONENT_IDX].cbData; pbAsn1Exp = rgRSAPubKeyBlob[MINASN1_RSA_PUBKEY_EXPONENT_IDX].pbData; // Strip off a leading 0 byte. Its there in the decoded ASN // integer for an unsigned integer with the leading bit set. if (cbExp > 1 && *pbAsn1Exp == 0) { pbAsn1Exp++; cbExp--; } if (sizeof(DWORD) < cbExp) goto ExceededMaxPubKeyExpLen; if (0 == cbModulus || 0 == cbExp) goto InvalidPubKey; // Update the BSAFE data structure from the parsed and length validated // ASN.1 public key modulus and exponent components. cbTmpLen = (sizeof(DWORD) * 2) - (cbModulus % (sizeof(DWORD) * 2)); if ((sizeof(DWORD) * 2) != cbTmpLen) cbTmpLen += sizeof(DWORD) * 2; memset(pBSafePubKeyContent, 0, sizeof(*pBSafePubKeyContent)); pBSafePubKey = &pBSafePubKeyContent->Header; pBSafePubKey->magic = RSA1; pBSafePubKey->keylen = cbModulus + cbTmpLen; pBSafePubKey->bitlen = cbModulus * 8; pBSafePubKey->datalen = cbModulus - 1; I_ReverseAndCopyBytes((BYTE *) &pBSafePubKey->pubexp, pbAsn1Exp, cbExp); I_ReverseAndCopyBytes(pBSafePubKeyContent->rgbModulus, pbAsn1Modulus, cbModulus); lErr = ERROR_SUCCESS; CommonReturn: return lErr; ExceededMaxPubKeyModulusLen: ExceededMaxPubKeyExpLen: InvalidPubKey: lErr = NTE_BAD_PUBLIC_KEY; goto CommonReturn; } // The basis for much of the code in this function can be found in // \nt\ds\win32\ntcrypto\scp\nt_sign.c LONG WINAPI I_VerifyPKCS1SigningFormat( IN BSAFE_PUB_KEY *pKey, IN ALG_ID HashAlgId, IN BYTE *pbHash, IN DWORD cbHash, IN BYTE *pbPKCS1Format ) { LONG lErr = ERROR_INTERNAL_ERROR; const BYTE **rgEncOptions; BYTE rgbTmpHash[MINCRYPT_MAX_HASH_LEN]; DWORD i; DWORD cb; BYTE *pbStart; DWORD cbTmp; switch (HashAlgId) { case CALG_MD2: rgEncOptions = md2Encodings; break; case CALG_MD5: rgEncOptions = md5Encodings; break; case CALG_SHA: rgEncOptions = shaEncodings; break; default: goto UnsupportedHash; } // Reverse the hash to match the signature. for (i = 0; i < cbHash; i++) rgbTmpHash[i] = pbHash[cbHash - (i + 1)]; // See if it matches. if (0 != memcmp(rgbTmpHash, pbPKCS1Format, cbHash)) { goto BadSignature; } cb = cbHash; for (i = 0; 0 != *rgEncOptions[i]; i += 1) { pbStart = (LPBYTE)rgEncOptions[i]; cbTmp = *pbStart++; if (0 == memcmp(&pbPKCS1Format[cb], pbStart, cbTmp)) { cb += cbTmp; // Adjust the end of the hash data. break; } } // check to make sure the rest of the PKCS #1 padding is correct if ((0x00 != pbPKCS1Format[cb]) || (0x00 != pbPKCS1Format[pKey->datalen]) || (0x1 != pbPKCS1Format[pKey->datalen - 1])) { goto BadSignature; } for (i = cb + 1; i < pKey->datalen - 1; i++) { if (0xff != pbPKCS1Format[i]) { goto BadSignature; } } lErr = ERROR_SUCCESS; CommonReturn: return lErr; UnsupportedHash: lErr = NTE_BAD_ALGID; goto CommonReturn; BadSignature: lErr = NTE_BAD_SIGNATURE; goto CommonReturn; } //+------------------------------------------------------------------------- // Verifies a signed hash. // // The ASN.1 encoded Public Key Info is parsed and used to decrypt the // signed hash. The decrypted signed hash is compared with the input // hash. // // If the signed hash was successfully verified, ERROR_SUCCESS is returned. // Otherwise, a nonzero error code is returned. // // Only RSA signed hashes are supported. // // Only MD2, MD5 and SHA1 hashes are supported. //-------------------------------------------------------------------------- LONG WINAPI MinCryptVerifySignedHash( IN ALG_ID HashAlgId, IN BYTE *pbHash, IN DWORD cbHash, IN PCRYPT_DER_BLOB pSignedHashContentBlob, IN PCRYPT_DER_BLOB pPubKeyInfoValueBlob ) { LONG lErr; LONG lSkipped; CRYPT_DER_BLOB rgPubKeyInfoBlob[MINASN1_PUBKEY_INFO_BLOB_CNT]; CRYPT_DER_BLOB rgRSAPubKeyBlob[MINASN1_RSA_PUBKEY_BLOB_CNT]; BSAFE_PUB_KEY_CONTENT BSafePubKeyContent; LPBSAFE_PUB_KEY pBSafePubKey; DWORD cbSignature; const BYTE *pbAsn1Signature; BYTE rgbBSafeIn[MAX_BSAFE_PUB_KEY_MODULUS_BYTE_LEN]; BYTE rgbBSafeOut[MAX_BSAFE_PUB_KEY_MODULUS_BYTE_LEN]; // Attempt to parse and convert the ASN.1 encoded public key into // an RSA BSAFE formatted key. lSkipped = MinAsn1ParsePublicKeyInfo( pPubKeyInfoValueBlob, rgPubKeyInfoBlob ); if (0 >= lSkipped) goto ParsePubKeyInfoError; lSkipped = MinAsn1ParseRSAPublicKey( &rgPubKeyInfoBlob[MINASN1_PUBKEY_INFO_PUBKEY_IDX], rgRSAPubKeyBlob ); if (0 >= lSkipped) goto ParseRSAPubKeyError; lErr = I_ConvertParsedRSAPubKeyToBSafePubKey( rgRSAPubKeyBlob, &BSafePubKeyContent ); if (ERROR_SUCCESS != lErr) goto CommonReturn; pBSafePubKey = &BSafePubKeyContent.Header; // Get the ASN.1 signature (BIG ENDIAN). // // It must be the same length as the public key cbSignature = pSignedHashContentBlob->cbData; pbAsn1Signature = pSignedHashContentBlob->pbData; if (cbSignature != pBSafePubKey->bitlen / 8) goto InvalidSignatureLen; // Decrypt the signature (LITTLE ENDIAN) assert(sizeof(rgbBSafeIn) >= cbSignature); I_ReverseAndCopyBytes(rgbBSafeIn, pbAsn1Signature, cbSignature); memset(&rgbBSafeIn[cbSignature], 0, sizeof(rgbBSafeIn) - cbSignature); memset(rgbBSafeOut, 0, sizeof(rgbBSafeOut)); if (!BSafeEncPublic(pBSafePubKey, rgbBSafeIn, rgbBSafeOut)) goto BSafeEncPublicError; lErr = I_VerifyPKCS1SigningFormat( pBSafePubKey, HashAlgId, pbHash, cbHash, rgbBSafeOut ); CommonReturn: return lErr; ParsePubKeyInfoError: ParseRSAPubKeyError: lErr = NTE_BAD_PUBLIC_KEY; goto CommonReturn; InvalidSignatureLen: BSafeEncPublicError: lErr = NTE_BAD_SIGNATURE; goto CommonReturn; }