windows-nt/Source/XPSP1/NT/net/tapi/tregupr2/crypt.c
2020-09-26 16:20:57 +08:00

888 lines
24 KiB
C

/****************************************************************************
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;
}