/* File: crypt32.cpp Title: CryptProtect APIs Author: Matt Thomlinson Date: 8/2/97 The CryptProtect API set allows an application to secure user data for online and offline storage. While the well-known problem of data storage is left to the calling application, this solves the relatively unsolved problem of how to cryptographically derive strong keys for storing the data. These APIs are initially available on NT5 only. Very little checking is done at this level to validate the caller. We believe that problem should be solved at a different level -- since all other system security is granular to the user, it is difficult to create a feature that provides something more granular. Instead, any process running under the logged-in user has the ability to decrypt any and all items it can retrieve. Callers should note that while items are being processed, UI may be spawned to notify the user. For user confirmation, the NT secure attention sequence is used to garner the wishes of the user. This behavior is set by the caller during protection. */ #include #pragma hdrstop #include "msaudite.h" #include "crypt.h" #define ALGID_DERIVEKEY_HASH CALG_SHA1 // doesn't change // USEC: can be as long as we want, since we castrate generated key later #define KEY_DERIVATION_BUFSIZE (128/8) #define DEFAULT_BLOCKSIZE_OVERRUN (128/8) // allow block ciphers to process up to a 128 bits at a time #define MS_BASE_CRYPTPROTECT_VERSION 0x01 DWORD WINAPI SPCryptProtect( PVOID pvContext, // server context PBYTE* ppbOut, // out encr data DWORD* pcbOut, // out encr cb PBYTE pbIn, // in ptxt data DWORD cbIn, // in ptxt cb LPCWSTR szDataDescr, // in PBYTE pbOptionalEntropy, // OPTIONAL DWORD cbOptionalEntropy, PSSCRYPTPROTECTDATA_PROMPTSTRUCT psPrompt, // OPTIONAL prompting struct DWORD dwFlags, BYTE* pbOptionalPassword, DWORD cbOptionalPassword ) { DWORD dwRet; LPWSTR szUser = NULL; PBYTE pbWritePtr = NULL; GUID guidMK; WCHAR wszMKGuidString[MAX_GUID_SZ_CHARS]; HCRYPTPROV hVerifyProv; HCRYPTHASH hHash = NULL; HCRYPTKEY hKey = NULL; PBYTE pbCrypt = NULL; DWORD cbCrypt = 0; DWORD cbEncrypted; // count of bytes to encrypt PBYTE pbEncrSalt = NULL; DWORD cbEncrSalt = 0; BYTE rgbPwdBuf[A_SHA_DIGEST_LEN]; DWORD cbMACSize = A_SHA_DIGEST_LEN; BYTE rgbEncrKey[KEY_DERIVATION_BUFSIZE]; BYTE rgbMACKey[KEY_DERIVATION_BUFSIZE]; LPBYTE pbMasterKey = NULL; DWORD cbMasterKey = 0; PBYTE pStreamFlagPtr; DWORD dwProtectionFlags = 0; DWORD dwDefaultCryptProvType = 0; DWORD dwAlgID_Encr_Alg = 0; DWORD dwAlgID_Encr_Alg_KeySize = 0; DWORD dwAlgID_MAC_Alg = 0; DWORD dwAlgID_MAC_Alg_KeySize = 0; PCRYPT_SERVER_CONTEXT pServerContext = (PCRYPT_SERVER_CONTEXT)pvContext; #if DBG D_DebugLog((DEB_TRACE_API,"SPCryptProtect 0x%x called\n", pServerContext)); if(pServerContext) { if(pServerContext->cbSize == sizeof(CRYPT_SERVER_CONTEXT)) { D_DebugLog((DEB_TRACE_API, " pServerContext->hBinding:%d\n", pServerContext->hBinding)); D_DebugLog((DEB_TRACE_API, " pServerContext->fOverrideToLocalSystem:%d\n", pServerContext->fOverrideToLocalSystem)); D_DebugLog((DEB_TRACE_API, " pServerContext->fImpersonating:%d\n", pServerContext->fImpersonating)); D_DebugLog((DEB_TRACE_API, " pServerContext->hToken:%d\n", pServerContext->hToken)); D_DebugLog((DEB_TRACE_API, " pServerContext->szUserStorageArea:%ls\n", pServerContext->szUserStorageArea)); D_DebugLog((DEB_TRACE_API, " pServerContext->WellKnownAccount:%d\n", pServerContext->WellKnownAccount)); } } D_DebugLog((DEB_TRACE_API, " pbInput:0x%x\n", pbIn)); D_DebugLog((DEB_TRACE_API, " pszDataDescr:%ls\n", szDataDescr)); D_DebugLog((DEB_TRACE_API, " pbOptionalEntropy:0x%x\n", pbOptionalEntropy)); D_DPAPIDumpHexData(DEB_TRACE_BUFFERS, " ", pbOptionalEntropy, cbOptionalEntropy); D_DebugLog((DEB_TRACE_API, " dwFlags:0x%x\n", dwFlags)); D_DebugLog((DEB_TRACE_API, " pbOptionalPassword:0x%x\n", pbOptionalPassword)); D_DPAPIDumpHexData(DEB_TRACE_BUFFERS, " ", pbOptionalPassword, cbOptionalPassword); #endif ZeroMemory(&guidMK, sizeof(guidMK)); wszMKGuidString[0] = 0; GetDefaultAlgInfo(&dwDefaultCryptProvType, &dwAlgID_Encr_Alg, &dwAlgID_Encr_Alg_KeySize, &dwAlgID_MAC_Alg, &dwAlgID_MAC_Alg_KeySize); if( dwFlags & CRYPTPROTECT_LOCAL_MACHINE ) { BOOL fOverrideToLocalSystem = TRUE; CPSOverrideToLocalSystem( pvContext, &fOverrideToLocalSystem, NULL // don't care what previous value was ); dwProtectionFlags |= CRYPTPROTECT_LOCAL_MACHINE; } if( dwFlags & CRYPTPROTECT_CRED_SYNC ) { if(dwFlags != CRYPTPROTECT_CRED_SYNC) { // If the user is reencrypting master keys, then no other flags // may be specified. Lets do one thing at a time, shall we? D_DebugLog((DEB_ERROR, "SPCryptProtect: Invalid flags 0x%x\n", dwFlags)); return ERROR_INVALID_PARAMETER; } dwRet = CPSImpersonateClient( pvContext ); if( dwRet == ERROR_SUCCESS ) { dwRet = InitiateSynchronizeMasterKeys( pvContext ); CPSRevertToSelf( pvContext ); } return dwRet; } if( dwFlags & CRYPTPROTECT_CRED_REGENERATE ) { if(dwFlags != CRYPTPROTECT_CRED_REGENERATE) { // If the user is reencrypting master keys, then no other flags // may be specified. Lets do one thing at a time, shall we? D_DebugLog((DEB_ERROR, "SPCryptProtect: Invalid flags 0x%x\n", dwFlags)); return ERROR_INVALID_PARAMETER; } dwRet = DpapiUpdateLsaSecret( pvContext ); return dwRet; } // // include additional flags // dwProtectionFlags |= (dwFlags & (CRYPTPROTECT_SYSTEM | CRYPTPROTECT_AUDIT)); // else no override; get provider by algs alone if (NULL == (hVerifyProv = GetCryptProviderHandle( dwDefaultCryptProvType, dwAlgID_Encr_Alg, &dwAlgID_Encr_Alg_KeySize, dwAlgID_MAC_Alg, &dwAlgID_MAC_Alg_KeySize)) ) { dwRet = GetLastError(); goto Ret; } dwRet = GetSpecifiedMasterKey( pvContext, &guidMK, &pbMasterKey, &cbMasterKey, FALSE // we don't know what master key we want to use - use preferred ); if(dwRet != ERROR_SUCCESS) goto Ret; MyGuidToStringW(&guidMK, wszMKGuidString); // // hash pbMasterKey to get rgbPwdBuf // FMyPrimitiveSHA( pbMasterKey, cbMasterKey, rgbPwdBuf ); // derive encr key { if (!RtlGenRandom( rgbEncrKey, sizeof(rgbEncrKey))) { dwRet = GetLastError(); goto Ret; } #if DBG // Leave here as regression check CheckMACInterop(rgbPwdBuf, sizeof(rgbPwdBuf), rgbEncrKey, sizeof(rgbEncrKey), hVerifyProv, ALGID_DERIVEKEY_HASH); #endif if (!FMyPrimitiveCryptHMAC( rgbPwdBuf, sizeof(rgbPwdBuf), rgbEncrKey, sizeof(rgbEncrKey), hVerifyProv, ALGID_DERIVEKEY_HASH, &hHash)) { dwRet = GetLastError(); goto Ret; } // add password if exists if (NULL != pbOptionalEntropy) { if (!CryptHashData( hHash, pbOptionalEntropy, cbOptionalEntropy, 0)) { dwRet = GetLastError(); goto Ret; } } // add prompted UI based password if exists. // will eventually come from SAS // if ( NULL != pbOptionalPassword && cbOptionalPassword ) { if (!CryptHashData( hHash, pbOptionalPassword, cbOptionalPassword, 0)) { dwRet = GetLastError(); goto Ret; } } if (!CryptDeriveKey( hVerifyProv, dwAlgID_Encr_Alg, hHash, CRYPT_CREATE_SALT, &hKey)) { dwRet = GetLastError(); goto Ret; } CryptDestroyHash(hHash); hHash = 0; // USEC -- (US Export Controls) if (ERROR_SUCCESS != (dwRet = GetSaltForExportControl( hVerifyProv, hKey, &pbEncrSalt, &cbEncrSalt)) ) goto Ret; } // derive MAC key { if (!RtlGenRandom( rgbMACKey, sizeof(rgbMACKey))) { dwRet = GetLastError(); goto Ret; } if (!FMyPrimitiveCryptHMAC( rgbPwdBuf, sizeof(rgbPwdBuf), rgbMACKey, sizeof(rgbMACKey), hVerifyProv, dwAlgID_MAC_Alg, &hHash)) { dwRet = GetLastError(); goto Ret; } // add password if exists if (NULL != pbOptionalEntropy) { if (!CryptHashData( hHash, pbOptionalEntropy, cbOptionalEntropy, 0)) { dwRet = GetLastError(); goto Ret; } } // add prompted UI based password if exists. // will eventually come from SAS // if ( NULL != pbOptionalPassword && cbOptionalPassword ) { if (!CryptHashData( hHash, pbOptionalPassword, cbOptionalPassword, 0)) { dwRet = GetLastError(); goto Ret; } } // USEC -- (US Export Controls) // Does not apply to MAC -- use strong key } // Decrypt the input buffer. if((dwFlags & CRYPTPROTECT_IN_PROCESS) == 0) { DWORD cbPadding; if((cbIn < RTL_ENCRYPT_MEMORY_SIZE) || (cbIn % RTL_ENCRYPT_MEMORY_SIZE)) { dwRet = ERROR_INVALID_DATA; goto Ret; } dwRet = RpcImpersonateClient(((PCRYPT_SERVER_CONTEXT)pvContext)->hBinding); if(dwRet == ERROR_SUCCESS) { NTSTATUS Status; Status = RtlDecryptMemory(pbIn, cbIn, RTL_ENCRYPT_OPTION_SAME_LOGON); if(!NT_SUCCESS(Status)) { dwRet = ERROR_DECRYPTION_FAILED; } RevertToSelf(); } // Remove padding if(dwRet == ERROR_SUCCESS) { cbPadding = pbIn[cbIn - 1]; if((cbPadding > 0) && (cbPadding <= cbIn) && (cbPadding <= RTL_ENCRYPT_MEMORY_SIZE)) { cbIn -= cbPadding; } else { dwRet = ERROR_INVALID_DATA; } } } // hash & encrypt the data cbCrypt = cbIn + DEFAULT_BLOCKSIZE_OVERRUN; if (NULL == (pbCrypt = (PBYTE)SSAlloc(cbCrypt)) ) { dwRet = ERROR_OUTOFMEMORY; goto Ret; } CopyMemory(pbCrypt, pbIn, cbIn); // now write data out: size? *pcbOut = sizeof(GUID) + 2*sizeof(DWORD); // dwVer + guidMK + dwFlags *pcbOut += sizeof(DWORD) + WSZ_BYTECOUNT(szDataDescr); // data description *pcbOut += 3*sizeof(DWORD) + sizeof(rgbEncrKey); // EncrAlgID + AlgIDKeySize + cbEncrKey + EncrKey *pcbOut += sizeof(DWORD) + cbEncrSalt; // Encr salt *pcbOut += 3*sizeof(DWORD) + sizeof(rgbMACKey); // MACAlgID + AlgIDKeySize + cbMACKey + MACKey *pcbOut += sizeof(DWORD) + cbCrypt; // size + encrypted data (guess) *pcbOut += sizeof(DWORD) + A_SHA_DIGEST_LEN; // MAC + MACsize *ppbOut = (PBYTE)SSAlloc(*pcbOut); if( *ppbOut == NULL ) { dwRet = ERROR_OUTOFMEMORY; goto Ret; } ZeroMemory( *ppbOut, *pcbOut ); pbWritePtr = *ppbOut; //////////////////////////////////////////////////////////////////// // FYI: Data format // ( Version | guidMKid | dwFlags | // cbDataDescr | szDataDescr | // // dwEncrAlgID | dwEncrAlgKeySize | // cbEncrKey | EncrKey | // cbEncrSalt | EncrSalt | // // dwMACAlgID | dwMACAlgKeySize | // cbMACKey | MACKey | // // cbEncrData | EncrData | // cbMAC | MAC ) // // NOTE: entire buffer from Version through EncrData is included in MAC //////////////////////////////////////////////////////////////////// // dwVersion *(DWORD UNALIGNED *)pbWritePtr = MS_BASE_CRYPTPROTECT_VERSION; pbWritePtr += sizeof(DWORD); // guid MKid CopyMemory(pbWritePtr, &guidMK, sizeof(GUID)); pbWritePtr += sizeof(GUID); // dwFlags -- written out later via pStreamFlagPtr pStreamFlagPtr = pbWritePtr; pbWritePtr += sizeof(DWORD); // cbDataDescr *(DWORD UNALIGNED *)pbWritePtr = WSZ_BYTECOUNT(szDataDescr); pbWritePtr += sizeof(DWORD); // szDataDescr CopyMemory(pbWritePtr, szDataDescr, WSZ_BYTECOUNT(szDataDescr)); pbWritePtr += WSZ_BYTECOUNT(szDataDescr); // dwEncrAlgID *(DWORD UNALIGNED *)pbWritePtr = dwAlgID_Encr_Alg; pbWritePtr += sizeof(DWORD); // dwEncrAlgKeySize *(DWORD UNALIGNED *)pbWritePtr = dwAlgID_Encr_Alg_KeySize; pbWritePtr += sizeof(DWORD); // cb EncrKey *(DWORD UNALIGNED *)pbWritePtr = sizeof(rgbEncrKey); pbWritePtr += sizeof(DWORD); // Encr Key CopyMemory(pbWritePtr, rgbEncrKey, sizeof(rgbEncrKey)); pbWritePtr += sizeof(rgbEncrKey); // cb Encr salt *(DWORD UNALIGNED *)pbWritePtr = cbEncrSalt; pbWritePtr += sizeof(DWORD); // Encr salt CopyMemory(pbWritePtr, pbEncrSalt, cbEncrSalt); pbWritePtr += cbEncrSalt; // dwMACAlgID *(DWORD UNALIGNED *)pbWritePtr = dwAlgID_MAC_Alg; pbWritePtr += sizeof(DWORD); // dwMACAlgKeySize *(DWORD UNALIGNED *)pbWritePtr = dwAlgID_MAC_Alg_KeySize; pbWritePtr += sizeof(DWORD); // cb MAC key *(DWORD UNALIGNED *)pbWritePtr = sizeof(rgbMACKey); pbWritePtr += sizeof(DWORD); // MAC key CopyMemory(pbWritePtr, rgbMACKey, sizeof(rgbMACKey)); pbWritePtr += sizeof(rgbMACKey); // USER GATING: only consider if prompt structure specified if ( psPrompt ) { if (psPrompt->cbSize != sizeof(SSCRYPTPROTECTDATA_PROMPTSTRUCT)) { dwRet = ERROR_INVALID_PARAMETER; goto Ret; } if ((psPrompt->dwPromptFlags & ~(CRYPTPROTECT_PROMPT_ON_PROTECT | CRYPTPROTECT_PROMPT_ON_UNPROTECT | CRYPTPROTECT_PROMPT_STRONG | CRYPTPROTECT_PROMPT_REQUIRE_STRONG)) != 0) { dwRet = ERROR_INVALID_PARAMETER; goto Ret; } if ((psPrompt->dwPromptFlags & CRYPTPROTECT_PROMPT_STRONG) && (pbOptionalPassword == NULL || cbOptionalPassword == 0) ) { dwRet = ERROR_INVALID_PARAMETER; goto Ret; } // UI: only if requested by PROMPT_ON_PROTECT if ( psPrompt->dwPromptFlags & CRYPTPROTECT_PROMPT_ON_PROTECT ) { if ( dwFlags & CRYPTPROTECT_UI_FORBIDDEN ) { dwRet = ERROR_PASSWORD_RESTRICTION; goto Ret; } // UI handled outside service until SAS support added. dwProtectionFlags |= CRYPTPROTECT_PROMPT_ON_PROTECT; } if ( psPrompt->dwPromptFlags & CRYPTPROTECT_PROMPT_ON_UNPROTECT ) dwProtectionFlags |= CRYPTPROTECT_PROMPT_ON_UNPROTECT; if ( psPrompt->dwPromptFlags & CRYPTPROTECT_PROMPT_STRONG ) dwProtectionFlags |= CRYPTPROTECT_PROMPT_STRONG; if ( psPrompt->dwPromptFlags & CRYPTPROTECT_PROMPT_REQUIRE_STRONG ) dwProtectionFlags |= CRYPTPROTECT_PROMPT_REQUIRE_STRONG; } // update stored protection flags in the stream *(DWORD UNALIGNED *)pStreamFlagPtr = dwProtectionFlags; // dansimon recommends that MAC be on encrypted data, such that the // MAC has no possibility of revealing info about the plaintext. cbEncrypted = cbIn; // then Encrypt pbIn if (!CryptEncrypt( hKey, NULL, TRUE, 0, pbCrypt, &cbEncrypted, cbCrypt)) { dwRet = GetLastError(); goto Ret; } // now cbCrypt is size of encrypted data cbCrypt = cbEncrypted; // Encrdata: len *(DWORD UNALIGNED *)pbWritePtr = cbCrypt; pbWritePtr += sizeof(DWORD); // Encrdata: val CopyMemory(pbWritePtr, pbCrypt, cbCrypt); pbWritePtr += cbCrypt; { // dansimon recommends that MAC be on encrypted data, such that the // MAC has no possibility of revealing info about the plaintext. // MAC from start to here if (!CryptHashData( hHash, *ppbOut, (DWORD)(pbWritePtr - *ppbOut), 0)) { dwRet = GetLastError(); goto Ret; } // cbMAC pbWritePtr += sizeof(DWORD); // skip cb write; retreive hash val first // MAC if (!CryptGetHashParam( hHash, HP_HASHVAL, pbWritePtr, &cbMACSize, 0)) { dwRet = GetLastError(); goto Ret; } // rewrite cbMAC before MAC *(DWORD UNALIGNED *)(pbWritePtr - sizeof(DWORD)) = cbMACSize; // make sure we didn't overstep SS_ASSERT(cbMACSize <= A_SHA_DIGEST_LEN); pbWritePtr += cbMACSize; } // assert allocation size was sufficient SS_ASSERT(*ppbOut + *pcbOut >= pbWritePtr); // reset output size *pcbOut = (DWORD)(pbWritePtr - *ppbOut); dwRet = ERROR_SUCCESS; Ret: if((dwProtectionFlags & CRYPTPROTECT_AUDIT) || (ERROR_SUCCESS != dwRet)) { WCHAR wszCryptoAlgs[2*MAX_STRING_ALGID_LENGTH + 2]; DWORD i; i = AlgIDToString(wszCryptoAlgs, dwAlgID_Encr_Alg, dwAlgID_Encr_Alg_KeySize); wszCryptoAlgs[i++]= L','; wszCryptoAlgs[i++]= L' '; AlgIDToString(&wszCryptoAlgs[i], dwAlgID_MAC_Alg, dwAlgID_MAC_Alg_KeySize); CPSAudit(pServerContext->hToken, SE_AUDITID_DPAPI_PROTECT, wszMKGuidString, // Key Identifier szDataDescr, // Data Description dwProtectionFlags, // Protected Data Flags wszCryptoAlgs, // Protection Algorithms dwRet); // Failure Reason } RtlSecureZeroMemory(rgbPwdBuf, sizeof(rgbPwdBuf)); RtlSecureZeroMemory(rgbEncrKey, sizeof(rgbEncrKey)); if(pbMasterKey) { RtlSecureZeroMemory(pbMasterKey, cbMasterKey); SSFree(pbMasterKey); } if (hKey) CryptDestroyKey(hKey); if (hHash) CryptDestroyHash(hHash); if (pbCrypt) SSFree(pbCrypt); if (pbEncrSalt) SSFree(pbEncrSalt); D_DebugLog((DEB_TRACE_API, "SPCryptProtect returned 0x%x\n", dwRet)); return dwRet; } DWORD WINAPI SPCryptUnprotect( PVOID pvContext, // server context PBYTE* ppbOut, // out ptxt data DWORD* pcbOut, // out ptxt cb PBYTE pbIn, // in encr data DWORD cbIn, // in encr cb LPWSTR* ppszDataDescr, // OPTIONAL PBYTE pbOptionalEntropy, // OPTIONAL DWORD cbOptionalEntropy, PSSCRYPTPROTECTDATA_PROMPTSTRUCT psPrompt, // OPTIONAL, prompting struct DWORD dwFlags, BYTE* pbOptionalPassword, DWORD cbOptionalPassword ) { DWORD dwRet; PBYTE pbReadPtr = pbIn; LPWSTR szUser = NULL; GUID guidMK; WCHAR wszMKGuidString[MAX_GUID_SZ_CHARS]; HCRYPTPROV hVerifyProv; HCRYPTKEY hKey = NULL; HCRYPTHASH hHash = NULL; BYTE rgbEncrKey[KEY_DERIVATION_BUFSIZE]; BYTE rgbMACKey[KEY_DERIVATION_BUFSIZE]; DWORD cbEncr; PBYTE pbEncrSalt = NULL; DWORD cbEncrSalt = 0; DWORD dwEncrAlgID = 0; DWORD dwEncrAlgKeySize = 0; DWORD dwMACAlgID = 0; DWORD dwMACAlgKeySize =0; DWORD cbEncrKeysize, cbMACKeysize; DWORD dwProtectionFlags = 0; BYTE rgbPwdBuf[A_SHA_DIGEST_LEN]; DWORD cbDataDescr; LPWSTR szDataDescr = NULL; LPBYTE pbMasterKey = NULL; DWORD cbMasterKey = 0; DWORD cbPlaintext; #if DBG D_DebugLog((DEB_TRACE_API,"SPCryptUnprotect 0x%x called\n", pvContext)); if(pvContext) { PCRYPT_SERVER_CONTEXT pServerContext = (PCRYPT_SERVER_CONTEXT)pvContext; if(pServerContext->cbSize == sizeof(CRYPT_SERVER_CONTEXT)) { D_DebugLog((DEB_TRACE_API, " pServerContext->hBinding:%d\n", pServerContext->hBinding)); D_DebugLog((DEB_TRACE_API, " pServerContext->fOverrideToLocalSystem:%d\n", pServerContext->fOverrideToLocalSystem)); D_DebugLog((DEB_TRACE_API, " pServerContext->fImpersonating:%d\n", pServerContext->fImpersonating)); D_DebugLog((DEB_TRACE_API, " pServerContext->hToken:%d\n", pServerContext->hToken)); D_DebugLog((DEB_TRACE_API, " pServerContext->szUserStorageArea:%ls\n", pServerContext->szUserStorageArea)); D_DebugLog((DEB_TRACE_API, " pServerContext->WellKnownAccount:%d\n", pServerContext->WellKnownAccount)); } } D_DebugLog((DEB_TRACE_API, " pbOptionalEntropy:0x%x\n", pbOptionalEntropy)); D_DPAPIDumpHexData(DEB_TRACE_BUFFERS, " ", pbOptionalEntropy, cbOptionalEntropy); D_DebugLog((DEB_TRACE_API, " dwFlags:0x%x\n", dwFlags)); D_DebugLog((DEB_TRACE_API, " pbOptionalPassword:0x%x\n", pbOptionalPassword)); D_DPAPIDumpHexData(DEB_TRACE_BUFFERS, " ", pbOptionalPassword, cbOptionalPassword); #endif *ppbOut = NULL; *pcbOut = 0; ZeroMemory(&guidMK, sizeof(guidMK)); ZeroMemory(rgbPwdBuf, sizeof(rgbPwdBuf)); DWORD dwDefaultCryptProvType = 0; DWORD dwAlgID_Encr_Alg = 0; DWORD dwAlgID_Encr_Alg_KeySize = 0; DWORD dwAlgID_MAC_Alg = 0; DWORD dwAlgID_MAC_Alg_KeySize = 0; // // If audit is turned on and the following vars are not set with the values // passed in, then the following would appear in the audit, // "Unknown 0x0 - 0" // dwEncrAlgID = 0; dwEncrAlgKeySize = 0; dwMACAlgID = 0; dwMACAlgKeySize = 0; wszMKGuidString[0] = 0; GetDefaultAlgInfo(&dwDefaultCryptProvType, &dwAlgID_Encr_Alg, &dwAlgID_Encr_Alg_KeySize, &dwAlgID_MAC_Alg, &dwAlgID_MAC_Alg_KeySize); //////////////////////////////////////////////////////////////////// // FYI: Data format // ( Version | guidMKid | dwFlags | // cbDataDescr | szDataDescr | // // dwEncrAlgID | dwEncrAlgKeySize | // cbEncrKey | EncrKey | // cbEncrSalt | EncrSalt | // // dwMACAlgID | dwMACAlgKeySize | // cbMACKey | MACKey | // // cbEncrData | EncrData | // cbMAC | MAC ) // // NOTE: entire buffer from ProvHeader through EncrData is included in MAC //////////////////////////////////////////////////////////////////// // Check for minimum input buffer size. if(cbIn < sizeof(DWORD) + // Version sizeof(GUID) + // guidMKid sizeof(DWORD) + // dwFlags sizeof(DWORD)) // cbDataDescr { dwRet = ERROR_INVALID_DATA; goto Ret; } // version check if (*(DWORD UNALIGNED *)pbReadPtr != MS_BASE_CRYPTPROTECT_VERSION) { dwRet = ERROR_INVALID_DATA; goto Ret; } pbReadPtr += sizeof(DWORD); cbIn -= sizeof(DWORD); // guidMKid CopyMemory(&guidMK, pbReadPtr, sizeof(GUID)); pbReadPtr += sizeof(GUID); cbIn -= sizeof(GUID); MyGuidToStringW(&guidMK, wszMKGuidString); D_DebugLog((DEB_TRACE, "Master key GUID:%ls\n", wszMKGuidString)); // dwFlags during protection dwProtectionFlags = *(DWORD UNALIGNED *)pbReadPtr; pbReadPtr += sizeof(DWORD); cbIn -= sizeof(DWORD); D_DebugLog((DEB_TRACE, "Protection flags:0x%x\n", dwProtectionFlags)); // // evaluate original CryptProtectData dwFlags to determine if // CRYPT_LOCAL_MACHINE set. // if( dwProtectionFlags & CRYPTPROTECT_LOCAL_MACHINE ) { BOOL fOverrideToLocalSystem = TRUE; CPSOverrideToLocalSystem( pvContext, &fOverrideToLocalSystem, NULL // don't care what previous value was ); } if((dwProtectionFlags ^ dwFlags) & CRYPTPROTECT_SYSTEM) { // // Attempted to use decrypt system data as a user, or user data // with the system flag dwRet = ERROR_INVALID_DATA; goto Ret; } // cbDataDescr cbDataDescr = *(DWORD UNALIGNED *)pbReadPtr; pbReadPtr += sizeof(DWORD); cbIn -= sizeof(DWORD); // Check for minimum input buffer size. if(cbIn < cbDataDescr + // szDataDescr sizeof(DWORD) + // dwEncrAlgID sizeof(DWORD) + // dwEncrAlgKeySize sizeof(DWORD)) // cbEncrKey { dwRet = ERROR_INVALID_DATA; goto Ret; } // szDataDescr szDataDescr = (LPWSTR)pbReadPtr; pbReadPtr += cbDataDescr; cbIn -= cbDataDescr; D_DebugLog((DEB_TRACE, "Description:%ls\n", szDataDescr)); // dwEncrAlgID dwEncrAlgID = *(DWORD UNALIGNED *)pbReadPtr; pbReadPtr += sizeof(DWORD); cbIn -= sizeof(DWORD); // dwEncrAlgKeySize dwEncrAlgKeySize = *(DWORD UNALIGNED *)pbReadPtr; pbReadPtr += sizeof(DWORD); cbIn -= sizeof(DWORD); D_DebugLog((DEB_TRACE, "Encrypt alg:0x%x, Size:%d bits\n", dwEncrAlgID, dwEncrAlgKeySize)); // cb Encr key cbEncrKeysize = *(DWORD UNALIGNED *)pbReadPtr; pbReadPtr += sizeof(DWORD); cbIn -= sizeof(DWORD); if (cbEncrKeysize > sizeof(rgbEncrKey)) { dwRet = ERROR_INVALID_DATA; goto Ret; } // Check for minimum input buffer size. if(cbIn < cbEncrKeysize + // EncrKey sizeof(DWORD)) // cbEncrSalt { dwRet = ERROR_INVALID_DATA; goto Ret; } // Encr key CopyMemory(rgbEncrKey, pbReadPtr, cbEncrKeysize); pbReadPtr += cbEncrKeysize; cbIn -= cbEncrKeysize; // cb Encr salt cbEncrSalt = *(DWORD UNALIGNED *)pbReadPtr; pbReadPtr += sizeof(DWORD); cbIn -= sizeof(DWORD); // Check for minimum input buffer size. if(cbIn < cbEncrSalt + // EncrSalt sizeof(DWORD) + // dwMACAlgID sizeof(DWORD) + // dwMACAlgKeySize sizeof(DWORD)) // cbMACKey { dwRet = ERROR_INVALID_DATA; goto Ret; } // Encr salt pbEncrSalt = (PBYTE)SSAlloc(cbEncrSalt); if( pbEncrSalt == NULL ) { dwRet = ERROR_OUTOFMEMORY; goto Ret; } CopyMemory(pbEncrSalt, pbReadPtr, cbEncrSalt); pbReadPtr += cbEncrSalt; cbIn -= cbEncrSalt; // dwMACAlgID dwMACAlgID = *(DWORD UNALIGNED *)pbReadPtr; pbReadPtr += sizeof(DWORD); cbIn -= sizeof(DWORD); // dwMACAlgKeySize dwMACAlgKeySize = *(DWORD UNALIGNED *)pbReadPtr; pbReadPtr += sizeof(DWORD); cbIn -= sizeof(DWORD); D_DebugLog((DEB_TRACE, "MAC alg:0x%x, Size:%d bits\n", dwMACAlgID, dwMACAlgKeySize)); // MAC key size cbMACKeysize = *(DWORD UNALIGNED *)pbReadPtr; pbReadPtr += sizeof(DWORD); cbIn -= sizeof(DWORD); if (cbMACKeysize > sizeof(rgbMACKey)) { dwRet = ERROR_INVALID_DATA; goto Ret; } // Check for minimum input buffer size. if(cbIn < cbMACKeysize) // cbMACKey { dwRet = ERROR_INVALID_DATA; goto Ret; } // MAC key CopyMemory(rgbMACKey, pbReadPtr, cbMACKeysize); pbReadPtr += cbMACKeysize; cbIn -= cbMACKeysize; if (NULL == (hVerifyProv = GetCryptProviderHandle( dwDefaultCryptProvType, dwEncrAlgID, &dwEncrAlgKeySize, dwMACAlgID, &dwMACAlgKeySize)) ) { dwRet = (DWORD)GetLastError(); goto Ret; } // USER GATING: when PROMPT_ON_UNPROTECT specified during CryptProtectData if ( CRYPTPROTECT_PROMPT_ON_UNPROTECT & dwProtectionFlags ) { if (dwFlags & CRYPTPROTECT_UI_FORBIDDEN) { dwRet = ERROR_PASSWORD_RESTRICTION; goto Ret; } if (psPrompt == NULL) { dwRet = ERROR_INVALID_PARAMETER; goto Ret; } if (psPrompt->cbSize != sizeof(SSCRYPTPROTECTDATA_PROMPTSTRUCT)) { dwRet = ERROR_INVALID_PARAMETER; goto Ret; } if ((psPrompt->dwPromptFlags & ~(CRYPTPROTECT_PROMPT_ON_PROTECT | CRYPTPROTECT_PROMPT_ON_UNPROTECT | CRYPTPROTECT_PROMPT_STRONG | CRYPTPROTECT_PROMPT_REQUIRE_STRONG) ) != 0) { dwRet = ERROR_INVALID_PARAMETER; goto Ret; } // UI handled outside service until SAS support added. } dwRet = GetSpecifiedMasterKey( pvContext, &guidMK, &pbMasterKey, &cbMasterKey, TRUE // we do know what master key we want to use ); if(dwRet != ERROR_SUCCESS) { DWORD dwAccount = 0; DebugLog((DEB_ERROR, "Unable to get specified master key:%ls, error 0x%x\n", wszMKGuidString, dwRet)); // // Is this call from one of the service accounts? If so, then attempt to // obtain the master key using the legacy method. // CPSQueryWellKnownAccount(pvContext, &dwAccount); if((dwAccount == DP_ACCOUNT_LOCAL_SERVICE) || (dwAccount == DP_ACCOUNT_NETWORK_SERVICE)) { DebugLog((DEB_ERROR, "Attempt service account legacy method.\n")); CPSSetWellKnownAccount(pvContext, 0); dwRet = GetSpecifiedMasterKey( pvContext, &guidMK, &pbMasterKey, &cbMasterKey, TRUE // we do know what master key we want to use ); CPSSetWellKnownAccount(pvContext, dwAccount); if(dwRet != ERROR_SUCCESS) { DebugLog((DEB_ERROR, "Still unable to get specified master key:%ls, error 0x%x\n", wszMKGuidString, dwRet)); goto Ret; } else { DebugLog((DEB_ERROR, "Master key successfully obtained using legacy method.\n")); } } else { goto Ret; } } // // hash pbMasterKey to get rgbPwdBuf // FMyPrimitiveSHA( pbMasterKey, cbMasterKey, rgbPwdBuf); // derive encr key { if (!FMyPrimitiveCryptHMAC( rgbPwdBuf, sizeof(rgbPwdBuf), rgbEncrKey, cbEncrKeysize, hVerifyProv, ALGID_DERIVEKEY_HASH, &hHash)) { dwRet = GetLastError(); goto Ret; } // add password if exists if (NULL != pbOptionalEntropy) { if (!CryptHashData( hHash, pbOptionalEntropy, cbOptionalEntropy, 0)) { dwRet = GetLastError(); goto Ret; } } // add prompted UI based password if exists. // will eventually come from SAS // if ( NULL != pbOptionalPassword && cbOptionalPassword ) { if (!CryptHashData( hHash, pbOptionalPassword, cbOptionalPassword, 0)) { dwRet = GetLastError(); goto Ret; } } if (!CryptDeriveKey( hVerifyProv, dwEncrAlgID, hHash, ((dwEncrAlgKeySize << 16) | CRYPT_CREATE_SALT), &hKey)) { dwRet = GetLastError(); goto Ret; } CryptDestroyHash(hHash); hHash = 0; // USEC -- (US Export Controls) if (ERROR_SUCCESS != (dwRet = SetSaltForExportControl( hKey, pbEncrSalt, cbEncrSalt)) ) goto Ret; } // derive MAC key { if (!FMyPrimitiveCryptHMAC( rgbPwdBuf, sizeof(rgbPwdBuf), rgbMACKey, cbMACKeysize, hVerifyProv, dwMACAlgID, &hHash)) { dwRet = GetLastError(); goto Ret; } // add password if exists if (NULL != pbOptionalEntropy) { if (!CryptHashData( hHash, pbOptionalEntropy, cbOptionalEntropy, 0)) { dwRet = GetLastError(); goto Ret; } } // add prompted UI based password if exists. // will eventually come from SAS // if ( NULL != pbOptionalPassword && cbOptionalPassword ) { if (!CryptHashData( hHash, pbOptionalPassword, cbOptionalPassword, 0)) { dwRet = GetLastError(); goto Ret; } } // USEC -- (US Export Controls) // does not apply -- use strong key } // Check for minimum input buffer size. if(cbIn < sizeof(DWORD)) // cbEncrData { dwRet = ERROR_INVALID_DATA; goto Ret; } // get encr size cbEncr = *(DWORD UNALIGNED *)pbReadPtr; pbReadPtr += sizeof(DWORD); cbIn -= sizeof(DWORD); // Check for minimum input buffer size. if(cbIn < cbEncr + // EncrData sizeof(DWORD) + // cbMAC A_SHA_DIGEST_LEN) // MAC { dwRet = ERROR_INVALID_DATA; goto Ret; } // dansimon recommends that MAC be on encrypted data, such that the // MAC has no possibility of revealing info about the plaintext. // MAC is from start thru encrypted data if (!CryptHashData( hHash, pbIn, (DWORD) ((pbReadPtr - pbIn) + cbEncr), 0)) { dwRet = GetLastError(); goto Ret; } cbPlaintext = cbEncr; if ((dwProtectionFlags & CRYPTPROTECT_NO_ENCRYPTION) == 0) { if (!CryptDecrypt( hKey, NULL, // hHash mattt 9/12/97 TRUE, 0, pbReadPtr, &cbPlaintext)) { dwRet = GetLastError(); if(NTE_BAD_DATA == dwRet) { dwRet = ERROR_INVALID_DATA; } goto Ret; } } { BYTE rgbComputedMAC[A_SHA_DIGEST_LEN]; // use MACPtr to skip past decr data, PBYTE pbMACPtr = pbReadPtr + cbEncr; DWORD cbMACsize = A_SHA_DIGEST_LEN; if (!CryptGetHashParam( hHash, HP_HASHVAL, rgbComputedMAC, &cbMACsize, 0)) { dwRet = GetLastError(); goto Ret; } // chk MAC size if (*(DWORD UNALIGNED *)pbMACPtr != cbMACsize) { dwRet = ERROR_INVALID_DATA; goto Ret; } pbMACPtr += sizeof(DWORD); // chk MAC if (0 != memcmp(pbMACPtr, rgbComputedMAC, cbMACsize) ) { dwRet = ERROR_INVALID_DATA; goto Ret; } } // // Write data out, encrypted so that rpc doesn't leave copies // laying around in plaintext. // *pcbOut = cbPlaintext; if((dwFlags & CRYPTPROTECT_IN_PROCESS) == 0) { DWORD cbPadding; NTSTATUS Status; cbPadding = RTL_ENCRYPT_MEMORY_SIZE - (*pcbOut) % RTL_ENCRYPT_MEMORY_SIZE; if(cbPadding == 0) { cbPadding += RTL_ENCRYPT_MEMORY_SIZE; } *ppbOut = (PBYTE)SSAlloc(*pcbOut + cbPadding); if(*ppbOut == NULL) { *pcbOut = 0; dwRet = ERROR_OUTOFMEMORY; goto Ret; } CopyMemory(*ppbOut, pbReadPtr, *pcbOut); FillMemory((*ppbOut) + (*pcbOut), cbPadding, (BYTE)cbPadding); *pcbOut += cbPadding; dwRet = RpcImpersonateClient(((PCRYPT_SERVER_CONTEXT)pvContext)->hBinding); if( dwRet != ERROR_SUCCESS ) { SSFree(*ppbOut); *ppbOut = NULL; *pcbOut = 0; goto Ret; } Status = RtlEncryptMemory(*ppbOut, *pcbOut, RTL_ENCRYPT_OPTION_SAME_LOGON); RevertToSelf(); if(!NT_SUCCESS(Status)) { SSFree(*ppbOut); *ppbOut = NULL; *pcbOut = 0; dwRet = RtlNtStatusToDosError(Status); goto Ret; } } else { // We're in-process, so don't bother encrypting output buffer. *ppbOut = (PBYTE)SSAlloc(*pcbOut); if(*ppbOut == NULL) { *pcbOut = 0; dwRet = ERROR_OUTOFMEMORY; goto Ret; } CopyMemory(*ppbOut, pbReadPtr, *pcbOut); } // optional: caller may want data descr if (ppszDataDescr) { *ppszDataDescr = (LPWSTR)SSAlloc(cbDataDescr); if(*ppszDataDescr == NULL) { SSFree(*ppbOut); *ppbOut = NULL; *pcbOut = 0; dwRet = ERROR_OUTOFMEMORY; goto Ret; } CopyMemory(*ppszDataDescr, szDataDescr, cbDataDescr); } dwRet = ERROR_SUCCESS; if(dwFlags & CRYPTPROTECT_VERIFY_PROTECTION ) { HCRYPTPROV hTestProv = GetCryptProviderHandle( dwDefaultCryptProvType, dwAlgID_Encr_Alg, &dwAlgID_Encr_Alg_KeySize, dwAlgID_MAC_Alg, &dwAlgID_MAC_Alg_KeySize); if(hTestProv) { // Verify encryption strengths // Never downgrade encryption strength, just check if we need // to upgrade if((dwAlgID_Encr_Alg_KeySize > dwEncrAlgKeySize) || (dwAlgID_MAC_Alg_KeySize > dwMACAlgKeySize)) { dwRet = CRYPT_I_NEW_PROTECTION_REQUIRED; } } } Ret: if((dwProtectionFlags & CRYPTPROTECT_AUDIT) || (ERROR_SUCCESS != dwRet)) { DWORD dwAuditRet = dwRet; WCHAR wszCryptoAlgs[2*MAX_STRING_ALGID_LENGTH + 2]; DWORD i; PCRYPT_SERVER_CONTEXT pServerContext = (PCRYPT_SERVER_CONTEXT)pvContext; i = AlgIDToString(wszCryptoAlgs, dwEncrAlgID, dwEncrAlgKeySize); wszCryptoAlgs[i++]= L','; wszCryptoAlgs[i++]= L' '; AlgIDToString(&wszCryptoAlgs[i], dwMACAlgID, dwMACAlgKeySize); if(CRYPT_I_NEW_PROTECTION_REQUIRED == dwAuditRet) { dwAuditRet = ERROR_SUCCESS; } CPSAudit(pServerContext->hToken, SE_AUDITID_DPAPI_UNPROTECT, wszMKGuidString, // Key Identifier szDataDescr, // Data Description 0, // Protected Data Flags wszCryptoAlgs, // Protection Algorithms dwAuditRet); // Failure Reason } RtlSecureZeroMemory(rgbPwdBuf, sizeof(rgbPwdBuf)); RtlSecureZeroMemory(rgbEncrKey, sizeof(rgbEncrKey)); if(pbMasterKey) { RtlSecureZeroMemory(pbMasterKey, cbMasterKey); SSFree(pbMasterKey); } if (hKey) CryptDestroyKey(hKey); if (hHash) CryptDestroyHash(hHash); if (pbEncrSalt) SSFree(pbEncrSalt); D_DebugLog((DEB_TRACE_API, "SPCryptUnprotect returned 0x%x\n", dwRet)); return dwRet; }