|
|
/****************************************************************************
Copyright (c) 1998-1999 Microsoft Corporation Module Name: crypt.c Abstract: Encryption/Decryption routines Author: radus - 11/05/98
Notes: Used for encrypting/decrypting of the PIN numbers. It is NOT thread-safe
Rev History:
****************************************************************************/
#define STRICT
#include <windows.h>
#include <windowsx.h>
#include <tchar.h>
#include <stdlib.h>
#include <wincrypt.h>
#include <shlwapi.h>
#include <shlwapip.h>
#include "tregupr2.h"
#include "debug.h"
// Context
static LONG gdwNrOfClients = 0; static PTSTR gpszSidText = NULL; static HCRYPTPROV ghCryptProvider = 0; static BOOL gbUseOnlyTheOldAlgorithm = TRUE; static BOOL gbCryptAvailChecked = FALSE; static BOOL gbCryptAvailable = TRUE;
static const CHAR MAGIC_1 = 'B'; static const CHAR MAGIC_5 = 'L'; static const CHAR MAGIC_2 = 'U'; static const CHAR MAGIC_4 = 'U'; static const CHAR MAGIC_3 = 'B';
#define ENCRYPTED_MARKER L'X'
// prototypes
static BOOL GetUserSidText(LPTSTR *); static BOOL GetUserTokenUser(TOKEN_USER **); static BOOL ConvertSidToText(PSID, LPTSTR, LPDWORD); static BOOL CreateSessionKey(HCRYPTPROV, LPTSTR, DWORD, HCRYPTKEY *); static void DestroySessionKey(HCRYPTKEY ); static void Unscrambler( DWORD, LPWSTR, LPWSTR ); static void CopyScrambled( LPWSTR, LPWSTR, DWORD);
DWORD TapiCryptInitialize(void) { DWORD dwNew; DWORD dwError; HCRYPTKEY hKey; PBYTE bTestData = "Testing"; DWORD dwTestSize = strlen(bTestData);
// Only one initialization
dwNew = InterlockedIncrement(&gdwNrOfClients); if(dwNew>1) return ERROR_SUCCESS; // By default
gbUseOnlyTheOldAlgorithm = TRUE; dwError = ERROR_SUCCESS;
#ifdef WINNT
// New encryption only for Windows NT
if(gbCryptAvailable || !gbCryptAvailChecked) {
// Acquire CryptoAPI context
if(CryptAcquireContext( &ghCryptProvider, NULL, MS_DEF_PROV, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT // No need of private/public keys
)) { // Get the user SID
if(GetUserSidText(&gpszSidText)) { if (gbCryptAvailChecked == FALSE) { if(CreateSessionKey(ghCryptProvider, gpszSidText, 0, &hKey)) { // try to use the test key and check for the NTE_PERM error meaning that we are not
// going to be able to use crypt
if (CryptEncrypt(hKey, 0, TRUE, 0, (BYTE *)bTestData, &dwTestSize, 0) == FALSE) { if (GetLastError() == NTE_PERM) { DBGOUT((5, "Encryption unavailable")); gbCryptAvailable = FALSE; } } // FOR TEST
// DBGOUT((5, "Encryption unavailable")); // Test Only
// gbCryptAvailable = FALSE; // Test Only
gbCryptAvailChecked = TRUE; DestroySessionKey(hKey); } else dwError = GetLastError(); } gbUseOnlyTheOldAlgorithm = !gbCryptAvailable; } else dwError = GetLastError(); } else { dwError = GetLastError(); DBGOUT((5, "CryptAcquireContext failed"));
} }
#endif // WINNT
return dwError; }
void TapiCryptUninitialize(void) { DWORD dwNew;
dwNew = InterlockedDecrement(&gdwNrOfClients); if(dwNew>0) return;
#ifdef WINNT
if(ghCryptProvider) { CryptReleaseContext(ghCryptProvider, 0); ghCryptProvider = 0; } if(gpszSidText) { GlobalFree(gpszSidText); gpszSidText = NULL; } #endif
gbUseOnlyTheOldAlgorithm = TRUE;
return; }
/////////////////////////////////
// TapiEncrypt
//
// Encrypts the text specified in pszSource.
// Uses the old scrambling algorithm or a cryptographic one.
// The result buffer should be a little bit larger than the source - for pads, etc.
//
DWORD TapiEncrypt(PWSTR pszSource, DWORD dwKey, PWSTR pszDest, DWORD *pdwLengthNeeded) { DWORD dwError; DWORD dwDataLength, dwLength, dwLengthDwords, dwLengthAlpha; // for speed
BYTE bBuffer1[0x20]; PBYTE pBuffer1 = NULL; HCRYPTKEY hKey = 0;
DWORD *pdwCrt1; WCHAR *pwcCrt2;
DWORD dwShift; DWORD dwCount, dwCount2;
#ifdef WINNT
if(!gbUseOnlyTheOldAlgorithm) { // A null PIN is not encrypted
if(*pszSource==L'\0') { if(pszDest) *pszDest = L'\0'; if(pdwLengthNeeded) *pdwLengthNeeded = 1;
return ERROR_SUCCESS; } dwDataLength = (wcslen(pszSource) + 1)*sizeof(WCHAR); // in bytes
dwLength = dwDataLength + 16; // space for pads, a marker etc.
dwLengthAlpha = dwLength*3; // due to the binary->alphabetic conversion
if(pszDest==NULL && pdwLengthNeeded != NULL) { *pdwLengthNeeded = dwLengthAlpha/sizeof(WCHAR); // length in characters
dwError = ERROR_SUCCESS; } else { ZeroMemory(bBuffer1, sizeof(bBuffer1));
pBuffer1 = dwLength>sizeof(bBuffer1) ? (PBYTE)GlobalAlloc(GPTR, dwLength) : bBuffer1; if(pBuffer1!=NULL) { // Copy the source
wcscpy((PWSTR)pBuffer1, pszSource); // create session key
if(CreateSessionKey(ghCryptProvider, gpszSidText, dwKey, &hKey)) { // Encrypt inplace
if(CryptEncrypt(hKey, 0, TRUE, 0, pBuffer1, &dwDataLength, dwLength)) { // Convert to UNICODE between 0030 - 006f
// I hope !
assert((dwDataLength % sizeof(DWORD))==0); assert(sizeof(DWORD)==4); assert(sizeof(DWORD) == 2*sizeof(WCHAR));
pdwCrt1 = (DWORD *)pBuffer1; pwcCrt2 = (WCHAR *)pszDest;
// Place a marker
*pwcCrt2++ = ENCRYPTED_MARKER;
// dwDataLength has the length in bytes of the ciphered data
dwLengthAlpha = dwDataLength*3; dwLengthDwords = dwDataLength / sizeof(DWORD); for(dwCount=0; dwCount<dwLengthDwords; dwCount++) { dwShift = *pdwCrt1++;
for(dwCount2=0; dwCount2<6; dwCount2++) { *pwcCrt2++ = (WCHAR)((dwShift & 0x3f) + 0x30); dwShift >>= 6; } } // Put a NULL terminator
*pwcCrt2++ = L'\0';
if(pdwLengthNeeded) *pdwLengthNeeded = (dwLengthAlpha/sizeof(WCHAR))+2; // including the NULL and the marker
dwError = ERROR_SUCCESS; } else dwError = GetLastError(); } else dwError = GetLastError(); } else dwError = GetLastError(); } if(pBuffer1 && pBuffer1!=bBuffer1) GlobalFree(pBuffer1); if(hKey!=0) DestroySessionKey(hKey); } else { #endif
if(pdwLengthNeeded != NULL) { *pdwLengthNeeded = wcslen(pszSource) + 1; // dim in characters
} if(pszDest!=NULL) { CopyScrambled(pszSource, pszDest, dwKey); } dwError = ERROR_SUCCESS;
#ifdef WINNT
} #endif
return dwError;
}
DWORD TapiDecrypt(PWSTR pszSource, DWORD dwKey, PWSTR pszDest, DWORD *pdwLengthNeeded) { DWORD dwError; DWORD dwLengthCrypted; DWORD dwDataLength; DWORD dwLengthDwords; HCRYPTKEY hKey = 0; WCHAR *pwcCrt1; DWORD *pdwCrt2; DWORD dwCount; DWORD dwCount2; DWORD dwShift;
// A null PIN is not encrypted
if(*pszSource==L'\0') { if(pszDest) *pszDest = L'\0'; if(pdwLengthNeeded) *pdwLengthNeeded = 1;
return ERROR_SUCCESS; }
dwError = ERROR_SUCCESS;
// If the first char is a 'X', we have encrypted data
if(*pszSource == ENCRYPTED_MARKER) { #ifdef WINNT
if(gbCryptAvailable && gdwNrOfClients>0) { dwLengthCrypted = wcslen(pszSource) +1 -2; // In characters, without the marker and the NULL
assert(dwLengthCrypted % 6 == 0); dwLengthDwords = dwLengthCrypted / 6;
if(pszDest==NULL && pdwLengthNeeded) { *pdwLengthNeeded = dwLengthDwords*(sizeof(DWORD)/sizeof(WCHAR)); dwError = ERROR_SUCCESS; } else { // Convert to binary
pwcCrt1 = pszSource + dwLengthCrypted; // end of the string, before the NULL
pdwCrt2 = ((DWORD *)pszDest) + dwLengthDwords -1; // Last DWORD
for(dwCount=0; dwCount<dwLengthDwords; dwCount++) { dwShift=0; for(dwCount2=0; dwCount2<6; dwCount2++) { dwShift <<= 6; dwShift |= ((*pwcCrt1-- - 0x30) & 0x3f); } *pdwCrt2-- = dwShift; }
if(CreateSessionKey(ghCryptProvider, gpszSidText, dwKey, &hKey)) { dwDataLength = dwLengthDwords * sizeof(DWORD); // Decrypt in place
if(CryptDecrypt(hKey, 0, TRUE, 0, (PBYTE)pszDest, &dwDataLength)) { dwDataLength /= sizeof(WCHAR); if(*(pszDest+dwDataLength-1)==L'\0') // The ending NULL was encrypted too
{ if(pdwLengthNeeded) *pdwLengthNeeded = dwDataLength;
dwError = ERROR_SUCCESS; } else { *pszDest = L'\0'; dwError = ERROR_INVALID_DATA; } } else dwError = GetLastError();
DestroySessionKey(hKey);
} else dwError = GetLastError(); } } else dwError = ERROR_INVALID_DATA; #else
dwError = ERROR_INVALID_DATA; #endif
} else { if(pdwLengthNeeded != NULL) { *pdwLengthNeeded = wcslen(pszSource) + 1; // dim in characters
} if(pszDest!=NULL) { Unscrambler(dwKey, pszSource, pszDest); } dwError = ERROR_SUCCESS; } return dwError; }
/////////////////////////////////
// TapiIsSafeToDisplaySensitiveData
//
// Detects if the current process is running in the "LocalSystem" security context.
// Returns FALSE if it is, TRUE if it is not.
// Returns also FALSE if an error occurs.
BOOL TapiIsSafeToDisplaySensitiveData(void) { #ifdef WINNT
DWORD dwError = ERROR_SUCCESS; TOKEN_USER *User = NULL; SID_IDENTIFIER_AUTHORITY SidAuth = SECURITY_NT_AUTHORITY; PSID SystemSid = NULL; BOOL bIsSafe = FALSE;
// Get the User info
if(GetUserTokenUser(&User)) { // Create a system SID
if(AllocateAndInitializeSid(&SidAuth, 1, SECURITY_LOCAL_SYSTEM_RID, 0, 0, 0, 0, 0, 0, 0, &SystemSid )) { // Compare the two sids
bIsSafe = !EqualSid(SystemSid, User->User.Sid);
FreeSid(SystemSid); } else { dwError = GetLastError(); }
GlobalFree(User); } else { dwError = GetLastError(); }
DBGOUT((5, "TapiIsSafeToDisplaySensitiveData - dwError=0x%x, Safe=%d", dwError, bIsSafe));
return bIsSafe;
#else // WINNT
return TRUE; // always safe
#endif
}
#ifdef WINNT
/////////////////////////////////
// GetUserSidText
//
// Retrieves the SID from the token of the current process in text format
BOOL GetUserSidText(LPTSTR *ppszResultSid) { TOKEN_USER *User = NULL; DWORD dwLength; LPTSTR pszSidText = NULL;
// Get the SID
if(!GetUserTokenUser(&User)) { DBGOUT((5, "GetMagic (0) failed, 0x%x", GetLastError())); return FALSE; }
// Query the space needed for the string format of the SID
dwLength=0; if(!ConvertSidToText(User->User.Sid, NULL, &dwLength) && (GetLastError() != ERROR_INSUFFICIENT_BUFFER)) { DBGOUT((5, "GetMagic (1) failed, 0x%x", GetLastError())); GlobalFree(User); return FALSE; }
// Alloc the space
pszSidText = (LPTSTR)GlobalAlloc(GMEM_FIXED, dwLength); if(pszSidText==NULL) { GlobalFree(User); return FALSE; } // Convert the SID in string format
if(!ConvertSidToText(User->User.Sid, pszSidText, &dwLength)) { DBGOUT((5, "GetMagic (2) failed, 0x%x", GetLastError())); GlobalFree(User); GlobalFree(pszSidText); return FALSE; }
GlobalFree(User);
// The caller should free the buffer
*ppszResultSid = pszSidText;
return TRUE; }
/////////////////////////////////
// GetUserTokenUser
//
// Retrieves the TOKEN_USER structure from the token of the current process
BOOL GetUserTokenUser(TOKEN_USER **ppszResultTokenUser) { HANDLE hToken = NULL; DWORD dwLength; TOKEN_USER *User = NULL; // Open the current process token (for read)
if(!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, FALSE, &hToken)) { if( GetLastError() == ERROR_NO_TOKEN) { // try with the process token
if (! OpenProcessToken ( GetCurrentProcess(), TOKEN_QUERY, &hToken)) { DBGOUT((5, "OpenProcessToken failed, 0x%x", GetLastError())); return FALSE; } } else { DBGOUT((5, "OpenThreadToken failed, 0x%x", GetLastError())); return FALSE; } } // Find the space needed for the SID
if(!GetTokenInformation(hToken, TokenUser, NULL, 0, &dwLength) && (GetLastError() != ERROR_INSUFFICIENT_BUFFER)) { DBGOUT((5, "GetTokenInformation (1) failed, 0x%x", GetLastError())); CloseHandle(hToken); return FALSE; } // Alloc the space
User = (TOKEN_USER *)GlobalAlloc(GMEM_FIXED, dwLength); if(User==NULL) { CloseHandle(hToken); return FALSE; } // Retrieve the SID
if(!GetTokenInformation(hToken, TokenUser, User, dwLength, &dwLength)) { DBGOUT((5, "GetTokenInformation (2) failed, 0x%x", GetLastError())); CloseHandle(hToken); GlobalFree(User); return FALSE; }
CloseHandle(hToken);
// The caller should free the buffer
*ppszResultTokenUser = User;
return TRUE; }
/////////////////////////////////
// ConvertSidToText
//
// Transforms a binary SID in string format
// Author Jeff Spelman
BOOL ConvertSidToText( PSID pSid, // binary Sid
LPTSTR TextualSid, // buffer for Textual representation of Sid
LPDWORD lpdwBufferLen // required/provided TextualSid buffersize
) { PSID_IDENTIFIER_AUTHORITY psia; DWORD dwSubAuthorities; DWORD dwSidRev=SID_REVISION; DWORD dwCounter; DWORD dwSidSize;
// Validate the binary SID.
if(!IsValidSid(pSid)) return FALSE;
// Get the identifier authority value from the SID.
psia = GetSidIdentifierAuthority(pSid);
// Get the number of subauthorities in the SID.
dwSubAuthorities = *GetSidSubAuthorityCount(pSid);
// Compute the buffer length.
// S-SID_REVISION- + IdentifierAuthority- + subauthorities- + NULL
dwSidSize=(15 + 12 + (12 * dwSubAuthorities) + 1) * sizeof(TCHAR);
// Check input buffer length.
// If too small, indicate the proper size and set last error.
if (*lpdwBufferLen < dwSidSize) { *lpdwBufferLen = dwSidSize; SetLastError(ERROR_INSUFFICIENT_BUFFER); return FALSE; }
// Add 'S' prefix and revision number to the string.
dwSidSize=wsprintf(TextualSid, TEXT("S-%lu-"), dwSidRev );
// Add SID identifier authority to the string.
if ( (psia->Value[0] != 0) || (psia->Value[1] != 0) ) { dwSidSize+=wsprintf(TextualSid + lstrlen(TextualSid), TEXT("0x%02hx%02hx%02hx%02hx%02hx%02hx"), (USHORT)psia->Value[0], (USHORT)psia->Value[1], (USHORT)psia->Value[2], (USHORT)psia->Value[3], (USHORT)psia->Value[4], (USHORT)psia->Value[5]); } else { dwSidSize+=wsprintf(TextualSid + lstrlen(TextualSid), TEXT("%lu"), (ULONG)(psia->Value[5] ) + (ULONG)(psia->Value[4] << 8) + (ULONG)(psia->Value[3] << 16) + (ULONG)(psia->Value[2] << 24) ); }
// Add SID subauthorities to the string.
//
for (dwCounter=0 ; dwCounter < dwSubAuthorities ; dwCounter++) { dwSidSize+=wsprintf(TextualSid + dwSidSize, TEXT("-%lu"), *GetSidSubAuthority(pSid, dwCounter) ); }
return TRUE; }
/////////////////////////////////
// CreateSessionKey
//
// Creates a session key derived from the user SID and a hint (currently the calling card ID)
//
//
BOOL CreateSessionKey(HCRYPTPROV hProv, LPTSTR pszSidText, DWORD dwHint, HCRYPTKEY *phKey) { HCRYPTHASH hHash = 0; CHAR szTmpBuff[0x20]; DWORD dwSize; LPSTR pszBuf; // Create a hash object
if(!CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash)) { DBGOUT((5, "CryptCreateHash failed, 0x%x, Prov=0x%x", GetLastError(), hProv)); return FALSE; } // the Sid is of type TCHAR but, for back compat reasons, we want to encrypt the ANSI
// version of this string. We can either:
// 1.) Convert the creation path for pszSid to ANSI (more correct solution)
// 2.) thunk pszSid to ansi before converting (lazy solution)
// I'm lazy so I'm choosing option #2. It should be safe to thunk to ANSI because the
// Sid should correctly round trip back to unicode.
dwSize = lstrlen(pszSidText)+1; pszBuf = (LPSTR)GlobalAlloc( GPTR, dwSize*sizeof(CHAR) ); if ( !pszBuf ) { // out of memory
CryptDestroyHash(hHash); return FALSE; } SHTCharToAnsi( pszSidText, pszBuf, dwSize );
#ifdef DEBUG
#ifdef UNICODE
{ // ensure that the SID round trips. If it doesn't round trip then the validity of this
// encription scheme is questionable on NT. The solution would be to encrypt using Unicode,
// but that would break back compat.
LPTSTR pszDebug; pszDebug = (LPTSTR)GlobalAlloc( GPTR, dwSize*sizeof(TCHAR) ); if ( pszDebug ) { SHAnsiToTChar(pszBuf, pszDebug, dwSize); if ( 0 != StrCmp( pszDebug, pszSidText ) ) { DBGOUT((1,"CRYPT ERROR! Sid doesn't round trip! FIX THIS!!!")); } GlobalFree(pszDebug); } } #endif
#endif
// hash the SID
if(!CryptHashData(hHash, (PBYTE)pszBuf, (dwSize)*sizeof(CHAR), 0)) { CryptDestroyHash(hHash); return FALSE; }
GlobalFree(pszBuf);
// hash a "magic" and the hint
ZeroMemory(szTmpBuff, sizeof(szTmpBuff));
wsprintfA(szTmpBuff, "-%c%c%c%c%c%x", MAGIC_1, MAGIC_2, MAGIC_3, MAGIC_4, MAGIC_5, dwHint); if(!CryptHashData(hHash, (PBYTE)szTmpBuff, sizeof(szTmpBuff), 0)) { CryptDestroyHash(hHash); return FALSE; } // Generate the key, use block alg
if(!CryptDeriveKey(hProv, CALG_RC2, hHash, 0, phKey)) { DBGOUT((5, "CryptDeriveKey failed, 0x%x", GetLastError())); CryptDestroyHash(hHash); return FALSE; } CryptDestroyHash(hHash); return TRUE;
}
/////////////////////////////////
// DestroySessionKey
//
// Destroys a session key
//
//
void DestroySessionKey(HCRYPTKEY hKey) { CryptDestroyKey(hKey); }
#endif //WINNT
// Old routines
#define IsWDigit(c) (((WCHAR)(c)) >= (WCHAR)'0' && ((WCHAR)(c)) <= (WCHAR)'9')
void Unscrambler( DWORD dwKey, LPWSTR lpszSrc, LPWSTR lpszDst )
{ UINT uIndex; UINT uSubKey; UINT uNewKey;
// InternalDebugOut((101, "Entering Unscrambler"));
if ( !lpszSrc || !lpszDst ) { goto done; }
uNewKey = (UINT)dwKey & 0x7FFF; uSubKey = (UINT)dwKey % 10;
for ( uIndex = 1; *lpszSrc ; lpszSrc++, lpszDst++, uIndex++ ) { if ( IsWDigit( *lpszSrc )) { // do the unscramble thang
//------------------------
uSubKey = ((*lpszSrc - (WCHAR)'0') - ((uSubKey + uIndex + uNewKey) % 10) + 10) % 10; *lpszDst = (WCHAR)(uSubKey + (WCHAR)'0'); } else *lpszDst = *lpszSrc; // just save the byte
}
done: *lpszDst = (WCHAR)'\0'; //InternalDebugOut((101, "Leaving Unscrambler"));
return; }
void CopyScrambled( LPWSTR lpszSrc, LPWSTR lpszDst, DWORD dwKey ) { UINT uIndex; UINT uSubKey; UINT uNewKey;
//nternalDebugOut((50, "Entering IniScrambler"));
if ( !lpszSrc || !lpszDst ) { goto done; } // end if
uNewKey = (UINT)dwKey & 0x7FFF; uSubKey = (UINT)dwKey % 10;
for ( uIndex = 1; *lpszSrc ; lpszSrc++, lpszDst++, uIndex++ ) { if ( IsWDigit( *lpszSrc )) { // do the scramble thang
//----------------------
*lpszDst = (WCHAR)(((uSubKey + (*lpszSrc - (WCHAR)'0') + uIndex + uNewKey) % 10) + (WCHAR)'0'); uSubKey = (UINT)(*lpszSrc - (WCHAR)'0'); } else *lpszDst = *lpszSrc; // just save the byte
} // end for
done:
*lpszDst = (WCHAR)'\0'; // InternalDebugOut((60, "Leaving IniScrambler"));
return; }
|