diff options
Diffstat (limited to 'src/classlibnative/cryptography')
-rw-r--r-- | src/classlibnative/cryptography/.gitmirror | 1 | ||||
-rw-r--r-- | src/classlibnative/cryptography/CMakeLists.txt | 6 | ||||
-rw-r--r-- | src/classlibnative/cryptography/cryptography.cpp | 3371 | ||||
-rw-r--r-- | src/classlibnative/cryptography/cryptography.h | 522 | ||||
-rw-r--r-- | src/classlibnative/cryptography/cryptography.nativeproj | 36 | ||||
-rw-r--r-- | src/classlibnative/cryptography/x509certificate.cpp | 1341 | ||||
-rw-r--r-- | src/classlibnative/cryptography/x509certificate.h | 166 |
7 files changed, 5443 insertions, 0 deletions
diff --git a/src/classlibnative/cryptography/.gitmirror b/src/classlibnative/cryptography/.gitmirror new file mode 100644 index 0000000000..f507630f94 --- /dev/null +++ b/src/classlibnative/cryptography/.gitmirror @@ -0,0 +1 @@ +Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror.
\ No newline at end of file diff --git a/src/classlibnative/cryptography/CMakeLists.txt b/src/classlibnative/cryptography/CMakeLists.txt new file mode 100644 index 0000000000..717e052bf0 --- /dev/null +++ b/src/classlibnative/cryptography/CMakeLists.txt @@ -0,0 +1,6 @@ +set( COMCRYPT_WKS_SOURCES + Cryptography.cpp + X509Certificate.cpp +) + +add_library( comcrypt_wks ${COMCRYPT_WKS_SOURCES}) diff --git a/src/classlibnative/cryptography/cryptography.cpp b/src/classlibnative/cryptography/cryptography.cpp new file mode 100644 index 0000000000..cacea14812 --- /dev/null +++ b/src/classlibnative/cryptography/cryptography.cpp @@ -0,0 +1,3371 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +// +// File: Cryptography.cpp +// + +// +// Native method implementations and helper code for supporting CAPI based operations +//--------------------------------------------------------------------------- + + + +#include "common.h" + +#include "field.h" +#include "cryptography.h" + +#if defined(FEATURE_CRYPTO) || defined(FEATURE_LEGACYNETCFCRYPTO) +const BYTE g_rgbPrivKey[] = +{ + 0x07, 0x02, 0x00, 0x00, 0x00, 0xA4, 0x00, 0x00, + 0x52, 0x53, 0x41, 0x32, 0x00, 0x02, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0xAB, 0xEF, 0xFA, 0xC6, + 0x7D, 0xE8, 0xDE, 0xFB, 0x68, 0x38, 0x09, 0x92, + 0xD9, 0x42, 0x7E, 0x6B, 0x89, 0x9E, 0x21, 0xD7, + 0x52, 0x1C, 0x99, 0x3C, 0x17, 0x48, 0x4E, 0x3A, + 0x44, 0x02, 0xF2, 0xFA, 0x74, 0x57, 0xDA, 0xE4, + 0xD3, 0xC0, 0x35, 0x67, 0xFA, 0x6E, 0xDF, 0x78, + 0x4C, 0x75, 0x35, 0x1C, 0xA0, 0x74, 0x49, 0xE3, + 0x20, 0x13, 0x71, 0x35, 0x65, 0xDF, 0x12, 0x20, + 0xF5, 0xF5, 0xF5, 0xC1, 0xED, 0x5C, 0x91, 0x36, + 0x75, 0xB0, 0xA9, 0x9C, 0x04, 0xDB, 0x0C, 0x8C, + 0xBF, 0x99, 0x75, 0x13, 0x7E, 0x87, 0x80, 0x4B, + 0x71, 0x94, 0xB8, 0x00, 0xA0, 0x7D, 0xB7, 0x53, + 0xDD, 0x20, 0x63, 0xEE, 0xF7, 0x83, 0x41, 0xFE, + 0x16, 0xA7, 0x6E, 0xDF, 0x21, 0x7D, 0x76, 0xC0, + 0x85, 0xD5, 0x65, 0x7F, 0x00, 0x23, 0x57, 0x45, + 0x52, 0x02, 0x9D, 0xEA, 0x69, 0xAC, 0x1F, 0xFD, + 0x3F, 0x8C, 0x4A, 0xD0, + + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x64, 0xD5, 0xAA, 0xB1, + 0xA6, 0x03, 0x18, 0x92, 0x03, 0xAA, 0x31, 0x2E, + 0x48, 0x4B, 0x65, 0x20, 0x99, 0xCD, 0xC6, 0x0C, + 0x15, 0x0C, 0xBF, 0x3E, 0xFF, 0x78, 0x95, 0x67, + 0xB1, 0x74, 0x5B, 0x60, + + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +const BYTE g_rgbSymKey[] = +{ + 0x01, 0x02, 0x00, 0x00, 0x02, 0x66, 0x00, 0x00, + 0x00, 0xA4, 0x00, 0x00, 0xAD, 0x89, 0x5D, 0xDA, + 0x82, 0x00, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x02, 0x00 +}; + +const BYTE g_rgbPubKey[] = +{ + 0x06, 0x02, 0x00, 0x00, 0x00, 0xa4, 0x00, 0x00, + 0x52, 0x53, 0x41, 0x31, 0x00, 0x02, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0xab, 0xef, 0xfa, 0xc6, + 0x7d, 0xe8, 0xde, 0xfb, 0x68, 0x38, 0x09, 0x92, + 0xd9, 0x42, 0x7e, 0x6b, 0x89, 0x9e, 0x21, 0xd7, + 0x52, 0x1c, 0x99, 0x3c, 0x17, 0x48, 0x4e, 0x3a, + 0x44, 0x02, 0xf2, 0xfa, 0x74, 0x57, 0xda, 0xe4, + 0xd3, 0xc0, 0x35, 0x67, 0xfa, 0x6e, 0xdf, 0x78, + 0x4c, 0x75, 0x35, 0x1c, 0xa0, 0x74, 0x49, 0xe3, + 0x20, 0x13, 0x71, 0x35, 0x65, 0xdf, 0x12, 0x20, + 0xf5, 0xf5, 0xf5, 0xc1 +}; + + +ProviderCache *ProviderCache::s_pCache = NULL; + +//--------------------------------------------------------------------------------------- +// +// Associate a default CSP name with a CSP type +// +// Arguments: +// dwType - type of CSP to associate the default name with +// pwzProvider - name of the default CSP for dwType +// +// Notes: +// Can throw an OOM if this is the first call into the method and +// the underlying cache has yet to be allocated. See ProviderCache::InternalCacheProvider +// for other details. + +// static +void ProviderCache::CacheProvider(DWORD dwType, __in_z LPWSTR pwzProvider) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + PRECONDITION(pwzProvider != NULL); + } + CONTRACTL_END; + + if (s_pCache == NULL) + { + NewHolder<ProviderCache> cacheHolder(new ProviderCache()); + LPVOID pvExchange = InterlockedCompareExchangeT(&s_pCache, + cacheHolder.GetValue(), + NULL); + if (pvExchange == NULL) + cacheHolder.SuppressRelease(); + } + + s_pCache->InternalCacheProvider(dwType, pwzProvider); +} + +//--------------------------------------------------------------------------------------- +// +// Get the CSP name associated with the CSP type +// +// Arguments: +// dwType - type of CSP to lookup the name of +// +// Return Value: +// Name of the CSP if it is cached, NULL if there is no association yet + +// static +LPCWSTR ProviderCache::GetProvider(DWORD dwType) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + if (s_pCache != NULL) + return s_pCache->InternalGetProvider(dwType); + else + return NULL; +} + +//--------------------------------------------------------------------------------------- +// +// Initialize the CSP cache +// +// Notes: +// Can throw an OOM the hashtable could not be setup properly +// + +ProviderCache::ProviderCache() + : m_crstCache(CrstCSPCache) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + LockOwner lockOwner = { &m_crstCache, IsOwnerOfCrst }; + if (!m_htCache.Init(MaxWindowsProviderType, &lockOwner)) + COMPlusThrowOM(); +} + +//--------------------------------------------------------------------------------------- +// +// Associate a default CSP name with a CSP type +// +// Arguments: +// dwType - type of CSP to associate the default name with +// pwzProvider - name of the default CSP for dwType +// + +void ProviderCache::InternalCacheProvider(DWORD dwType, __in_z LPWSTR pwzProvider) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + CAN_TAKE_LOCK; + PRECONDITION(pwzProvider != NULL); + } + CONTRACTL_END; + + CrstHolder lockHolder(&m_crstCache); + if (GetProvider(dwType) == NULL) + m_htCache.InsertValue(dwType, reinterpret_cast<HashDatum>(pwzProvider)); +} + +//--------------------------------------------------------------------------------------- +// +// Get the CSP name associated with the CSP type +// +// Arguments: +// dwType - type of CSP to lookup the name of +// +// Return Value: +// Name of the CSP if it is cached, NULL if there is no association yet + +LPCWSTR ProviderCache::InternalGetProvider(DWORD dwType) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + HashDatum datum; + if (!m_htCache.GetValue(dwType, &datum)) + return NULL; + + _ASSERTE(datum != NULL); + return reinterpret_cast<LPCWSTR>(datum); +} +#endif // FEATURE_CRYPTO + +#if defined(FEATURE_X509) || defined(FEATURE_CRYPTO) || defined(FEATURE_LEGACYNETCFCRYPTO) +// +// Throw a runtime exception based on the HRESULT passed in. +// + +void CryptoHelper::COMPlusThrowCrypto(HRESULT hr) { + CONTRACTL { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } CONTRACTL_END; + + GCX_COOP(); + + MethodDescCallSite throwMethod(METHOD__CRYPTO_EXCEPTION__THROW); + + ARG_SLOT args[] = { + (ARG_SLOT) hr + }; + throwMethod.Call(args); +} + +BOOL CryptoHelper::WszCryptAcquireContext_SO_TOLERANT (HCRYPTPROV *phProv, LPCWSTR pwszContainer, LPCWSTR pwszProvider, DWORD dwProvType, DWORD dwFlags) +{ + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_MODE_PREEMPTIVE; + STATIC_CONTRACT_SO_INTOLERANT; + + BOOL fResult = FALSE; + DWORD dwLastError = 0; + +#ifdef FEATURE_CRYPTO + // Specifying both verify context (for an ephemeral key) and machine keyset (for a persisted machine key) + // does not make sense. Additionally, Widows is beginning to lock down against uses of MACHINE_KEYSET + // (for instance in the app container), even if verify context is present. Therefore, if we're using + // an ephemeral key, strip out MACHINE_KEYSET from the flags. + if ((dwFlags & CRYPT_VERIFYCONTEXT) && (dwFlags & CRYPT_MACHINE_KEYSET)) + { + dwFlags &= ~CRYPT_MACHINE_KEYSET; + } +#endif // FEATURE_CRYPTO + + BEGIN_SO_TOLERANT_CODE_CALLING_HOST(GetThread()) + + { + LeaveRuntimeHolder lrh((size_t)::CryptAcquireContextW); + fResult = WszCryptAcquireContext (phProv, pwszContainer, pwszProvider, dwProvType, dwFlags); + if (!fResult) + dwLastError = ::GetLastError(); + } + + END_SO_TOLERANT_CODE_CALLING_HOST; + + // END_SO_TOLERANT_CODE overwrites lasterror. Let's reset it. + ::SetLastError(dwLastError); + return fResult; +} + +// +// Helper method to get a Unicode copy of a managed string object. The Unicode string has to be freed with delete []. +// + +WCHAR* CryptoHelper::STRINGREFToUnicode (STRINGREF s) { + CONTRACTL { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + PRECONDITION(s != NULL); + } CONTRACTL_END; + + int cchUnicodeChar = s->GetStringLength(); + WCHAR* pwszUnicode = new WCHAR[cchUnicodeChar + 1]; + memcpy (pwszUnicode, s->GetBuffer(), cchUnicodeChar * sizeof(WCHAR)); + pwszUnicode[cchUnicodeChar] = W('\0'); + + return pwszUnicode; +} +#endif // FEATURE_X509 || FEATURE_CRYPTO + +#if defined(FEATURE_CRYPTO) || defined(FEATURE_LEGACYNETCFCRYPTO) +// +// Helper method to generate a random key container name. +// The caller is responsible for freeing the memory allocated. +// + +WCHAR* CryptoHelper::GetRandomKeyContainer() { + CONTRACTL { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } CONTRACTL_END; + + GUID guid; + HRESULT hr = CoCreateGuid(&guid); + if (hr != S_OK) + COMPlusThrowHR(hr); + + WCHAR* pwszKeyContainerName = new WCHAR[50]; + memcpy(pwszKeyContainerName, W("CLR"), 4 * sizeof(WCHAR)); + + if (GuidToLPWSTR(guid, &pwszKeyContainerName[3], 45) == 0) { + DWORD lastError = GetLastError(); + delete [] pwszKeyContainerName; + COMPlusThrowHR(HRESULT_FROM_WIN32(lastError)); + } + + return pwszKeyContainerName; +} +#endif // FEATURE_CRYPTO + +#if defined(FEATURE_CRYPTO) || defined(FEATURE_LEGACYNETCFCRYPTO) || defined(FEATURE_X509) +// +// Helper method to get a Unicode string from an ANSI string. The Unicode string has to be freed with delete []. +// + +WCHAR* CryptoHelper::AnsiToUnicode (__in_z char* pszAnsi) { + CONTRACTL { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } CONTRACTL_END; + + int cchUnicodeChar = WszMultiByteToWideChar(CP_ACP, + 0, + pszAnsi, + -1, + NULL, + 0); + if (cchUnicodeChar == 0) + COMPlusThrowWin32(); + + WCHAR* pwszUnicode = new WCHAR[cchUnicodeChar]; + cchUnicodeChar = WszMultiByteToWideChar(CP_ACP, + 0, + pszAnsi, + -1, + pwszUnicode, + cchUnicodeChar); + if (cchUnicodeChar == 0) { + DWORD lastError = GetLastError(); + delete [] pwszUnicode; + COMPlusThrowHR(HRESULT_FROM_WIN32(lastError)); + } + + return pwszUnicode; +} + +// +// Helper method to construct a managed array from an unamanged byte array. The array ref has to be protected. +// + +void CryptoHelper::ByteArrayToU1ARRAYREF (LPBYTE pb, DWORD cb, U1ARRAYREF* u1) { + CONTRACTL { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + PRECONDITION(CheckPointer(pb)); + } CONTRACTL_END; + + OBJECTREF array = AllocatePrimitiveArray(ELEMENT_TYPE_U1, cb); + SetObjectReference((OBJECTREF*) u1, array, NULL); + memcpyNoGCRefs((*u1)->GetDirectPointerToNonObjectElements(), pb, cb); +} +#endif // FEATURE_CRYPTO || FEATURE_X509 + +#if defined(FEATURE_CRYPTO) || defined(FEATURE_LEGACYNETCFCRYPTO) +// +// memrev +// + +inline void CryptoHelper::memrev(LPBYTE pb, DWORD cb) { + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } CONTRACTL_END; + + BYTE b; + LPBYTE pbEnd = pb+cb-1; + LPBYTE pbStart = pb; + + for (DWORD i=0; i<cb/2; i++, pbStart++, pbEnd--) { + b = *pbStart; + *pbStart = *pbEnd; + *pbEnd = b; + } +} +#endif // FEATURE_CRYPTO + +#if defined(FEATURE_CRYPTO) || defined(FEATURE_LEGACYNETCFCRYPTO) || defined(FEATURE_X509) +// +// Helper method to construct a byte array from a managed array ref. The unmanaged byte array pointer has to freed with delete []. +// + +BYTE* CryptoHelper::U1ARRAYREFToByteArray (U1ARRAYREF u1) { + CONTRACTL { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + PRECONDITION(u1 != NULL); + } CONTRACTL_END; + + BYTE* pb = new BYTE[u1->GetNumComponents()]; + memcpy(pb, (LPBYTE) u1->GetDirectPointerToNonObjectElements(), u1->GetNumComponents()); + + return pb; +} + +// +// Helper method to get an ANSI string from a Unicode string. The ANSI string has to be freed with delete []. +// + +char* CryptoHelper::UnicodeToAnsi (__in_z WCHAR* pwszUnicode) { + CONTRACTL { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } CONTRACTL_END; + + int cchAnsiChar; + cchAnsiChar = WszWideCharToMultiByte(CP_ACP, + 0, + pwszUnicode, + -1, + NULL, + 0, + NULL, + NULL); + if (cchAnsiChar == 0) + COMPlusThrowWin32(); + + char* pszAnsi = new char[cchAnsiChar]; + cchAnsiChar = WszWideCharToMultiByte(CP_ACP, + 0, + pwszUnicode, + -1, + pszAnsi, + cchAnsiChar, + NULL, + NULL); + if (cchAnsiChar == 0) { + DWORD lastError = GetLastError(); + delete [] pszAnsi; + COMPlusThrowHR(HRESULT_FROM_WIN32(lastError)); + } + + return pszAnsi; +} +#endif // FEATURE_CRYPTO || FEATURE_X509 + +#if defined(FEATURE_CRYPTO) || defined(FEATURE_LEGACYNETCFCRYPTO) +BOOL CryptoHelper::CryptGenKey_SO_TOLERANT (HCRYPTPROV hProv, ALG_ID Algid, DWORD dwFlags, HCRYPTKEY* phKey) +{ + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_GC_NOTRIGGER; + STATIC_CONTRACT_MODE_PREEMPTIVE; + STATIC_CONTRACT_SO_INTOLERANT; + + BOOL fResult = FALSE; + DWORD dwLastError = 0; + + BEGIN_SO_TOLERANT_CODE_CALLING_HOST(GetThread()) + + fResult = CryptGenKey (hProv, Algid, dwFlags, phKey); + if (!fResult) + dwLastError = ::GetLastError(); + + END_SO_TOLERANT_CODE_CALLING_HOST; + + // END_SO_TOLERANT_CODE overwrites lasterror. Let's reset it. + ::SetLastError(dwLastError); + return fResult; +} + +// +// Check to see if a better CSP than the one requested is available +// DSS providers are supersets of each other in the following order: +// 1. MS_ENH_DSS_DH_PROV +// 2. MS_DEF_DSS_DH_PROV +// +// This will return the best provider which is a superset of wszProvider, +// or NULL if there is no upgrade available on the machine. +// +LPCWSTR CryptoHelper::UpgradeDSS(DWORD dwProvType, __in_z LPCWSTR wszProvider) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + PRECONDITION(dwProvType == PROV_DSS_DH); + PRECONDITION(wszProvider != NULL); + } + CONTRACTL_END; + + LPCWSTR wszUpgrade = NULL; + HandleCSPHolder hProv = NULL; + + if (wcscmp(wszProvider, MS_DEF_DSS_DH_PROV_W) == 0) + { + // If this is the base DSS/DH provider, see if we can use the enhanced provider instead. + if (CryptoHelper::WszCryptAcquireContext_SO_TOLERANT(&hProv, NULL, MS_ENH_DSS_DH_PROV_W, dwProvType, CRYPT_VERIFYCONTEXT)) + wszUpgrade = MS_ENH_DSS_DH_PROV_W; + } + + return wszUpgrade; +} + +// +// Check to see if a better CSP than the one requested is available +// RSA providers are supersets of each other in the following order: +// 1. MS_ENH_RSA_AES_PROV +// 2. MS_ENHANCED_PROV +// 3. MS_DEF_PROV +// +// This will return the best provider which is a superset of wszProvider, +// or NULL if there is no upgrade available on the machine. +// +LPCWSTR CryptoHelper::UpgradeRSA(DWORD dwProvType, __in_z LPCWSTR wszProvider) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + PRECONDITION(dwProvType == PROV_RSA_FULL); + PRECONDITION(wszProvider != NULL); + } + CONTRACTL_END; + + bool requestedEnhanced = wcscmp(wszProvider, MS_ENHANCED_PROV_W) == 0; + bool requestedBase = wcscmp(wszProvider, MS_DEF_PROV_W) == 0; + + LPCWSTR wszUpgrade = NULL; + HandleCSPHolder hProv = NULL; + + if (requestedBase || requestedEnhanced) + { + // attempt to use the AES provider + if (CryptoHelper::WszCryptAcquireContext_SO_TOLERANT(&hProv, NULL, MS_ENH_RSA_AES_PROV_W, dwProvType, CRYPT_VERIFYCONTEXT)) + wszUpgrade = MS_ENH_RSA_AES_PROV_W; + } + else if (wszUpgrade == NULL && requestedBase) + { + // if AES wasn't available and we requested the base CSP, try the enhanced one + if (CryptoHelper::WszCryptAcquireContext_SO_TOLERANT(&hProv, NULL, MS_ENHANCED_PROV_W, dwProvType, CRYPT_VERIFYCONTEXT)) + wszUpgrade = MS_ENHANCED_PROV_W; + } + + return wszUpgrade; +} + +// +// WARNING: This function side-effects its first argument (hProv) +// MSProviderCryptImportKey does an "exponent-of-one" import of specified +// symmetric key material into a CSP. However, it clobbers any exchange key pair +// already in hProv. +// + +HRESULT COMCryptography::ExponentOfOneImport (HCRYPTPROV hProv, + LPBYTE rgbKeyMaterial, + DWORD cbKeyMaterial, + DWORD dwKeyAlg, + DWORD dwFlags, + HCRYPTKEY* phKey) { + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } CONTRACTL_END; + + HRESULT hr = S_OK; + + LPBYTE pb = NULL; + BLOBHEADER * pbhdr = NULL; + BYTE rgb[sizeof(g_rgbSymKey)]; + + // Do this check here as a sanity check to avoid buffer overruns + // variable bufSize used to allow for overflow. + DWORD bufSize= cbKeyMaterial + sizeof(ALG_ID) + sizeof(BLOBHEADER); + if (bufSize < cbKeyMaterial || bufSize >= sizeof(g_rgbSymKey)) + return E_FAIL; + + memcpy(rgb, g_rgbSymKey, sizeof(g_rgbSymKey)); + + pbhdr = (BLOBHEADER *) rgb; + pbhdr->aiKeyAlg = dwKeyAlg; + pb = &rgb[sizeof(*pbhdr)]; + *((ALG_ID *) pb) = CALG_RSA_KEYX; + + pb += sizeof(ALG_ID); + for (DWORD i=0; i<cbKeyMaterial; i++) + pb[cbKeyMaterial-i-1] = rgbKeyMaterial[i]; + pb[cbKeyMaterial] = 0; + + HandleKeyHolder hPrivKey(NULL); + if (!CryptImportKey(hProv, g_rgbPrivKey, sizeof(g_rgbPrivKey), 0, 0, &hPrivKey)) { + hr = HRESULT_FROM_GetLastError(); + LOG((LF_SECURITY, LL_INFO10000, "Error [%#x]: COMCryptography::ExponentOfOneImport --> CryptImportKey failed.\n", hr)); + } + + if (!CryptImportKey(hProv, rgb, sizeof(rgb), hPrivKey, dwFlags, phKey)) { + hr = HRESULT_FROM_GetLastError(); + LOG((LF_SECURITY, LL_INFO10000, "Error [%#x]: COMCryptography::ExponentOfOneImport --> CryptImportKey failed.\n", hr)); + } + + return hr; +} + +HRESULT COMCryptography::PlainTextKeyBlobImport (HCRYPTPROV hProv, + LPBYTE rgbKeyMaterial, + DWORD cbKeyMaterial, + DWORD dwKeyAlg, + DWORD dwFlags, + HCRYPTKEY* phKey) { + CONTRACTL { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + } CONTRACTL_END; + + HRESULT hr = S_OK; + + DWORD cb = cbKeyMaterial + sizeof(DWORD) + sizeof(BLOBHEADER); + NewArrayHolder<BYTE> pbHolder(new BYTE[cb]); + LPBYTE pb = (LPBYTE) pbHolder.GetValue(); + + BLOBHEADER * pbhdr = (BLOBHEADER *) pb; + pbhdr->bType = PLAINTEXTKEYBLOB; + pbhdr->bVersion = CUR_BLOB_VERSION; + pbhdr->reserved = 0x0000; + pbhdr->aiKeyAlg = dwKeyAlg; + + pb += sizeof(*pbhdr); + *((DWORD *) pb) = cbKeyMaterial; + pb += sizeof(DWORD); + memcpy(pb, rgbKeyMaterial, cbKeyMaterial); + + if (!CryptImportKey(hProv, pbHolder, cb, 0, dwFlags, phKey)) { + hr = HRESULT_FROM_GetLastError(); + LOG((LF_SECURITY, LL_INFO10000, "Error [%#x]: COMCryptography::PlainTextKeyBlobImport --> CryptImportKey failed.\n", hr)); + } + + return hr; +} + +HRESULT COMCryptography::LoadKey (LPBYTE rgbKeyMaterial, + DWORD cbKeyMaterial, + HCRYPTPROV hprov, + DWORD dwCalg, + DWORD dwFlags, + HCRYPTKEY* phkey) { + WRAPPER_NO_CONTRACT; + + HRESULT hr = PlainTextKeyBlobImport(hprov, rgbKeyMaterial, cbKeyMaterial, dwCalg, dwFlags, phkey); + if (FAILED(hr)) + hr = ExponentOfOneImport(hprov, rgbKeyMaterial, cbKeyMaterial, dwCalg, dwFlags, phkey); + return hr; +} + +// +// WARNING: This function side-effects its first argument (hProv) +// + +HRESULT COMCryptography::UnloadKey(HCRYPTPROV hprov, + HCRYPTKEY hkey, + LPBYTE* ppb, + DWORD* pcb) { + CONTRACTL { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } CONTRACTL_END; + + DWORD cbOut = 0; + HandleKeyHolder hPubKey(NULL); + + HRESULT hr = S_OK; + if (!CryptImportKey(hprov, g_rgbPubKey, sizeof(g_rgbPubKey), 0, 0, &hPubKey)) { + hr = HRESULT_FROM_GetLastError(); + LOG((LF_SECURITY, LL_INFO10000, "Error [%#x]: COMCryptography::UnloadKey --> CryptImportKey failed.\n", hr)); + return hr; + } + + if (!CryptExportKey(hkey, hPubKey, SIMPLEBLOB, 0, NULL, &cbOut)) { + hr = HRESULT_FROM_GetLastError(); + LOG((LF_SECURITY, LL_INFO10000, "Error [%#x]: COMCryptography::UnloadKey --> CryptExportKey failed.\n", hr)); + return hr; + } + + NewArrayHolder<BYTE> pbOut(new BYTE[cbOut]); + if (!CryptExportKey(hkey, hPubKey, SIMPLEBLOB, 0, pbOut, &cbOut)) { + hr = HRESULT_FROM_GetLastError(); + LOG((LF_SECURITY, LL_INFO10000, "Error [%#x]: COMCryptography::UnloadKey --> CryptExportKey failed.\n", hr)); + return hr; + } + + // Get size of the item + LPBYTE pb2 = pbOut + sizeof(BLOBHEADER) + sizeof(DWORD); + DWORD i= cbOut - sizeof(BLOBHEADER) - sizeof(DWORD) - 2; + if (i >= cbOut) { + // integer overflow + return E_FAIL; + } + while (i > 0) { + if (pb2[i] == 0) + break; + i--; + } + + // Now allocate the return buffer + *ppb = new BYTE[i]; + + memcpy(*ppb, pb2, i); + CryptoHelper::memrev(*ppb, i); + *pcb = i; + + return hr; +} + +// +// GetDefaultProvider +// +// Description: +// Find the default provider name to be used in the case that we +// were not actually passed in a provider name. The main purpose +// of this code is really to deal with the enhanched/default provider +// problems given to us by CAPI. +// +// Returns: +// name of the provider to be used. +// + +LPCWSTR COMCryptography::GetDefaultProvider(DWORD dwType) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + // If we have already gotten a name for this provider type, then just return it. + LPCWSTR pwszCached = ProviderCache::GetProvider(dwType); + if (pwszCached != NULL) + return pwszCached; + + // figure out how big the provider name is + DWORD cbProviderName = 0; + if (!WszCryptGetDefaultProvider(dwType, NULL, CRYPT_MACHINE_DEFAULT, NULL, &cbProviderName)) + { + DWORD dwLastError = GetLastError(); + LOG((LF_SECURITY, LL_INFO10, "Error [%#x]: CryptGetDefaultProvider(%d)", dwLastError, dwType)); + return NULL; + } + + // get the CSP name from CAPI + NewArrayHolder<WCHAR> pwszProviderName(new WCHAR[cbProviderName]); + if (!WszCryptGetDefaultProvider(dwType, NULL, CRYPT_MACHINE_DEFAULT, pwszProviderName, &cbProviderName)) + { + DWORD dwLastError = GetLastError(); + LOG((LF_SECURITY, LL_INFO10, "Error [%#x]: CryptGetDefaultProvider(%d)", dwLastError, dwType)); + return NULL; + } + + { + GCX_PREEMP(); + + // check to see if there are upgrades available for the requested CSP + LPCWSTR wszUpgrade = NULL; + if (dwType == PROV_RSA_FULL) + wszUpgrade = CryptoHelper::UpgradeRSA(dwType, pwszProviderName); + else if (dwType == PROV_DSS_DH) + wszUpgrade = CryptoHelper::UpgradeDSS(dwType, pwszProviderName); + + if (wszUpgrade != NULL) + { + LOG((LF_SECURITY, LL_INFO10, "Upgrading from CSP %s to CSP %s", pwszProviderName, wszUpgrade)); + + pwszProviderName.Release(); + const size_t cchProvider = wcslen(wszUpgrade) + 1; + pwszProviderName = new WCHAR[cchProvider]; + wcscpy_s(pwszProviderName, cchProvider, wszUpgrade); + } + } + + ProviderCache::CacheProvider(dwType, pwszProviderName); + pwszProviderName.SuppressRelease(); + + LOG((LF_SECURITY, LL_INFO100, "Using CSP %s as default for CSP type %d", pwszProviderName, dwType)); + return pwszProviderName; +} + +// converts a big-endian byte array to a DWORD value +inline DWORD COMCryptography::ConvertByteArrayToDWORD (LPBYTE pb, DWORD cb) { + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + PRECONDITION((cb <= 4 && cb >= 0)); + } CONTRACTL_END; + + DWORD dwOutput = 0; + for (DWORD i = 0; i < cb; i++) { + dwOutput = dwOutput << 8; + dwOutput += pb[i]; + } + return dwOutput; +} + +// output of this routine is always big endian +inline void COMCryptography::ConvertIntToByteArray(DWORD dwInput, LPBYTE * ppb, DWORD * pcb) { + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + SO_INTOLERANT; + } CONTRACTL_END; + + if (dwInput == 0) { + *ppb = new BYTE[1]; + (*ppb)[0] = 0; + return; + } + + *ppb = new BYTE[4]; + + DWORD t1 = dwInput; // t1 is remaining value to account for + DWORD t2; // t2 is (t1 % 256) & 0xFF + DWORD i = 0; + + while (t1 > 0) { + t2 = (t1 % 256) & 0xFF; + (*ppb)[i] = static_cast<BYTE>(t2); + t1 = (t1 - t2) >> 8; + i++; + } + + *pcb = i; + CryptoHelper::memrev(*ppb, i); +} + +// Maps CspProviderFlags enumeration into CAPI flags. +DWORD COMCryptography::MapCspKeyFlags (DWORD dwFlags) { + DWORD dwCapiFlags = 0; + if ((dwFlags & CSP_PROVIDER_FLAGS_USE_NON_EXPORTABLE_KEY) == 0) + dwCapiFlags |= CRYPT_EXPORTABLE; + if (dwFlags & CSP_PROVIDER_FLAGS_USE_ARCHIVABLE_KEY) + dwCapiFlags |= CRYPT_ARCHIVABLE; + if (dwFlags & CSP_PROVIDER_FLAGS_USE_USER_PROTECTED_KEY) + dwCapiFlags |= CRYPT_USER_PROTECTED; + + return dwCapiFlags; +} + +// Maps CspProviderFlags enumeration into CAPI flags. +DWORD COMCryptography::MapCspProviderFlags (DWORD dwFlags) { + DWORD dwCapiFlags = 0; + if (dwFlags & CSP_PROVIDER_FLAGS_USE_MACHINE_KEYSTORE) + dwCapiFlags |= CRYPT_MACHINE_KEYSET; + if (dwFlags & CSP_PROVIDER_FLAGS_USE_CRYPT_SILENT) + dwCapiFlags |= CRYPT_SILENT; + if (dwFlags & CSP_PROVIDER_FLAGS_CREATE_EPHEMERAL_KEY) + dwCapiFlags |= CRYPT_VERIFYCONTEXT; + + return dwCapiFlags; +} + +// +// OpenCSP +// +// Description: +// OpenCSP performs the core work of opening and creating CSPs and +// containers in CSPs. +// + +HRESULT COMCryptography::OpenCSP(OBJECTREF * pSafeThis, DWORD dwFlags, CRYPT_PROV_CTX * pProvCtx) { + CONTRACTL { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + } CONTRACTL_END; + + NewArrayHolder<WCHAR> pwszProviderHolder = NULL; // if we need to allocate the CSP name ourselves + // store it here so it can be released + LPCWSTR pwszProvider = NULL; // location where the CSP name will be read + // regardless of where we loaded it from + + NewArrayHolder<WCHAR> pwszContainer = NULL; + + // + // Look for the provider type + // + + FieldDesc * pFD = MscorlibBinder::GetField(FIELD__CSP_PARAMETERS__PROVIDER_TYPE); + DWORD dwType = pFD->GetValue32(*pSafeThis); + + // + // Look for the provider name + // + + pFD = MscorlibBinder::GetField(FIELD__CSP_PARAMETERS__PROVIDER_NAME); + + OBJECTREF objref = pFD->GetRefValue(*pSafeThis); + STRINGREF strProvider = ObjectToSTRINGREF(*(StringObject **) &objref); + if (strProvider != NULL) { + LPCWSTR pwsz = strProvider->GetBuffer(); + if ((pwsz != NULL) && (*pwsz != 0)) { + pwszProviderHolder = CryptoHelper::STRINGREFToUnicode(strProvider); + pProvCtx->m_fReleaseProvider = TRUE; + pwszProvider = pwszProviderHolder; + } + else { + pwszProvider = GetDefaultProvider(dwType); + pProvCtx->m_fReleaseProvider = FALSE; + STRINGREF str = StringObject::NewString(pwszProvider); + pFD->SetRefValue(*pSafeThis, (OBJECTREF)str); + } + } else { + pwszProvider = GetDefaultProvider(dwType); + pProvCtx->m_fReleaseProvider = FALSE; + STRINGREF str = StringObject::NewString(pwszProvider); + pFD->SetRefValue(*pSafeThis, (OBJECTREF)str); + } + + // look to see if the user specified that we should pass + // CRYPT_MACHINE_KEYSET to CAPI to use machine key storage instead + // of user key storage + DWORD dwCspProviderFlags = 0; + + objref=NULL; + GCPROTECT_BEGIN (objref); + + pFD = MscorlibBinder::GetField(FIELD__CSP_PARAMETERS__FLAGS); + dwCspProviderFlags = pFD->GetValue32(*pSafeThis); + + // If the user specified CSP_PROVIDER_FLAGS_USE_DEFAULT_KEY_CONTAINER, + // then ignore the container name and hand back the default container + + pFD = MscorlibBinder::GetField(FIELD__CSP_PARAMETERS__KEY_CONTAINER_NAME); + if ((dwCspProviderFlags & CSP_PROVIDER_FLAGS_USE_DEFAULT_KEY_CONTAINER) == 0) { + // Look for the key container name + objref = pFD->GetRefValue(*pSafeThis); + STRINGREF strContainer = ObjectToSTRINGREF(*(StringObject **) &objref); + if (strContainer != NULL) { + LPWSTR pwsz = strContainer->GetBuffer(); + if ((pwsz != NULL) && (*pwsz != 0)) + pwszContainer = CryptoHelper::STRINGREFToUnicode(strContainer); + } + } + + GCPROTECT_END (); + + // Go ahead and try to open the CSP. If we fail, make sure the CSP + // returned is 0 as that is going to be the error check in the caller. + HandleCSPHolder hProv(NULL); + { + GCX_PREEMP(); + dwFlags |= MapCspProviderFlags(dwCspProviderFlags); + + if (!CryptoHelper::WszCryptAcquireContext_SO_TOLERANT(&hProv, pwszContainer, pwszProvider, dwType, dwFlags)) + return HRESULT_FROM_GetLastError(); + } + + // CRYPT_PROV_CTX takes ownership of these resources, and frees them in its Release + hProv.SuppressRelease(); + pwszContainer.SuppressRelease(); + pwszProviderHolder.SuppressRelease(); + + pProvCtx->m_hProv = hProv; + pProvCtx->m_pwszContainer = pwszContainer; + pProvCtx->m_pwszProvider = pwszProvider; + pProvCtx->m_dwType = dwType; + pProvCtx->m_dwFlags = dwFlags; + + // If we are using CRYPT_VERIFYCONTEXT this is an ephemeral key, so clear the persist flag + if (dwFlags & CRYPT_VERIFYCONTEXT) + pProvCtx->m_fPersistKeyInCsp = FALSE; + + return S_OK; +} + +// +// FCALL functions +// + +// +// Native method to open a CSP using CRYPT_VERIFYCONTEXT +// + +FCIMPL2(void, COMCryptography::_AcquireCSP, Object* cspParametersUNSAFE, SafeHandle** hProvUNSAFE) +{ + FCALL_CONTRACT; + + OBJECTREF cspParameters = (OBJECTREF) cspParametersUNSAFE; + SAFEHANDLE hProvSAFE = (SAFEHANDLE) *hProvUNSAFE; + + HELPER_METHOD_FRAME_BEGIN_2(cspParameters, hProvSAFE); + + // + // We want to just open this CSP. Passing in verify context will + // open it and, if a container is given, map to open the container. + // + + NewHolder<CRYPT_PROV_CTX> pProvCtx(new CRYPT_PROV_CTX()); + // protect the allocated structure with a holder + HRESULT hr = OpenCSP(&cspParameters, CRYPT_VERIFYCONTEXT, pProvCtx); + + if (FAILED(hr)) { + LOG((LF_SECURITY, LL_INFO10000, "Error [%#x]: COMCryptography::OpenCSP failed.\n", hr)); + CryptoHelper::COMPlusThrowCrypto(hr); + } + + // we never want to delete a key container when using CRYPT_VERIFYCONTEXT + pProvCtx->m_fPersistKeyInCsp = TRUE; + + // Set the handle field + hProvSAFE->SetHandle((void*) pProvCtx.GetValue()); + pProvCtx.SuppressRelease(); + + HELPER_METHOD_FRAME_END(); +} +FCIMPLEND + +// +// This method opens an existing key container. +// It returns FALSE if the container could not be found. +// + +FCIMPL3(HRESULT, COMCryptography::_OpenCSP, Object* cspParametersUNSAFE, DWORD dwFlags, SafeHandle** hProvUNSAFE) +{ + FCALL_CONTRACT; + + HRESULT hr = S_OK; + OBJECTREF cspParameters = (OBJECTREF) cspParametersUNSAFE; + SAFEHANDLE hProvSAFE = (SAFEHANDLE) *hProvUNSAFE; + + HELPER_METHOD_FRAME_BEGIN_RET_2(cspParameters, hProvSAFE); + + NewHolder<CRYPT_PROV_CTX> pProvCtx(new CRYPT_PROV_CTX()); + // We never want to delete a key container if it's already there. + pProvCtx->m_fPersistKeyInCsp = TRUE; + + hr = OpenCSP(&cspParameters, dwFlags, pProvCtx); + if (SUCCEEDED(hr)) { + // Set the handle field + hProvSAFE->SetHandle((void*) pProvCtx.GetValue()); + pProvCtx.SuppressRelease(); + } + + HELPER_METHOD_FRAME_END(); + return hr; +} +FCIMPLEND + +// +// Native method for calling a CSP to get random bytes. +// + +void QCALLTYPE COMCryptography::GetBytes(CRYPT_PROV_CTX * pProvCtx, BYTE * pbOut, INT32 cb) +{ + QCALL_CONTRACT; + + BEGIN_QCALL; + + NewArrayHolder<BYTE> buffer = new BYTE[cb]; + + if (!CryptGenRandom(pProvCtx->m_hProv, cb, buffer)) + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + + memcpyNoGCRefs(pbOut, buffer, cb); + + END_QCALL; +} + +// +// Native method for calling a CSP to get random bytes. +// + +void QCALLTYPE COMCryptography::GetNonZeroBytes(CRYPT_PROV_CTX * pProvCtx, BYTE * pbOut, INT32 cb) +{ + QCALL_CONTRACT; + + BEGIN_QCALL; + + NewArrayHolder<BYTE> pb = new BYTE[cb]; + INT32 i = 0; + + while (i < cb) { + if (!CryptGenRandom(pProvCtx->m_hProv, cb, pb)) + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + + for (INT32 j=0; (i<cb) && (j<cb); j++) { + if (pb[j] != 0) pbOut[i++] = pb[j]; + } + } + + END_QCALL; +} + +// +// Release our handle to a CSP, potentially deleting the referenced key +// +// Arguments: +// pProviderContext - CSP context to release +// +// +// Notes: +// This is the target of the System.Security.Cryptography.SafeProvHandle.FreeCsp QCall +// + +// static +void QCALLTYPE COMCryptography::FreeCsp(__in_opt CRYPT_PROV_CTX *pProviderContext) +{ + QCALL_CONTRACT; + + BEGIN_QCALL; + + if (pProviderContext) + pProviderContext->Release(); + + END_QCALL; +} + +// +// _SearchForAlgorithm +// +// Method for determining whether a CSP supports a particular +// algorithm and (optionally) a key size of that algorithm +// +BOOL QCALLTYPE COMCryptography::SearchForAlgorithm(CRYPT_PROV_CTX * pProvCtx, DWORD dwAlgID, DWORD dwKeyLength) +{ + QCALL_CONTRACT; + + BOOL result = FALSE; + + BEGIN_QCALL; + + DWORD dwFlags = CRYPT_FIRST; + DWORD cbData = 0; + // First, we have to get the max size of the PP + if (CryptGetProvParam(pProvCtx->m_hProv, PP_ENUMALGS_EX, NULL, &cbData, dwFlags)) { + + // Allocate pbData + NewArrayHolder<BYTE> pbData = new BYTE[cbData]; + while (CryptGetProvParam(pProvCtx->m_hProv, PP_ENUMALGS_EX, pbData, &cbData, dwFlags)) { + dwFlags = 0; // so we don't use CRYPT_FIRST more than once + PROV_ENUMALGS_EX *provdata = (PROV_ENUMALGS_EX *) pbData.GetValue(); + ALG_ID provAlgID = provdata->aiAlgid; + DWORD provMinLength = provdata->dwMinLen; + DWORD provMaxLength = provdata->dwMaxLen; + + // OK, now check to see if we have an alg match + if ((ALG_ID) dwAlgID == provAlgID) { + // OK, see if we have a keylength match, or if we don't care + if ((dwKeyLength == 0) || + (dwKeyLength >= provMinLength) && + (dwKeyLength <= provMaxLength)) { + result = TRUE; + break; + } + } // keep looping + } + } + + END_QCALL; + + return result; +} + +// +// This method creates a new key container. +// + +FCIMPL3(void, COMCryptography::_CreateCSP, Object* cspParametersUNSAFE, CLR_BOOL randomKeyContainer, SafeHandle** hProvUNSAFE) +{ + FCALL_CONTRACT; + + OBJECTREF cspParameters = (OBJECTREF) cspParametersUNSAFE; + SAFEHANDLE hProvSAFE = (SAFEHANDLE) *hProvUNSAFE; + + HELPER_METHOD_FRAME_BEGIN_2(cspParameters, hProvSAFE); + + NewHolder<CRYPT_PROV_CTX> pProvCtx(new CRYPT_PROV_CTX()); + + // We always want to delete the random key container we create + pProvCtx->m_fPersistKeyInCsp = (randomKeyContainer ? FALSE : TRUE); + + DWORD dwFlags = CRYPT_NEWKEYSET; + if (randomKeyContainer) { + dwFlags |= CRYPT_VERIFYCONTEXT; + } + + HRESULT hr = OpenCSP(&cspParameters, dwFlags, pProvCtx); + + if (FAILED(hr)) { + LOG((LF_SECURITY, LL_INFO10000, "Error [%#x]: COMCryptography::OpenCSP failed.\n", hr)); + CryptoHelper::COMPlusThrowCrypto(hr); + } + + // Set the handle field + hProvSAFE->SetHandle((void*) pProvCtx.GetValue()); + pProvCtx.SuppressRelease(); + + HELPER_METHOD_FRAME_END(); +} +FCIMPLEND + +CRYPT_HASH_CTX * COMCryptography::CreateHash(CRYPT_PROV_CTX * pProvCtx, DWORD dwHashType) +{ + QCALL_CONTRACT; + + CRYPT_HASH_CTX * pHashCtx = NULL; + + BEGIN_QCALL; + + HandleHashHolder hHash = NULL; + HRESULT hr = S_OK; + + if (!CryptCreateHash(pProvCtx->m_hProv, dwHashType, NULL, 0, &hHash)) { + hr = HRESULT_FROM_GetLastError(); + } + + if (FAILED(hr)) { + LOG((LF_SECURITY, LL_INFO10000, "Error [%#x]: COMCryptography::_CreateHash failed.\n", hr)); + CryptoHelper::COMPlusThrowCrypto(hr); + } + + pHashCtx = new CRYPT_HASH_CTX(pProvCtx, hHash); + hHash.SuppressRelease(); + + END_QCALL; + + return pHashCtx; +} + +void QCALLTYPE COMCryptography::DeriveKey(CRYPT_PROV_CTX * pProvCtx, DWORD dwCalgKey, DWORD dwCalgHash, + LPCBYTE pbPwd, DWORD cbPwd, DWORD dwFlags, LPBYTE pbIVIn, DWORD cbIVIn, + QCall::ObjectHandleOnStack retKey) +{ + QCALL_CONTRACT; + + BEGIN_QCALL; + + HandleHashHolder hHash(NULL); + HandleKeyHolder hKey(NULL); + + NewArrayHolder<BYTE> bufferPwd = new BYTE[cbPwd]; + memcpyNoGCRefs (bufferPwd, pbPwd, cbPwd * sizeof(BYTE)); + + NewArrayHolder<BYTE> rgbKey(NULL); + NewArrayHolder<BYTE> pbIV(NULL); + DWORD cb = 0; + DWORD cbIV = 0; + + if (!CryptCreateHash(pProvCtx->m_hProv, dwCalgHash, NULL, 0, &hHash)) + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + + // Hash the password string + if (!CryptHashData(hHash, pbPwd, cbPwd, 0)) + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + + // Create a block cipher session key based on the hash of the password + if (!CryptDeriveKey(pProvCtx->m_hProv, dwCalgKey, hHash, dwFlags | CRYPT_EXPORTABLE, &hKey)) + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + + HRESULT hr = UnloadKey(pProvCtx->m_hProv, hKey, &rgbKey, &cb); + if (FAILED(hr)) + CryptoHelper::COMPlusThrowCrypto(hr); + + // Get the length of the IV + cbIV = 0; + if (!CryptGetKeyParam(hKey, KP_IV, NULL, &cbIV, 0)) + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + + // Now allocate space for the IV vector + pbIV = new BYTE[cbIV]; + if (!CryptGetKeyParam(hKey, KP_IV, pbIV, &cbIV, 0)) + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + + // Check to avoid writing in the wrong location of the GC heap + if (cbIV != cbIVIn) + COMPlusThrow(kCryptographicException, W("Cryptography_PasswordDerivedBytes_InvalidIV")); + memcpyNoGCRefs (pbIVIn, pbIV, cbIV); + + retKey.SetByteArray(rgbKey, cb); + + END_QCALL; +} + +FCIMPL8(DWORD, COMCryptography::_DecryptData, SafeHandle* hKeyUNSAFE, U1Array* dataUNSAFE, + INT32 dwOffset, INT32 dwCount, U1Array** outputUNSAFE, INT32 dwOutputOffset, DWORD dwPaddingMode, CLR_BOOL fLast) +{ + FCALL_CONTRACT; + + struct _gc + { + U1ARRAYREF data; + U1ARRAYREF output; + SAFEHANDLE hKeySAFE; + } gc; + + gc.data = (U1ARRAYREF) dataUNSAFE; + gc.output = (U1ARRAYREF) *outputUNSAFE; + gc.hKeySAFE = (SAFEHANDLE) hKeyUNSAFE; + + INT32 dwResult = 0; + + HELPER_METHOD_FRAME_BEGIN_RET_PROTECT(gc); + + DWORD cb2 = dwCount; + // Do this check here as a sanity check. Also, this will catch bugs in CryptoAPITransform + if (dwOffset < 0 || dwCount < 0 || dwCount > (INT32) gc.data->GetNumComponents() || dwOffset > ((INT32) gc.data->GetNumComponents() - dwCount)) + CryptoHelper::COMPlusThrowCrypto(NTE_BAD_DATA); + + NewArrayHolder<BYTE> pb(new BYTE[cb2]); + memcpy(pb, dwOffset + (LPBYTE) gc.data->GetDirectPointerToNonObjectElements(), cb2); + + { + SafeHandleHolder shh(&gc.hKeySAFE); + CRYPT_KEY_CTX * pKeyCtx = CryptoHelper::DereferenceSafeHandle<CRYPT_KEY_CTX>(gc.hKeySAFE); + { + GCX_PREEMP(); + // always call decryption with false, deal with padding manually + if (!CryptDecrypt(pKeyCtx->m_hKey, NULL, FALSE, 0, pb, &cb2)) + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + } + } + + DWORD dwPadLen = 0; + if (fLast) { + switch(dwPaddingMode) { + case CRYPTO_PADDING_NONE: + // we don't remove any padding + break; + case CRYPTO_PADDING_Zeros: + // nothing to check for here + break; + case CRYPTO_PADDING_PKCS5: + // PKCS5 padding is as follows: FF FF FF FF FF FF FF FF FF 07 07 07 07 07 07 07 + dwPadLen = cb2 > 0 ? pb[cb2 - 1] : 0; + + if (cb2 < BLOCK_LEN || dwPadLen <= 0 || dwPadLen > BLOCK_LEN) + CryptoHelper::COMPlusThrowCrypto(NTE_BAD_DATA); + + // Check the padding bytes are all correct + for (DWORD index = cb2 - dwPadLen; index + 1 < cb2; index++) + if (pb[index] != dwPadLen) + CryptoHelper::COMPlusThrowCrypto(NTE_BAD_DATA); + break; + case CRYPTO_PADDING_ISO_10126: + // The padding is as follows: FF FF FF FF FF FF FF FF FF 7D 2A 75 EF F8 EF 07 + dwPadLen = cb2 > 0 ? pb[cb2 - 1] : 0; + if (cb2 < BLOCK_LEN || dwPadLen <= 0 || dwPadLen > BLOCK_LEN) + CryptoHelper::COMPlusThrowCrypto(NTE_BAD_DATA); + + // Just ignore the random bytes + break; + case CRYPTO_PADDING_ANSI_X_923: + // The padding is as follows: FF FF FF FF FF FF FF FF FF 00 00 00 00 00 00 07 + dwPadLen = cb2 > 0 ? pb[cb2 - 1] : 0; + if (cb2 < BLOCK_LEN || dwPadLen <= 0 || dwPadLen > BLOCK_LEN) + CryptoHelper::COMPlusThrowCrypto(NTE_BAD_DATA); + + // Check the padding bytes are all zeros + for (DWORD index = cb2 - dwPadLen; index + 1 < cb2; index++) + if (pb[index] != 0) + CryptoHelper::COMPlusThrowCrypto(NTE_BAD_DATA); + break; + } + } + + dwResult = (cb2 - dwPadLen); + if (dwResult < 0) + CryptoHelper::COMPlusThrowCrypto(NTE_BAD_DATA); + + if (gc.output == NULL) { + gc.output = (U1ARRAYREF) AllocatePrimitiveArray(ELEMENT_TYPE_U1, dwResult); + memcpyNoGCRefs(gc.output->GetDirectPointerToNonObjectElements(), pb, dwResult); + SetObjectReference((OBJECTREF*) outputUNSAFE, (OBJECTREF) gc.output, gc.output->GetAppDomain()); + } else { + if (dwOutputOffset < 0 || dwResult < 0 || dwResult > (INT32) gc.output->GetNumComponents() || dwOutputOffset > ((INT32) gc.output->GetNumComponents() - dwResult)) + CryptoHelper::COMPlusThrowCrypto(NTE_BAD_DATA); + memcpyNoGCRefs(dwOutputOffset + (LPBYTE) gc.output->GetDirectPointerToNonObjectElements(), pb, dwResult); + } + + HELPER_METHOD_FRAME_END(); + return dwResult; +} +FCIMPLEND + +//--------------------------------------------------------------------------------------- +// +// Decrypt a symmetric key using the private key in pKeyContext +// +// Arguments: +// pKeyContext - private key used for decrypting pbEncryptedKey +// pbEncryptedKey - [in] encrypted symmetric key +// cbEncryptedKey - size, in bytes, of pbEncryptedKey +// fOAEP - TRUE to use OAEP padding, FALSE to use PKCS #1 type 2 padding +// ohRetDecryptedKey - [out] decrypted key +// +// Notes: +// pbEncryptedKey is byte-reversed from the format that CAPI expects. This is for compatibility with +// previous CLR versions and other RSA implementations. +// +// This method is the target of the System.Security.Cryptography.RSACryptoServiceProvider.DecryptKey QCall +// + +// static +void QCALLTYPE COMCryptography::DecryptKey(__in CRYPT_KEY_CTX *pKeyContext, + __in_bcount(cbEncryptedKey) BYTE *pbEncryptedKey, + DWORD cbEncryptedKey, + BOOL fOAEP, + QCall::ObjectHandleOnStack ohRetDecryptedKey) +{ + CONTRACTL + { + QCALL_CHECK; + PRECONDITION(CheckPointer(pKeyContext)); + PRECONDITION(CheckPointer(pbEncryptedKey)); + PRECONDITION(cbEncryptedKey >= 0); + } + CONTRACTL_END; + + BEGIN_QCALL; + + NewArrayHolder<BYTE> pbKey = new BYTE[cbEncryptedKey]; + memcpy_s(pbKey, cbEncryptedKey, pbEncryptedKey, cbEncryptedKey); + CryptoHelper::memrev(pbKey, cbEncryptedKey); + + DWORD dwDecryptFlags = fOAEP ? CRYPT_OAEP : 0; + DWORD cbDecryptedKey = cbEncryptedKey; + if (!CryptDecrypt(pKeyContext->m_hKey, NULL, TRUE, dwDecryptFlags, pbKey, &cbDecryptedKey)) + { + HRESULT hrDecrypt = HRESULT_FROM_GetLastError(); + + // If we're using OAEP mode and we recieved an NTE_BAD_FLAGS error, then OAEP is not supported on + // this platform (XP+ only). Throw a generic cryptographic exception if we failed to decrypt OAEP + // padded data in order to prevent a chosen ciphertext attack. We will allow NTE_BAD_KEY out, since + // that error does not relate to the padding. Otherwise just throw a cryptographic exception based on + // the error code. + if ((dwDecryptFlags & CRYPT_OAEP) == CRYPT_OAEP && hrDecrypt != NTE_BAD_KEY) + { + if (hrDecrypt == NTE_BAD_FLAGS) + COMPlusThrow(kCryptographicException, W("Cryptography_OAEP_XPOnly")); + else + COMPlusThrow(kCryptographicException, W("Cryptography_OAEPDecoding")); + } + else + { + CryptoHelper::COMPlusThrowCrypto(hrDecrypt); + } + } + + // CryptDecrypt operates in place, so pbKey now has the plaintext version of the key. + // cbDecryptedKey was updated to indicate the number of bytes of plaintext that are in the buffer. + ohRetDecryptedKey.SetByteArray(pbKey, cbDecryptedKey); + END_QCALL; +} + +FCIMPL8(DWORD, COMCryptography::_EncryptData, SafeHandle* hKeyUNSAFE, U1Array* dataUNSAFE, + INT32 dwOffset, INT32 dwCount, U1Array** outputUNSAFE, INT32 dwOutputOffset, DWORD dwPaddingMode, CLR_BOOL fLast) +{ + FCALL_CONTRACT; + + struct _gc + { + U1ARRAYREF data; + U1ARRAYREF output; + SAFEHANDLE hKeySAFE; + } gc; + + gc.data = (U1ARRAYREF) dataUNSAFE; + gc.output = (U1ARRAYREF) *outputUNSAFE; + gc.hKeySAFE = (SAFEHANDLE) hKeyUNSAFE; + + DWORD cb2 = dwCount; + + HELPER_METHOD_FRAME_BEGIN_RET_PROTECT(gc); + + DWORD cb = dwCount + (fLast ? 16 : 0); // account for an extra padding block that will be added by CAPI + DWORD cbPartial = (dwCount % BLOCK_LEN); + + // Do this check here as a sanity check. Also, this will catch bugs in CryptoAPITransform + if (dwOffset < 0 || dwCount < 0 || dwCount > (INT32) gc.data->GetNumComponents() || dwOffset > ((INT32) gc.data->GetNumComponents() - dwCount)) + CryptoHelper::COMPlusThrowCrypto(NTE_BAD_DATA); + + NewArrayHolder<BYTE> pb(new BYTE[cb]); + + // initialize memory + memset(pb, 0, cb); + memcpy(pb, dwOffset + (LPBYTE) gc.data->GetDirectPointerToNonObjectElements(), dwCount); + + { + SafeHandleHolder shh(&gc.hKeySAFE); + CRYPT_KEY_CTX * pKeyCtx = CryptoHelper::DereferenceSafeHandle<CRYPT_KEY_CTX>(gc.hKeySAFE); + _ASSERTE(pKeyCtx->m_pProvCtx); + CRYPT_PROV_CTX * pProvCtx = pKeyCtx->m_pProvCtx; + + // Deal with padding modes by hand: we need this because Crypto API only supports PKCS#5 padding + if (fLast) { + DWORD dwPadLen = BLOCK_LEN - cbPartial; + switch(dwPaddingMode) { + case CRYPTO_PADDING_NONE: + if (cbPartial > 0) + COMPlusThrow(kCryptographicException, W("Cryptography_SSE_InvalidDataSize")); + break; + case CRYPTO_PADDING_Zeros: + // no further processing required, just adjust the input count + // we don't add zeros if we've got a full number of blocks + if (cbPartial != 0) + cb2 += dwPadLen; + break; + case CRYPTO_PADDING_PKCS5: + // PKCS5 padding is as follows: FF FF FF FF FF FF FF FF FF 07 07 07 07 07 07 07 + cb2 += dwPadLen; + for (DWORD index = dwCount; index < cb2; index++) + pb[index] = static_cast<BYTE>(dwPadLen); + break; + case CRYPTO_PADDING_ISO_10126: + // The padding is as follows: FF FF FF FF FF FF FF FF FF 7D 2A 75 EF F8 EF 07 + cb2 += dwPadLen; + { + GCX_PREEMP(); + // get some random bytes + if (!CryptGenRandom(pProvCtx->m_hProv, dwPadLen-1, pb+dwCount)) { + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + } + } + pb[cb2 - 1] = static_cast<BYTE>(dwPadLen); + break; + case CRYPTO_PADDING_ANSI_X_923: + // The padding is as follows: FF FF FF FF FF FF FF FF FF 00 00 00 00 00 00 07 + cb2 += dwPadLen; + for (DWORD index = dwCount; index < cb2-1; index++) + pb[index] = 0; + pb[cb2 - 1] = static_cast<BYTE>(dwPadLen); + break; + } + } + + { + GCX_PREEMP(); + // We have done the padding ourselves, so let's just pass the provided fLast flag + // so we ensure the key is initialized for future use + if (!CryptEncrypt(pKeyCtx->m_hKey, NULL, fLast, 0, pb, &cb2, cb)) + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + } + } + + // ignore the last padding block added by CAPI + if (fLast) cb2 -= BLOCK_LEN; + + if (gc.output == NULL) { + gc.output = (U1ARRAYREF) AllocatePrimitiveArray(ELEMENT_TYPE_U1, cb2); + memcpyNoGCRefs(gc.output->GetDirectPointerToNonObjectElements(), pb, cb2); + SetObjectReference((OBJECTREF*) outputUNSAFE, (OBJECTREF) gc.output, gc.output->GetAppDomain()); + } else { + if (dwOutputOffset < 0 || (INT32) cb2 < 0 || cb2 > gc.output->GetNumComponents() || dwOutputOffset > ((INT32) gc.output->GetNumComponents() - (INT32) cb2)) + CryptoHelper::COMPlusThrowCrypto(NTE_BAD_DATA); + memcpyNoGCRefs(dwOutputOffset + (LPBYTE) gc.output->GetDirectPointerToNonObjectElements(), pb, cb2); + } + + HELPER_METHOD_FRAME_END(); + return cb2; +} +FCIMPLEND + + +//--------------------------------------------------------------------------------------- +// +// Encrypt a symmetric key using the public key in pKeyContext +// +// Arguments: +// pKeyContext - [in] public to encrypt pbKey with +// pbKey - [in] symmetric key to encrypt +// cbKey - size, in bytes, of pbKey +// fOAEP - TRUE to use OAEP padding, FALSE to use PKCS #1 type 2 padding +// ohRetEncryptedKey - [out] byte array holding the encrypted key +// +// Notes: +// The returned value in ohRetEncryptedKey is byte-reversed from the version CAPI gives us. This is for +// compatibility with previous releases of the CLR and other RSA implementations. +// +// This method is the target of the EncryptKey QCall in System.Security.Cryptography.RSACryptoServiceProvider. +// + +// static +void QCALLTYPE COMCryptography::EncryptKey(__in CRYPT_KEY_CTX *pKeyContext, + __in_bcount(cbKey) BYTE *pbKey, + DWORD cbKey, + BOOL fOAEP, + QCall::ObjectHandleOnStack ohRetEncryptedKey) +{ + CONTRACTL + { + QCALL_CHECK; + PRECONDITION(CheckPointer(pKeyContext)); + PRECONDITION(CheckPointer(pbKey)); + PRECONDITION(cbKey >= 0); + } + CONTRACTL_END; + + BEGIN_QCALL; + + DWORD dwEncryptFlags = fOAEP ? CRYPT_OAEP : 0; + + // Figure out how big the encrypted key will be + DWORD cbEncryptedKey = cbKey; + if (!CryptEncrypt(pKeyContext->m_hKey, NULL, TRUE, dwEncryptFlags, NULL, &cbEncryptedKey, cbEncryptedKey)) + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + + // pbData is an in/out buffer for CryptEncrypt. allocate space for the encrypted key, and copy the + // plaintext key into that space. Since encrypted keys will have padding applied, the size of the encrypted + // key should always be larger than the plaintext key, so use that to determine the buffer size. + _ASSERTE(cbEncryptedKey >= cbKey); + NewArrayHolder<BYTE> pbEncryptedKey = new BYTE[cbEncryptedKey]; + memcpy_s(pbEncryptedKey, cbEncryptedKey, pbKey, cbKey); + + // Encrypt for real - the last parameter is the total size of the in/out buffer, while the second to last + // parameter specifies the size of the plaintext to encrypt. + if (!CryptEncrypt(pKeyContext->m_hKey, NULL, TRUE, dwEncryptFlags, pbEncryptedKey, &cbKey, cbEncryptedKey)) + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + + _ASSERTE(cbKey == cbEncryptedKey); + CryptoHelper::memrev(pbEncryptedKey, cbEncryptedKey); + ohRetEncryptedKey.SetByteArray(pbEncryptedKey, cbEncryptedKey); + END_QCALL; +} + + +void QCALLTYPE COMCryptography::EndHash(CRYPT_HASH_CTX * pHashCtx, QCall::ObjectHandleOnStack retHash) +{ + QCALL_CONTRACT; + + BEGIN_QCALL; + + DWORD cbHash = 0; + DWORD cbHashCount = sizeof(cbHash); + if (!CryptGetHashParam(pHashCtx->m_hHash, HP_HASHSIZE, reinterpret_cast<BYTE *>(&cbHash), &cbHashCount, 0)) + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + + NewArrayHolder<BYTE> pb = new BYTE[cbHash]; + if (!CryptGetHashParam(pHashCtx->m_hHash, HP_HASHVAL, pb, &cbHash, 0)) + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + + retHash.SetByteArray(pb, cbHash); + + END_QCALL; +} + +// +// Exports key information of an RSACryptoServiceProvider/DSACryptoServiceProvider into a CAPI key blob (PKCS#1 format). +// + +void QCALLTYPE COMCryptography::ExportCspBlob(CRYPT_KEY_CTX * pKeyCtx, DWORD dwBlobType, QCall::ObjectHandleOnStack retBlob) +{ + QCALL_CONTRACT; + + BEGIN_QCALL; + + HRESULT hr = S_OK; + NewArrayHolder<BYTE> pbRawData(NULL); + DWORD cbRawData = 0; + + if (!CryptExportKey(pKeyCtx->m_hKey, NULL, dwBlobType, 0, NULL, &cbRawData)) + hr = HRESULT_FROM_GetLastError(); + + if (FAILED(hr)) { + LOG((LF_SECURITY, LL_INFO10000, "Error [%#x]: COMCryptography::_ExportCspBlob failed.\n", hr)); + CryptoHelper::COMPlusThrowCrypto(hr); + } + + pbRawData = new BYTE[cbRawData]; + if (!CryptExportKey(pKeyCtx->m_hKey, NULL, dwBlobType, 0, pbRawData, &cbRawData)) + hr = HRESULT_FROM_GetLastError(); + + if (FAILED(hr)) { + LOG((LF_SECURITY, LL_INFO10000, "Error [%#x]: COMCryptography::_ExportCspBlob failed.\n", hr)); + CryptoHelper::COMPlusThrowCrypto(hr); + } + + retBlob.SetByteArray(pbRawData, cbRawData); + + END_QCALL; +} + +// +// _ExportKey +// + +#ifdef _PREFAST_ +#pragma warning(push) +#pragma warning(disable:21000) // Suppress PREFast warning about overly large function +#endif +FCIMPL3(void, COMCryptography::_ExportKey, SafeHandle* hKeyUNSAFE, DWORD dwBlobType, Object* theKeyUNSAFE) +{ + FCALL_CONTRACT; + + HRESULT hr = S_OK; + OBJECTREF theKey = (OBJECTREF) theKeyUNSAFE; + SAFEHANDLE hKeySAFE = (SAFEHANDLE) hKeyUNSAFE; + + HELPER_METHOD_FRAME_BEGIN_2(theKey, hKeySAFE); + + DWORD cb; + BOOL f; + DWORD dwFlags = 0; + NewArrayHolder<BYTE> pb(NULL); + + struct __LocalGCR { + RSA_CSPREF rsaKey; + DSA_CSPREF dsaKey; + } _gcr; + + _gcr.rsaKey = NULL; + _gcr.dsaKey = NULL; + + { + SafeHandleHolder shh(&hKeySAFE); + CRYPT_KEY_CTX * pKeyCtx = CryptoHelper::DereferenceSafeHandle<CRYPT_KEY_CTX>(hKeySAFE); + _ASSERTE(pKeyCtx); + { + GCX_PREEMP(); + // calg + ALG_ID dwCalg; + cb = sizeof(dwCalg); + if (CryptGetKeyParam(pKeyCtx->m_hKey, KP_ALGID, (LPBYTE) &dwCalg, &cb, 0)) { + // We need to add the VER3 handle for DH and DSS keys so that we can + // get the fullest possible amount of information. + if (dwCalg == CALG_DSS_SIGN) + dwFlags |= CRYPT_BLOB_VER3; + } +retry: + f = CryptExportKey(pKeyCtx->m_hKey, NULL, dwBlobType, dwFlags, NULL, &cb); + if (!f) { + if (dwFlags & CRYPT_BLOB_VER3) { + dwFlags &= ~CRYPT_BLOB_VER3; + goto retry; + } + hr = HRESULT_FROM_GetLastError(); + } + + if (FAILED(hr)) { + LOG((LF_SECURITY, LL_INFO100, "Error [%#x]: COMCryptography::_ExportKey failed.\n", hr)); + goto lExit; + } + + pb = new BYTE[cb]; + if (!CryptExportKey(pKeyCtx->m_hKey, NULL, dwBlobType, dwFlags, pb, &cb)) + hr = HRESULT_FROM_GetLastError(); + } + } + + DWORD cbMalloced = cb; + LPBYTE pbX = NULL; + DWORD cbKey = 0; + + GCPROTECT_BEGIN(_gcr); + + if (FAILED(hr)) { + LOG((LF_SECURITY, LL_INFO10000, "Error [%#x]: COMCryptography::_ExportKey failed.\n", hr)); + goto Exit; + } + + BLOBHEADER * pblob = (BLOBHEADER *) pb.GetValue(); + KEY_HEADER * pKeyInfo = NULL; + + switch (pblob->aiKeyAlg) { + case CALG_RSA_KEYX: + case CALG_RSA_SIGN: + VALIDATEOBJECTREF(theKey); + _gcr.rsaKey = (RSA_CSPREF) theKey; + + if (dwBlobType == PUBLICKEYBLOB) { + pKeyInfo = (KEY_HEADER *) pb.GetValue(); + cb = (pKeyInfo->rsa.bitlen/8); + + pbX = pb + sizeof(BLOBHEADER) + sizeof(RSAPUBKEY); + + // Exponent + NewArrayHolder<BYTE> pbExponent(NULL); + DWORD cbExponent = 0; + ConvertIntToByteArray(pKeyInfo->rsa.pubexp, &pbExponent, &cbExponent); + OBJECTREF arrayExponent = AllocatePrimitiveArray(ELEMENT_TYPE_U1, cbExponent); + SetObjectReference((OBJECTREF *) &_gcr.rsaKey->m_Exponent, + arrayExponent, + _gcr.rsaKey->GetAppDomain()); + memcpyNoGCRefs(_gcr.rsaKey->m_Exponent->GetDirectPointerToNonObjectElements(), pbExponent, cbExponent); + + // Modulus + OBJECTREF arrayModulus = AllocatePrimitiveArray(ELEMENT_TYPE_U1, cb); + SetObjectReference((OBJECTREF *) &_gcr.rsaKey->m_Modulus, + arrayModulus, + _gcr.rsaKey->GetAppDomain()); + CryptoHelper::memrev(pbX, cb); + memcpyNoGCRefs(_gcr.rsaKey->m_Modulus->GetDirectPointerToNonObjectElements(), + pbX, cb); + pbX += cb; + } + else if (dwBlobType == PRIVATEKEYBLOB) { + pKeyInfo = (KEY_HEADER *) pb.GetValue(); + cb = (pKeyInfo->rsa.bitlen/8); + DWORD cbHalfModulus = (cb + 1)/2; + + pbX = pb + sizeof(BLOBHEADER) + sizeof(RSAPUBKEY); + + // Exponent + NewArrayHolder<BYTE> pbExponent(NULL); + DWORD cbExponent = 0; + ConvertIntToByteArray(pKeyInfo->rsa.pubexp, &pbExponent, &cbExponent); + OBJECTREF arrayExponent = AllocatePrimitiveArray(ELEMENT_TYPE_U1, cbExponent); + SetObjectReference((OBJECTREF *) &_gcr.rsaKey->m_Exponent, + arrayExponent, + _gcr.rsaKey->GetAppDomain()); + memcpyNoGCRefs(_gcr.rsaKey->m_Exponent->GetDirectPointerToNonObjectElements(), pbExponent, cbExponent); + + // Modulus + OBJECTREF arrayModulus = AllocatePrimitiveArray(ELEMENT_TYPE_U1, cb); + SetObjectReference((OBJECTREF *) &_gcr.rsaKey->m_Modulus, + arrayModulus, + _gcr.rsaKey->GetAppDomain()); + CryptoHelper::memrev(pbX, cb); + memcpyNoGCRefs(_gcr.rsaKey->m_Modulus->GetDirectPointerToNonObjectElements(), + pbX, cb); + pbX += cb; + + // P + OBJECTREF arrayP = AllocatePrimitiveArray(ELEMENT_TYPE_U1, cbHalfModulus); + SetObjectReference((OBJECTREF *) &_gcr.rsaKey->m_P, + arrayP, + _gcr.rsaKey->GetAppDomain()); + CryptoHelper::memrev(pbX, cbHalfModulus); + memcpyNoGCRefs(_gcr.rsaKey->m_P->GetDirectPointerToNonObjectElements(), + pbX, cbHalfModulus); + pbX += cbHalfModulus; + + // Q + OBJECTREF arrayQ = AllocatePrimitiveArray(ELEMENT_TYPE_U1, cbHalfModulus); + SetObjectReference((OBJECTREF *) &_gcr.rsaKey->m_Q, + arrayQ, + _gcr.rsaKey->GetAppDomain()); + CryptoHelper::memrev(pbX, cbHalfModulus); + memcpyNoGCRefs(_gcr.rsaKey->m_Q->GetDirectPointerToNonObjectElements(), + pbX, cbHalfModulus); + pbX += cbHalfModulus; + + // dp + OBJECTREF arrayDP = AllocatePrimitiveArray(ELEMENT_TYPE_U1, cbHalfModulus); + SetObjectReference((OBJECTREF *) &_gcr.rsaKey->m_dp, + arrayDP, + _gcr.rsaKey->GetAppDomain()); + CryptoHelper::memrev(pbX, cbHalfModulus); + memcpyNoGCRefs(_gcr.rsaKey->m_dp->GetDirectPointerToNonObjectElements(), + pbX, cbHalfModulus); + pbX += cbHalfModulus; + + // dq + OBJECTREF arrayDQ = AllocatePrimitiveArray(ELEMENT_TYPE_U1, cbHalfModulus); + SetObjectReference((OBJECTREF *) &_gcr.rsaKey->m_dq, + arrayDQ, + _gcr.rsaKey->GetAppDomain()); + CryptoHelper::memrev(pbX, cbHalfModulus); + memcpyNoGCRefs(_gcr.rsaKey->m_dq->GetDirectPointerToNonObjectElements(), + pbX, cbHalfModulus); + pbX += cbHalfModulus; + + // InvQ + OBJECTREF arrayInverseQ = AllocatePrimitiveArray(ELEMENT_TYPE_U1, cbHalfModulus); + SetObjectReference((OBJECTREF *) &_gcr.rsaKey->m_InverseQ, + arrayInverseQ, + _gcr.rsaKey->GetAppDomain()); + CryptoHelper::memrev(pbX, cbHalfModulus); + memcpyNoGCRefs(_gcr.rsaKey->m_InverseQ->GetDirectPointerToNonObjectElements(), + pbX, cbHalfModulus); + pbX += cbHalfModulus; + + // d + OBJECTREF arrayD = AllocatePrimitiveArray(ELEMENT_TYPE_U1, cb); + SetObjectReference((OBJECTREF *) &_gcr.rsaKey->m_d, + arrayD, + _gcr.rsaKey->GetAppDomain()); + CryptoHelper::memrev(pbX, cb); + memcpyNoGCRefs(_gcr.rsaKey->m_d->GetDirectPointerToNonObjectElements(), + pbX, cb); + pbX += cb; + } + else { + hr = E_FAIL; + goto Exit; + } + break; + + case CALG_DSS_SIGN: + _gcr.dsaKey = (DSA_CSPREF) theKey; + // we have to switch on whether the blob is v3 or not, because we have different + // info available if it is... + if (pblob->bVersion > 0x2) { + if (dwBlobType == PUBLICKEYBLOB) { + int cbP, cbQ, cbJ; + DSSPUBKEY_VER3 * pdss; + + pdss = (DSSPUBKEY_VER3 *) (pb + sizeof(BLOBHEADER)); + cbP = (pdss->bitlenP+7)/8; + cbQ = (pdss->bitlenQ+7)/8; + cbJ = (pdss->bitlenJ+7)/8; + pbX = pb + sizeof(BLOBHEADER) + sizeof(DSSPUBKEY_VER3); + + // P + OBJECTREF arrayP = AllocatePrimitiveArray(ELEMENT_TYPE_U1, cbP); + SetObjectReference((OBJECTREF *) &_gcr.dsaKey->m_P, arrayP, _gcr.dsaKey->GetAppDomain()); + CryptoHelper::memrev(pbX, cbP); + memcpyNoGCRefs(_gcr.dsaKey->m_P->GetDirectPointerToNonObjectElements(), pbX, cbP); + pbX += cbP; + + // Q + OBJECTREF arrayQ = AllocatePrimitiveArray(ELEMENT_TYPE_U1, cbQ); + SetObjectReference((OBJECTREF *) &_gcr.dsaKey->m_Q, arrayQ, _gcr.dsaKey->GetAppDomain()); + CryptoHelper::memrev(pbX, cbQ); + memcpyNoGCRefs(_gcr.dsaKey->m_Q->GetDirectPointerToNonObjectElements(), pbX, cbQ); + pbX += cbQ; + + // G + OBJECTREF arrayG = AllocatePrimitiveArray(ELEMENT_TYPE_U1, cbP); + SetObjectReference((OBJECTREF *) &_gcr.dsaKey->m_G, arrayG, _gcr.dsaKey->GetAppDomain()); + CryptoHelper::memrev(pbX, cbP); + memcpyNoGCRefs(_gcr.dsaKey->m_G->GetDirectPointerToNonObjectElements(), pbX, cbP); + pbX += cbP; + + // J + if (cbJ > 0) { + OBJECTREF arrayJ = AllocatePrimitiveArray(ELEMENT_TYPE_U1, cbJ); + SetObjectReference((OBJECTREF *) &_gcr.dsaKey->m_J, arrayJ, _gcr.dsaKey->GetAppDomain()); + CryptoHelper::memrev(pbX, cbJ); + memcpyNoGCRefs(_gcr.dsaKey->m_J->GetDirectPointerToNonObjectElements(), pbX, cbJ); + pbX += cbJ; + } + + // Y + OBJECTREF arrayY = AllocatePrimitiveArray(ELEMENT_TYPE_U1, cbP); + SetObjectReference((OBJECTREF *) &_gcr.dsaKey->m_Y, arrayY, _gcr.dsaKey->GetAppDomain()); + CryptoHelper::memrev(pbX, cbP); + memcpyNoGCRefs(_gcr.dsaKey->m_Y->GetDirectPointerToNonObjectElements(), pbX, cbP); + pbX += cbP; + + if (pdss->DSSSeed.counter != 0xFFFFFFFF) { + // seed + OBJECTREF arraySeed = AllocatePrimitiveArray(ELEMENT_TYPE_U1, 20); + SetObjectReference((OBJECTREF *) &_gcr.dsaKey->m_seed, arraySeed, _gcr.dsaKey->GetAppDomain()); + CryptoHelper::memrev(pdss->DSSSeed.seed, 20); + memcpyNoGCRefs(_gcr.dsaKey->m_seed->GetDirectPointerToNonObjectElements(), pdss->DSSSeed.seed, 20); + // pdss->DSSSeed.c + _gcr.dsaKey->m_counter = pdss->DSSSeed.counter; + } + } + else { + int cbP, cbQ, cbJ, cbX; + DSSPRIVKEY_VER3 * pdss; + + pdss = (DSSPRIVKEY_VER3 *) (pb + sizeof(BLOBHEADER)); + cbP = (pdss->bitlenP+7)/8; + cbQ = (pdss->bitlenQ+7)/8; + cbJ = (pdss->bitlenJ+7)/8; + cbX = (pdss->bitlenX+7)/8; + pbX = pb + sizeof(BLOBHEADER) + sizeof(DSSPRIVKEY_VER3); + + // P + OBJECTREF arrayP = AllocatePrimitiveArray(ELEMENT_TYPE_U1, cbP); + SetObjectReference((OBJECTREF *) &_gcr.dsaKey->m_P, arrayP, _gcr.dsaKey->GetAppDomain()); + CryptoHelper::memrev(pbX, cbP); + memcpyNoGCRefs(_gcr.dsaKey->m_P->GetDirectPointerToNonObjectElements(), pbX, cbP); + pbX += cbP; + + // Q + OBJECTREF arrayQ = AllocatePrimitiveArray(ELEMENT_TYPE_U1, cbQ); + SetObjectReference((OBJECTREF *) &_gcr.dsaKey->m_Q, arrayQ, _gcr.dsaKey->GetAppDomain()); + CryptoHelper::memrev(pbX, cbQ); + memcpyNoGCRefs(_gcr.dsaKey->m_Q->GetDirectPointerToNonObjectElements(), pbX, cbQ); + pbX += cbQ; + + // G + OBJECTREF arrayG = AllocatePrimitiveArray(ELEMENT_TYPE_U1, cbP); + SetObjectReference((OBJECTREF *) &_gcr.dsaKey->m_G, arrayG, _gcr.dsaKey->GetAppDomain()); + CryptoHelper::memrev(pbX, cbP); + memcpyNoGCRefs(_gcr.dsaKey->m_G->GetDirectPointerToNonObjectElements(), pbX, cbP); + pbX += cbP; + + // J + if (pdss->bitlenJ > 0) { + OBJECTREF arrayJ = AllocatePrimitiveArray(ELEMENT_TYPE_U1, cbJ); + SetObjectReference((OBJECTREF *) &_gcr.dsaKey->m_J, arrayJ, _gcr.dsaKey->GetAppDomain()); + CryptoHelper::memrev(pbX, cbJ); + memcpyNoGCRefs(_gcr.dsaKey->m_J->GetDirectPointerToNonObjectElements(), pbX, cbJ); + pbX += cbJ; + } + + // Y + OBJECTREF arrayY = AllocatePrimitiveArray(ELEMENT_TYPE_U1, cbP); + SetObjectReference((OBJECTREF *) &_gcr.dsaKey->m_Y, arrayY, _gcr.dsaKey->GetAppDomain()); + CryptoHelper::memrev(pbX, cbP); + memcpyNoGCRefs(_gcr.dsaKey->m_Y->GetDirectPointerToNonObjectElements(), pbX, cbP); + pbX += cbP; + + // X + OBJECTREF arrayX = AllocatePrimitiveArray(ELEMENT_TYPE_U1, cbX); + SetObjectReference((OBJECTREF *) &_gcr.dsaKey->m_X, arrayX, _gcr.dsaKey->GetAppDomain()); + CryptoHelper::memrev(pbX, cbX); + memcpyNoGCRefs(_gcr.dsaKey->m_X->GetDirectPointerToNonObjectElements(), pbX, cbX); + pbX += cbX; + + if (pdss->DSSSeed.counter != 0xFFFFFFFF) { + // seed + OBJECTREF arraySeed = AllocatePrimitiveArray(ELEMENT_TYPE_U1, 20); + SetObjectReference((OBJECTREF *) &_gcr.dsaKey->m_seed, arraySeed, _gcr.dsaKey->GetAppDomain()); + CryptoHelper::memrev(pdss->DSSSeed.seed, 20); + memcpyNoGCRefs(_gcr.dsaKey->m_seed->GetDirectPointerToNonObjectElements(), pdss->DSSSeed.seed, 20); + // pdss->DSSSeed.c + _gcr.dsaKey->m_counter = pdss->DSSSeed.counter; + } + } + } else { + // old-style blobs + if (dwBlobType == PUBLICKEYBLOB) { + int cbP, cbQ; + DSSPUBKEY * pdss; + DSSSEED * pseedstruct; + + pdss = (DSSPUBKEY *) (pb + sizeof(BLOBHEADER)); + cbP = (pdss->bitlen+7)/8; // bitlen is size of modulus + cbQ = DSS_Q_LEN; // Q is always 20 bytes in length + pbX = pb + sizeof(BLOBHEADER) + sizeof(DSSPUBKEY); + + // P + OBJECTREF arrayP = AllocatePrimitiveArray(ELEMENT_TYPE_U1, cbP); + SetObjectReference((OBJECTREF *) &_gcr.dsaKey->m_P, arrayP, _gcr.dsaKey->GetAppDomain()); + CryptoHelper::memrev(pbX, cbP); + memcpyNoGCRefs(_gcr.dsaKey->m_P->GetDirectPointerToNonObjectElements(), pbX, cbP); + pbX += cbP; + + // Q + OBJECTREF arrayQ = AllocatePrimitiveArray(ELEMENT_TYPE_U1, cbQ); + SetObjectReference((OBJECTREF *) &_gcr.dsaKey->m_Q, arrayQ, _gcr.dsaKey->GetAppDomain()); + CryptoHelper::memrev(pbX, cbQ); + memcpyNoGCRefs(_gcr.dsaKey->m_Q->GetDirectPointerToNonObjectElements(), pbX, cbQ); + pbX += cbQ; + + // G + OBJECTREF arrayG = AllocatePrimitiveArray(ELEMENT_TYPE_U1, cbP); + SetObjectReference((OBJECTREF *) &_gcr.dsaKey->m_G, arrayG, _gcr.dsaKey->GetAppDomain()); + CryptoHelper::memrev(pbX, cbP); + memcpyNoGCRefs(_gcr.dsaKey->m_G->GetDirectPointerToNonObjectElements(), pbX, cbP); + pbX += cbP; + + // Y + OBJECTREF arrayY = AllocatePrimitiveArray(ELEMENT_TYPE_U1, cbP); + SetObjectReference((OBJECTREF *) &_gcr.dsaKey->m_Y, arrayY, _gcr.dsaKey->GetAppDomain()); + CryptoHelper::memrev(pbX, cbP); + memcpyNoGCRefs(_gcr.dsaKey->m_Y->GetDirectPointerToNonObjectElements(), pbX, cbP); + pbX += cbP; + + pseedstruct = (DSSSEED *) pbX; + if (pseedstruct->counter > 0) { + // seed & counter + OBJECTREF arraySeed = AllocatePrimitiveArray(ELEMENT_TYPE_U1, 20); + SetObjectReference((OBJECTREF *) &_gcr.dsaKey->m_seed, arraySeed, _gcr.dsaKey->GetAppDomain()); + // seed is always 20 bytes + CryptoHelper::memrev(pseedstruct->seed, 20); + memcpyNoGCRefs(_gcr.dsaKey->m_seed->GetDirectPointerToNonObjectElements(), pseedstruct->seed, 20); + pbX += 20; + + // pdss->DSSSeed.c + _gcr.dsaKey->m_counter = pseedstruct->counter; + pbX += sizeof(DWORD); + } + } + else { + int cbP, cbQ, cbX; + DSSPUBKEY * pdss; + DSSSEED * pseedstruct; + + pdss = (DSSPUBKEY *) (pb + sizeof(BLOBHEADER)); + cbP = (pdss->bitlen+7)/8; //bitlen is size of modulus + cbQ = DSS_Q_LEN; // Q is always 20 bytes in length + pbX = pb + sizeof(BLOBHEADER) + sizeof(DSSPUBKEY); + + // P + OBJECTREF arrayP = AllocatePrimitiveArray(ELEMENT_TYPE_U1, cbP); + SetObjectReference((OBJECTREF *) &_gcr.dsaKey->m_P, arrayP, _gcr.dsaKey->GetAppDomain()); + CryptoHelper::memrev(pbX, cbP); + memcpyNoGCRefs(_gcr.dsaKey->m_P->GetDirectPointerToNonObjectElements(), pbX, cbP); + pbX += cbP; + + // Q + OBJECTREF arrayQ = AllocatePrimitiveArray(ELEMENT_TYPE_U1, cbQ); + SetObjectReference((OBJECTREF *) &_gcr.dsaKey->m_Q, arrayQ, _gcr.dsaKey->GetAppDomain()); + CryptoHelper::memrev(pbX, cbQ); + memcpyNoGCRefs(_gcr.dsaKey->m_Q->GetDirectPointerToNonObjectElements(), pbX, cbQ); + pbX += cbQ; + + // G + OBJECTREF arrayG = AllocatePrimitiveArray(ELEMENT_TYPE_U1, cbP); + SetObjectReference((OBJECTREF *) &_gcr.dsaKey->m_G, arrayG, _gcr.dsaKey->GetAppDomain()); + CryptoHelper::memrev(pbX, cbP); + memcpyNoGCRefs(_gcr.dsaKey->m_G->GetDirectPointerToNonObjectElements(), pbX, cbP); + pbX += cbP; + + // X + cbX = 20; // X must be 20 bytes in length + OBJECTREF arrayX = AllocatePrimitiveArray(ELEMENT_TYPE_U1, cbX); + SetObjectReference((OBJECTREF *) &_gcr.dsaKey->m_X, arrayX, _gcr.dsaKey->GetAppDomain()); + CryptoHelper::memrev(pbX, cbX); + memcpyNoGCRefs(_gcr.dsaKey->m_X->GetDirectPointerToNonObjectElements(), pbX, cbX); + pbX += cbX; + + pseedstruct = (DSSSEED *) pbX; + if (pseedstruct->counter > 0) { + // seed + OBJECTREF arraySeed = AllocatePrimitiveArray(ELEMENT_TYPE_U1, 20); + SetObjectReference((OBJECTREF *) &_gcr.dsaKey->m_seed, arraySeed, _gcr.dsaKey->GetAppDomain()); + CryptoHelper::memrev(pseedstruct->seed, 20); + memcpyNoGCRefs(_gcr.dsaKey->m_seed->GetDirectPointerToNonObjectElements(), pseedstruct->seed, 20); + pbX += 20; + // pdss->DSSSeed.c + _gcr.dsaKey->m_counter = pseedstruct->counter; + pbX += sizeof(DWORD); + } + + // Add this sanity check here to avoid reading from the heap + cbKey = (DWORD)(pbX - pb); + if (cbKey > cbMalloced) { + hr = E_FAIL; + goto Exit; + } + + // OK, we have one more thing to do. Because old DSS shared the DSSPUBKEY struct for both public and private keys, + // when we have a private key blob we get X but not Y. TO get Y, we have to do another export asking for a public key blob + + { + SafeHandleHolder shh(&hKeySAFE); + CRYPT_KEY_CTX * pKeyCtx = CryptoHelper::DereferenceSafeHandle<CRYPT_KEY_CTX>(hKeySAFE); + { + GCX_PREEMP(); + f = CryptExportKey(pKeyCtx->m_hKey, NULL, PUBLICKEYBLOB, dwFlags, NULL, &cb); + + if (!f) { + hr = HRESULT_FROM_GetLastError(); + goto Exit; + } + + pb = new BYTE[cb]; + cbMalloced = cb; + + f = CryptExportKey(pKeyCtx->m_hKey, NULL, PUBLICKEYBLOB, dwFlags, pb, &cb); + if (!f) { + hr = HRESULT_FROM_GetLastError(); + goto Exit; + } + } + } + + // skip over header, DSSPUBKEY, P, Q and G. Y is of size cbP + pbX = pb + sizeof(BLOBHEADER) + sizeof(DSSPUBKEY) + cbP + cbQ + cbP; + OBJECTREF arrayY = AllocatePrimitiveArray(ELEMENT_TYPE_U1, cbP); + SetObjectReference((OBJECTREF *) &_gcr.dsaKey->m_Y, arrayY, _gcr.dsaKey->GetAppDomain()); + CryptoHelper::memrev(pbX, cbP); + memcpyNoGCRefs(_gcr.dsaKey->m_Y->GetDirectPointerToNonObjectElements(), pbX, cbP); + pbX += cbP; + } + } + break; + + default: + hr = E_FAIL; + goto Exit; + } + + // Add this sanity check here to avoid reading from the heap + cbKey = (DWORD)(pbX - pb); + if (cbKey > cbMalloced) { + hr = E_FAIL; + goto Exit; + } + + hr = S_OK; + +Exit: ; + GCPROTECT_END(); + +lExit: + + if (FAILED(hr)) + CryptoHelper::COMPlusThrowCrypto(hr); + + HELPER_METHOD_FRAME_END(); +} +FCIMPLEND +#ifdef _PREFAST_ +#pragma warning(pop) +#endif + +// +// We implicitely assume these methods are not going to do a LoadLibrary +// + + +//--------------------------------------------------------------------------------------- +// +// Release our handle to a hash, potentially also releasing the provider +// +// Arguments: +// pHashContext - Hash context to release +// +// Notes: +// This is the target of the System.Security.Cryptography.SafeHashHandle.FreeHash QCall +// + +// static +void QCALLTYPE COMCryptography::FreeHash(__in_opt CRYPT_HASH_CTX *pHashContext) +{ + QCALL_CONTRACT; + + BEGIN_QCALL; + + if (pHashContext) + pHashContext->Release(); + + END_QCALL; +} + +//--------------------------------------------------------------------------------------- +// +// Release our handle to a key, potentially also releasing the provider +// +// Arguments: +// pKeyContext - Key context to release +// +// Notes: +// This is the target of the System.Security.Cryptography.SafeKeyHandle.FreeKey QCall +// + +// static +void QCALLTYPE COMCryptography::FreeKey(__in_opt CRYPT_KEY_CTX *pKeyContext) +{ + QCALL_CONTRACT; + + BEGIN_QCALL; + + if (pKeyContext) + pKeyContext->Release(); + + END_QCALL; +} + +// +// Native method for creation of a key in a CSP +// + +FCIMPL5(void, COMCryptography::_GenerateKey, SafeHandle* hProvUNSAFE, DWORD dwCalg, DWORD dwFlags, DWORD dwKeySize, SafeHandle** hKeyUNSAFE) +{ + FCALL_CONTRACT; + + SAFEHANDLE hProvSAFE = (SAFEHANDLE) hProvUNSAFE; + SAFEHANDLE hKeySAFE = (SAFEHANDLE) *hKeyUNSAFE; + + HELPER_METHOD_FRAME_BEGIN_2(hProvSAFE, hKeySAFE); + + HandleKeyHolder hKey(NULL); + CRYPT_PROV_CTX * pProvCtx = NULL; + HRESULT hr = S_OK; + + { + SafeHandleHolder shh(&hProvSAFE); + pProvCtx = CryptoHelper::DereferenceSafeHandle<CRYPT_PROV_CTX>(hProvSAFE); + { + GCX_PREEMP(); + DWORD dwCapiFlags = MapCspKeyFlags (dwFlags) | (dwKeySize << 16); + if (!CryptoHelper::CryptGenKey_SO_TOLERANT(pProvCtx->m_hProv, dwCalg, dwCapiFlags, &hKey)) + hr = HRESULT_FROM_GetLastError(); + } + } + + if (FAILED(hr)) { + LOG((LF_SECURITY, LL_INFO10000, "Error [%#x]: COMCryptography::_GenerateKey failed.\n", hr)); + CryptoHelper::COMPlusThrowCrypto(hr); + } + + CRYPT_KEY_CTX * pKeyCtx = new CRYPT_KEY_CTX(pProvCtx, hKey); + pKeyCtx->m_dwKeySpec = dwCalg; + hKeySAFE->SetHandle((void*) pKeyCtx); + hKey.SuppressRelease(); + + HELPER_METHOD_FRAME_END(); +} +FCIMPLEND + + +//--------------------------------------------------------------------------------------- +// +// Check to see if the CLR should enforce the machine wide FIPS algorithm policy +// +// Return Value: +// True if the CLR should prevent any non-FIPS certified algorithms from being +// instantiated if the FIPS algorithm policy is set on the machine, false otherwise. +// + +FCIMPL0(FC_BOOL_RET, COMCryptography::_GetEnforceFipsPolicySetting) +{ + FCALL_CONTRACT; + FC_RETURN_BOOL(g_pConfig->EnforceFIPSPolicy()); +} +FCIMPLEND + +// +// This method acquires key specific parameters. +// It will pop up a UI if the key is user protected. +// + +FCIMPL2(U1Array*, COMCryptography::_GetKeyParameter, SafeHandle* hKeyUNSAFE, DWORD dwKeyParam) +{ + FCALL_CONTRACT; + + U1ARRAYREF ret = NULL; + SAFEHANDLE hKeySAFE = (SAFEHANDLE) hKeyUNSAFE; + + HELPER_METHOD_FRAME_BEGIN_RET_2(ret, hKeySAFE); + + DWORD cb = 0; + NewArrayHolder<BYTE> pb(NULL); + NewArrayHolder<WCHAR> pwszUnicode(NULL); + + SafeHandleHolder shh(&hKeySAFE); + CRYPT_KEY_CTX * pKeyCtx = CryptoHelper::DereferenceSafeHandle<CRYPT_KEY_CTX>(hKeySAFE); + switch (dwKeyParam) { + case CLR_KEYLEN: + // Some Csp's may pop up a UI here since we don't use CRYPT_SILENT flag + // which is not supported in downlevel platforms + if (!CryptGetKeyParam(pKeyCtx->m_hKey, KP_KEYLEN, NULL, &cb, 0)) + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + pb = new BYTE[cb]; + if (!CryptGetKeyParam(pKeyCtx->m_hKey, KP_KEYLEN, pb, &cb, 0)) + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + CryptoHelper::ByteArrayToU1ARRAYREF(pb, cb, &ret); + break; + + case CLR_PUBLICKEYONLY: + // returns whether the key is a public only key + pb = new BYTE[4]; + *((DWORD*) pb.GetValue()) = pKeyCtx->m_fPublicOnly; + CryptoHelper::ByteArrayToU1ARRAYREF(pb, 4, &ret); + break; + + case CLR_ALGID: + // returns the algorithm ID for the key + if (!CryptGetKeyParam(pKeyCtx->m_hKey, KP_ALGID, NULL, &cb, 0)) + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + pb = new BYTE[cb]; + if (!CryptGetKeyParam(pKeyCtx->m_hKey, KP_ALGID, pb, &cb, 0)) + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + CryptoHelper::ByteArrayToU1ARRAYREF(pb, cb, &ret); + break; + + default: + _ASSERTE(FALSE); + } + + HELPER_METHOD_FRAME_END(); + return (U1Array*) OBJECTREFToObject(ret); +} +FCIMPLEND + +// +// _GetKeySetSecurityInfo +// + +FCIMPL3(U1Array*, COMCryptography::_GetKeySetSecurityInfo, SafeHandle* hProvUNSAFE, DWORD dwSecurityInformation, DWORD* pdwErrorCode) +{ + FCALL_CONTRACT; + + U1ARRAYREF ret = NULL; + SAFEHANDLE hProvSAFE = (SAFEHANDLE) hProvUNSAFE; + + HELPER_METHOD_FRAME_BEGIN_RET_1(hProvSAFE); + + SafeHandleHolder shh(&hProvSAFE); + CRYPT_PROV_CTX * pProvCtx = CryptoHelper::DereferenceSafeHandle<CRYPT_PROV_CTX>(hProvSAFE); + + DWORD cb = 0; + NewHolder<BYTE> pSD(NULL); + *pdwErrorCode = 0; + if (!CryptGetProvParam(pProvCtx->m_hProv, PP_KEYSET_SEC_DESCR, NULL, &cb, dwSecurityInformation)) { + *pdwErrorCode = GetLastError(); + goto Error; + } + + pSD = new BYTE[cb]; + if (!CryptGetProvParam(pProvCtx->m_hProv, PP_KEYSET_SEC_DESCR, pSD, &cb, dwSecurityInformation)) { + *pdwErrorCode = GetLastError(); + goto Error; + } + + if (pSD != NULL) + CryptoHelper::ByteArrayToU1ARRAYREF(pSD, cb, &ret); + +Error:; + + HELPER_METHOD_FRAME_END(); + return (U1Array*) OBJECTREFToObject(ret); +} +FCIMPLEND + + +BOOL QCALLTYPE COMCryptography::GetPersistKeyInCsp(CRYPT_PROV_CTX * pProvCtx) +{ + QCALL_CONTRACT; + + BOOL fResult = TRUE; + + BEGIN_QCALL; + + fResult = pProvCtx->m_fPersistKeyInCsp; + + END_QCALL; + + return fResult; +} + +// +// This method queries the key container and gets some of its properties. +// Those properties should never cause a UI to display. +// + +FCIMPL3(Object*, COMCryptography::_GetProviderParameter, SafeHandle* hProvUNSAFE, DWORD dwKeySpec, DWORD dwKeyParam) +{ + FCALL_CONTRACT; + + OBJECTREF ret = NULL; + SAFEHANDLE hProvSAFE = (SAFEHANDLE) hProvUNSAFE; + + HELPER_METHOD_FRAME_BEGIN_RET_2(ret, hProvSAFE); + + DWORD cb = 0; + NewArrayHolder<BYTE> pb(NULL); + NewArrayHolder<WCHAR> pwszUnicode(NULL); + DWORD dwImpType = 0; + HandleKeyHolder hKey(NULL); + + SafeHandleHolder shh(&hProvSAFE); + CRYPT_PROV_CTX * pProvCtx = CryptoHelper::DereferenceSafeHandle<CRYPT_PROV_CTX>(hProvSAFE); + + switch (dwKeyParam) { + case CLR_EXPORTABLE: + cb = sizeof(dwImpType); + if (!CryptGetProvParam(pProvCtx->m_hProv, PP_IMPTYPE, (PBYTE) &dwImpType, &cb, 0)) + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + + if (!(dwImpType & CRYPT_IMPL_HARDWARE)) { + if (!CryptGetUserKey(pProvCtx->m_hProv, dwKeySpec, &hKey)) + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + + DWORD dwPermissions; + dwPermissions = 0; + cb = sizeof(dwPermissions); + if (!CryptGetKeyParam(hKey, KP_PERMISSIONS, (PBYTE) &dwPermissions, &cb, 0)) + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + + pb = new BYTE[4]; + *((DWORD*) pb.GetValue()) = dwPermissions & CRYPT_EXPORT ? 1 : 0; + } else { + // We assume hardware keys are not exportable + pb = new BYTE[4]; + *((DWORD*) pb.GetValue()) = 0; + } + CryptoHelper::ByteArrayToU1ARRAYREF(pb, 4, (U1ARRAYREF*) &ret); + break; + + case CLR_REMOVABLE: + cb = sizeof(dwImpType); + if (!CryptGetProvParam(pProvCtx->m_hProv, PP_IMPTYPE, (PBYTE) &dwImpType, &cb, 0)) + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + + pb = new BYTE[4]; + *((DWORD*) pb.GetValue()) = dwImpType & CRYPT_IMPL_REMOVABLE ? 1 : 0; + CryptoHelper::ByteArrayToU1ARRAYREF(pb, 4, (U1ARRAYREF*) &ret); + break; + + case CLR_HARDWARE: + cb = sizeof(dwImpType); + if (!CryptGetProvParam(pProvCtx->m_hProv, PP_IMPTYPE, (PBYTE) &dwImpType, &cb, 0)) + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + + pb = new BYTE[4]; + *((DWORD*) pb.GetValue()) = dwImpType & CRYPT_IMPL_HARDWARE ? 1 : 0; + CryptoHelper::ByteArrayToU1ARRAYREF(pb, 4, (U1ARRAYREF*) &ret); + break; + + case CLR_ACCESSIBLE: + pb = new BYTE[4]; + *((DWORD*) pb.GetValue()) = CryptGetUserKey(pProvCtx->m_hProv, dwKeySpec, &hKey) ? 1 : 0; + CryptoHelper::ByteArrayToU1ARRAYREF(pb, 4, (U1ARRAYREF*) &ret); + break; + + case CLR_PROTECTED: + cb = sizeof(dwImpType); + if (!CryptGetProvParam(pProvCtx->m_hProv, PP_IMPTYPE, (PBYTE) &dwImpType, &cb, 0)) + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + + BOOL isProtected; + if (dwImpType & CRYPT_IMPL_HARDWARE) { + // Assume hardware keys are protected + isProtected = TRUE; + } else { + isProtected = FALSE; + } + + pb = new BYTE[4]; + *((DWORD*) pb.GetValue()) = isProtected; + CryptoHelper::ByteArrayToU1ARRAYREF(pb, 4, (U1ARRAYREF*) &ret); + break; + + case CLR_UNIQUE_CONTAINER: + if (!CryptGetProvParam(pProvCtx->m_hProv, PP_UNIQUE_CONTAINER, NULL, &cb, 0)) + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + pb = new BYTE[cb]; + if (!CryptGetProvParam(pProvCtx->m_hProv, PP_UNIQUE_CONTAINER, pb, &cb, 0)) + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + pwszUnicode = CryptoHelper::AnsiToUnicode((char*) pb.GetValue()); + ret = StringObject::NewString(pwszUnicode); + break; + + default: + _ASSERTE(FALSE); + } + + HELPER_METHOD_FRAME_END(); + return OBJECTREFToObject(ret); +} +FCIMPLEND + +// +// _GetUserKey +// +// Native method to get the user key pair of a key container +// + +FCIMPL3(HRESULT, COMCryptography::_GetUserKey, SafeHandle* hProvUNSAFE, DWORD dwKeySpec, SafeHandle** hKeyUNSAFE) +{ + FCALL_CONTRACT; + + HRESULT hr = S_OK; + + SAFEHANDLE hProvSAFE = (SAFEHANDLE) hProvUNSAFE; + SAFEHANDLE hKeySAFE = (SAFEHANDLE) *hKeyUNSAFE; + + HELPER_METHOD_FRAME_BEGIN_RET_2(hProvSAFE, hKeySAFE); + + HandleKeyHolder hKey(NULL); + CRYPT_PROV_CTX * pProvCtx = NULL; + { + SafeHandleHolder shh(&hProvSAFE); + pProvCtx = CryptoHelper::DereferenceSafeHandle<CRYPT_PROV_CTX>(hProvSAFE); + { + GCX_PREEMP(); + if (!CryptGetUserKey(pProvCtx->m_hProv, dwKeySpec, &hKey)) + hr = HRESULT_FROM_GetLastError(); + } + } + + if (hr == S_OK) { + CRYPT_KEY_CTX * pKeyCtx = new CRYPT_KEY_CTX(pProvCtx, hKey); + pKeyCtx->m_dwKeySpec = dwKeySpec; + hKeySAFE->SetHandle((void*) pKeyCtx); + hKey.SuppressRelease(); + } + + LOG((LF_SECURITY, LL_INFO10000, "COMCryptography::_GetUserKey returned error code [%#x].\n", hr)); + + HELPER_METHOD_FRAME_END(); + return hr; +} +FCIMPLEND + +void QCALLTYPE COMCryptography::HashData(CRYPT_HASH_CTX * pHashCtx, LPCBYTE pData, DWORD cbData, DWORD dwStart, DWORD dwSize) +{ + QCALL_CONTRACT; + + BEGIN_QCALL; + + // Do this check here as a sanity check. Also, this will catch bugs in CryptoAPITransform + if (dwStart < 0 || dwSize < 0 || dwSize > cbData || dwStart > (cbData - dwSize)) + CryptoHelper::COMPlusThrowCrypto(NTE_BAD_DATA); + + if (!CryptHashData(pHashCtx->m_hHash, pData + dwStart, dwSize, 0)) + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + + END_QCALL; +} + +// +// WARNING: This function side-effects hCSP +// + +FCIMPL5(void, COMCryptography::_ImportBulkKey, SafeHandle* hProvUNSAFE, DWORD dwCalg, CLR_BOOL useSalt, U1Array* rgbKeyUNSAFE, SafeHandle** hKeyUNSAFE) +{ + FCALL_CONTRACT; + + struct _gc + { + U1ARRAYREF rgbKey; + SAFEHANDLE hProvSAFE; + SAFEHANDLE hKeySAFE; + } gc; + + gc.rgbKey = (U1ARRAYREF) rgbKeyUNSAFE; + gc.hProvSAFE = (SAFEHANDLE) hProvUNSAFE; + gc.hKeySAFE = (SAFEHANDLE) *hKeyUNSAFE; + + HELPER_METHOD_FRAME_BEGIN_PROTECT(gc); + + HandleKeyHolder hKey(NULL); + DWORD cbKey = gc.rgbKey->GetNumComponents(); + NewArrayHolder<BYTE> buffer(new BYTE[cbKey]); + memcpyNoGCRefs (buffer, (LPBYTE) gc.rgbKey->GetDirectPointerToNonObjectElements(), cbKey); + CRYPT_PROV_CTX * pProvCtx = NULL; + HRESULT hr = S_OK; + + { + SafeHandleHolder shh(&gc.hProvSAFE); + pProvCtx = CryptoHelper::DereferenceSafeHandle<CRYPT_PROV_CTX>(gc.hProvSAFE); + { + GCX_PREEMP(); + // If we are running in rsabase.dll compatibility mode, make sure 11 bytes of + // zero salt are generated when using a 40 bits RC2 key. + DWORD dwFlags = (dwCalg == CALG_RC2 ? CRYPT_EXPORTABLE | CRYPT_NO_SALT : CRYPT_EXPORTABLE); + if (useSalt && dwCalg == CALG_RC2) + dwFlags &= ~CRYPT_NO_SALT; + hr = LoadKey(buffer, cbKey, pProvCtx->m_hProv, dwCalg, dwFlags, &hKey); + } + } + + if (FAILED(hr)) { + LOG((LF_SECURITY, LL_INFO10000, "Error [%#x]: COMCryptography::_ImportBulkKey failed.\n", hr)); + CryptoHelper::COMPlusThrowCrypto(hr); + } + + CRYPT_KEY_CTX * pKeyCtx = new CRYPT_KEY_CTX(pProvCtx, hKey); + gc.hKeySAFE->SetHandle((void*) pKeyCtx); + hKey.SuppressRelease(); + + HELPER_METHOD_FRAME_END(); +} +FCIMPLEND + +// +// Imports a CSP blob into an RSACryptoServiceProvider/DSACryptoServiceProvider managed object. The blob format is CAPI key blob format (PKCS#1) +// + +FCIMPL4(DWORD, COMCryptography::_ImportCspBlob, U1Array* rawDataUNSAFE, SafeHandle* hProvUNSAFE, DWORD dwFlags, SafeHandle** hKeyUNSAFE) +{ + FCALL_CONTRACT; + + DWORD dwKeySpec = 0; + + struct _gc + { + U1ARRAYREF rawDataSAFE; + SAFEHANDLE hProvSAFE; + SAFEHANDLE hKeySAFE; + } gc; + + gc.rawDataSAFE = (U1ARRAYREF) rawDataUNSAFE; + gc.hProvSAFE = (SAFEHANDLE) hProvUNSAFE; + gc.hKeySAFE = (SAFEHANDLE) *hKeyUNSAFE; + + HELPER_METHOD_FRAME_BEGIN_RET_PROTECT(gc); + + CRYPT_PROV_CTX * pProvCtx = NULL; + HandleKeyHolder hKey(NULL); + HRESULT hr = S_OK; + + DWORD dwCapiFlags = MapCspKeyFlags (dwFlags); + + if (gc.rawDataSAFE == NULL) + COMPlusThrowArgumentNull(W("rawData")); + + NewArrayHolder<BYTE> pbRawData(CryptoHelper::U1ARRAYREFToByteArray(gc.rawDataSAFE)); + DWORD cbRawData = gc.rawDataSAFE->GetNumComponents(); + + PUBLICKEYSTRUC* pPubKeyStruc = (PUBLICKEYSTRUC*) pbRawData.GetValue(); + // If this is a public key, ignore the CRYPT_EXPORTABLE flag. + if (pPubKeyStruc->bType == PUBLICKEYBLOB) + dwCapiFlags &= ~CRYPT_EXPORTABLE; + + { + SafeHandleHolder shh(&gc.hProvSAFE); + pProvCtx = CryptoHelper::DereferenceSafeHandle<CRYPT_PROV_CTX>(gc.hProvSAFE); + { + GCX_PREEMP(); + if (!CryptImportKey(pProvCtx->m_hProv, pbRawData, cbRawData, NULL, dwCapiFlags, &hKey)) + hr = HRESULT_FROM_GetLastError(); + } + } + + if (FAILED(hr)) { + LOG((LF_SECURITY, LL_INFO10000, "Error [%#x]: COMCryptography::_ImportCspBlob failed.\n", hr)); + CryptoHelper::COMPlusThrowCrypto(hr); + } + + CRYPT_KEY_CTX * pKeyCtx = new CRYPT_KEY_CTX(pProvCtx, hKey); + pKeyCtx->m_dwKeySpec = (pPubKeyStruc->aiKeyAlg == CALG_RSA_KEYX ? AT_KEYEXCHANGE : AT_SIGNATURE); + pKeyCtx->m_fPublicOnly = (pPubKeyStruc->bType == PUBLICKEYBLOB ? TRUE : FALSE); + gc.hKeySAFE->SetHandle((void*) pKeyCtx); + + dwKeySpec = pKeyCtx->m_dwKeySpec; + hKey.SuppressRelease(); + + HELPER_METHOD_FRAME_END(); + return dwKeySpec; +} +FCIMPLEND + +// +// _ImportKey +// + +#ifdef _PREFAST_ +#pragma warning(push) +#pragma warning(disable:21000) // Suppress PREFast warning about overly large function +#endif +FCIMPL5(void, COMCryptography::_ImportKey, SafeHandle* hProvUNSAFE, DWORD dwCalg, DWORD dwFlags, Object* refKeyUNSAFE, SafeHandle** hKeyUNSAFE) +{ + FCALL_CONTRACT; + + struct _gc + { + OBJECTREF refKey; + SAFEHANDLE hProvSAFE; + SAFEHANDLE hKeySAFE; + } gc; + + gc.refKey = (OBJECTREF) refKeyUNSAFE; + gc.hProvSAFE = (SAFEHANDLE) hProvUNSAFE; + gc.hKeySAFE = (SAFEHANDLE) *hKeyUNSAFE; + + HELPER_METHOD_FRAME_BEGIN_PROTECT(gc); + + BOOL fPrivate = FALSE; + HandleKeyHolder hKey(NULL); + NewArrayHolder<BYTE> pbKey(NULL); + DWORD cbKey = 0; + LPBYTE pbX = NULL; + KEY_HEADER* pKeyInfo = NULL; + + switch (dwCalg) { + case CALG_DSS_SIGN: { + DWORD cbP; + DWORD cbQ; + DWORD cbX = 0; + DWORD cbJ = 0; + DSA_CSPREF dssKey; + + VALIDATEOBJECTREF(gc.refKey); + dssKey = (DSA_CSPREF) gc.refKey; + + // Validate the DSA structure first + // P, Q and G are required. Q is a 160 bit divisor of P-1 and G is an element of Z_p + if (dssKey->m_P == NULL || dssKey->m_Q == NULL || dssKey->m_Q->GetNumComponents() != DSS_Q_LEN) + CryptoHelper::COMPlusThrowCrypto(NTE_BAD_DATA); + cbP = dssKey->m_P->GetNumComponents(); + cbQ = dssKey->m_Q->GetNumComponents(); + if (dssKey->m_G == NULL || dssKey->m_G->GetNumComponents() != cbP) + CryptoHelper::COMPlusThrowCrypto(NTE_BAD_DATA); + // If J is present, it should be less than the size of P: J = (P-1) / Q + // This is only a sanity check. Not doing it here is not really an issue as CAPI will fail. + if (dssKey->m_J != NULL && dssKey->m_J->GetNumComponents() >= cbP) + CryptoHelper::COMPlusThrowCrypto(NTE_BAD_DATA); + // Y is present for V3 DSA key blobs, Y = g^j mod P + if (dssKey->m_Y != NULL && dssKey->m_Y->GetNumComponents() != cbP) + CryptoHelper::COMPlusThrowCrypto(NTE_BAD_DATA); + // The seed is allways a 20 byte array + if (dssKey->m_seed != NULL && dssKey->m_seed->GetNumComponents() != 20) + CryptoHelper::COMPlusThrowCrypto(NTE_BAD_DATA); + // The private key is less than q-1 + if (dssKey->m_X != NULL && dssKey->m_X->GetNumComponents() != DSS_Q_LEN) + CryptoHelper::COMPlusThrowCrypto(NTE_BAD_DATA); + + // Compute size of data to include + cbKey = 3*cbP + cbQ + sizeof(KEY_HEADER) + sizeof(DSSSEED); + if (dssKey->m_X != 0) { + cbX = dssKey->m_X->GetNumComponents(); + cbKey += cbX; + } + if (dssKey->m_J != NULL) { + cbJ = dssKey->m_J->GetNumComponents(); + cbKey += cbJ; + } + pbKey = new BYTE[cbKey]; + + // Public or Private import? + + pKeyInfo = (KEY_HEADER *) pbKey.GetValue(); + pKeyInfo->blob.bType = PUBLICKEYBLOB; + pKeyInfo->blob.bVersion = CUR_BLOB_VERSION; + pKeyInfo->blob.reserved = 0; + pKeyInfo->blob.aiKeyAlg = dwCalg; + + if (cbX != 0) { + pKeyInfo->blob.bType = PRIVATEKEYBLOB; + fPrivate = TRUE; + } + + // + // If y is present and this is a private key, or + // If y and J are present and this is a public key, + // this should be a v3 blob + // + // make the assumption that if the item is present, there are bytes + + if (((dssKey->m_Y != NULL) && fPrivate) || + ((dssKey->m_Y != NULL) && (dssKey->m_J != NULL))) { + pKeyInfo->blob.bVersion = 0x3; + } + + pbX = pbKey + sizeof(pKeyInfo->blob); + if (pKeyInfo->blob.bVersion == 0x3) { + if (fPrivate) { + pbX += sizeof(pKeyInfo->dss_priv_v3); + pKeyInfo->dss_priv_v3.bitlenP = cbP*8; + pKeyInfo->dss_priv_v3.bitlenQ = cbQ*8; + pKeyInfo->dss_priv_v3.bitlenJ = cbJ*8; + pKeyInfo->dss_priv_v3.bitlenX = cbX*8; + pKeyInfo->dss_priv_v3.magic = DSS_PRIV_MAGIC_VER3; + } + else { + pbX += sizeof(pKeyInfo->dss_pub_v3); + pKeyInfo->dss_pub_v3.bitlenP = cbP*8; + pKeyInfo->dss_pub_v3.bitlenQ = cbQ*8; + pKeyInfo->dss_pub_v3.bitlenJ = cbJ*8; + pKeyInfo->dss_pub_v3.magic = DSS_PUB_MAGIC_VER3; + } + } + else { + if (fPrivate) { + pKeyInfo->dss_v2.magic = DSS_PRIVATE_MAGIC; + } + else { + pKeyInfo->dss_v2.magic = DSS_MAGIC; + } + pKeyInfo->dss_v2.bitlen = cbP*8; + pbX += sizeof(pKeyInfo->dss_v2); + } + + // P + memcpy(pbX, dssKey->m_P->GetDirectPointerToNonObjectElements(), cbP); + CryptoHelper::memrev(pbX, cbP); + pbX += cbP; + + // Q + memcpy(pbX, dssKey->m_Q->GetDirectPointerToNonObjectElements(), cbQ); + CryptoHelper::memrev(pbX, cbQ); + pbX += cbQ; + + // G + memcpy(pbX, dssKey->m_G->GetDirectPointerToNonObjectElements(), cbP); + CryptoHelper::memrev(pbX, cbP); + pbX += cbP; + + if (pKeyInfo->blob.bVersion == 0x3) { + // J -- if present then bVersion == 3; + if (dssKey->m_J != NULL) { + memcpy(pbX, dssKey->m_J->GetDirectPointerToNonObjectElements(), cbJ); + CryptoHelper::memrev(pbX, cbJ); + pbX += cbJ; + } + } + + if (!fPrivate || (pKeyInfo->blob.bVersion == 0x3)) { + // Y -- if present then bVersion == 3; + if (dssKey->m_Y != NULL) { + memcpy(pbX, dssKey->m_Y->GetDirectPointerToNonObjectElements(), cbP); + CryptoHelper::memrev(pbX, cbP); + pbX += cbP; + } + } + + // X -- if present then private + if (fPrivate) { + memcpy(pbX, dssKey->m_X->GetDirectPointerToNonObjectElements(), cbX); + CryptoHelper::memrev(pbX, cbX); + pbX += cbX; + } + + if ((dssKey->m_seed == NULL) || (dssKey->m_seed->GetNumComponents() == 0)){ + // No seed present, so set them to zero + if (pKeyInfo->blob.bVersion == 0x3) { + if (fPrivate) { + memset(&pKeyInfo->dss_priv_v3.DSSSeed, 0xFFFFFFFF, sizeof(DSSSEED)); + } + else { + memset(&pKeyInfo->dss_pub_v3.DSSSeed, 0xFFFFFFFF, sizeof(DSSSEED)); + } + } + else { + memset(pbX, 0xFFFFFFFF, sizeof(DSSSEED)); + pbX += sizeof(DSSSEED); + } + } else { + if (pKeyInfo->blob.bVersion == 0x3) { + if (fPrivate) { + pKeyInfo->dss_priv_v3.DSSSeed.counter = dssKey->m_counter; + memcpy(pKeyInfo->dss_priv_v3.DSSSeed.seed, dssKey->m_seed->GetDirectPointerToNonObjectElements(), 20); + CryptoHelper::memrev(pKeyInfo->dss_priv_v3.DSSSeed.seed, 20); + } else { + pKeyInfo->dss_pub_v3.DSSSeed.counter = dssKey->m_counter; + memcpy(pKeyInfo->dss_pub_v3.DSSSeed.seed, dssKey->m_seed->GetDirectPointerToNonObjectElements(), 20); + CryptoHelper::memrev(pKeyInfo->dss_pub_v3.DSSSeed.seed, 20); + } + } else { + memcpy(pbX,&dssKey->m_counter, sizeof(DWORD)); + pbX += sizeof(DWORD); + // now the seed + memcpy(pbX, dssKey->m_seed->GetDirectPointerToNonObjectElements(), 20); + CryptoHelper::memrev(pbX, 20); + pbX += 20; + } + } + + cbKey = (DWORD)(pbX - pbKey); + break; + } + + case CALG_RSA_SIGN: + case CALG_RSA_KEYX: { + RSA_CSPREF rsaKey; + + VALIDATEOBJECTREF(gc.refKey); + rsaKey = (RSA_CSPREF) gc.refKey; + + // Validate the RSA structure first + if (rsaKey->m_Modulus == NULL) + CryptoHelper::COMPlusThrowCrypto(NTE_BAD_DATA); + // The exponent is a DWORD, so the byte array must not be longer than 4 bytes + if (rsaKey->m_Exponent == NULL || rsaKey->m_Exponent->GetNumComponents() > 4) + CryptoHelper::COMPlusThrowCrypto(NTE_BAD_DATA); + + DWORD cb = rsaKey->m_Modulus->GetNumComponents(); + DWORD cbHalfModulus = (cb + 1)/2; + // We assume that if P != null, then so are Q, DP, DQ, InverseQ and D + if (rsaKey->m_P != NULL) { + if (rsaKey->m_P->GetNumComponents() != cbHalfModulus) + CryptoHelper::COMPlusThrowCrypto(NTE_BAD_DATA); + if (rsaKey->m_Q == NULL || rsaKey->m_Q->GetNumComponents() != cbHalfModulus) + CryptoHelper::COMPlusThrowCrypto(NTE_BAD_DATA); + if (rsaKey->m_dp == NULL || rsaKey->m_dp->GetNumComponents() != cbHalfModulus) + CryptoHelper::COMPlusThrowCrypto(NTE_BAD_DATA); + if (rsaKey->m_dq == NULL || rsaKey->m_dq->GetNumComponents() != cbHalfModulus) + CryptoHelper::COMPlusThrowCrypto(NTE_BAD_DATA); + if (rsaKey->m_InverseQ == NULL || rsaKey->m_InverseQ->GetNumComponents() != cbHalfModulus) + CryptoHelper::COMPlusThrowCrypto(NTE_BAD_DATA); + if (rsaKey->m_d == NULL || rsaKey->m_d->GetNumComponents() != cb) + CryptoHelper::COMPlusThrowCrypto(NTE_BAD_DATA); + } + + // Compute the size of the data to include + pbKey = new BYTE[2*cb + 5*cbHalfModulus + sizeof(KEY_HEADER)]; + + // Public or private import? + + pKeyInfo = (KEY_HEADER *) pbKey.GetValue(); + pKeyInfo->blob.bType = PUBLICKEYBLOB; // will change to PRIVATEKEYBLOB if necessary + pKeyInfo->blob.bVersion = CUR_BLOB_VERSION; + pKeyInfo->blob.reserved = 0; + pKeyInfo->blob.aiKeyAlg = dwCalg; + + pKeyInfo->rsa.magic = RSA_PUB_MAGIC; // will change to RSA_PRIV_MAGIC below if necesary + pKeyInfo->rsa.bitlen = cb*8; + pKeyInfo->rsa.pubexp = ConvertByteArrayToDWORD(rsaKey->m_Exponent->GetDirectPointerToNonObjectElements(), rsaKey->m_Exponent->GetNumComponents()); + pbX = pbKey + sizeof(BLOBHEADER) + sizeof(RSAPUBKEY); + + // Copy over the modulus -- put in for both public & private + + memcpy(pbX, rsaKey->m_Modulus->GetDirectPointerToNonObjectElements(), cb); + CryptoHelper::memrev(pbX, cb); + pbX += cb; + + // + // See if we are doing private keys. + // + + if ((rsaKey->m_P != 0) && (rsaKey->m_P->GetNumComponents() != 0)) { + pKeyInfo->blob.bType = PRIVATEKEYBLOB; + pKeyInfo->rsa.magic = RSA_PRIV_MAGIC; + fPrivate = TRUE; + + // Copy over P + memcpy(pbX, rsaKey->m_P->GetDirectPointerToNonObjectElements(), cbHalfModulus); + CryptoHelper::memrev(pbX, cbHalfModulus); + pbX += cbHalfModulus; + + // Copy over Q + memcpy(pbX, rsaKey->m_Q->GetDirectPointerToNonObjectElements(), cbHalfModulus); + CryptoHelper::memrev(pbX, cbHalfModulus); + pbX += cbHalfModulus; + + // Copy over dp + memcpy(pbX, rsaKey->m_dp->GetDirectPointerToNonObjectElements(), cbHalfModulus); + CryptoHelper::memrev(pbX, cbHalfModulus); + pbX += cbHalfModulus; + + // Copy over dq + memcpy(pbX, rsaKey->m_dq->GetDirectPointerToNonObjectElements(), cbHalfModulus); + CryptoHelper::memrev(pbX, cbHalfModulus); + pbX += cbHalfModulus; + + // Copy over InvQ + memcpy(pbX, rsaKey->m_InverseQ->GetDirectPointerToNonObjectElements(), cbHalfModulus); + CryptoHelper::memrev(pbX, cbHalfModulus); + pbX += cbHalfModulus; + + // Copy over d + memcpy(pbX, rsaKey->m_d->GetDirectPointerToNonObjectElements(), cb); + CryptoHelper::memrev(pbX, cb); + pbX += cb; + } + cbKey = (DWORD)(pbX - pbKey); + break; + } + + default: + COMPlusThrow(kCryptographicException, IDS_EE_CRYPTO_UNKNOWN_OPERATION); + } + + DWORD dwCapiFlags = MapCspKeyFlags(dwFlags); + if (!fPrivate) + dwCapiFlags &= ~CRYPT_EXPORTABLE; + + CRYPT_PROV_CTX * pProvCtx = NULL; + HRESULT hr = S_OK; + + { + SafeHandleHolder shh(&gc.hProvSAFE); + pProvCtx = CryptoHelper::DereferenceSafeHandle<CRYPT_PROV_CTX>(gc.hProvSAFE); + { + GCX_PREEMP(); + if (!CryptImportKey(pProvCtx->m_hProv, pbKey, cbKey, NULL, dwCapiFlags, &hKey)) + hr = HRESULT_FROM_GetLastError(); + } + } + + if (FAILED(hr)) { + LOG((LF_SECURITY, LL_INFO10000, "Error [%#x]: COMCryptography::_ImportKey failed.\n", hr)); + CryptoHelper::COMPlusThrowCrypto(hr); + } + + CRYPT_KEY_CTX * pKeyCtx = new CRYPT_KEY_CTX(pProvCtx, hKey); + pKeyCtx->m_dwKeySpec = (dwCalg == CALG_RSA_KEYX ? AT_KEYEXCHANGE : AT_SIGNATURE); + pKeyCtx->m_fPublicOnly = (pKeyInfo->blob.bType == PUBLICKEYBLOB ? TRUE : FALSE); + gc.hKeySAFE->SetHandle((void*) pKeyCtx); + hKey.SuppressRelease(); + + HELPER_METHOD_FRAME_END(); +} +FCIMPLEND +#ifdef _PREFAST_ +#pragma warning(pop) +#endif + +//--------------------------------------------------------------------------------------- +// +// Check to see if we should default to calculating HMAC values correctly or in Whidbey +// compatibility mode. +// +// Return Value: +// Default compatibiltiy mode for HMACSHA384 and HMACSHA512. TRUE indicates that we +// should match Whidbey, false to use the correct calculation. +// + +FCIMPL0(FC_BOOL_RET, COMCryptography::_ProduceLegacyHMACValues) +{ + FCALL_CONTRACT; + + FC_RETURN_BOOL(g_pConfig->LegacyHMACMode()); +} +FCIMPLEND + +// +// SetKeyParamDw +// +// Sets the value of a key parameter +// + +void QCALLTYPE COMCryptography::SetKeyParamDw(CRYPT_KEY_CTX * pKeyCtx, DWORD param, DWORD dwValue) +{ + QCALL_CONTRACT; + + BEGIN_QCALL; + + if (!CryptSetKeyParam(pKeyCtx->m_hKey, param, (LPBYTE) &dwValue, 0)) + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + + END_QCALL; +} + +// +// SetKeyParamRgb +// + +void QCALLTYPE COMCryptography::SetKeyParamRgb(CRYPT_KEY_CTX * pKeyCtx, DWORD dwParam, LPCBYTE pValue, DWORD cbValue) +{ + QCALL_CONTRACT; + + BEGIN_QCALL; + + NewArrayHolder<BYTE> buffer = new BYTE[cbValue]; + memcpyNoGCRefs (buffer, pValue, cbValue); + + if (!CryptSetKeyParam(pKeyCtx->m_hKey, dwParam, buffer, 0)) + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + + END_QCALL; +} + +// +// SetKeySetSecurityInfo +// + +DWORD QCALLTYPE COMCryptography::SetKeySetSecurityInfo(CRYPT_PROV_CTX * pProvCtx, DWORD dwSecurityInformation, LPCBYTE pSecurityDescriptor) +{ + QCALL_CONTRACT; + + DWORD dwErrorCode = 0; + + BEGIN_QCALL; + + if (!CryptSetProvParam(pProvCtx->m_hProv, + PP_KEYSET_SEC_DESCR, + pSecurityDescriptor, + dwSecurityInformation)) + dwErrorCode = GetLastError(); + + END_QCALL; + + return dwErrorCode; +} + +// +// SetPersistKeyInCsp +// + +void QCALLTYPE COMCryptography::SetPersistKeyInCsp(CRYPT_PROV_CTX * pProvCtx, BOOL fPersistKeyInCsp) +{ + QCALL_CONTRACT; + + BEGIN_QCALL; + + pProvCtx->m_fPersistKeyInCsp = fPersistKeyInCsp; + + END_QCALL; +} + +// +// SetProviderParameter +// + +void QCALLTYPE COMCryptography::SetProviderParameter(CRYPT_PROV_CTX * pProvCtx, DWORD dwKeySpec, DWORD dwProvParam, INT_PTR pbData) +{ + QCALL_CONTRACT; + + BEGIN_QCALL; + + switch (dwProvParam) { + case CLR_PP_CLIENT_HWND: + if (!CryptSetProvParam(pProvCtx->m_hProv, PP_CLIENT_HWND, (LPBYTE) pbData, 0)) + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + break; + + case CLR_PP_PIN: + if (!CryptSetProvParam(pProvCtx->m_hProv, (dwKeySpec == AT_SIGNATURE ? PP_SIGNATURE_PIN : PP_KEYEXCHANGE_PIN), (LPBYTE) pbData, 0)) + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + break; + + default: + _ASSERTE(FALSE); + } + + END_QCALL; +} + +// +// SignValue +// + +void QCALLTYPE COMCryptography::SignValue(CRYPT_KEY_CTX * pKeyCtx, DWORD dwKeySpec, DWORD dwCalgKey, DWORD dwCalgHash, + LPCBYTE pbHash, DWORD cbHash, QCall::ObjectHandleOnStack retSignature) +{ + QCALL_CONTRACT; + + BEGIN_QCALL; + + HandleHashHolder hHash = NULL; + + // Make sure it's either RSA or DSA key + _ASSERTE(dwCalgKey == CALG_DSS_SIGN || dwCalgKey == CALG_RSA_SIGN); + + NewArrayHolder<BYTE> buffer = new BYTE[cbHash]; + memcpyNoGCRefs (buffer, pbHash, cbHash); + + _ASSERTE(pKeyCtx->m_pProvCtx); + CRYPT_PROV_CTX * pProvCtx = pKeyCtx->m_pProvCtx; + + // Take the hash value and create a hash object in the correct CSP. + if (!CryptCreateHash(pProvCtx->m_hProv, dwCalgHash, NULL, 0, &hHash)) + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + + DWORD dwHashSize = 0; + DWORD cbHashSize = sizeof(dwHashSize); + if (!CryptGetHashParam(hHash, HP_HASHSIZE, reinterpret_cast<BYTE *>(&dwHashSize), &cbHashSize, 0)) + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + + if (dwHashSize != cbHash) + CryptoHelper::COMPlusThrowCrypto(NTE_BAD_HASH); + + if (!CryptSetHashParam(hHash, HP_HASHVAL, buffer, 0)) + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + + // Find out how long the signature is going to be + DWORD cbSignature = 0; + if (!WszCryptSignHash(hHash, dwKeySpec, NULL, 0, NULL, &cbSignature)) + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + + // Allocate the buffer to hold the signature + NewArrayHolder<BYTE> buffer2 = new BYTE[cbSignature]; + + // Now do the actual signature into the return buffer + if (!WszCryptSignHash(hHash, dwKeySpec, NULL, 0, buffer2, &cbSignature)) + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + + switch(dwCalgKey) { + case CALG_RSA_SIGN: + // the RSA signature needs to be reversed + CryptoHelper::memrev(buffer2, cbSignature); + break; + case CALG_DSS_SIGN: + // A DSA signature consists of two 20-byte components, each of which + // must be reversed in place + if (cbSignature != 40) + COMPlusThrow(kCryptographicException, W("Cryptography_InvalidDSASignatureSize")); + CryptoHelper::memrev(buffer2, 20); + CryptoHelper::memrev(buffer2 + 20, 20); + break; + } + + retSignature.SetByteArray(buffer2, cbSignature); + + END_QCALL; +} + +// +// VerifySign +// + +BOOL QCALLTYPE COMCryptography::VerifySign(CRYPT_KEY_CTX * pKeyCtx, DWORD dwCalgKey, DWORD dwCalgHash, + LPCBYTE pbHash, DWORD cbHash, LPCBYTE pbSignature, DWORD cbSignature) +{ + QCALL_CONTRACT; + + BOOL result = FALSE; + + BEGIN_QCALL; + + // Make sure it's either RSA or DSA key + _ASSERTE(dwCalgKey == CALG_DSS_SIGN || dwCalgKey == CALG_RSA_SIGN); + + HandleHashHolder hHash = NULL; + + // + // Take the hash value and create a hash object in the correct CSP. + // + + NewArrayHolder<BYTE> bufferHash(new BYTE[cbHash]); + memcpyNoGCRefs (bufferHash, pbHash, cbHash * sizeof(BYTE)); + NewArrayHolder<BYTE> bufferSignature(new BYTE[cbSignature]); + memcpyNoGCRefs (bufferSignature, pbSignature, cbSignature * sizeof(BYTE)); + + switch(dwCalgKey) { + case CALG_RSA_SIGN: + // the RSA signature needs to be reversed + CryptoHelper::memrev(bufferSignature, cbSignature); + break; + case CALG_DSS_SIGN: + // A DSA signature consists of two 20-byte components, each of which + // must be reversed in place + if (cbSignature != 40) + COMPlusThrow(kCryptographicException, W("Cryptography_InvalidDSASignatureSize")); + CryptoHelper::memrev(bufferSignature, 20); + CryptoHelper::memrev(bufferSignature + 20, 20); + break; + } + + CRYPT_PROV_CTX * pProvCtx = pKeyCtx->m_pProvCtx; + + if (!CryptCreateHash(pProvCtx->m_hProv, dwCalgHash, NULL, 0, &hHash)) + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + + DWORD dwHashSize = 0; + DWORD cbHashSize = sizeof(dwHashSize); + if (!CryptGetHashParam(hHash, HP_HASHSIZE, reinterpret_cast<BYTE *>(&dwHashSize), &cbHashSize, 0)) + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + + if (dwHashSize != cbHash) + CryptoHelper::COMPlusThrowCrypto(NTE_BAD_HASH); + + if (!CryptSetHashParam(hHash, HP_HASHVAL, bufferHash, 0)) + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + + // Now see if the signature verifies. + result = WszCryptVerifySignature(hHash, bufferSignature, cbSignature, pKeyCtx->m_hKey, NULL, 0); + + END_QCALL; + + return result; +} +#endif // FEATURE_CRYPTO + diff --git a/src/classlibnative/cryptography/cryptography.h b/src/classlibnative/cryptography/cryptography.h new file mode 100644 index 0000000000..0d9a1b6cb0 --- /dev/null +++ b/src/classlibnative/cryptography/cryptography.h @@ -0,0 +1,522 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +// + +// + + +#ifndef _CRYPTOGRAPHY_H_ +#define _CRYPTOGRAPHY_H_ + +#include <wincrypt.h> + +#if defined(FEATURE_CRYPTO) || defined(FEATURE_LEGACYNETCFCRYPTO) +#include "fcall.h" + +class DSA_CSP_Object : public Object { +public: + U1ARRAYREF m_P; // ubyte[] + U1ARRAYREF m_Q; // ubyte[] + U1ARRAYREF m_G; // ubyte[] + U1ARRAYREF m_Y; // ubyte[] - optional + U1ARRAYREF m_J; // ubyte[] - optional + U1ARRAYREF m_X; // ubyte[] - optional - private key + U1ARRAYREF m_seed; // ubyte[] - optional - paired with counter + DWORD m_counter; // DWORD - optional +}; + +class RSA_CSP_Object : public Object { +public: + U1ARRAYREF m_Exponent; // ubyte[] + U1ARRAYREF m_Modulus; // ubyte[] + U1ARRAYREF m_P; // ubyte[] - optional + U1ARRAYREF m_Q; // ubyte[] - optional + U1ARRAYREF m_dp; // ubyte[] - optional + U1ARRAYREF m_dq; // ubyte[] - optional + U1ARRAYREF m_InverseQ; // ubyte[] - optional + U1ARRAYREF m_d; // ubyte[] - optional +}; + +#ifdef USE_CHECKED_OBJECTREFS +typedef REF<DSA_CSP_Object> DSA_CSPREF; +typedef REF<RSA_CSP_Object> RSA_CSPREF; +#else // !_DEBUG +typedef DSA_CSP_Object * DSA_CSPREF; +typedef RSA_CSP_Object * RSA_CSPREF; +#endif // _DEBUG + +#endif // #if defined(FEATURE_CRYPTO) || defined(FEATURE_LEGACYNETCFCRYPTO) + +#define DSS_MAGIC 0x31535344 +#define DSS_PRIVATE_MAGIC 0x32535344 +#define DSS_PUB_MAGIC_VER3 0x33535344 +#define DSS_PRIV_MAGIC_VER3 0x34535344 +#define RSA_PUB_MAGIC 0x31415352 +#define RSA_PRIV_MAGIC 0x32415352 + +#define DSS_Q_LEN 20 + +// Keep in sync with managed definition in System.Security.Cryptography.Utils +#define CLR_KEYLEN 1 +#define CLR_PUBLICKEYONLY 2 +#define CLR_EXPORTABLE 3 +#define CLR_REMOVABLE 4 +#define CLR_HARDWARE 5 +#define CLR_ACCESSIBLE 6 +#define CLR_PROTECTED 7 +#define CLR_UNIQUE_CONTAINER 8 +#define CLR_ALGID 9 +#define CLR_PP_CLIENT_HWND 10 +#define CLR_PP_PIN 11 + +#define MAX_CACHE_DEFAULT_PROVIDERS 20 +// size of a symmetric key block size. 8 is the only supported for now +#define BLOCK_LEN 8 + +// Dependency in managed : System/Security/Cryptography/Crypto.cs +#define CRYPTO_PADDING_NONE 1 +#define CRYPTO_PADDING_PKCS5 2 +#define CRYPTO_PADDING_Zeros 3 +#define CRYPTO_PADDING_ANSI_X_923 4 +#define CRYPTO_PADDING_ISO_10126 5 + +// These flags match those defined for the CspProviderFlags enum in +// src/bcl/system/security/cryptography/CryptoAPITransform.cs + +#define CSP_PROVIDER_FLAGS_USE_MACHINE_KEYSTORE 0x0001 +#define CSP_PROVIDER_FLAGS_USE_DEFAULT_KEY_CONTAINER 0x0002 +#define CSP_PROVIDER_FLAGS_USE_NON_EXPORTABLE_KEY 0x0004 +#define CSP_PROVIDER_FLAGS_USE_EXISTING_KEY 0x0008 +#define CSP_PROVIDER_FLAGS_USE_ARCHIVABLE_KEY 0x0010 +#define CSP_PROVIDER_FLAGS_USE_USER_PROTECTED_KEY 0x0020 +#define CSP_PROVIDER_FLAGS_USE_CRYPT_SILENT 0x0040 +#define CSP_PROVIDER_FLAGS_CREATE_EPHEMERAL_KEY 0x0080 + + +#if defined(FEATURE_CRYPTO) || defined(FEATURE_LEGACYNETCFCRYPTO) || defined(FEATURE_X509) +class CryptoHelper { +public: + static void COMPlusThrowCrypto (HRESULT hr); + static BOOL WszCryptAcquireContext_SO_TOLERANT (HCRYPTPROV *phProv, LPCWSTR pwszContainer, LPCWSTR pwszProvider, DWORD dwProvType, DWORD dwFlags); + static WCHAR* STRINGREFToUnicode (STRINGREF s); + static WCHAR* AnsiToUnicode (__in_z char* pszAnsi); + static void ByteArrayToU1ARRAYREF (LPBYTE pb, DWORD cb, U1ARRAYREF* u1); + static BYTE* U1ARRAYREFToByteArray (U1ARRAYREF u1); + static char* UnicodeToAnsi (__in_z WCHAR* pwszUnicode); +#if defined(FEATURE_CRYPTO) || defined(FEATURE_LEGACYNETCFCRYPTO) + static WCHAR* GetRandomKeyContainer (); + static inline void memrev (LPBYTE pb, DWORD cb); + static BOOL CryptGenKey_SO_TOLERANT (HCRYPTPROV hProv, ALG_ID Algid, DWORD dwFlags, HCRYPTKEY* phKey); +#endif // FEATURE_CRYPTO + + static LPCWSTR UpgradeDSS(DWORD dwProvType, __in_z LPCWSTR wszProvider); + static LPCWSTR UpgradeRSA(DWORD dwProvType, __in_z LPCWSTR wszProvider); + + // Since crytpo classes use safe handles where the handles are really pointers to structures, we + // need to ensure that they weren't freed and set to NULL if the handle was used in an unsafe + // multithreaded way. This method unpacks the handle, ensuring it doesn't contain a NULL pointer. + template<class T> + static T * DereferenceSafeHandle(const SAFEHANDLE &handle) + { + CONTRACT(T *) + { + POSTCONDITION(RETVAL != NULL); + THROWS; + GC_TRIGGERS; + } + CONTRACT_END; + + T * pValue = static_cast<T *>(handle->GetHandle()); + if (!pValue) + { + LOG((LF_SECURITY, LL_INFO10000, "Attempt to access a NULL crypto handle, possible unsafe use of a crypto function from multiple threads")); + COMPlusThrowCrypto(E_POINTER); + } + + RETURN(pValue); + } +}; +#endif // FEATURE_CRYPTO || FEATURE_X509 + +#if defined(FEATURE_CRYPTO) || defined(FEATURE_LEGACYNETCFCRYPTO) +typedef struct { + BLOBHEADER blob; + union { + DSSPRIVKEY_VER3 dss_priv_v3; + DSSPUBKEY_VER3 dss_pub_v3; + DSSPUBKEY dss_v2; + RSAPUBKEY rsa; + }; +} KEY_HEADER; + +// We need to define this unmanaged memory structure to hold +// all the information relevant to the CSP in order to guarantee +// critical finalization of the resources +typedef struct CRYPT_PROV_CTX { +private: + // We implicitely assume this method is not going to do a LoadLibrary + HRESULT DeleteKeyContainer() { + CONTRACTL { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } CONTRACTL_END; + + GCX_PREEMP(); + + HCRYPTPROV hProv = NULL; + if (!CryptoHelper::WszCryptAcquireContext_SO_TOLERANT(&hProv, m_pwszContainer, m_pwszProvider, + m_dwType, (m_dwFlags & CRYPT_MACHINE_KEYSET) | CRYPT_DELETEKEYSET)) + return HRESULT_FROM_GetLastError(); + return S_OK; + } + +public: + HCRYPTPROV m_hProv; + LPCWSTR m_pwszContainer; + LPCWSTR m_pwszProvider; + DWORD m_dwType; + DWORD m_dwFlags; + BOOL m_fPersistKeyInCsp; + BOOL m_fReleaseProvider; + Volatile<ULONG> m_refCount; + + CRYPT_PROV_CTX() : + m_hProv(0), + m_pwszContainer(NULL), + m_pwszProvider(NULL), + m_fReleaseProvider(TRUE), + m_dwType(0), + m_dwFlags(0), + m_fPersistKeyInCsp(TRUE), + m_refCount(1) { + LIMITED_METHOD_CONTRACT; + } + + // This can be called twice. Also it can be called by multiple threads. The only + // invariant that needs to be enforced is that when the refCount reaches 0, the + // object is not going to be referenced anymore by a CRYPT_KEY_CTX or a CRYPT_HASH_CTX. + // But this is true in the way we use this in the managed side. + void Release () { + CONTRACTL { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } CONTRACTL_END; + + ULONG cbRef = InterlockedDecrement((LONG*)&m_refCount); + if (cbRef == 0) { + // Make sure not to delete a key that we want to keep in the key container or an ephemeral key + if (m_fPersistKeyInCsp == FALSE && !(m_dwFlags & CRYPT_VERIFYCONTEXT)) { + // We cannot throw if we fail to delete the key container, since this code runs on the + // finalizer thread and any exception will tear the process + DeleteKeyContainer(); + } + + if (m_pwszContainer) { + delete[] m_pwszContainer; + m_pwszContainer = NULL; + } + + // The provider strings are allocated per process, so + // we should not free m_pwszProvider unless specified otherwise + if (m_fReleaseProvider) { + if (m_pwszProvider) { + delete[] m_pwszProvider; + m_pwszProvider = NULL; + } + } + + // We need to free the CSP handle -- make sure not to throw since this code could be on the + // finalizer thread. + if (m_hProv != 0) + { + CryptReleaseContext(m_hProv, 0); + m_hProv = 0; + } + + delete this; + } + } + +} CRYPT_PROV_CTX; + +#if defined(FEATURE_CRYPTO) || defined(FEATURE_LEGACYNETCFCRYPTO) +// A key handle needs to be freed before the provider handle +// it was loaded into is freed; so we need to keep a pointer to +// the CRYPT_PROV_CTX and make the managed SafeKeyHandle contain +// a pointer to the CRYPT_KEY_CTX pointer +typedef struct CRYPT_KEY_CTX { +public: + CRYPT_PROV_CTX *m_pProvCtx; + HCRYPTKEY m_hKey; + DWORD m_dwKeySpec; + BOOL m_fPublicOnly; + + CRYPT_KEY_CTX(CRYPT_PROV_CTX * pProvCtx, HCRYPTKEY hKey) : + m_pProvCtx(pProvCtx), + m_hKey(hKey), + m_dwKeySpec(0), + m_fPublicOnly(FALSE) { + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + PRECONDITION(hKey != NULL); + PRECONDITION(CheckPointer(m_pProvCtx)); + PRECONDITION((m_pProvCtx->m_refCount >= 1)); // We can't acquire a dead CRYPT_PROV_CTX + } CONTRACTL_END; + + InterlockedIncrement((LONG*)&m_pProvCtx->m_refCount); + } + + void Release () { + CONTRACTL { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } CONTRACTL_END; + + // We cannot throw if CryptDestroyKey fails, since this can be executed on the finalizer thread + if (m_hKey) + CryptDestroyKey(m_hKey); + + // We need to release the reference to the CSP handle + if (m_pProvCtx) + m_pProvCtx->Release(); + + delete this; + } + +} CRYPT_KEY_CTX; + +// A hash handle needs to be freed before the provider handle +// it was loaded into is freed; so we need to keep a pointer to +// the CRYPT_PROV_CTX and make the managed SafeHashHandle contain +// a pointer to the CRYPT_HASH_CTX pointer +typedef struct CRYPT_HASH_CTX { +public: + CRYPT_PROV_CTX *m_pProvCtx; + HCRYPTHASH m_hHash; + + CRYPT_HASH_CTX(CRYPT_PROV_CTX * pProvCtx, HCRYPTHASH hHash) : + m_pProvCtx(pProvCtx), + m_hHash(hHash) { + CONTRACTL { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + PRECONDITION(hHash != NULL); + PRECONDITION(CheckPointer(m_pProvCtx)); + PRECONDITION((m_pProvCtx->m_refCount >= 1)); // We can't acquire a dead CRYPT_PROV_CTX + } CONTRACTL_END; + + InterlockedIncrement((LONG*)&m_pProvCtx->m_refCount); + } + + void Release () { + CONTRACTL { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } CONTRACTL_END; + + // We cannot throw if CryptDestroyHash fails since this code could run on the finalizer thread + if (m_hHash) + CryptDestroyHash(m_hHash); + + // We need to release the reference to the CSP handle + if (m_pProvCtx) + m_pProvCtx->Release(); + + delete this; + } + +} CRYPT_HASH_CTX; +#endif // FEATURE_CRYPTO + +class COMCryptography +{ +public: + +#if defined(FEATURE_CRYPTO) || defined(FEATURE_LEGACYNETCFCRYPTO) + // + // QCalls from RSACryptoServiceProvider + // + + // Decrypt a symmetric key using the private key in pKeyContext + static + void QCALLTYPE DecryptKey(__in CRYPT_KEY_CTX *pKeyContext, + __in_bcount(cbEncryptedKey) BYTE *pbEncryptedKey, + DWORD cbEncryptedKey, + BOOL fOAEP, + QCall::ObjectHandleOnStack ohRetDecryptedKey); + + // Encrypt a symmetric key using the public key in pKeyContext + static + void QCALLTYPE EncryptKey(__in CRYPT_KEY_CTX *pKeyContext, + __in_bcount(cbKey) BYTE *pbKey, + DWORD cbKey, + BOOL fOAEP, + QCall::ObjectHandleOnStack ohRetEncryptedKey); + + // + // SafeHandle release QCALLS + // +#endif // FEATURE_CRYPTO + // Release our handle to a CSP, potentially deleting the referenced key. + static + void QCALLTYPE FreeCsp(__in_opt CRYPT_PROV_CTX *pProviderContext); + +#if defined(FEATURE_CRYPTO) || defined(FEATURE_LEGACYNETCFCRYPTO) + // Release our handle to a hash, potentially also releasing the provider + static + void QCALLTYPE FreeHash(__in_opt CRYPT_HASH_CTX *pHashContext); + + // Release our handle to a key, potentially also releasing the provider + static + void QCALLTYPE FreeKey(__in_opt CRYPT_KEY_CTX *pKeyContext); + + // + // Util QCALLS + // + + static + CRYPT_HASH_CTX * CreateHash(CRYPT_PROV_CTX * pProvCtx, DWORD dwHashType); + + static + void QCALLTYPE DeriveKey(CRYPT_PROV_CTX * pProvCtx, DWORD dwCalgKey, DWORD dwCalgHash, + LPCBYTE pbPwd, DWORD cbPwd, DWORD dwFlags, LPBYTE pbIVIn, DWORD cbIVIn, + QCall::ObjectHandleOnStack retKey); + + static + void QCALLTYPE EndHash(CRYPT_HASH_CTX * pHashCtx, QCall::ObjectHandleOnStack retHash); + + static + void QCALLTYPE ExportCspBlob(CRYPT_KEY_CTX * pKeyCtx, DWORD dwBlobType, QCall::ObjectHandleOnStack retBlob); +#endif // FEATURE_CRYPTO + + static + void QCALLTYPE GetBytes(CRYPT_PROV_CTX * pProvCtx, BYTE * pbOut, INT32 cb); + + static + void QCALLTYPE GetNonZeroBytes(CRYPT_PROV_CTX * pProvCtx, BYTE * pbOut, INT32 cb); + +#if defined(FEATURE_CRYPTO) || defined(FEATURE_LEGACYNETCFCRYPTO) + static + BOOL QCALLTYPE GetPersistKeyInCsp(CRYPT_PROV_CTX * pProvCtx); + + static + void QCALLTYPE HashData(CRYPT_HASH_CTX * pHashCtx, LPCBYTE pData, DWORD cbData, DWORD dwStart, DWORD dwSize); + + static + BOOL QCALLTYPE SearchForAlgorithm(CRYPT_PROV_CTX * pProvCtx, DWORD dwAlgID, DWORD dwKeyLength); + + static + void QCALLTYPE SetKeyParamDw(CRYPT_KEY_CTX * pKeyCtx, DWORD dwParam, DWORD dwValue); + + static + void QCALLTYPE SetKeyParamRgb(CRYPT_KEY_CTX * pKeyCtx, DWORD dwParam, LPCBYTE pValue, DWORD cbValue); + + static + DWORD QCALLTYPE SetKeySetSecurityInfo(CRYPT_PROV_CTX * pProvCtx, DWORD dwSecurityInformation, LPCBYTE pSecurityDescriptor); + + static + void QCALLTYPE SetPersistKeyInCsp(CRYPT_PROV_CTX * pProvCtx, BOOL fPersistKeyInCsp); + + static + void QCALLTYPE SetProviderParameter(CRYPT_PROV_CTX * pProvCtx, DWORD dwKeySpec, DWORD dwProvParam, INT_PTR pbData); + + static + void QCALLTYPE SignValue(CRYPT_KEY_CTX * pKeyCtx, DWORD dwKeySpec, DWORD dwCalgKey, DWORD dwCalgHash, + LPCBYTE pbHash, DWORD cbHash, QCall::ObjectHandleOnStack retSignature); + + static + BOOL QCALLTYPE VerifySign(CRYPT_KEY_CTX * pKeyCtx, DWORD dwCalgKey, DWORD dwCalgHash, + LPCBYTE pbHash, DWORD cbHash, LPCBYTE pbSignature, DWORD cbSignature); + +#endif // FEATURE_CRYPTO + +public: + // + // FCalls from System.Security.Cryptography.Utils + // + + static FCDECL2(void, _AcquireCSP, Object* cspParametersUNSAFE, SafeHandle** hProvUNSAFE); + static FCDECL3(HRESULT, _OpenCSP, Object* cspParametersUNSAFE, DWORD dwFlags, SafeHandle** hProvUNSAFE); + static FCDECL0(StringObject*, _GetRandomKeyContainer); + static LPCWSTR GetDefaultProvider(DWORD dwType); + +#if defined(FEATURE_CRYPTO) || defined(FEATURE_LEGACYNETCFCRYPTO) + static FCDECL3(void, _CreateCSP, Object* cspParametersUNSAFE, CLR_BOOL randomKeyContainer, SafeHandle** hProvUNSAFE); + static FCDECL8(DWORD, _DecryptData, SafeHandle* hKeyUNSAFE, U1Array* dataUNSAFE, INT32 dwOffset, INT32 dwCount, U1Array** outputUNSAFE, INT32 dwOutputOffset, DWORD dwPaddingMode, CLR_BOOL fLast); + static FCDECL8(DWORD, _EncryptData, SafeHandle* hKeyUNSAFE, U1Array* dataUNSAFE, INT32 dwOffset, INT32 dwCount, U1Array** outputUNSAFE, INT32 dwOutputOffset, DWORD dwPaddingMode, CLR_BOOL fLast); + static FCDECL3(void, _ExportKey, SafeHandle* hKeyUNSAFE, DWORD dwBlobType, Object* theKeyUNSAFE); + static FCDECL5(void, _GenerateKey, SafeHandle* hProvUNSAFE, DWORD dwCalg, DWORD dwFlags, DWORD dwKeySize, SafeHandle** hKeyUNSAFE); + static FCDECL0(FC_BOOL_RET, _GetEnforceFipsPolicySetting); + static FCDECL2(U1Array*, _GetKeyParameter, SafeHandle* hKeyUNSAFE, DWORD dwKeyParam); + static FCDECL3(U1Array*, _GetKeySetSecurityInfo, SafeHandle* hProvUNSAFE, DWORD dwSecurityInformation, DWORD* pdwErrorCode); + static FCDECL3(Object*, _GetProviderParameter, SafeHandle* hKeyUNSAFE, DWORD dwKeySpec, DWORD dwKeyParam); + static FCDECL3(HRESULT, _GetUserKey, SafeHandle* hProvUNSAFE, DWORD dwKeySpec, SafeHandle** hKeyUNSAFE); + static FCDECL5(void, _ImportBulkKey, SafeHandle* hProvUNSAFE, DWORD dwCalg, CLR_BOOL useSalt, U1Array* rgbKeyUNSAFE, SafeHandle** hKeyUNSAFE); + static FCDECL4(DWORD, _ImportCspBlob, U1Array* rawDataUNSAFE, SafeHandle* hProvUNSAFE, DWORD dwFlags, SafeHandle** hKeyUNSAFE); + static FCDECL5(void, _ImportKey, SafeHandle* hProvUNSAFE, DWORD dwCalg, DWORD dwFlags, Object* refKeyUNSAFE, SafeHandle** hKeyUNSAFE); + static FCDECL0(FC_BOOL_RET, _ProduceLegacyHMACValues); +#endif // FEATURE_CRYPTO + +private: + static HRESULT OpenCSP(OBJECTREF * pSafeThis, DWORD dwFlags, CRYPT_PROV_CTX * pProvCtxStruct); + static DWORD MapCspProviderFlags (DWORD dwFlags); + +#if defined(FEATURE_CRYPTO) || defined(FEATURE_LEGACYNETCFCRYPTO) + static HRESULT MSProviderCryptImportKey(HCRYPTPROV hProv, LPBYTE rgbSymKey, DWORD cbSymKey, DWORD dwFlags, HCRYPTKEY * phkey); + static HRESULT ExponentOfOneImport(HCRYPTPROV hProv, LPBYTE rgbKeyMaterial, DWORD cbKeyMaterial, DWORD dwKeyAlg, DWORD dwFlags, HCRYPTKEY * phkey); + static HRESULT PlainTextKeyBlobImport(HCRYPTPROV hProv, LPBYTE rgbKeyMaterial, DWORD cbKeyMaterial, DWORD dwKeyAlg, DWORD dwFlags, HCRYPTKEY * phkey); + static HRESULT LoadKey(LPBYTE rgbKeyMaterial, DWORD cbKeyMaterial, HCRYPTPROV hprov, DWORD dwCalg, DWORD dwFlags, HCRYPTKEY * phkey); + static HRESULT UnloadKey(HCRYPTPROV hprov, HCRYPTKEY hkey, LPBYTE * ppb, DWORD * pcb); + static inline DWORD ConvertByteArrayToDWORD (LPBYTE pb, DWORD cb); + static inline void ConvertIntToByteArray(DWORD dwInput, LPBYTE * ppb, DWORD * pcb); + static DWORD MapCspKeyFlags (DWORD dwFlags); +#endif //FEATURE_CRYPTO + +}; // class COMCryptography + +// @telesto - with talk of registry access, sounds like this should be #ifdef out from Telesto? +//--------------------------------------------------------------------------------------- +// +// Cache of CSP data we've already looked up in the registry +// +// Notes: +// This cache is thread safe. If a CSP is not stored in the cache, it will +// return NULL rather than throwing. Attempting to store multiple CSPs with +// the same type will result in only the first CSP being stored. +// + +class ProviderCache +{ +public: + // Associate a name with a CSP type + static void CacheProvider(DWORD dwType, __in_z LPWSTR pwzProvider); + + // Get the name that's associated with the given type, NULL if there is no association setup + static LPCWSTR GetProvider(DWORD dwType); + +private: + // The largest type of CSP that Windows defines. This should be updated as new CSP types are defined + static const DWORD MaxWindowsProviderType = PROV_RSA_AES; + + static ProviderCache *s_pCache; // singleton cache instance + + EEIntHashTable m_htCache; // Mapping between cached provider types and CSP names + Crst m_crstCache; // Lock guarding access to m_htCache + + ProviderCache(); + + void InternalCacheProvider(DWORD dwType, __in_z LPWSTR pwzProvider); + LPCWSTR InternalGetProvider(DWORD dwType); +}; +#endif // FEATURE_CRYPTO -- review flags + +#endif // !_CRYPTOGRPAPHY_H_ diff --git a/src/classlibnative/cryptography/cryptography.nativeproj b/src/classlibnative/cryptography/cryptography.nativeproj new file mode 100644 index 0000000000..44b8f93aea --- /dev/null +++ b/src/classlibnative/cryptography/cryptography.nativeproj @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="dogfood"> + <Import Project="$(_NTDRIVE)$(_NTROOT)\ndp\clr\clr.props" /> + <Import Project="$(ClrBase)\src\debug\SetDebugTargetLocal.props" /> + <PropertyGroup Label="Globals"> + <SccProjectName>SAK</SccProjectName> + <SccAuxPath>SAK</SccAuxPath> + <SccLocalPath>SAK</SccLocalPath> + <SccProvider>SAK</SccProvider> + </PropertyGroup> + <PropertyGroup> + <BuildCoreBinaries>true</BuildCoreBinaries> + <BuildSysBinaries>true</BuildSysBinaries> + <NoWarningDotH>true</NoWarningDotH> + <ClWarningLevel>4</ClWarningLevel> + <UserIncludes> + $(UserIncludes); + .; + ..\inc; + $(Clrbase)\src\vm; + $(Clrbase)\src\vm\$(TargetCpu); + $(VCToolsIncPath); + $(Clrbase)\src\strongname\inc; + ..\bcltype + </UserIncludes> + <OutputName>comcrypt_wks</OutputName> + <OutputPath>$(ClrLibDest)</OutputPath> + <TargetType>LIBRARY</TargetType> + <CDefines>$(CDefines);UNICODE;_UNICODE</CDefines> + </PropertyGroup> + <ItemGroup> + <CppCompile Include="Cryptography.cpp" /> + <CppCompile Include="X509Certificate.cpp" /> + </ItemGroup> + <Import Project="$(_NTDRIVE)$(_NTROOT)\ndp\clr\clr.targets" /> +</Project> diff --git a/src/classlibnative/cryptography/x509certificate.cpp b/src/classlibnative/cryptography/x509certificate.cpp new file mode 100644 index 0000000000..e4e3ba9989 --- /dev/null +++ b/src/classlibnative/cryptography/x509certificate.cpp @@ -0,0 +1,1341 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +// +// File: X509Certificate.cpp +// + +// +// Native method implementations and helper code for supporting CAPI based operations on X509 signatures +// +//--------------------------------------------------------------------------- + + +#include "common.h" + +#ifdef FEATURE_X509 + +#include "x509certificate.h" + +#if !defined(FEATURE_CORECLR) +// +// Builds a certificate chain using the specified policy. +// + +HRESULT X509Helper::BuildChain (PCCERT_CONTEXT pCertContext, + HCERTSTORE hCertStore, + LPCSTR pszPolicy, + PCCERT_CHAIN_CONTEXT * ppChainContext) +{ + CONTRACTL { + THROWS; // THROWS because the delay-loading of crypt32.dll may fail when we call CertGetCertificateChain() + GC_TRIGGERS; + MODE_ANY; + } CONTRACTL_END; + + GCX_PREEMP(); + + CERT_CHAIN_PARA ChainPara = {0}; + LPSTR rgpszUsageIdentifier[1] = {NULL}; + + // Initialize the structure size. + ChainPara.cbSize = sizeof(ChainPara); + + // Check policy. + if (CERT_CHAIN_POLICY_BASE == pszPolicy) { + // No EKU for base policy. + } + else + return CERT_E_INVALID_POLICY; + + // Build the chain. + if (!CertGetCertificateChain(NULL, + pCertContext, + NULL, + hCertStore, + &ChainPara, + 0, + NULL, + ppChainContext)) { + LOG((LF_SECURITY, LL_INFO10000, "Error [%#x]: CertGetCertificateChain failed.\n", HRESULT_FROM_GetLastError())); + return HRESULT_FROM_GetLastError(); + } + + return S_OK; +} + +// +// decodes an ASN encoded data. The caller is responsible for calling delete[] to free the allocated memory. +// + +HRESULT X509Helper::DecodeObject(LPCSTR pszStructType, + LPBYTE pbEncoded, + DWORD cbEncoded, + void** ppvDecoded, + DWORD* pcbDecoded) { + CONTRACTL { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } CONTRACTL_END; + + GCX_PREEMP(); + + DWORD cbDecoded = 0; + if (!CryptDecodeObject(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + pszStructType, + pbEncoded, + cbEncoded, + 0, + NULL, + &cbDecoded)) { + LOG((LF_SECURITY, LL_INFO10000, "Error [%#x]: CryptDecodeObject failed.\n", HRESULT_FROM_GetLastError())); + return HRESULT_FROM_GetLastError(); + } + + *ppvDecoded = (void*) new BYTE[cbDecoded]; + if (!CryptDecodeObject(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + pszStructType, + pbEncoded, + cbEncoded, + 0, + *ppvDecoded, + &cbDecoded)) { + LOG((LF_SECURITY, LL_INFO10000, "Error [%#x]: CryptDecodeObject failed.\n", HRESULT_FROM_GetLastError())); + return HRESULT_FROM_GetLastError(); + } + + if (pcbDecoded) + *pcbDecoded = cbDecoded; + + return S_OK; +} + +// +// Deletes a key container given a certificate context. +// + +BOOL X509Helper::DeleteKeyContainer (PCCERT_CONTEXT pCertContext) { + CONTRACTL { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } CONTRACTL_END; + + GCX_PREEMP(); + + NewHolder<CRYPT_KEY_PROV_INFO> pProvInfo; + DWORD cbData = 0; + if (!CertGetCertificateContextProperty(pCertContext, + CERT_KEY_PROV_INFO_PROP_ID, + NULL, + &cbData)) + return TRUE; + + pProvInfo = (CRYPT_KEY_PROV_INFO*) new BYTE[cbData]; + if (!CertGetCertificateContextProperty(pCertContext, + CERT_KEY_PROV_INFO_PROP_ID, + (void*) pProvInfo.GetValue(), + &cbData)) + return FALSE; + + // First disassociate the key from the cert. + if (!CertSetCertificateContextProperty(pCertContext, + CERT_KEY_PROV_INFO_PROP_ID, + 0, + NULL)) + return FALSE; + + HCRYPTPROV hProv = NULL; + return CryptoHelper::WszCryptAcquireContext_SO_TOLERANT(&hProv, + pProvInfo->pwszContainerName, + pProvInfo->pwszProvName, + pProvInfo->dwProvType, + (pProvInfo->dwFlags & CRYPT_MACHINE_KEYSET) | CRYPT_DELETEKEYSET); +} +#endif // !FEATURE_CORECLR + +// +// Loads a certificate or store from a blob and returns that content type. +// + +DWORD X509Helper::LoadFromBlob (CERT_BLOB* pCertBlob, + __in_opt WCHAR* pwszPassword, + DWORD dwFlags, + PCCERT_CONTEXT* pCertContext, + HCERTSTORE* phCertStore, + HCRYPTMSG* phCryptMsg) { + CONTRACTL { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } CONTRACTL_END; + + DWORD dwContentType = 0; + GCX_PREEMP(); + if (!CryptQueryObject(CERT_QUERY_OBJECT_BLOB, + pCertBlob, + dwFlags, + X509_CERT_FORMAT_FLAGS, + 0, + NULL, + &dwContentType, + NULL, + phCertStore, + phCryptMsg, + (const void **)pCertContext)) { + LOG((LF_SECURITY, LL_INFO10000, "Error [%#x]: CryptQueryObject failed.\n", HRESULT_FROM_GetLastError())); + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + } + + return dwContentType; +} + +// +// Loads a certificate or store from a file and returns that content type. +// + +DWORD X509Helper::LoadFromFile (__in_z WCHAR* pwszFileName, + __in_opt WCHAR* pwszPassword, + DWORD dwFlags, + PCCERT_CONTEXT* pCertContext, + HCERTSTORE* phCertStore, + HCRYPTMSG* phCryptMsg) { + CONTRACTL { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } CONTRACTL_END; + + DWORD dwContentType = 0; + GCX_PREEMP(); + if (!CryptQueryObject(CERT_QUERY_OBJECT_FILE, + pwszFileName, + dwFlags, + X509_CERT_FORMAT_FLAGS, + 0, + NULL, + &dwContentType, + NULL, + phCertStore, + phCryptMsg, + (const void **)pCertContext)) { + LOG((LF_SECURITY, LL_INFO10000, "Error [%#x]: CryptQueryObject failed.\n", HRESULT_FROM_GetLastError())); + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + } + + return dwContentType; +} + +// +// Reads a file into memory. The caller is responsible for deleting the allocated memory. +// + +HRESULT X509Helper::ReadFileIntoMemory (LPCWSTR wszFileName, + LPBYTE* ppbBuffer, + DWORD* pdwBufLen) +{ + CONTRACTL { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } CONTRACTL_END; + + GCX_PREEMP(); + + HandleHolder hFile(WszCreateFile (wszFileName, + GENERIC_READ, + FILE_SHARE_READ, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, + NULL)); + if (hFile == INVALID_HANDLE_VALUE) + return HRESULT_FROM_GetLastError(); + + DWORD dwFileLen = SafeGetFileSize(hFile, 0); + if (dwFileLen == 0xFFFFFFFF) + return HRESULT_FROM_GetLastError(); + + _ASSERTE(ppbBuffer); + *ppbBuffer = new BYTE[dwFileLen]; + + if ((SetFilePointer(hFile, 0, NULL, FILE_BEGIN) == 0xFFFFFFFF) || + (!ReadFile(hFile, *ppbBuffer, dwFileLen, pdwBufLen, NULL))) { + delete[] *ppbBuffer; + *ppbBuffer = 0; + return HRESULT_FROM_GetLastError(); + } + + _ASSERTE(dwFileLen == *pdwBufLen); + return S_OK; +} + +// +// Returns the name for the subject or issuer. +// dwFlags : 0 for subject name or CERT_NAME_ISSUER_FLAG for issuer name. +// dwDisplayType: display type. +// +// It is the caller's responsibility to free the allocated buffer. +// + +WCHAR* COMX509Certificate::GetCertNameInfo(PCCERT_CONTEXT pCertContext, DWORD dwFlags, DWORD dwDisplayType, DWORD dwStrType) { + CONTRACTL { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } CONTRACTL_END; + + GCX_PREEMP(); + + DWORD cchCount = 0; + if ((cchCount = CertGetNameString(pCertContext, + dwDisplayType, + dwFlags, + &dwStrType, + NULL, + 0)) == 0) { + LOG((LF_SECURITY, LL_INFO10000, "Error [%#x]: CertGetNameString failed.\n", HRESULT_FROM_GetLastError())); + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + } + + NewArrayHolder<WCHAR> pwszName(new WCHAR[cchCount]); + if (!CertGetNameString(pCertContext, + dwDisplayType, + dwFlags, + &dwStrType, + pwszName, + cchCount)) { + LOG((LF_SECURITY, LL_INFO10000, "Error [%#x]: CertGetNameString failed.\n", HRESULT_FROM_GetLastError())); + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + } + + pwszName.SuppressRelease(); + return pwszName; +} + +#if !defined(FEATURE_CORECLR) +// +// Finds the first certificate with a private key in a PFX store. +// If none has a private key, we take the first certificatee in the PFX store. +// + +PCCERT_CONTEXT COMX509Certificate::FilterPFXStore (CERT_BLOB* pfxBlob, __in_z WCHAR* pwszPassword, DWORD dwFlags) { + CONTRACTL { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } CONTRACTL_END; + + GCX_PREEMP(); + + HandleCertStoreHolder hCertStore = NULL; + hCertStore = PFXImportCertStore(pfxBlob, pwszPassword, dwFlags); + if (hCertStore == NULL) { + LOG((LF_SECURITY, LL_INFO10000, "Error [%#x]: PFXImportCertStore failed.\n", HRESULT_FROM_GetLastError())); + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + } + + // now filter the PFX store + PCCERT_CONTEXT pCertContext = NULL; + PCCERT_CONTEXT pEnumContext = NULL; + DWORD cb = 0; + + // Find the first cert with private key, if none, then simply take the very first cert. + while ((pEnumContext = CertEnumCertificatesInStore(hCertStore, pEnumContext)) != NULL) { + if (CertGetCertificateContextProperty(pEnumContext, CERT_KEY_PROV_INFO_PROP_ID, NULL, &cb)) { + if (pCertContext != NULL) { + if (CertGetCertificateContextProperty(pCertContext, CERT_KEY_PROV_INFO_PROP_ID, NULL, &cb)) { + X509Helper::DeleteKeyContainer(pEnumContext); + } else { + CertFreeCertificateContext(pCertContext); + pCertContext = CertDuplicateCertificateContext(pEnumContext); + } + } else { + pCertContext = CertDuplicateCertificateContext(pEnumContext); + } + } else { + // Keep the first one. + if (pCertContext == NULL) + pCertContext = CertDuplicateCertificateContext(pEnumContext); + } + // Don't free pEnumContext here, as CertEnumCertificatesInStore will do it for us + } + + if (pCertContext == NULL) + CryptoHelper::COMPlusThrowCrypto(ERROR_INVALID_PARAMETER); + + return pCertContext; +} + +// +// Finds the signer certificate in a PKCS7 signed store. +// + +PCCERT_CONTEXT COMX509Certificate::GetSignerInPKCS7Store (HCERTSTORE hCertStore, HCRYPTMSG hCryptMsg) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + GCX_PREEMP(); + + // make sure that there is at least one signer of the certificate store + DWORD dwSigners; + DWORD cbSigners = sizeof(dwSigners); + if (!CryptMsgGetParam(hCryptMsg, + CMSG_SIGNER_COUNT_PARAM, + 0, + &dwSigners, + &cbSigners)) + { + LOG((LF_SECURITY, LL_INFO10000, "Error [%#x]: CryptMsgGetParam(CMSG_SIGNER_COUNT_PARAM) failed.\n", HRESULT_FROM_GetLastError())); + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + } + + if (dwSigners == 0) + CryptoHelper::COMPlusThrowCrypto(CRYPT_E_SIGNER_NOT_FOUND); + + // get the first signer from the store, and use that as the loaded certificate + DWORD cbData = 0; + if (!CryptMsgGetParam(hCryptMsg, + CMSG_SIGNER_INFO_PARAM, + 0, + NULL, + &cbData)) + { + LOG((LF_SECURITY, LL_INFO10000, "Error [%#x]: CryptMsgGetParam(CMS_SIGNER_INFO_PARAM) failed.\n", HRESULT_FROM_GetLastError())); + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + } + + NewArrayHolder<CMSG_SIGNER_INFO> pCmsgSigner = (CMSG_SIGNER_INFO*) new BYTE[cbData]; + if (!CryptMsgGetParam(hCryptMsg, + CMSG_SIGNER_INFO_PARAM, + 0, + pCmsgSigner, + &cbData)) + { + LOG((LF_SECURITY, LL_INFO10000, "Error [%#x]: CryptMsgGetParam failed.\n", HRESULT_FROM_GetLastError())); + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + } + + CERT_INFO CertInfo; + CertInfo.Issuer = pCmsgSigner->Issuer; + CertInfo.SerialNumber = pCmsgSigner->SerialNumber; + PCCERT_CONTEXT pCertContext = CertFindCertificateInStore(hCertStore, + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + 0, + CERT_FIND_SUBJECT_CERT, + (LPVOID) &CertInfo, + NULL); + + if (pCertContext == NULL) + { + LOG((LF_SECURITY, LL_INFO10000, "Error [%#x]: CertFindCertificateInStore failed.\n", HRESULT_FROM_GetLastError())); + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + } + + return pCertContext; +} +#endif // !FEATURE_CORECLR + +// +// FCALL methods +// + +// +// The certificate context ref count is incremented by CertDuplicateCertificateContext, +// so it is the caller's responsibility to free the context. +// + +FCIMPL2(void, COMX509Certificate::DuplicateCertContext, INT_PTR handle, SafeHandle** ppCertUNSAFE) +{ + FCALL_CONTRACT; + + SAFEHANDLE pCertSAFE = (SAFEHANDLE) *ppCertUNSAFE; + + HELPER_METHOD_FRAME_BEGIN_1(pCertSAFE); + + PCCERT_CONTEXT pCertContext = (PCCERT_CONTEXT) handle; + PCCERT_CONTEXT pCertDup = NULL; + BOOL bDelKeyContainer = FALSE; + { + GCX_PREEMP(); + if ((pCertDup = CertDuplicateCertificateContext(pCertContext)) == NULL) + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_WIN32(ERROR_INVALID_HANDLE)); + + DATA_BLOB blob; + DWORD cbData = 0; + if (CertGetCertificateContextProperty(pCertDup, + CERT_DELETE_KEYSET_PROP_ID, // This value should be defined in wincrypt.h + // as well to avoid conflicts. + (void*) &blob, + &cbData)) + bDelKeyContainer = TRUE; + } + + CERT_CTX* pCert = new CERT_CTX(pCertDup); +#if !defined(FEATURE_CORECLR) + pCert->m_fDelKeyContainer = bDelKeyContainer; +#endif // !FEATURE_CORECLR + + pCertSAFE->SetHandle((void*) pCert); + + HELPER_METHOD_FRAME_END(); +} +FCIMPLEND + +// +// Free a handle to a CERT_CTX structure. Critical finalizer method for SafeCertContextHandle. +// + +FCIMPL1(void, COMX509Certificate::FreePCertContext, INT_PTR pCertCtx) +{ + FCALL_CONTRACT; + + HELPER_METHOD_FRAME_BEGIN_0(); + + BOOL bRet = TRUE; + CERT_CTX* pCert = (CERT_CTX*) pCertCtx; + + if (pCert) + bRet = pCert->Release(); + + // Add this assert to debug failures to free resources + _ASSERTE(bRet); + + HELPER_METHOD_FRAME_END(); +} +FCIMPLEND + +// +// ASN encoded certificate as a byte array. +// + +FCIMPL1(U1Array*, COMX509Certificate::GetCertRawData, SafeHandle* pCertUNSAFE) +{ + FCALL_CONTRACT; + + SAFEHANDLE pCertSAFE = (SAFEHANDLE) pCertUNSAFE; + U1ARRAYREF pbRawData = NULL; + + HELPER_METHOD_FRAME_BEGIN_RET_2(pbRawData, pCertSAFE); + + SafeHandleHolder shh(&pCertSAFE); + CERT_CTX* pCert = CryptoHelper::DereferenceSafeHandle<CERT_CTX>(pCertSAFE); + + CryptoHelper::ByteArrayToU1ARRAYREF(pCert->m_pCtx->pbCertEncoded, + pCert->m_pCtx->cbCertEncoded, + &pbRawData); + + HELPER_METHOD_FRAME_END(); + return (U1Array*) OBJECTREFToObject(pbRawData); +} +FCIMPLEND + +// +// Returns the NotAfter field. +// + +FCIMPL2(void, COMX509Certificate::GetDateNotAfter, SafeHandle* pCertUNSAFE, FILETIME* pFileTime) +{ + FCALL_CONTRACT; + + SAFEHANDLE pCertSAFE = (SAFEHANDLE) pCertUNSAFE; + + HELPER_METHOD_FRAME_BEGIN_1(pCertSAFE); + + SafeHandleHolder shh(&pCertSAFE); + CERT_CTX* pCert = CryptoHelper::DereferenceSafeHandle<CERT_CTX>(pCertSAFE); + + pFileTime->dwLowDateTime = pCert->m_pCtx->pCertInfo->NotAfter.dwLowDateTime; + pFileTime->dwHighDateTime = pCert->m_pCtx->pCertInfo->NotAfter.dwHighDateTime; + + HELPER_METHOD_FRAME_END(); +} +FCIMPLEND + +// +// Returns the NotBefore field. +// + +FCIMPL2(void, COMX509Certificate::GetDateNotBefore, SafeHandle* pCertUNSAFE, FILETIME* pFileTime) +{ + FCALL_CONTRACT; + + SAFEHANDLE pCertSAFE = (SAFEHANDLE) pCertUNSAFE; + + HELPER_METHOD_FRAME_BEGIN_1(pCertSAFE); + + SafeHandleHolder shh(&pCertSAFE); + CERT_CTX* pCert = CryptoHelper::DereferenceSafeHandle<CERT_CTX>(pCertSAFE); + + pFileTime->dwLowDateTime = pCert->m_pCtx->pCertInfo->NotBefore.dwLowDateTime; + pFileTime->dwHighDateTime = pCert->m_pCtx->pCertInfo->NotBefore.dwHighDateTime; + + HELPER_METHOD_FRAME_END(); +} +FCIMPLEND + +// +// Issuer name as a string. +// + +FCIMPL2(StringObject*, COMX509Certificate::GetIssuerName, SafeHandle* pCertUNSAFE, CLR_BOOL fLegacyV1Mode) +{ + FCALL_CONTRACT; + + STRINGREF issuerString = NULL; + SAFEHANDLE pCertSAFE = (SAFEHANDLE) pCertUNSAFE; + + HELPER_METHOD_FRAME_BEGIN_RET_2(issuerString, pCertSAFE); + + DWORD dwStrType = CERT_X500_NAME_STR; + if (!fLegacyV1Mode) + dwStrType |= CERT_NAME_STR_REVERSE_FLAG; + + SafeHandleHolder shh(&pCertSAFE); + CERT_CTX* pCert = CryptoHelper::DereferenceSafeHandle<CERT_CTX>(pCertSAFE); + + DWORD cchCount = 0; + if ((cchCount = CertGetNameString(pCert->m_pCtx, + CERT_NAME_RDN_TYPE, + CERT_NAME_ISSUER_FLAG, + &dwStrType, + NULL, + 0)) == 0) { + LOG((LF_SECURITY, LL_INFO10000, "Error [%#x]: CertGetNameString failed.\n", HRESULT_FROM_GetLastError())); + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + } + + NewArrayHolder<WCHAR> pwszIssuerName(new WCHAR[cchCount]); + if (!CertGetNameString(pCert->m_pCtx, + CERT_NAME_RDN_TYPE, + CERT_NAME_ISSUER_FLAG, + &dwStrType, + pwszIssuerName, + cchCount)) { + LOG((LF_SECURITY, LL_INFO10000, "Error [%#x]: CertGetNameString failed.\n", HRESULT_FROM_GetLastError())); + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + } + + issuerString = StringObject::NewString(pwszIssuerName); + + HELPER_METHOD_FRAME_END(); + return (StringObject*) OBJECTREFToObject(issuerString); +} +FCIMPLEND + +// +// Returns the public key friendly name. +// + +FCIMPL1(StringObject*, COMX509Certificate::GetPublicKeyOid, SafeHandle* pCertUNSAFE) +{ + FCALL_CONTRACT; + + STRINGREF oidString = NULL; + SAFEHANDLE pCertSAFE = (SAFEHANDLE) pCertUNSAFE; + + HELPER_METHOD_FRAME_BEGIN_RET_2(oidString, pCertSAFE); + + SafeHandleHolder shh(&pCertSAFE); + CERT_CTX* pCert = CryptoHelper::DereferenceSafeHandle<CERT_CTX>(pCertSAFE); + + NewArrayHolder<WCHAR> pwszOid(CryptoHelper::AnsiToUnicode((char*) pCert->m_pCtx->pCertInfo->SubjectPublicKeyInfo.Algorithm.pszObjId)); + oidString = StringObject::NewString(pwszOid); + + HELPER_METHOD_FRAME_END(); + return (StringObject*) OBJECTREFToObject(oidString); +} +FCIMPLEND + +// +// Returns the public key ASN encoded parameters. +// + +FCIMPL1(U1Array*, COMX509Certificate::GetPublicKeyParameters, SafeHandle* pCertUNSAFE) +{ + FCALL_CONTRACT; + + U1ARRAYREF pbParameters = NULL; + SAFEHANDLE pCertSAFE = (SAFEHANDLE) pCertUNSAFE; + + HELPER_METHOD_FRAME_BEGIN_RET_2(pbParameters, pCertSAFE); + + SafeHandleHolder shh(&pCertSAFE); + CERT_CTX* pCert = CryptoHelper::DereferenceSafeHandle<CERT_CTX>(pCertSAFE); + + BOOL bInheritedParams = FALSE; + +#if defined(FEATURE_CORECLR) + // We'll support RSA-based certificates only in Telesto, not DSS. + // This is in lieu of looking up the OID info, as it would require implementing + // CryptFindOIDInfo in the PAL, along with the tables of OID infos relating to + // at least the OIDs whose Algid's are CALG_DSS_SIGN. + _ASSERTE(pCert->m_pCtx->pCertInfo->SubjectPublicKeyInfo.Algorithm.pszObjId != NULL); + if(strncmp(szOID_RSA_RSA, pCert->m_pCtx->pCertInfo->SubjectPublicKeyInfo.Algorithm.pszObjId, COUNTOF(szOID_RSA_RSA)) != 0) + CryptoHelper::COMPlusThrowCrypto(COR_E_PLATFORMNOTSUPPORTED); +#else // FEATURE_CORECLR + PCCRYPT_OID_INFO pOidInfo = NULL; + { + GCX_PREEMP(); + + // check to see if this is the most common case -- szOID_RSA_RSA. If so, we know that this is not + // a DSS cert, so we don't need to get extra information about the OID to determine that. If it + // is not, then we can first check in the public key OID group before falling back to check in + // all OID groups. + _ASSERTE(pCert->m_pCtx->pCertInfo->SubjectPublicKeyInfo.Algorithm.pszObjId != NULL); + if(strncmp(szOID_RSA_RSA, pCert->m_pCtx->pCertInfo->SubjectPublicKeyInfo.Algorithm.pszObjId, COUNTOF(szOID_RSA_RSA)) != 0) + { + pOidInfo = CryptFindOIDInfo(CRYPT_OID_INFO_OID_KEY, pCert->m_pCtx->pCertInfo->SubjectPublicKeyInfo.Algorithm.pszObjId, CRYPT_PUBKEY_ALG_OID_GROUP_ID); + if(pOidInfo == NULL) + pOidInfo = CryptFindOIDInfo(CRYPT_OID_INFO_OID_KEY, pCert->m_pCtx->pCertInfo->SubjectPublicKeyInfo.Algorithm.pszObjId, 0); + } + } + + // + // DSS certificates may not have the DSS parameters in the certificate. In this case, we try to build + // the certificate chain and propagate the parameters down from the certificate chain. + // + + if (pOidInfo != NULL && pOidInfo->Algid == CALG_DSS_SIGN) { + if ((pCert->m_pCtx->pCertInfo->SubjectPublicKeyInfo.Algorithm.Parameters.cbData == 0) || + (*pCert->m_pCtx->pCertInfo->SubjectPublicKeyInfo.Algorithm.Parameters.pbData == NULL_ASN_TAG)) { + // Build the chain to inherit parameters in the property, if not already inherited. + DWORD cbData = 0; + HRESULT hr = S_OK; + if (!CertGetCertificateContextProperty(pCert->m_pCtx, + CERT_PUBKEY_ALG_PARA_PROP_ID, + NULL, + &cbData)) { + // build the chain and ignore any errors during the chain building + HandleCertChainHolder pChainContext = NULL; + hr = X509Helper::BuildChain(pCert->m_pCtx, NULL, CERT_CHAIN_POLICY_BASE, &pChainContext); + if (SUCCEEDED(hr)) { + if (!CertGetCertificateContextProperty(pCert->m_pCtx, + CERT_PUBKEY_ALG_PARA_PROP_ID, + NULL, + &cbData)) { + hr = HRESULT_FROM_GetLastError(); + LOG((LF_SECURITY, LL_INFO10000, "Error [%#x]: CERT_PUBKEY_ALG_PARA_PROP_ID property not found.\n", hr)); + } + } + } + if (FAILED(hr)) + CryptoHelper::COMPlusThrowCrypto(hr); + + // The property exists; get it for real. + NewArrayHolder<BYTE> pbData = new BYTE[cbData]; + if (!CertGetCertificateContextProperty(pCert->m_pCtx, + CERT_PUBKEY_ALG_PARA_PROP_ID, + (void*) pbData.GetValue(), + &cbData)) + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + + CryptoHelper::ByteArrayToU1ARRAYREF(pbData, cbData, &pbParameters); + bInheritedParams = TRUE; + } + } +#endif // (FEATURE_CORECLR) else + + if (!bInheritedParams) { + CryptoHelper::ByteArrayToU1ARRAYREF(pCert->m_pCtx->pCertInfo->SubjectPublicKeyInfo.Algorithm.Parameters.pbData, + pCert->m_pCtx->pCertInfo->SubjectPublicKeyInfo.Algorithm.Parameters.cbData, + &pbParameters); + } + + HELPER_METHOD_FRAME_END(); + return (U1Array*) OBJECTREFToObject(pbParameters); +} +FCIMPLEND + +// +// Returns the public key ASN encoded value. +// + +FCIMPL1(U1Array*, COMX509Certificate::GetPublicKeyValue, SafeHandle* pCertUNSAFE) +{ + FCALL_CONTRACT; + + U1ARRAYREF pbKeyValue = NULL; + SAFEHANDLE pCertSAFE = (SAFEHANDLE) pCertUNSAFE; + + HELPER_METHOD_FRAME_BEGIN_RET_2(pbKeyValue, pCertSAFE); + + SafeHandleHolder shh(&pCertSAFE); + CERT_CTX* pCert = CryptoHelper::DereferenceSafeHandle<CERT_CTX>(pCertSAFE); + + CryptoHelper::ByteArrayToU1ARRAYREF(pCert->m_pCtx->pCertInfo->SubjectPublicKeyInfo.PublicKey.pbData, + pCert->m_pCtx->pCertInfo->SubjectPublicKeyInfo.PublicKey.cbData, + &pbKeyValue); + + HELPER_METHOD_FRAME_END(); + return (U1Array*) OBJECTREFToObject(pbKeyValue); +} +FCIMPLEND + +// +// Serial number as a byte array. +// + +FCIMPL1(U1Array*, COMX509Certificate::GetSerialNumber, SafeHandle* pCertUNSAFE) +{ + FCALL_CONTRACT; + + SAFEHANDLE pCertSAFE = (SAFEHANDLE) pCertUNSAFE; + U1ARRAYREF pbSerialNumber = NULL; + HELPER_METHOD_FRAME_BEGIN_RET_2(pbSerialNumber, pCertSAFE); + + SafeHandleHolder shh(&pCertSAFE); + CERT_CTX* pCert = CryptoHelper::DereferenceSafeHandle<CERT_CTX>(pCertSAFE); + + CryptoHelper::ByteArrayToU1ARRAYREF(pCert->m_pCtx->pCertInfo->SerialNumber.pbData, + pCert->m_pCtx->pCertInfo->SerialNumber.cbData, + &pbSerialNumber); + + HELPER_METHOD_FRAME_END(); + return (U1Array*) OBJECTREFToObject(pbSerialNumber); +} +FCIMPLEND + +// +// Subject info as a string. +// + +FCIMPL3(StringObject*, COMX509Certificate::GetSubjectInfo, SafeHandle* pCertUNSAFE, DWORD dwDisplayType, CLR_BOOL fLegacyV1Mode) +{ + FCALL_CONTRACT; + + STRINGREF subjectString = NULL; + SAFEHANDLE pCertSAFE = (SAFEHANDLE) pCertUNSAFE; + + HELPER_METHOD_FRAME_BEGIN_RET_2(subjectString, pCertSAFE); + + SafeHandleHolder shh(&pCertSAFE); + CERT_CTX* pCert = CryptoHelper::DereferenceSafeHandle<CERT_CTX>(pCertSAFE); + + DWORD dwStrType = CERT_X500_NAME_STR; + if (!fLegacyV1Mode) + dwStrType |= CERT_NAME_STR_REVERSE_FLAG; + NewArrayHolder<WCHAR> pwszSubjName(GetCertNameInfo(pCert->m_pCtx, 0, dwDisplayType, dwStrType)); + subjectString = StringObject::NewString(pwszSubjName); + + HELPER_METHOD_FRAME_END(); + return (StringObject*) OBJECTREFToObject(subjectString); +} +FCIMPLEND + +// +// Returns the thumbprint of the certificate. +// + +FCIMPL1(U1Array*, COMX509Certificate::GetThumbprint, SafeHandle* pCertUNSAFE) +{ + FCALL_CONTRACT; + + SAFEHANDLE pCertSAFE = (SAFEHANDLE) pCertUNSAFE; + U1ARRAYREF pbThumbprint = NULL; + + HELPER_METHOD_FRAME_BEGIN_RET_2(pbThumbprint, pCertSAFE); + + SafeHandleHolder shh(&pCertSAFE); + CERT_CTX* pCert = CryptoHelper::DereferenceSafeHandle<CERT_CTX>(pCertSAFE); + + DWORD dwSize = 0; + if(!CertGetCertificateContextProperty(pCert->m_pCtx, + CERT_SHA1_HASH_PROP_ID, + NULL, + &dwSize)) { + LOG((LF_SECURITY, LL_INFO10000, "Error [%#x]: CertGetCertificateContextProperty failed.\n", HRESULT_FROM_GetLastError())); + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + } + + pbThumbprint = (U1ARRAYREF) AllocatePrimitiveArray(ELEMENT_TYPE_U1, dwSize); + if(!CertGetCertificateContextProperty(pCert->m_pCtx, + CERT_SHA1_HASH_PROP_ID, + pbThumbprint->m_Array, + &dwSize)) { + LOG((LF_SECURITY, LL_INFO10000, "Error [%#x]: CertGetCertificateContextProperty failed.\n", HRESULT_FROM_GetLastError())); + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + } + + HELPER_METHOD_FRAME_END(); + return (U1Array*) OBJECTREFToObject(pbThumbprint); +} +FCIMPLEND + +// +// Opens the blob and gets its type, then loads a certificate from it. Depending on the blob type, +// the blob can contain 1 or more certificates. If more than 1, we select the most likely choice. +// + +FCIMPL5(void, COMX509Certificate::LoadCertFromBlob, U1Array* dataUNSAFE, + __in_z WCHAR* pwszPassword, DWORD dwFlags, CLR_BOOL persistKeySet, SafeHandle** ppCertUNSAFE); +{ + FCALL_CONTRACT; + + struct _gc { + U1ARRAYREF dataSAFE; + SAFEHANDLE pCertSAFE; + } gc; + + gc.dataSAFE = (U1ARRAYREF) dataUNSAFE; + gc.pCertSAFE = (SAFEHANDLE) *ppCertUNSAFE; + + HELPER_METHOD_FRAME_BEGIN_PROTECT(gc); + + NewArrayHolder<BYTE> buffer(CryptoHelper::U1ARRAYREFToByteArray(gc.dataSAFE)); + CERT_BLOB certBlob = {gc.dataSAFE->GetNumComponents(), buffer}; + HandleCertContextHolder pCertContext(NULL); + + HandleCertStoreHolder hCertStore(NULL); + HandleCryptMsgHolder hCryptMsg(NULL); + + DWORD dwContentType; dwContentType = X509Helper::LoadFromBlob(&certBlob, + pwszPassword, + X509_CERT_CONTENT_FLAGS, + &pCertContext, + &hCertStore, + &hCryptMsg); + +#if !defined(FEATURE_CORECLR) + if (dwContentType == CERT_QUERY_CONTENT_PKCS7_SIGNED + || dwContentType == CERT_QUERY_CONTENT_PKCS7_SIGNED_EMBED) { + pCertContext = GetSignerInPKCS7Store(hCertStore, hCryptMsg); + } else if (dwContentType == CERT_QUERY_CONTENT_PFX) { + pCertContext = FilterPFXStore(&certBlob, pwszPassword, dwFlags); + } +#endif // !FEATURE_CORECLR + + CERT_CTX* pCert = new CERT_CTX(pCertContext); +#if !defined(FEATURE_CORECLR) + if (dwContentType == CERT_QUERY_CONTENT_PFX) + pCert->m_fDelKeyContainer = (persistKeySet == FALSE); +#endif // !FEATURE_CORECLR + + gc.pCertSAFE->SetHandle((void*) pCert); + pCertContext.SuppressRelease(); + + HELPER_METHOD_FRAME_END(); +} +FCIMPLEND + +// +// Opens the file and gets its type, then loads a certificate from it. Depending on the blob type, +// the blob can contain 1 or more certificates. If more than 1, we select the most likely choice. +// + +FCIMPL5(void, COMX509Certificate::LoadCertFromFile, StringObject* fileNameUNSAFE, + __in_z WCHAR* pwszPassword, DWORD dwFlags, CLR_BOOL persistKeySet, SafeHandle** ppCertUNSAFE) +{ + FCALL_CONTRACT; + + struct _gc + { + STRINGREF fileNameSAFE; + SAFEHANDLE pCertSAFE; + } gc; + + gc.fileNameSAFE = (STRINGREF) fileNameUNSAFE; + gc.pCertSAFE = (SAFEHANDLE) *ppCertUNSAFE; + + HELPER_METHOD_FRAME_BEGIN_PROTECT(gc); + + NewArrayHolder<WCHAR> pwszFileName(CryptoHelper::STRINGREFToUnicode(gc.fileNameSAFE)); + + HandleCertStoreHolder hCertStore(NULL); + HandleCryptMsgHolder hCryptMsg(NULL); + + HandleCertContextHolder pCertContext(NULL); + DWORD dwContentType; dwContentType = X509Helper::LoadFromFile(pwszFileName, + pwszPassword, + X509_CERT_CONTENT_FLAGS, + &pCertContext, + &hCertStore, + &hCryptMsg); + +#if !defined(FEATURE_CORECLR) + if (dwContentType == CERT_QUERY_CONTENT_PKCS7_SIGNED || dwContentType == CERT_QUERY_CONTENT_PKCS7_SIGNED_EMBED) { + pCertContext = GetSignerInPKCS7Store(hCertStore, hCryptMsg); + } else if (dwContentType == CERT_QUERY_CONTENT_PFX) { + NewArrayHolder<BYTE> pb = NULL; + DWORD cb = 0; + // read the file + HRESULT hr = X509Helper::ReadFileIntoMemory(gc.fileNameSAFE->GetBuffer(), &pb, &cb); + if (FAILED(hr)) { + LOG((LF_SECURITY, LL_INFO10000, "Error [%#x]: ReadFileIntoMemory failed.\n", hr)); + CryptoHelper::COMPlusThrowCrypto(hr); + } + CERT_BLOB certBlob = {cb, pb}; + pCertContext = FilterPFXStore(&certBlob, pwszPassword, dwFlags); + } +#endif // !FEATURE_CORECLR + + CERT_CTX* pCert = new CERT_CTX(pCertContext); +#if !defined(FEATURE_CORECLR) + if (dwContentType == CERT_QUERY_CONTENT_PFX) + pCert->m_fDelKeyContainer = (persistKeySet == FALSE); +#endif // !FEATURE_CORECLR + + gc.pCertSAFE->SetHandle((void*) pCert); + pCertContext.SuppressRelease(); + + HELPER_METHOD_FRAME_END(); +} +FCIMPLEND + +// +// This method opens the blob and returns its type. +// + +FCIMPL1(DWORD, COMX509Certificate::QueryCertBlobType, U1Array* dataUNSAFE) +{ + FCALL_CONTRACT; + + DWORD dwContentType = 0; + U1ARRAYREF dataSAFE = (U1ARRAYREF) dataUNSAFE; + + HELPER_METHOD_FRAME_BEGIN_RET_1(dataSAFE); + + NewArrayHolder<BYTE> buffer(CryptoHelper::U1ARRAYREFToByteArray(dataSAFE)); + CERT_BLOB certBlob = {dataSAFE->GetNumComponents(), buffer}; + + { + GCX_PREEMP(); + if (!CryptQueryObject(CERT_QUERY_OBJECT_BLOB, + &certBlob, + X509_CERT_CONTENT_FLAGS, + X509_CERT_FORMAT_FLAGS, + 0, + NULL, + &dwContentType, + NULL, + NULL, + NULL, + NULL)) { + LOG((LF_SECURITY, LL_INFO10000, "Error [%#x]: CryptQueryObject failed.\n", HRESULT_FROM_GetLastError())); + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + } + } + + HELPER_METHOD_FRAME_END(); + return dwContentType; + +} +FCIMPLEND + +// +// This method opens the file and returns its type. +// + +FCIMPL1(DWORD, COMX509Certificate::QueryCertFileType, StringObject* fileNameUNSAFE) +{ + FCALL_CONTRACT; + + DWORD dwContentType = 0; + STRINGREF fileNameSAFE = (STRINGREF) fileNameUNSAFE; + + HELPER_METHOD_FRAME_BEGIN_RET_1(fileNameSAFE); + + NewArrayHolder<WCHAR> buffer(CryptoHelper::STRINGREFToUnicode(fileNameSAFE)); + + { + GCX_PREEMP(); + if (!CryptQueryObject(CERT_QUERY_OBJECT_FILE, + buffer, + X509_CERT_CONTENT_FLAGS, + X509_CERT_FORMAT_FLAGS, + 0, + NULL, + &dwContentType, + NULL, + NULL, + NULL, + NULL)) { + LOG((LF_SECURITY, LL_INFO10000, "Error [%#x]: CryptQueryObject failed.\n", HRESULT_FROM_GetLastError())); + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + } + } + + HELPER_METHOD_FRAME_END(); + return dwContentType; + +} +FCIMPLEND + +// +// FCALL methods +// + +#if !defined(FEATURE_CORECLR) +// +// Add a certificate to the store. +// Added certificates are not persisted for non-system stores. +// + +FCIMPL2(void, COMX509Store::AddCertificate, SafeHandle* hStoreUNSAFE, SafeHandle* pCertUNSAFE) +{ + FCALL_CONTRACT; + + SAFEHANDLE hStoreSAFE = (SAFEHANDLE) hStoreUNSAFE; + SAFEHANDLE pCertSAFE = (SAFEHANDLE) pCertUNSAFE; + + HELPER_METHOD_FRAME_BEGIN_2(hStoreSAFE, pCertSAFE); + + SafeHandleHolder shh1(&hStoreSAFE); + SafeHandleHolder shh2(&pCertSAFE); + + HCERTSTORE hCertStore = (HCERTSTORE) hStoreSAFE->GetHandle(); + CERT_CTX* pCert = CryptoHelper::DereferenceSafeHandle<CERT_CTX>(pCertSAFE); + + GCX_PREEMP(); + if (!CertAddCertificateLinkToStore(hCertStore, + pCert->m_pCtx, + CERT_STORE_ADD_ALWAYS, + NULL)) { + LOG((LF_SECURITY, LL_INFO10000, "Error [%#x]: CertAddCertificateContextToStore failed.\n", HRESULT_FROM_GetLastError())); + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + } + + HELPER_METHOD_FRAME_END(); +} +FCIMPLEND + +// +// Exports a memory store of certificates into a byte array. +// + +FCIMPL3(U1Array*, COMX509Store::ExportCertificatesToBlob, SafeHandle* hStoreUNSAFE, DWORD dwContentType, __in_z WCHAR* pwszPassword) +{ + FCALL_CONTRACT; + + struct _gc + { + U1ARRAYREF pbBlob; + SAFEHANDLE hStoreSAFE; + } gc; + + gc.pbBlob = NULL; + gc.hStoreSAFE = (SAFEHANDLE) hStoreUNSAFE; + + HELPER_METHOD_FRAME_BEGIN_RET_PROTECT(gc); + + SafeHandleHolder shh(&gc.hStoreSAFE); + HCERTSTORE hCertStore = (HCERTSTORE) gc.hStoreSAFE->GetHandle(); + + HandleCertContextHolder pEnumContext(NULL); + NewArrayHolder<BYTE> pbEncoded(NULL); + DWORD dwSaveAs = CERT_STORE_SAVE_AS_PKCS7; + CRYPT_DATA_BLOB DataBlob = {0, NULL}; + + switch(dwContentType) { + case X509_CERT_TYPE: + pEnumContext = CertEnumCertificatesInStore(hCertStore, pEnumContext); + if (pEnumContext.GetValue() != NULL) + CryptoHelper::ByteArrayToU1ARRAYREF(pEnumContext->pbCertEncoded, + pEnumContext->cbCertEncoded, + &gc.pbBlob); + break; + + case X509_SERIALIZED_CERT_TYPE: + pEnumContext = CertEnumCertificatesInStore(hCertStore, pEnumContext); + if (pEnumContext.GetValue() != NULL) { + DWORD cbEncoded = 0; + if (!CertSerializeCertificateStoreElement(pEnumContext, + 0, + NULL, + &cbEncoded)) { + LOG((LF_SECURITY, LL_INFO10000, "Error [%#x]: CertSerializeCertificateStoreElement failed.\n", HRESULT_FROM_GetLastError())); + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + } + pbEncoded = new BYTE[cbEncoded]; + if (!CertSerializeCertificateStoreElement(pEnumContext, + 0, + pbEncoded, + &cbEncoded)) { + LOG((LF_SECURITY, LL_INFO10000, "Error [%#x]: CertSerializeCertificateStoreElement failed.\n", HRESULT_FROM_GetLastError())); + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + } + CryptoHelper::ByteArrayToU1ARRAYREF(pbEncoded, cbEncoded, &gc.pbBlob); + } + break; + + case X509_PFX_TYPE: + { + GCX_PREEMP(); + if (!PFXExportCertStore(hCertStore, + &DataBlob, + pwszPassword, + EXPORT_PRIVATE_KEYS | REPORT_NOT_ABLE_TO_EXPORT_PRIVATE_KEY)) { + LOG((LF_SECURITY, LL_INFO10000, "Error [%#x]: PFXExportCertStorePFXExportCertStore failed.\n", HRESULT_FROM_GetLastError())); + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + } + pbEncoded = new BYTE[DataBlob.cbData]; + DataBlob.pbData = pbEncoded; + if (!PFXExportCertStore(hCertStore, + &DataBlob, + pwszPassword, + EXPORT_PRIVATE_KEYS | REPORT_NOT_ABLE_TO_EXPORT_PRIVATE_KEY)) { + LOG((LF_SECURITY, LL_INFO10000, "Error [%#x]: PFXExportCertStorePFXExportCertStore failed.\n", HRESULT_FROM_GetLastError())); + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + } + } + CryptoHelper::ByteArrayToU1ARRAYREF(DataBlob.pbData, DataBlob.cbData, &gc.pbBlob); + break; + + case X509_SERIALIZED_STORE_TYPE: + dwSaveAs = CERT_STORE_SAVE_AS_STORE; + // falling through + case X509_PKCS7_TYPE: + { + GCX_PREEMP(); + // determine the required length + if (!CertSaveStore(hCertStore, + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + dwSaveAs, + CERT_STORE_SAVE_TO_MEMORY, + (void *) &DataBlob, + 0)) { + LOG((LF_SECURITY, LL_INFO10000, "Error [%#x]: CertSaveStore failed.\n", HRESULT_FROM_GetLastError())); + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + } + pbEncoded = new BYTE[DataBlob.cbData]; + DataBlob.pbData = pbEncoded; + // now save the store to a memory blob + if (!CertSaveStore(hCertStore, + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + dwSaveAs, + CERT_STORE_SAVE_TO_MEMORY, + (void *) &DataBlob, + 0)) { + LOG((LF_SECURITY, LL_INFO10000, "Error [%#x]: CertSaveStore failed.\n", HRESULT_FROM_GetLastError())); + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + } + } + CryptoHelper::ByteArrayToU1ARRAYREF(DataBlob.pbData, DataBlob.cbData, &gc.pbBlob); + break; + + default: + COMPlusThrow(kCryptographicException, W("Cryptography_X509_InvalidContentType")); + } + + HELPER_METHOD_FRAME_END(); + return (U1Array*) OBJECTREFToObject(gc.pbBlob); +} +FCIMPLEND + +// +// Free an HCERTSTORE handle. Critical finalizer method for SafeCertStoreHandle. +// + +FCIMPL1(void, COMX509Store::FreeCertStoreContext, INT_PTR hCertStore) +{ + FCALL_CONTRACT; + + HELPER_METHOD_FRAME_BEGIN_0(); + + BOOL bRet = TRUE; + HCERTSTORE hStore = (HCERTSTORE) hCertStore; + if (hStore) + bRet = CertCloseStore(hStore, 0); + + // Add this assert to debug failures to free resources + _ASSERTE(bRet); + + HELPER_METHOD_FRAME_END(); +} +FCIMPLEND + +// +// Open a certificate store. +// + +FCIMPL4(void, COMX509Store::OpenX509Store, DWORD dwType, DWORD dwFlags, StringObject* storeNameUNSAFE, SafeHandle** phStoreUNSAFE) +{ + FCALL_CONTRACT; + + STRINGREF storeNameSAFE = (STRINGREF) storeNameUNSAFE; + SAFEHANDLE hStoreSAFE = (SAFEHANDLE) *phStoreUNSAFE; + + HELPER_METHOD_FRAME_BEGIN_2(storeNameSAFE, hStoreSAFE); + + NewArrayHolder<WCHAR> pwszStoreName(NULL); + if (storeNameSAFE != NULL) { + DWORD dwSize = storeNameSAFE->GetStringLength(); + if (dwSize > 0) + pwszStoreName = CryptoHelper::STRINGREFToUnicode(storeNameSAFE); + } + + HCERTSTORE hCertStore = NULL; + { + GCX_PREEMP(); + hCertStore = CertOpenStore((LPCSTR)(size_t)dwType, + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + NULL, + dwFlags | CERT_STORE_DEFER_CLOSE_UNTIL_LAST_FREE_FLAG, + pwszStoreName); + if (hCertStore == NULL) { + LOG((LF_SECURITY, LL_INFO10000, "Error [%#x]: CertOpenStore failed.\n", HRESULT_FROM_GetLastError())); + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + } + } + + hStoreSAFE->SetHandle(hCertStore); + + HELPER_METHOD_FRAME_END(); +} +FCIMPLEND + +// COMX509Store::RemoveCertificate is an FCALL that is not consumed by managed code +// nor is it referred to by native code. This #ifdef makes it be dead code, which it +// would otherwise not be and require CertFindCertificateInStore, which is otherwise +// not required, and thus does not need to get implemented in the PAL. + +// +// Remove a certificate from the store. +// Removed certificates are not persisted for non-system stores. +// + +FCIMPL2(void, COMX509Store::RemoveCertificate, SafeHandle* hStoreUNSAFE, SafeHandle* pCertUNSAFE) +{ + FCALL_CONTRACT; + + SAFEHANDLE hStoreSAFE = (SAFEHANDLE) hStoreUNSAFE; + SAFEHANDLE pCertSAFE = (SAFEHANDLE) pCertUNSAFE; + + HELPER_METHOD_FRAME_BEGIN_2(hStoreSAFE, pCertSAFE); + + SafeHandleHolder shh1(&hStoreSAFE); + SafeHandleHolder shh2(&pCertSAFE); + + HCERTSTORE hCertStore = (HCERTSTORE) hStoreSAFE->GetHandle(); + CERT_CTX* pCert = CryptoHelper::DereferenceSafeHandle<CERT_CTX>(pCertSAFE); + + // Find the certificate in the store. + PCCERT_CONTEXT pCert2 = NULL; + if ((pCert2 = CertFindCertificateInStore(hCertStore, + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + 0, + CERT_FIND_EXISTING, + (const void *) pCert->m_pCtx, + NULL)) == NULL) + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + + // Remove from the store. + if (!CertDeleteCertificateFromStore(pCert2)) + // CertDeleteCertificateFromStore always releases the context regardless of success + // or failure so we don't need to manually release it + CryptoHelper::COMPlusThrowCrypto(HRESULT_FROM_GetLastError()); + + HELPER_METHOD_FRAME_END(); +} +FCIMPLEND +#endif // !FEATURE_CORECLR + +#endif // FEATURE_X509 diff --git a/src/classlibnative/cryptography/x509certificate.h b/src/classlibnative/cryptography/x509certificate.h new file mode 100644 index 0000000000..5defa87bb8 --- /dev/null +++ b/src/classlibnative/cryptography/x509certificate.h @@ -0,0 +1,166 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +//+-------------------------------------------------------------------------- +// + +// +//--------------------------------------------------------------------------- +// + +// + + +#ifndef _X509CERTIFICATE_H_ +#define _X509CERTIFICATE_H_ + +#include "cryptography.h" + +#define CERT_DELETE_KEYSET_PROP_ID 101 // This value shall be defined in wincrypt.h later to avoid conflicts. + +#if defined(FEATURE_CORECLR) +#define X509_CERT_CONTENT_FLAGS CERT_QUERY_CONTENT_FLAG_CERT +#define X509_CERT_FORMAT_FLAGS CERT_QUERY_FORMAT_FLAG_BINARY +#else // !FEATURE_CORECLR +#define X509_CERT_CONTENT_FLAGS (\ + CERT_QUERY_CONTENT_FLAG_CERT | CERT_QUERY_CONTENT_FLAG_SERIALIZED_CERT | \ + CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED | CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED | \ + CERT_QUERY_CONTENT_FLAG_PFX) +#define X509_CERT_FORMAT_FLAGS CERT_QUERY_FORMAT_FLAG_ALL +#endif // (FEATURE_CORECLR) else + +FORCEINLINE void VoidCertFreeCertificateContext(PCCERT_CONTEXT pCert) { LIMITED_METHOD_CONTRACT; CertFreeCertificateContext(pCert); } +FORCEINLINE void VoidCertCloseStore(HCERTSTORE hCertStore) { LIMITED_METHOD_CONTRACT; CertCloseStore(hCertStore, 0); } +FORCEINLINE void VoidCryptMsgClose(HCRYPTMSG hCryptMsg) { LIMITED_METHOD_CONTRACT; CryptMsgClose(hCryptMsg); } +#if !defined(FEATURE_CORECLR) +FORCEINLINE void VoidCertFreeCertificateChain(PCCERT_CHAIN_CONTEXT pChainContext) { LIMITED_METHOD_CONTRACT; CertFreeCertificateChain(pChainContext); } +#endif // !FEATURE_CORECLR + +typedef Wrapper<PCCERT_CONTEXT, DoNothing<PCCERT_CONTEXT>, VoidCertFreeCertificateContext, 0> HandleCertContextHolder; +typedef Wrapper<HCERTSTORE, DoNothing<HCERTSTORE>, VoidCertCloseStore, 0> HandleCertStoreHolder; +typedef Wrapper<HCRYPTMSG, DoNothing<HCRYPTMSG>, VoidCryptMsgClose, 0> HandleCryptMsgHolder; +#if !defined(FEATURE_CORECLR) +typedef Wrapper<PCCERT_CHAIN_CONTEXT, DoNothing<PCCERT_CHAIN_CONTEXT>, VoidCertFreeCertificateChain, 0> HandleCertChainHolder; +#endif // !FEATURE_CORECLR + +class X509Helper { +public: +#if !defined(FEATURE_CORECLR) + static HRESULT BuildChain (PCCERT_CONTEXT pCertContext, HCERTSTORE hCertStore, + LPCSTR pszPolicy, PCCERT_CHAIN_CONTEXT * ppChainContext); + static HRESULT DecodeObject(LPCSTR pszStructType, LPBYTE pbEncoded, + DWORD cbEncoded, void** ppvDecoded, DWORD* pcbDecoded); + static BOOL DeleteKeyContainer (PCCERT_CONTEXT pCertContext); +#endif // !FEATURE_CORECLR + static DWORD LoadFromBlob (CERT_BLOB* pCertBlob, __in_opt WCHAR* pwszPassword, DWORD dwFlags, + PCCERT_CONTEXT* pCertContext, HCERTSTORE* phCertStore, HCRYPTMSG* phCryptMsg); + static DWORD LoadFromFile (__in_z WCHAR* pwszFileName, __in_opt WCHAR* pwszPassword, DWORD dwFlags, + PCCERT_CONTEXT* pCertContext, HCERTSTORE* phCertStore, HCRYPTMSG* phCryptMsg); + static HRESULT ReadFileIntoMemory(LPCWSTR wszFileName, LPBYTE* ppbBuffer, DWORD* pdwBufLen); +}; + +class COMX509Certificate { +private: +#if !defined(FEATURE_CORECLR) + static PCCERT_CONTEXT FilterPFXStore (CERT_BLOB* pfxBlob, __in_z WCHAR* pwszPassword, DWORD dwFlags); +#endif // !FEATURE_CORECLR + static WCHAR* GetCertNameInfo(PCCERT_CONTEXT pCertContext, DWORD dwNameType, DWORD dwDisplayType, DWORD dwStrType); +#if !defined(FEATURE_CORECLR) + static PCCERT_CONTEXT GetSignerInPKCS7Store (HCERTSTORE hCertStore, HCRYPTMSG hCryptMsg); +#endif // !FEATURE_CORECLR + +public: + static FCDECL2(void, DuplicateCertContext, INT_PTR handle, SafeHandle** ppCertUNSAFE); + static FCDECL1(void, FreePCertContext, INT_PTR pCertCtx); + static FCDECL1(U1Array*, GetCertRawData, SafeHandle* pCertUNSAFE); + static FCDECL2(void, GetDateNotAfter, SafeHandle* pCertUNSAFE, FILETIME* pFileTime); + static FCDECL2(void, GetDateNotBefore, SafeHandle* pCertUNSAFE, FILETIME* pFileTime); + static FCDECL2(StringObject*, GetIssuerName, SafeHandle* pCertUNSAFE, CLR_BOOL fLegacyV1Mode); + static FCDECL1(StringObject*, GetPublicKeyOid, SafeHandle* pCertUNSAFE); + static FCDECL1(U1Array*, GetPublicKeyParameters, SafeHandle* pCertUNSAFE); + static FCDECL1(U1Array*, GetPublicKeyValue, SafeHandle* pCertUNSAFE); + static FCDECL1(U1Array*, GetSerialNumber, SafeHandle* pCertUNSAFE); + static FCDECL3(StringObject*, GetSubjectInfo, SafeHandle* pCertUNSAFE, DWORD dwDisplayType, CLR_BOOL fLegacyV1Mode); + static FCDECL1(U1Array*, GetThumbprint, SafeHandle* pCertUNSAFE); + static FCDECL5(void, LoadCertFromBlob, U1Array* dataUNSAFE, __in_z WCHAR* pwszPassword, DWORD dwFlags, CLR_BOOL persistKeySet, SafeHandle** ppCertUNSAFE); + static FCDECL5(void, LoadCertFromFile, StringObject* fileNameUNSAFE, __in_z WCHAR* pwszPassword, DWORD dwFlags, CLR_BOOL persistKeySet, SafeHandle** ppCertUNSAFE); + static FCDECL1(DWORD, QueryCertBlobType, U1Array* dataUNSAFE); + static FCDECL1(DWORD, QueryCertFileType, StringObject* fileNameUNSAFE); +}; + +#define X509_STORE_CONTENT_FLAGS (\ + CERT_QUERY_CONTENT_FLAG_CERT | CERT_QUERY_CONTENT_FLAG_SERIALIZED_CERT | \ + CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED | CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED | \ + CERT_QUERY_CONTENT_FLAG_PKCS7_UNSIGNED | CERT_QUERY_CONTENT_FLAG_PFX | \ + CERT_QUERY_CONTENT_FLAG_SERIALIZED_STORE) + +#define NULL_ASN_TAG 0x05 + +// Keep in sync with System.Security.Cryptography.X509Certificates.X509ContentType +enum X509_ASSERTION_CONTENT_TYPE { + UNKNOWN_TYPE = 0x00, + X509_CERT_TYPE = 0x01, + X509_SERIALIZED_CERT_TYPE = 0x02, + X509_PFX_TYPE = 0x03, + X509_SERIALIZED_STORE_TYPE = 0x04, + X509_PKCS7_TYPE = 0x05, + X509_AUTHENTICODE_TYPE = 0x06 +}; + +// We need to define this unmanaged memory structure to hold +// all the information relevant to the cert context in order to guarantee +// critical finalization of the resources +typedef struct CERT_CTX { +public: + PCCERT_CONTEXT m_pCtx; +#if !defined(FEATURE_CORECLR) + BOOL m_fDelKeyContainer; +#endif // !FEATURE_CORECLR + + CERT_CTX(PCCERT_CONTEXT pCertContext) : + m_pCtx(pCertContext) +#if !defined(FEATURE_CORECLR) + ,m_fDelKeyContainer(FALSE) +#endif // !FEATURE_CORECLR + { + LIMITED_METHOD_CONTRACT; + } + + // This method should not be called twice. + BOOL Release () { + WRAPPER_NO_CONTRACT; +#if !defined(FEATURE_CORECLR) + if (m_fDelKeyContainer) + X509Helper::DeleteKeyContainer(m_pCtx); +#endif // !FEATURE_CORECLR + + // We need to free the cert context. + if (m_pCtx != NULL) + if (!CertFreeCertificateContext(m_pCtx)) + return FALSE; + + m_pCtx = 0; + delete this; + return TRUE; + } + +} CERT_CTX; + +#ifndef FEATURE_CORECLR +class COMX509Store { +public: + static FCDECL2(void, AddCertificate, SafeHandle* hStoreUNSAFE, SafeHandle* pCertUNSAFE); + static FCDECL3(U1Array*, ExportCertificatesToBlob, SafeHandle* hStoreUNSAFE, DWORD dwContentType, __in_z WCHAR* pwszPassword); + static FCDECL1(void, FreeCertStoreContext, INT_PTR hCertStore); + static FCDECL4(void, OpenX509Store, DWORD dwType, DWORD dwFlags, StringObject* storeNameUNSAFE, SafeHandle** phStoreUNSAFE); + // RemoveCertificate is an FCALL that is not consumed by managed code + // nor is it referred to by native code. This #ifdef makes it be dead code, which it + // would otherwise not be and require CertFindCertificateInStore, which is otherwise + // not required, and thus does not need to get implemented in the PAL. + static FCDECL2(void, RemoveCertificate, SafeHandle* hStoreUNSAFE, SafeHandle* pCertUNSAFE); +}; +#endif // !FEATURE_CORECLR + +#endif // !_X509CERTIFICATE_H_ |