xsec/enc/WinCAPI/WinCAPICryptoKeyRSA.cpp (547 lines of code) (raw):

/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /* * XSEC * * WinCAPICryptoKeyRSA := RSA Keys * * Author(s): Berin Lautenbach * * $Id$ * */ #if defined (XSEC_HAVE_WINCAPI) #include <xsec/enc/XSECCryptoException.hpp> #include <xsec/enc/WinCAPI/WinCAPICryptoProvider.hpp> #include <xsec/enc/WinCAPI/WinCAPICryptoKeyRSA.hpp> #include <xsec/enc/XSCrypt/XSCryptCryptoBase64.hpp> #include <xsec/framework/XSECError.hpp> #include "../../utils/XSECAlgorithmSupport.hpp" #include <xercesc/util/Janitor.hpp> XSEC_USING_XERCES(ArrayJanitor); #if !defined (CRYPT_OAEP) # define CRYPT_OAEP 0x00000040 # define KP_OAEP_PARAMS 36 #endif #if !defined (CRYPT_DECRYPT_RSA_NO_PADDING_CHECK) # define CRYPT_DECRYPT_RSA_NO_PADDING_CHECK 0x00000020 #endif WinCAPICryptoKeyRSA::WinCAPICryptoKeyRSA(HCRYPTPROV prov) { // Create a new key to be loaded as we go m_key = 0; m_p = prov; m_keySpec = 0; mp_exponent = NULL; m_exponentLen = 0; mp_modulus = NULL; m_modulusLen = 0; }; WinCAPICryptoKeyRSA::~WinCAPICryptoKeyRSA() { // If we have a RSA, delete it if (m_key != 0) CryptDestroyKey(m_key); if (mp_exponent) delete[] mp_exponent; if (mp_modulus) delete[] mp_modulus; }; WinCAPICryptoKeyRSA::WinCAPICryptoKeyRSA(HCRYPTPROV prov, HCRYPTKEY k) : m_p(prov) { m_key = k; // NOTE - We OWN this handle m_keySpec = 0; mp_exponent = mp_modulus = NULL; m_exponentLen = m_modulusLen = 0; } WinCAPICryptoKeyRSA::WinCAPICryptoKeyRSA(HCRYPTPROV prov, DWORD keySpec, bool isPrivate) : m_p(prov) { if (isPrivate == false) { throw XSECCryptoException(XSECCryptoException::RSAError, "WinCAPICryptoKeyRSA - Public keys defined via keySpec ctor not yet supported"); } if (!CryptGetUserKey(prov, keySpec, &m_key)) { throw XSECCryptoException(XSECCryptoException::RSAError, "WinCAPI:RSA Unable to retrieve user key"); } //m_key = 0; m_keySpec = keySpec; mp_exponent = mp_modulus = NULL; m_exponentLen = m_modulusLen = 0; } const XMLCh * WinCAPICryptoKeyRSA::getProviderName() const { return DSIGConstants::s_unicodeStrPROVWinCAPI; } // Generic key functions XSECCryptoKey::KeyType WinCAPICryptoKeyRSA::getKeyType() const { // Find out what we have if (m_key == 0) { if (m_keySpec != 0) return KEY_RSA_PRIVATE; if (mp_exponent == NULL || mp_modulus == NULL) return KEY_NONE; else return KEY_RSA_PUBLIC; } if (m_keySpec != 0) return KEY_RSA_PAIR; return KEY_RSA_PUBLIC; } // -------------------------------------------------------------------------------- // Load key from parameters // -------------------------------------------------------------------------------- void WinCAPICryptoKeyRSA::loadPublicModulusBase64BigNums(const char* b64, unsigned int len) { if (mp_modulus != NULL) { delete[] mp_modulus; mp_modulus = NULL; // In case we get an exception } mp_modulus = WinCAPICryptoProvider::b642WinBN(b64, len, m_modulusLen); } void WinCAPICryptoKeyRSA::loadPublicExponentBase64BigNums(const char* b64, unsigned int len) { if (mp_exponent != NULL) { delete[] mp_exponent; mp_exponent = NULL; // In case we get an exception } mp_exponent = WinCAPICryptoProvider::b642WinBN(b64, len, m_exponentLen); } HCRYPTKEY WinCAPICryptoKeyRSA::importKey() const { if (m_key != 0 || mp_exponent == NULL || mp_modulus == NULL) return m_key; // Create a RSA Public-Key blob // First build a buffer to hold everything BYTE * blobBuffer; unsigned int blobBufferLen = WINCAPI_BLOBHEADERLEN + WINCAPI_RSAPUBKEYLEN + m_modulusLen; XSECnew(blobBuffer, BYTE[blobBufferLen]); ArrayJanitor<BYTE> j_blobBuffer(blobBuffer); // Blob header BLOBHEADER * header = (BLOBHEADER *) blobBuffer; header->bType = PUBLICKEYBLOB; header->bVersion = 0x02; // We are using a version 2 blob header->reserved = 0; header->aiKeyAlg = CALG_RSA_SIGN; // Now the public key header RSAPUBKEY * pubkey = (RSAPUBKEY *) (blobBuffer + WINCAPI_BLOBHEADERLEN); pubkey->magic = 0x31415352; // ASCII encoding of RSA1 pubkey->bitlen = m_modulusLen * 8; // Number of bits in prime modulus pubkey->pubexp = 0; BYTE * i = ((BYTE *) &(pubkey->pubexp)); for (unsigned int j = 0; j < m_exponentLen; ++j) *i++ = mp_exponent[j]; // Now copy in the modulus i = (BYTE *) (pubkey); i += WINCAPI_RSAPUBKEYLEN; memcpy(i, mp_modulus, m_modulusLen); // Now that we have the blob, import BOOL fResult = CryptImportKey( m_p, blobBuffer, blobBufferLen, 0, // Not signed 0, // No flags &m_key); if (fResult == 0) { throw XSECCryptoException(XSECCryptoException::RSAError, "WinCAPI:RSA Error attempting to import key parameters"); } return m_key; } // -------------------------------------------------------------------------------- // Verify a signature encoded as a Base64 string // -------------------------------------------------------------------------------- bool WinCAPICryptoKeyRSA::verifySHA1PKCS1Base64Signature(const unsigned char * hashBuf, unsigned int hashLen, const char * base64Signature, unsigned int sigLen, XSECCryptoHash::HashType type) const { // Use the currently loaded key to validate the Base64 encoded signature if (m_key == 0) { // Try to import from the parameters importKey(); if (m_key == 0) { throw XSECCryptoException(XSECCryptoException::RSAError, "WinCAPI:RSA - Attempt to validate signature with empty key"); } } /* Is this a hash we support? */ ALG_ID alg; switch (type) { case (XSECCryptoHash::HASH_MD5): alg = CALG_MD5; break; case (XSECCryptoHash::HASH_SHA1): alg=CALG_SHA1; break; default: throw XSECCryptoException(XSECCryptoException::RSAError, "WinCAPI:RSA Unsupported hash algorithm for RSA sign - only MD5 or SHA1 supported"); } // Decode the signature unsigned char * rawSig; DWORD rawSigLen; XSECnew(rawSig, BYTE [sigLen]); ArrayJanitor<BYTE> j_rawSig(rawSig); // Decode the signature XSCryptCryptoBase64 b64; b64.decodeInit(); rawSigLen = b64.decode((unsigned char *) base64Signature, sigLen, rawSig, sigLen); rawSigLen += b64.decodeFinish(&rawSig[rawSigLen], sigLen - rawSigLen); BYTE * rawSigFinal; XSECnew(rawSigFinal, BYTE[rawSigLen]); ArrayJanitor<BYTE> j_rawSigFinal(rawSigFinal); BYTE * j, *l; j = rawSig; l = rawSigFinal + rawSigLen - 1; while (l >= rawSigFinal) { *l-- = *j++; } // Have to create a Windows hash object and feed in the hash BOOL fResult; HCRYPTHASH h; fResult = CryptCreateHash(m_p, alg, 0, 0, &h); if (!fResult) { throw XSECCryptoException(XSECCryptoException::RSAError, "WinCAPI:RSA - Error creating Windows Hash Object"); } // Feed the hash value into the newly created hash object fResult = CryptSetHashParam( h, HP_HASHVAL, (unsigned char *) hashBuf, 0); if (!fResult) { throw XSECCryptoException(XSECCryptoException::RSAError, "WinCAPI:RSA - Error Setting Hash Value in Windows Hash object"); } // Now validate fResult = CryptVerifySignature( h, rawSigFinal, rawSigLen, m_key, NULL, 0); if (!fResult) { DWORD error = GetLastError(); if (error != NTE_BAD_SIGNATURE) { if (h) CryptDestroyHash(h); throw XSECCryptoException(XSECCryptoException::RSAError, "WinCAPI:RSA - Error occurred in RSA validation"); } if (h) CryptDestroyHash(h); return false; } if (h) CryptDestroyHash(h); return true; } // -------------------------------------------------------------------------------- // Sign and encode result as a Base64 string // -------------------------------------------------------------------------------- unsigned int WinCAPICryptoKeyRSA::signSHA1PKCS1Base64Signature(unsigned char * hashBuf, unsigned int hashLen, char * base64SignatureBuf, unsigned int base64SignatureBufLen, XSECCryptoHash::HashType type) const { // Sign a pre-calculated hash using this key if (m_keySpec == 0) { throw XSECCryptoException(XSECCryptoException::RSAError, "WinCAPI:RSA - Attempt to sign data using a public or un-loaded key"); } /* Is this a hash we support? */ ALG_ID alg; switch (type) { case (XSECCryptoHash::HASH_MD5): alg = CALG_MD5; break; case (XSECCryptoHash::HASH_SHA1): alg = CALG_SHA1; break; default: throw XSECCryptoException(XSECCryptoException::RSAError, "WinCAPI:RSA Unsupported hash algorithm for RSA sign - only MD5 or SHA1 supported"); } // Have to create a Windows hash object and feed in the hash BOOL fResult; HCRYPTHASH h; fResult = CryptCreateHash(m_p, alg, 0, 0, &h); if (!fResult) { throw XSECCryptoException(XSECCryptoException::RSAError, "WinCAPI:RSA - Error creating Windows Hash Object"); } // Feed the hash value into the newly created hash object fResult = CryptSetHashParam( h, HP_HASHVAL, hashBuf, 0); if (!fResult) { if (h) CryptDestroyHash(h); throw XSECCryptoException(XSECCryptoException::RSAError, "WinCAPI:RSA - Error Setting Hash Value in Windows Hash object"); } // Now sign DWORD rawSigLen; fResult = CryptSignHash( h, m_keySpec, NULL, 0, NULL, &rawSigLen); if (!fResult || rawSigLen < 1) { if (h) CryptDestroyHash(h); throw XSECCryptoException(XSECCryptoException::RSAError, "WinCAPI:RSA - Error occurred obtaining RSA sig length"); } BYTE * rawSig; XSECnew(rawSig, BYTE[rawSigLen]); ArrayJanitor<BYTE> j_rawSig(rawSig); fResult = CryptSignHash( h, m_keySpec, NULL, 0, rawSig, &rawSigLen); if (!fResult || rawSigLen < 1) { // Free the hash if (h) CryptDestroyHash(h); throw XSECCryptoException(XSECCryptoException::RSAError, "WinCAPI:RSA - Error occurred signing hash"); } // Free the hash if (h) CryptDestroyHash(h); // Now encode into a signature block BYTE *rawSigFinal; XSECnew(rawSigFinal, BYTE[rawSigLen]); ArrayJanitor<BYTE> j_rawSigFinal(rawSigFinal); BYTE * i, * j; i = rawSig; j = rawSigFinal + rawSigLen - 1; while (j >= rawSigFinal) { *j-- = *i++; } // Now encode XSCryptCryptoBase64 b64; b64.encodeInit(); unsigned int ret = b64.encode(rawSigFinal, rawSigLen, (unsigned char *) base64SignatureBuf, base64SignatureBufLen); ret += b64.encodeFinish((unsigned char *) &base64SignatureBuf[ret], base64SignatureBufLen - ret); return ret; } XSECCryptoKey * WinCAPICryptoKeyRSA::clone() const { WinCAPICryptoKeyRSA * ret; XSECnew(ret, WinCAPICryptoKeyRSA(m_p)); if (m_key != 0) { // CryptDuplicateKey is not supported in Windows NT, so we need to export and then // reimport the key to get a copy BYTE keyBuf[2048]; DWORD keyBufLen = 2048; CryptExportKey(m_key, 0, PUBLICKEYBLOB, 0, keyBuf, &keyBufLen); // Now re-import CryptImportKey(m_p, keyBuf, keyBufLen, NULL, 0, &ret->m_key); } ret->m_keySpec = m_keySpec; ret->m_exponentLen = m_exponentLen; if (mp_exponent != NULL) { XSECnew(ret->mp_exponent, BYTE[m_exponentLen]); memcpy(ret->mp_exponent, mp_exponent, m_exponentLen); } else ret->mp_exponent = NULL; ret->m_modulusLen = m_modulusLen; if (mp_modulus != NULL) { XSECnew(ret->mp_modulus, BYTE[m_modulusLen]); memcpy(ret->mp_modulus, mp_modulus, m_modulusLen); } else ret->mp_modulus = NULL; return ret; } // -------------------------------------------------------------------------------- // decrypt a buffer // -------------------------------------------------------------------------------- unsigned int WinCAPICryptoKeyRSA::privateDecrypt(const unsigned char * inBuf, unsigned char * plainBuf, unsigned int inLength, unsigned int maxOutLength, PaddingType padding, const XMLCh* hashURI, const XMLCh* mgfURI, unsigned char* params, unsigned int paramsLen) const { // Perform a decrypt if (m_key == NULL) { throw XSECCryptoException(XSECCryptoException::RSAError, "WinCAPI:RSA - Attempt to decrypt data with empty key"); } // Have to reverse ordering of input : DWORD decryptSize = inLength; // memcpy(plainBuf, inBuf, inLength); for (unsigned int i = 0; i < inLength; ++i) { plainBuf[i] = inBuf[inLength - 1 - i]; } switch (padding) { case XSECCryptoKeyRSA::PAD_PKCS_1_5 : if (!CryptDecrypt(m_key, 0, TRUE, 0, plainBuf, &decryptSize)) { throw XSECCryptoException(XSECCryptoException::RSAError, "WinCAPI:RSA privateKeyDecrypt - Error Decrypting PKCS1_5 padded RSA encrypt"); } break; case XSECCryptoKeyRSA::PAD_OAEP : if (XSECAlgorithmSupport::getMGF1HashType(mgfURI) != XSECCryptoHash::HASH_SHA1) { throw XSECCryptoException(XSECCryptoException::UnsupportedAlgorithm, "WinCAPI:RSA - Unsupported OAEP MGF algorithm"); } if (XSECAlgorithmSupport::getHashType(hashURI) != XSECCryptoHash::HASH_SHA1) { throw XSECCryptoException(XSECCryptoException::UnsupportedAlgorithm, "WinCAPI:RSA - Unsupported OAEP digest algorithm"); } else if (paramsLen > 0) { throw XSECCryptoException(XSECCryptoException::UnsupportedError, "WinCAPI::setOAEPParams - OAEP parameters are not supported by Windows Crypto API"); } if (!CryptDecrypt(m_key, 0, TRUE, CRYPT_OAEP, plainBuf, &decryptSize)) { throw XSECCryptoException(XSECCryptoException::RSAError, "WinCAPI:RSA privateKeyDecrypt - Error Decrypting PKCS1v2 OAEP padded RSA encrypt"); } break; default : throw XSECCryptoException(XSECCryptoException::RSAError, "WiNCAPI:RSA - Unknown padding method"); } return decryptSize; } // -------------------------------------------------------------------------------- // encrypt a buffer // -------------------------------------------------------------------------------- unsigned int WinCAPICryptoKeyRSA::publicEncrypt(const unsigned char* inBuf, unsigned char* cipherBuf, unsigned int inLength, unsigned int maxOutLength, PaddingType padding, const XMLCh* hashURI, const XMLCh* mgfURI, unsigned char* params, unsigned int paramsLen) const { // Perform an encrypt if (m_key == 0) { throw XSECCryptoException(XSECCryptoException::RSAError, "WinCAPI:RSA - Attempt to encrypt data with empty key"); } DWORD encryptSize = inLength; memcpy(cipherBuf, inBuf, inLength); switch (padding) { case XSECCryptoKeyRSA::PAD_PKCS_1_5 : if (!CryptEncrypt(m_key, 0, /* No Hash */ TRUE, /* Is Final */ 0, /* No flags */ cipherBuf, &encryptSize, maxOutLength)) { throw XSECCryptoException(XSECCryptoException::RSAError, "WinCAPI:RSA publicKeyEncrypt - Error performing encrypt"); } if (encryptSize <= 0) { throw XSECCryptoException(XSECCryptoException::RSAError, "WinCAPI:RSA publicKeyEncrypt - Error performing PKCS1_5 padded RSA encrypt"); } break; case XSECCryptoKeyRSA::PAD_OAEP : if (XSECAlgorithmSupport::getHashType(hashURI) != XSECCryptoHash::HASH_SHA1) { throw XSECCryptoException(XSECCryptoException::UnsupportedAlgorithm, "WinCAPI:RSA - OAEP padding method requires SHA-1 digest method"); } else if (XSECAlgorithmSupport::getMGF1HashType(mgfURI) != XSECCryptoHash::HASH_SHA1) { throw XSECCryptoException(XSECCryptoException::UnsupportedAlgorithm, "WinCAPI:RSA - Unsupported OAEP MGF algorithm"); } else if (paramsLen > 0) { throw XSECCryptoException(XSECCryptoException::UnsupportedError, "WinCAPI::setOAEPParams - OAEP parameters are not supported by Windows Crypto API"); } if (!CryptEncrypt(m_key, 0, /* No Hash */ TRUE, /* Is Final */ CRYPT_OAEP, cipherBuf, &encryptSize, maxOutLength)) { throw XSECCryptoException(XSECCryptoException::RSAError, "WinCAPI:RSA publicKeyEncrypt - Error performing encrypt"); } if (encryptSize <= 0) { throw XSECCryptoException(XSECCryptoException::RSAError, "WinCAPI:RSA publicKeyEncrypt - Error performing OAEP RSA encrypt"); } break; default : throw XSECCryptoException(XSECCryptoException::RSAError, "WinCAPI:RSA - Unknown padding method"); } // Reverse the output unsigned char *tbuf; XSECnew(tbuf, unsigned char[encryptSize]); ArrayJanitor<unsigned char> j_tbuf(tbuf); memcpy(tbuf, cipherBuf, encryptSize); for (unsigned int i = 0; i < encryptSize; ++i) cipherBuf[i] = tbuf[encryptSize - 1 - i]; return encryptSize; } // -------------------------------------------------------------------------------- // Size in bytes // -------------------------------------------------------------------------------- unsigned int WinCAPICryptoKeyRSA::getLength() const { DWORD len; DWORD pLen = 4; if (!CryptGetKeyParam(m_key, KP_BLOCKLEN, (BYTE *) &len, &pLen, 0)) { throw XSECCryptoException(XSECCryptoException::RSAError, "WinCAPI:RSA - Error determining key size"); } return len / 8; } // -------------------------------------------------------------------------------- // Some utility functions // -------------------------------------------------------------------------------- void WinCAPICryptoKeyRSA::loadParamsFromKey() { if (m_key == 0) { if (m_keySpec == 0) return; // See of we can get the user key if (!CryptGetUserKey(m_p, m_keySpec, &m_key)) return; } // Export key into a keyblob BOOL fResult; DWORD blobLen; fResult = CryptExportKey( m_key, 0, PUBLICKEYBLOB, 0, NULL, &blobLen); if (fResult == 0 || blobLen < 1) { throw XSECCryptoException(XSECCryptoException::RSAError, "WinCAPI:RSA - Error exporting public key"); } BYTE * blob; XSECnew(blob, BYTE[blobLen]); ArrayJanitor<BYTE> j_blob(blob); fResult = CryptExportKey( m_key, 0, PUBLICKEYBLOB, 0, blob, &blobLen); if (fResult == 0 || blobLen < 1) { throw XSECCryptoException(XSECCryptoException::RSAError, "WinCAPI:RSA - Error exporting public key"); } RSAPUBKEY * pk = (RSAPUBKEY *) ( blob + WINCAPI_BLOBHEADERLEN ); DWORD keyLen = pk->bitlen / 8; // Copy the keys BYTE * i = (BYTE *) ( pk ); i += WINCAPI_RSAPUBKEYLEN; if (mp_modulus != NULL) delete[] mp_modulus; m_modulusLen = keyLen; XSECnew(mp_modulus, BYTE[m_modulusLen]); memcpy(mp_modulus, i, m_modulusLen); // Take the simple way out XSECnew(mp_exponent, BYTE[4]); *((DWORD *) mp_exponent) = pk->pubexp; // Now cut any leading 0s (Windows is LE, so start least significant end) m_exponentLen = 3; while (m_exponentLen > 0 && mp_exponent[m_exponentLen] == 0) m_exponentLen--; m_exponentLen++; // Make it a length as apposed to an offset } unsigned int WinCAPICryptoKeyRSA::getExponentBase64BigNums(char* b64, unsigned int len) { if (m_key == 0 && m_keySpec == 0 && mp_exponent == NULL) { return 0; // Nothing we can do } if (mp_exponent == NULL) { loadParamsFromKey(); } unsigned int bLen; unsigned char * b = WinCAPICryptoProvider::WinBN2b64(mp_exponent, m_exponentLen, bLen); if (bLen > len) bLen = len; memcpy(b64, b, bLen); delete[] b; return bLen; } unsigned int WinCAPICryptoKeyRSA::getModulusBase64BigNums(char* b64, unsigned int len) { if (m_key == 0 && m_keySpec == 0 && mp_modulus == NULL) { return 0; // Nothing we can do } if (mp_modulus == NULL) { loadParamsFromKey(); } unsigned int bLen; unsigned char * b = WinCAPICryptoProvider::WinBN2b64(mp_modulus, m_modulusLen, bLen); if (bLen > len) bLen = len; memcpy(b64, b, bLen); delete[] b; return bLen; } #endif /* WINCAPI */