//+---------------------------------------------------------------------------- // // File: pwutil.cpp // // Module: Common Source // // Synopsis: Simple encryption funcs - borrowed from RAS // // Copyright (c) 1994-1999 Microsoft Corporation // // Author: nickball Created 08/03/99 // //+---------------------------------------------------------------------------- #define PASSWORDMAGIC 0xA5 VOID ReverseSzA( CHAR* psz ) /* Reverses order of characters in 'psz'. */ { CHAR* pszBegin; CHAR* pszEnd; for (pszBegin = psz, pszEnd = psz + lstrlenA( psz ) - 1; pszBegin < pszEnd; ++pszBegin, --pszEnd) { CHAR ch = *pszBegin; *pszBegin = *pszEnd; *pszEnd = ch; } } VOID ReverseSzW( WCHAR* psz ) /* Reverses order of characters in 'psz'. */ { WCHAR* pszBegin; WCHAR* pszEnd; for (pszBegin = psz, pszEnd = psz + lstrlenW( psz ) - 1; pszBegin < pszEnd; ++pszBegin, --pszEnd) { WCHAR ch = *pszBegin; *pszBegin = *pszEnd; *pszEnd = ch; } } VOID CmDecodePasswordA( IN OUT CHAR* pszPassword ) /* Un-obfuscate 'pszPassword' in place. ** ** Returns Nothing */ { CmEncodePasswordA( pszPassword ); } VOID CmDecodePasswordW( IN OUT WCHAR* pszPassword ) /* Un-obfuscate 'pszPassword' in place. ** ** Returns the address of 'pszPassword'. */ { CmEncodePasswordW( pszPassword ); } VOID CmEncodePasswordA( IN OUT CHAR* pszPassword ) /* Obfuscate 'pszPassword' in place to foil memory scans for passwords. ** ** Returns Nothing */ { if (pszPassword) { CHAR* psz; ReverseSzA( pszPassword ); for (psz = pszPassword; *psz != '\0'; ++psz) { if (*psz != PASSWORDMAGIC) *psz ^= PASSWORDMAGIC; } } } VOID CmEncodePasswordW( IN OUT WCHAR* pszPassword ) /* Obfuscate 'pszPassword' in place to foil memory scans for passwords. ** ** Returns Nothing */ { if (pszPassword) { WCHAR* psz; ReverseSzW( pszPassword ); for (psz = pszPassword; *psz != L'\0'; ++psz) { if (*psz != PASSWORDMAGIC) *psz ^= PASSWORDMAGIC; } } } VOID CmWipePasswordA( IN OUT CHAR* pszPassword ) /* Zero out the memory occupied by a password. ** ** Returns Nothing */ { if (pszPassword) { CHAR* psz = pszPassword; // // We are assuming the string is NULL terminated, thus we just need to pass // the actual string length (converted to bytes) to be wiped. The is no need // to include the NULL character in the count. // psz = (CHAR*)CmSecureZeroMemory((PVOID)psz, lstrlenA(psz) * sizeof(CHAR)); } } VOID CmWipePasswordW( IN OUT WCHAR* pszPassword ) /* Zero out the memory occupied by a password. ** ** Returns Nothing */ { if (pszPassword) { WCHAR* psz = pszPassword; // // We are assuming the string is NULL terminated, thus we just need to pass // the actual string length (converted to bytes) to be wiped. The is no need // to include the NULL character in the count. // psz = (WCHAR*)CmSecureZeroMemory((PVOID)psz, lstrlenW(psz) * sizeof(WCHAR)); } } //+---------------------------------------------------------------------------- // // Function: CmSecureZeroMemory // // Synopsis: RtlSecureZeroMemory isn't available on all platforms so we took // its implementation. // // Arguments: ptr - memory pointer // cnt - size in bytes of memory to clear // // Returns: poniter to beginning of memory // //+---------------------------------------------------------------------------- PVOID CmSecureZeroMemory(IN PVOID ptr, IN SIZE_T cnt) { if (ptr) { volatile char *vptr = (volatile char *)ptr; while (cnt) { *vptr = 0; vptr++; cnt--; } } return ptr; } // Only include this code in CMDial32.dll #ifdef _ICM_INC //+---------------------------------------------------------------------------- // Class: CSecurePassword // // Function: CSecurePassword // // Synopsis: Constructor // // Arguments: none // // Returns: Nothing // // History: 11/05/2002 tomkel Created // //+---------------------------------------------------------------------------- CSecurePassword::CSecurePassword() { this->Init(); } //+---------------------------------------------------------------------------- // // Function: ~CSecurePassword // // Synopsis: Destructor. Unloads DLL, tries to clear memory & free memory. // Makes sure we don't have a memory leak. // // Arguments: none // // Returns: Nothing // //+---------------------------------------------------------------------------- CSecurePassword::~CSecurePassword() { this->UnInit(); // // Assert if m_iAllocAndFreeCounter isn't zero. It means we are leaking memory. // Each GetPasswordWithAlloc call increments this // Each ClearAndFree call decrements this. // MYDBGASSERT(0 == m_iAllocAndFreeCounter); } //+---------------------------------------------------------------------------- // // Function: Init // // Synopsis: Initializes member variables. // // Arguments: none // // Returns: Nothing // //+---------------------------------------------------------------------------- VOID CSecurePassword::Init() { m_iAllocAndFreeCounter = 0; m_fIsLibAndFuncPtrsAvail = FALSE; m_pEncryptedPW = NULL; fnCryptProtectData = NULL; fnCryptUnprotectData = NULL; m_fIsEmptyString = TRUE; m_fIsHandleToPassword = FALSE; // By default just set it to PWLEN m_dwMaxDataLen = PWLEN; this->ClearMemberVars(); } //+---------------------------------------------------------------------------- // // Function: SetPassword // // Synopsis: We take this string that is passed in and store it // internally. Based on the OS it encrypts // or encodes it, thus we don't store it in clear. This method handles // 0 length strings, which can be used to clear the member // variable. If a RAS password handle (consists of 16 '*'), // there is no need for us to encrypt it. To optimize this // we set an member flag specifying that currently this // instance just hold a handle to a password. On downlevel // platforms we don't use expensive encryption calls, thus // the logic doesn't distinguish between a normal password // and a password handle. // // Arguments: szPassword - password in clear text. // // Returns: TRUE - if everything succeeded // FALSE - if something failed // //+---------------------------------------------------------------------------- BOOL CSecurePassword::SetPassword(IN LPWSTR pszPassword) { BOOL fRetCode = FALSE; DWORD dwRetCode = ERROR_SUCCESS; DWORD dwPwLen = 0; // // OS_NT5 expands to a few function calls, so just cache the result and reuse it below // BOOL fIsNT5OrAbove = OS_NT5; // // If there is an allocated blob then free it first so we don't leak memory. // this->ClearMemberVars(); m_fIsEmptyString = ((NULL == pszPassword) || (TEXT('\0') == pszPassword[0])); if (m_fIsEmptyString) { // // No need to continue, since password can be NULL the code below that compares // it to a handle (16 *s) would be dereferencing a NULL // m_fIsHandleToPassword = FALSE; return TRUE; } // // Check whether this is a handle to a password (****************) // m_fIsHandleToPassword = (fIsNT5OrAbove && (0 == lstrcmpW(c_pszSavedPasswordToken, pszPassword))); // // If the internal flag is set, there is no need to encrypt or decrypt this string. // if (m_fIsHandleToPassword) { return TRUE; } // // Make sure the password that is being encrypted is shorter than the allowed maximum // dwPwLen = lstrlenU(pszPassword); if (m_dwMaxDataLen < dwPwLen) { return FALSE; } if (fIsNT5OrAbove) { m_pEncryptedPW = (DATA_BLOB*)CmMalloc(sizeof(DATA_BLOB)); if (m_pEncryptedPW) { dwRetCode = this->EncodePassword((dwPwLen + 1) * sizeof(WCHAR), (PBYTE)pszPassword, m_pEncryptedPW); if (ERROR_SUCCESS == dwRetCode) { fRetCode = TRUE; } else { // // Free the allocated DATA_BLOB so that decryption doesn't cause issue in case caller // ends up calling it. And reset internal flags. // this->ClearMemberVars(); m_fIsEmptyString = TRUE; m_fIsHandleToPassword = FALSE; CMTRACE1(TEXT("CSecurePassword::SetPassword - this->EncodePassword failed. 0x%x"), dwRetCode); } } } else { // // Downlevel (Win9x, NT4) we don't support encryption // lstrcpynU(m_tszPassword, pszPassword, CELEMS(m_tszPassword)); CmEncodePassword(m_tszPassword); fRetCode = TRUE; } MYDBGASSERT(fRetCode); return fRetCode; } //+---------------------------------------------------------------------------- // // Function: GetPasswordWithAlloc // // Synopsis: Allocates a buffer and copies the clear-text password into it. // Based on the OS it decrypts or decodes it since it's not stored // in clear. If the internal password is an empty string we allocate // an empty string buffer. This is done for consistency since the caller // needs to call our free method so memory isn't leaked. If we are storing // a RAS password handle (consists of 16 '*') we actually didn't store it, // but only set our internal flag. In this case we need to allocate a buffer // with 16 * and return it. On downlevel platforms we don't use expensive // decryption calls, thus the logic doesn't distinguish between a // normal password and a password handle. // // Arguments: pszClearPw - holds a pointer to a buffer that was allocated by this // class. // pcbClearPw - hold the size of the allocated buffer in bytes // // Returns: TRUE - if everything succeeded // FALSE - if something failed // //+---------------------------------------------------------------------------- BOOL CSecurePassword::GetPasswordWithAlloc(OUT LPWSTR* pszClearPw, OUT DWORD* pcbClearPw) { BOOL fRetCode = FALSE; DWORD dwRetCode = ERROR_SUCCESS; DWORD cbData = 0; PBYTE pbData = NULL; if ((NULL == pszClearPw) || (NULL == pcbClearPw)) { MYDBGASSERT(FALSE); return FALSE; } *pszClearPw = NULL; *pcbClearPw = 0; if (OS_NT5) { if (m_fIsEmptyString) { // // In case there is nothing saved in this class, just allocate an empty string // and return it back. This at least doesn't have to decrypt and empty string. // DWORD cbLen = sizeof(WCHAR); LPWSTR szTemp = (LPWSTR)LocalAlloc(LMEM_ZEROINIT, cbLen); if (szTemp) { *pszClearPw = szTemp; *pcbClearPw = cbLen; fRetCode = TRUE; m_iAllocAndFreeCounter++; } } else { // // Check if this instance is just a handle to a RAS password (16 *) // If so, then just allocate that string and return it to the caller, // otherwise proceed normally and decrypt our blob. // if (m_fIsHandleToPassword) { size_t nLen = lstrlenW(c_pszSavedPasswordToken) + 1; DWORD cbLen = nLen * sizeof(WCHAR); LPWSTR szTemp = (LPWSTR)LocalAlloc(LMEM_ZEROINIT, cbLen); if (szTemp) { lstrcpynW(szTemp, c_pszSavedPasswordToken, nLen); *pszClearPw = szTemp; *pcbClearPw = cbLen; fRetCode = TRUE; m_iAllocAndFreeCounter++; } } else { if (m_pEncryptedPW) { dwRetCode = this->DecodePassword(m_pEncryptedPW, &cbData, &pbData); if ((NO_ERROR == dwRetCode) && pbData && cbData) { *pszClearPw = (LPWSTR)pbData; *pcbClearPw = cbData; fRetCode = TRUE; m_iAllocAndFreeCounter++; } } } } } else { // // Downlevel (Win9x, NT4) doesn't support 16 * // size_t nLen = lstrlenU(m_tszPassword) + 1; LPTSTR pszBuffer = (LPWSTR)CmMalloc(nLen * sizeof(TCHAR)); if (pszBuffer) { // // Copy our encoded buffer to the newly allocated buffer // We can do this because d/encoding is done in place // lstrcpynU(pszBuffer, m_tszPassword, nLen); // // Decode the outgoing buffer // CmDecodePassword(pszBuffer); *pszClearPw = (LPWSTR)pszBuffer; *pcbClearPw = nLen * sizeof(TCHAR); fRetCode = TRUE; m_iAllocAndFreeCounter++; } } MYDBGASSERT(fRetCode); return fRetCode; } //+---------------------------------------------------------------------------- // // Function: ClearAndFree // // Synopsis: Clear then free a buffer that was allocated by this class. Notice that // on downlevel platforms the way a buffer is freed differs. That // is because encrypting and decrypting needs us to free it // using LocalFree. For downlevel platforms we chose CM's standard // way of allocating memory (CmMalloc) and it now needs to be // freed using CmFree. // // Arguments: pszClearPw - holds a pointer to a buffer that was allocated by this // class. // cbClearPw - size of the allocated buffer in bytes // // Returns: TRUE - if everything succeeded // FALSE - if something failed // //+---------------------------------------------------------------------------- VOID CSecurePassword::ClearAndFree(IN OUT LPWSTR* pszClearPw, IN DWORD cbClearPw) { if ((NULL == pszClearPw) || (0 == cbClearPw)) { return; } if (NULL == *pszClearPw) { return; } CmSecureZeroMemory(*pszClearPw, cbClearPw); if (OS_NT5) { // // Uses LocalFree because CryptProtectData requires this way // to free its memory // LocalFree(*pszClearPw); } else { // // We used CmMalloc to allocate so we need to call CmFree // CmFree(*pszClearPw); } *pszClearPw = NULL; m_iAllocAndFreeCounter--; return; } //+---------------------------------------------------------------------------- // // Function: ClearMemberVars // // Synopsis: Clears our member variables. Notice that we only clear the // passwords & member variables. This doesn't mean that m_fIsEmptyString // should be set. This needs to be a private method // because it doesn't reset the empty or password handle flags. Thus // outside callers should NOT use this, because it would create an invalid // state. // // Arguments: none // // Returns: none // //+---------------------------------------------------------------------------- VOID CSecurePassword::ClearMemberVars() { if (OS_NT5) { if (m_pEncryptedPW) { this->FreePassword(m_pEncryptedPW); CmFree(m_pEncryptedPW); m_pEncryptedPW = NULL; } } else { // // Zero out the password buffer // CmSecureZeroMemory((PVOID)m_tszPassword, sizeof(m_tszPassword)); } } //+---------------------------------------------------------------------------- // // Function: UnInit // // Synopsis: Unloads DLL, clear and frees memory. // // Arguments: none // // Returns: none // //+---------------------------------------------------------------------------- VOID CSecurePassword::UnInit() { this->UnloadCrypt32(); this->ClearMemberVars(); m_fIsHandleToPassword = FALSE; m_fIsEmptyString = FALSE; } //+---------------------------------------------------------------------------- // // Function: UnloadCrypt32 // // Synopsis: Unloads DLL // // Arguments: none // // Returns: none // //+---------------------------------------------------------------------------- VOID CSecurePassword::UnloadCrypt32() { fnCryptProtectData = NULL; fnCryptUnprotectData = NULL; m_dllCrypt32.Unload(); m_fIsLibAndFuncPtrsAvail = FALSE; } //+---------------------------------------------------------------------------- // // Function: EncodePassword // // Synopsis: Encrypts data using CryptProtectData // // Arguments: cbPassword - size of buffer in bytes // pbPassword - pointer to a buffer to encrypt // pDataBlobPassword - pointer to an allocated DATA_BLOB structure // // Returns: none // //+---------------------------------------------------------------------------- DWORD CSecurePassword::EncodePassword(IN DWORD cbPassword, IN PBYTE pbPassword, OUT DATA_BLOB* pDataBlobPassword) { DWORD dwErr = NO_ERROR; DATA_BLOB DataBlobIn; if(NULL == pDataBlobPassword) { dwErr = E_INVALIDARG; CMTRACE(TEXT("CSecurePassword::EncodePassword - NULL == pDataBlobPassword")); goto done; } if( (0 == cbPassword) || (NULL == pbPassword)) { // // nothing to encrypt. // dwErr = E_INVALIDARG; CMTRACE(TEXT("CSecurePassword::EncodePassword - E_INVALIDARG")); goto done; } // // If Crypt32.DLL is not loaded, try to loaded and get the // function pointers. // if (FALSE == m_fIsLibAndFuncPtrsAvail) { if (FALSE == this->LoadCrypt32AndGetFuncPtrs()) { // // This failed, thus we can't continue. We should free memory. // this->ClearMemberVars(); m_fIsEmptyString = TRUE; m_fIsHandleToPassword = FALSE; dwErr = ERROR_DLL_INIT_FAILED; CMTRACE(TEXT("CSecurePassword::EncodePassword - this-> LoadCrypt32AndGetFuncPtrs failed.")); goto done; } } ZeroMemory(pDataBlobPassword, sizeof(DATA_BLOB)); DataBlobIn.cbData = cbPassword; DataBlobIn.pbData = pbPassword; if (fnCryptProtectData) { LPCWSTR wszDesc[] = {TEXT("Readable description of data.")}; LPWSTR pszDesc = NULL; if (OS_W2K) { // // The crypto API needs this, but only on Win2K // pszDesc = (LPWSTR)wszDesc; } if(!fnCryptProtectData( &DataBlobIn, (LPCWSTR)pszDesc, NULL, NULL, NULL, CRYPTPROTECT_UI_FORBIDDEN, pDataBlobPassword)) { dwErr = GetLastError(); CMTRACE1(TEXT("CSecurePassword::EncodePassword - fnCryptProtectData failed. 0x%x"), dwErr); goto done; } } else { CMTRACE(TEXT("CSecurePassword::EncodePassword - ERROR_FUNCTION_NOT_CALLED")); dwErr = ERROR_FUNCTION_NOT_CALLED; } done: MYDBGASSERT(NO_ERROR == dwErr); return dwErr; } //+---------------------------------------------------------------------------- // // Function: DecodePassword // // Synopsis: Decrypts data using CryptUnprotectData // // Arguments: pDataBlobPassword - pointer to a DATA_BLOB structure to be decrypted // cbPassword - pointer that holds the size of buffer in bytes // pbPassword - pointer to a buffer to encrypt // // // Returns: none // //+---------------------------------------------------------------------------- DWORD CSecurePassword::DecodePassword(IN DATA_BLOB* pDataBlobPassword, OUT DWORD* pcbPassword, OUT PBYTE* ppbPassword) { DWORD dwErr = NO_ERROR; DATA_BLOB DataOut; if( (NULL == pDataBlobPassword) || (NULL == pcbPassword) || (NULL == ppbPassword)) { dwErr = E_INVALIDARG; goto done; } *pcbPassword = 0; *ppbPassword = NULL; if( (NULL == pDataBlobPassword->pbData) || (0 == pDataBlobPassword->cbData)) { // // nothing to decrypt. Just return success. // goto done; } // // If Crypt32.DLL is not loaded, try to loaded and get the // function pointers. // if (FALSE == m_fIsLibAndFuncPtrsAvail) { if (FALSE == this->LoadCrypt32AndGetFuncPtrs()) { // // This failed, thus we can't continue. We should free memory. // this->ClearMemberVars(); m_fIsEmptyString = TRUE; m_fIsHandleToPassword = FALSE; dwErr = ERROR_DLL_INIT_FAILED; goto done; } } ZeroMemory(&DataOut, sizeof(DATA_BLOB)); if (fnCryptUnprotectData) { if(!fnCryptUnprotectData( pDataBlobPassword, NULL, NULL, NULL, NULL, CRYPTPROTECT_UI_FORBIDDEN, &DataOut)) { dwErr = GetLastError(); goto done; } dwErr = NO_ERROR; *pcbPassword = DataOut.cbData; *ppbPassword = DataOut.pbData; } else { dwErr = ERROR_FUNCTION_NOT_CALLED; } done: MYDBGASSERT(NO_ERROR == dwErr); return dwErr; } //+---------------------------------------------------------------------------- // // Function: FreePassword // // Synopsis: Frees data in DATA_BLOB structure // // Arguments: pDBPassword - pointer to a DATA_BLOB structure // // Returns: none // //+---------------------------------------------------------------------------- VOID CSecurePassword::FreePassword(IN DATA_BLOB *pDBPassword) { if(NULL == pDBPassword) { return; } if(NULL != pDBPassword->pbData) { CmSecureZeroMemory(pDBPassword->pbData, pDBPassword->cbData); LocalFree(pDBPassword->pbData); } // // Clear sensitive data. // CmSecureZeroMemory(pDBPassword, sizeof(DATA_BLOB)); } //+---------------------------------------------------------------------------- // // Function: LoadCrypt32AndGetFuncPtrs // // Synopsis: Loads crypt32.dll and gets function pointer to needed methods // // Arguments: none // // Returns: TRUE - if .DLL was loaded and function pointers were retrieved // FALSE - when an error was encountered // //+---------------------------------------------------------------------------- BOOL CSecurePassword::LoadCrypt32AndGetFuncPtrs() { BOOL fRetVal = FALSE; if (OS_NT5) { if (FALSE == m_fIsLibAndFuncPtrsAvail) { fRetVal = m_dllCrypt32.Load(TEXT("crypt32.dll")); if (fRetVal) { fnCryptProtectData = (fnCryptProtectDataFunc)m_dllCrypt32.GetProcAddress("CryptProtectData"); fnCryptUnprotectData = (fnCryptUnprotectDataFunc)m_dllCrypt32.GetProcAddress("CryptUnprotectData"); if (fnCryptProtectData && fnCryptUnprotectData) { CMTRACE(TEXT("CSecurePassword::LoadCrypt32AndGetFuncPtrs - success")); m_fIsLibAndFuncPtrsAvail = TRUE; fRetVal = TRUE; } else { CMTRACE(TEXT("CSecurePassword::LoadCrypt32AndGetFuncPtrs - missing function pointers")); this->UnloadCrypt32(); } } else { CMTRACE(TEXT("CSecurePassword::LoadCrypt32AndGetFuncPtrs - m_dllCrypt32.Load failed")); } } else { fRetVal = m_fIsLibAndFuncPtrsAvail; } } MYDBGASSERT(fRetVal); return fRetVal; } //+---------------------------------------------------------------------------- // // Function: IsEmptyString // // Synopsis: Used as a shortcut so we don't have to encrypt/decrypt in case // we stored an empty string. // // Arguments: none // // Returns: TRUE - if instance is suppose to be holding an empty string // FALSE - if currenttly saved string is not empty // //+---------------------------------------------------------------------------- BOOL CSecurePassword::IsEmptyString() { return m_fIsEmptyString; } //+---------------------------------------------------------------------------- // // Function: IsHandleToPassword // // Synopsis: Used as a shortcut so we don't have to encrypt/decrypt in case // we stored a handle to a RAS password (16 *). // // Arguments: none // // Returns: TRUE - if instance is suppose to be holding **************** // FALSE - if currenttly saved string is a normal password // //+---------------------------------------------------------------------------- BOOL CSecurePassword::IsHandleToPassword() { return m_fIsHandleToPassword; } //+---------------------------------------------------------------------------- // // Function: SetMaxDataLenToProtect // // Synopsis: Set the maximum length password to protect. This value will be // checked when encrypting a password. // // Arguments: dwMaxDataLen - maximum password length in characters // // Returns: Nothing // //+---------------------------------------------------------------------------- VOID CSecurePassword::SetMaxDataLenToProtect(DWORD dwMaxDataLen) { m_dwMaxDataLen = dwMaxDataLen; } //+---------------------------------------------------------------------------- // // Function: GetMaxDataLenToProtect // // Synopsis: Get the maximum length password that this class can protect. // // Arguments: none // // Returns: DWORD - maximum password length // //+---------------------------------------------------------------------------- DWORD CSecurePassword::GetMaxDataLenToProtect() { return m_dwMaxDataLen; } #endif // _ICM_INC