/* ** p r o p c r y p . c p p ** ** Purpose: ** Functions to provide blob-level access to the pstore ** ** History ** 3/04/97: (t-erikne) support for non-pstore systems ** 2/15/97: (t-erikne) rewritten for pstore ** 12/04/96: (sbailey) created ** ** Copyright (C) Microsoft Corp. 1996, 1997. */ #include "pch.hxx" #include "propcryp.h" #include #include /////////////////////////////////////////////////////////////////////////// // // Structures, definitions // #define OBFUSCATOR 0x14151875; #define PROT_SIZEOF_HEADER 0x02 // 2 bytes in the header #define PROT_SIZEOF_XORHEADER (PROT_SIZEOF_HEADER+sizeof(DWORD)) #define PROT_VERSION_1 0x01 #define PROT_PASS_XOR 0x01 #define PROT_PASS_PST 0x02 // Layout of registry data (v0) // // /-------------------------------- // | protected store name, a LPWSTR // \-------------------------------- // // // Layout of registry data (v1) // // /---------------------------------------------------------------------- // | version (1 b) =0x01 | type (1 b) =PROT_PASS_* | data (see below) // \---------------------------------------------------------------------- // // data for PROT_PASS_PST // struct _data // { LPWSTR szPSTItemName; } // data for PROT_PASS_XOR // struct _data // { DWORD cb; BYTE pb[cb]; } // /////////////////////////////////////////////////////////////////////////// // // Prototypes // static inline BOOL FDataIsValidV0(BLOB *pblob); static BOOL FDataIsValidV1(BYTE *pb); static inline BOOL FDataIsPST(BYTE *pb); static HRESULT XOREncodeProp(const BLOB *const pClear, BLOB *const pEncoded); static HRESULT XORDecodeProp(const BLOB *const pEncoded, BLOB *const pClear); /////////////////////////////////////////////////////////////////////////// // // Admin functions (init, addref, release, ctor, dtor) // HRESULT HrCreatePropCrypt(CPropCrypt **ppPropCrypt) { *ppPropCrypt = new CPropCrypt(); if (NULL == *ppPropCrypt) return TRAPHR(E_OUTOFMEMORY); return (*ppPropCrypt)->HrInit(); } CPropCrypt::CPropCrypt(void) : m_cRef(1), m_fInit(FALSE), m_pISecProv(NULL) { } CPropCrypt::~CPropCrypt(void) { ReleaseObj(m_pISecProv); } ULONG CPropCrypt::AddRef(void) { return ++m_cRef; } ULONG CPropCrypt::Release(void) { if (0 != --m_cRef) return m_cRef; delete this; return 0; } HRESULT CPropCrypt::HrInit(void) { HRESULT hr; PST_PROVIDERID provId = MS_BASE_PSTPROVIDER_ID; Assert(!m_pISecProv); if (FAILED(hr = PStoreCreateInstance(&m_pISecProv, &provId, NULL, 0))) { // this is true because we will now handle // all transactions without the protected store m_fInit = TRUE; hr = S_OK; } else if (SUCCEEDED(hr = PSTCreateTypeSubType_NoUI( m_pISecProv, &PST_IDENT_TYPE_GUID, PST_IDENT_TYPE_STRING, &PST_IMNACCT_SUBTYPE_GUID, PST_IMNACCT_SUBTYPE_STRING))) { m_fInit = TRUE; } return hr; } /////////////////////////////////////////////////////////////////////////// // // Public encode/decode/delete functions // /////////////////////////////////////////////////////////////////////////// HRESULT CPropCrypt::HrEncodeNewProp(LPSTR szAccountName, BLOB *pClear, BLOB *pEncoded) { HRESULT hr = S_OK; const int cchFastbuf = 50; WCHAR szWfast[cchFastbuf]; LPWSTR szWalloc = NULL; LPWSTR wszCookie = NULL; BLOB blob; DWORD dwErr; int cchW; AssertSz (pClear && pEncoded, "Null Parameter"); pEncoded->pBlobData = NULL; if (m_fInit == FALSE) return TRAPHR(E_FAIL); if (!m_pISecProv) { // protected store does not exist hr = XOREncodeProp(pClear, pEncoded); goto exit; } if (szAccountName) { if (!MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, szAccountName, -1, szWfast, cchFastbuf)) { dwErr = GetLastError(); if (ERROR_INSUFFICIENT_BUFFER == dwErr) { // get proper size and alloc buffer cchW = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, szAccountName, -1, NULL, 0); if (FAILED(hr = HrAlloc((LPVOID *)&szWalloc, cchW*sizeof(WCHAR)))) goto exit; if (!(MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, szAccountName, -1, szWalloc, cchW))) { hr = GetLastError(); goto exit; } } else { hr = dwErr; goto exit; } } } else { szWfast[0] = '\000'; } if (SUCCEEDED(hr = PSTSetNewData(m_pISecProv, &PST_IDENT_TYPE_GUID, &PST_IMNACCT_SUBTYPE_GUID, szWalloc?szWalloc:szWfast, pClear, pEncoded))) { BYTE *pb = pEncoded->pBlobData; DWORD sz = pEncoded->cbSize; Assert(pb); pEncoded->cbSize += PROT_SIZEOF_HEADER; //N This realloc is annoying. If we assume the memory allocator used //N by the PST function, we could be smarter.... if (FAILED(hr = HrAlloc((LPVOID *)&pEncoded->pBlobData, pEncoded->cbSize))) goto exit; pEncoded->pBlobData[0] = PROT_VERSION_1; pEncoded->pBlobData[1] = PROT_PASS_PST; Assert(2 == PROT_SIZEOF_HEADER); CopyMemory(&pEncoded->pBlobData[PROT_SIZEOF_HEADER], pb, sz); PSTFreeHandle(pb); } exit: if (szWalloc) MemFree(szWalloc); if (FAILED(hr) && pEncoded->pBlobData) MemFree(pEncoded->pBlobData); return hr; } HRESULT CPropCrypt::HrEncode(BLOB *pClear, BLOB *pEncoded) { HRESULT hr; PST_PROMPTINFO PromptInfo = { sizeof(PST_PROMPTINFO), 0, NULL, L""}; AssertSz (pClear && pEncoded && pClear->pBlobData && pClear->cbSize, "Null Parameter"); if (m_fInit == FALSE) return TRAPHR(E_FAIL); if (m_pISecProv) { if (FDataIsValidV1(pEncoded->pBlobData) && FDataIsPST(pEncoded->pBlobData)) { Assert(pEncoded->cbSize-PROT_SIZEOF_HEADER == (lstrlenW((LPWSTR)(pEncoded->pBlobData+PROT_SIZEOF_HEADER))+1)*sizeof(WCHAR)); tryagain: hr = m_pISecProv->WriteItem( PST_KEY_CURRENT_USER, &PST_IDENT_TYPE_GUID, &PST_IMNACCT_SUBTYPE_GUID, (LPCWSTR)&pEncoded->pBlobData[PROT_SIZEOF_HEADER], (DWORD)pClear->cbSize, pClear->pBlobData, &PromptInfo, PST_CF_NONE, 0); if (PST_E_TYPE_NO_EXISTS == hr) { DOUTL(DOUTL_CPROP, "PropCryp: somebody ruined my type or subtype"); hr = PSTCreateTypeSubType_NoUI( m_pISecProv, &PST_IDENT_TYPE_GUID, PST_IDENT_TYPE_STRING, &PST_IMNACCT_SUBTYPE_GUID, PST_IMNACCT_SUBTYPE_STRING); if (SUCCEEDED(hr)) goto tryagain; } } else { #ifdef DEBUG if (FDataIsValidV0(pEncoded)) DOUTL(DOUTL_CPROP, "PropCryp: V0 to V1 upgrade"); else if (!FDataIsValidV1(pEncoded->pBlobData)) DOUTL(DOUTL_CPROP, "PropCryp: invalid data on save"); #endif // now we have XOR data in a PST environment hr = HrEncodeNewProp(NULL, pClear, pEncoded); } } else { // protected store does not exist hr = XOREncodeProp(pClear, pEncoded); } return TrapError(hr); } /* HrDecode: ** ** Purpose: ** Uses the protstor functions to retrieve a piece of secure data ** unless the data is not pstore, then it maps to the XOR function ** Takes: ** IN pEncoded - blob containing name to pass to PSTGetData ** OUT pClear - blob containing property data ** Notes: ** pBlobData in pClear must be freed with a call to CoTaskMemFree() ** Returns: ** hresult */ HRESULT CPropCrypt::HrDecode(BLOB *pEncoded, BLOB *pClear) { HRESULT hr; AssertSz(pEncoded && pEncoded->pBlobData && pClear, TEXT("Null Parameter")); pClear->pBlobData = NULL; if (m_fInit == FALSE) return TRAPHR(E_FAIL); if (!FDataIsValidV1(pEncoded->pBlobData)) { if (FDataIsValidV0(pEncoded)) { DOUTL(DOUTL_CPROP, "PropCryp: obtaining v0 value"); // looks like we might have a v0 blob: the name string hr = PSTGetData(m_pISecProv, &PST_IDENT_TYPE_GUID, &PST_IMNACCT_SUBTYPE_GUID, (LPCWSTR)pEncoded->pBlobData, pClear); } else hr = E_InvalidValue; } else if (FDataIsPST(pEncoded->pBlobData)) { Assert(pEncoded->cbSize-PROT_SIZEOF_HEADER == (lstrlenW((LPWSTR)(pEncoded->pBlobData+PROT_SIZEOF_HEADER))+1)*sizeof(WCHAR)); hr = PSTGetData(m_pISecProv, &PST_IDENT_TYPE_GUID, &PST_IMNACCT_SUBTYPE_GUID, (LPCWSTR)&pEncoded->pBlobData[PROT_SIZEOF_HEADER], pClear); } else { hr = XORDecodeProp(pEncoded, pClear); } return hr; } HRESULT CPropCrypt::HrDelete(BLOB *pProp) { HRESULT hr; PST_PROMPTINFO PromptInfo = { sizeof(PST_PROMPTINFO), 0, NULL, L""}; if (m_fInit == FALSE) return TRAPHR(E_FAIL); if (m_pISecProv && FDataIsValidV1(pProp->pBlobData) && FDataIsPST(pProp->pBlobData)) { Assert(pProp->cbSize-PROT_SIZEOF_HEADER == (lstrlenW((LPWSTR)(pProp->pBlobData+PROT_SIZEOF_HEADER))+1)*sizeof(WCHAR)); hr = m_pISecProv->DeleteItem( PST_KEY_CURRENT_USER, &PST_IDENT_TYPE_GUID, &PST_IMNACCT_SUBTYPE_GUID, (LPCWSTR)&pProp->pBlobData[PROT_SIZEOF_HEADER], &PromptInfo, 0); } else // nothing to do hr = S_OK; return hr; } /////////////////////////////////////////////////////////////////////////// // // XOR functions // /////////////////////////////////////////////////////////////////////////// HRESULT XOREncodeProp(const BLOB *const pClear, BLOB *const pEncoded) { DWORD dwSize; DWORD last, last2; DWORD *pdwCypher; DWORD dex; pEncoded->cbSize = pClear->cbSize+PROT_SIZEOF_XORHEADER; if (!MemAlloc((LPVOID *)&pEncoded->pBlobData, pEncoded->cbSize)) return E_OUTOFMEMORY; // set up header data Assert(2 == PROT_SIZEOF_HEADER); pEncoded->pBlobData[0] = PROT_VERSION_1; pEncoded->pBlobData[1] = PROT_PASS_XOR; *((DWORD *)&(pEncoded->pBlobData[2])) = pClear->cbSize; // nevermind that the pointer is offset by the header size, this is // where we start to write out the modified password pdwCypher = (DWORD *)&(pEncoded->pBlobData[PROT_SIZEOF_XORHEADER]); dex = 0; last = OBFUSCATOR; // 0' = 0 ^ ob if (dwSize = pClear->cbSize / sizeof(DWORD)) { // case where data is >= 4 bytes for (; dex < dwSize; dex++) { last2 = ((DWORD *)pClear->pBlobData)[dex]; // 1 pdwCypher[dex] = last2 ^ last; // 1' = 1 ^ 0 last = last2; // save 1 for the 2 round } } // if we have bits left over // note that dwSize is computed now in bits if (dwSize = (pClear->cbSize % sizeof(DWORD))*8) { // need to not munge memory that isn't ours last >>= sizeof(DWORD)*8-dwSize; pdwCypher[dex] &= ((DWORD)-1) << dwSize; pdwCypher[dex] |= ((((DWORD *)pClear->pBlobData)[dex] & (((DWORD)-1) >> (sizeof(DWORD)*8-dwSize))) ^ last); } return S_OK; } HRESULT XORDecodeProp(const BLOB *const pEncoded, BLOB *const pClear) { DWORD dwSize; DWORD last; DWORD *pdwCypher; DWORD dex; // we use CoTaskMemAlloc to be in line with the PST implementation pClear->cbSize = pEncoded->pBlobData[2]; if (!(pClear->pBlobData = (BYTE *)CoTaskMemAlloc(pClear->cbSize))) return E_OUTOFMEMORY; // should have been tested by now Assert(FDataIsValidV1(pEncoded->pBlobData)); Assert(!FDataIsPST(pEncoded->pBlobData)); // nevermind that the pointer is offset by the header size, this is // where the password starts pdwCypher = (DWORD *)&(pEncoded->pBlobData[PROT_SIZEOF_XORHEADER]); dex = 0; last = OBFUSCATOR; if (dwSize = pClear->cbSize / sizeof(DWORD)) { // case where data is >= 4 bytes for (; dex < dwSize; dex++) last = ((DWORD *)pClear->pBlobData)[dex] = pdwCypher[dex] ^ last; } // if we have bits left over if (dwSize = (pClear->cbSize % sizeof(DWORD))*8) { // need to not munge memory that isn't ours last >>= sizeof(DWORD)*8-dwSize; ((DWORD *)pClear->pBlobData)[dex] &= ((DWORD)-1) << dwSize; ((DWORD *)pClear->pBlobData)[dex] |= ((pdwCypher[dex] & (((DWORD)-1) >> (sizeof(DWORD)*8-dwSize))) ^ last); } return S_OK; } /////////////////////////////////////////////////////////////////////////// // // Other static functions // /////////////////////////////////////////////////////////////////////////// BOOL FDataIsValidV1(BYTE *pb) { return pb && pb[0] == PROT_VERSION_1 && (pb[1] == PROT_PASS_XOR || pb[1] == PROT_PASS_PST); } BOOL FDataIsValidV0(BLOB *pblob) { return ((lstrlenW((LPWSTR)pblob->pBlobData)+1)*sizeof(WCHAR) == pblob->cbSize); } BOOL FDataIsPST(BYTE *pb) #ifdef DEBUG { if (pb) if (pb[1] == PROT_PASS_PST) { DOUTL(DOUTL_CPROP, "PropCryp: Data is PST"); return TRUE; } else { DOUTL(DOUTL_CPROP, "PropCryp: Data is XOR"); return FALSE; } else return FALSE; } #else { return pb && pb[1] == PROT_PASS_PST; } #endif