You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1220 lines
31 KiB
1220 lines
31 KiB
// Copyright (c) 2002 Microsoft Corporation
|
|
//
|
|
// Encrypted string class
|
|
//
|
|
// 18 March 2002
|
|
|
|
|
|
|
|
#ifndef _PUBLIC_ENCRYPTEDSTRING_
|
|
#define _PUBLIC_ENCRYPTEDSTRING_
|
|
|
|
#include <strsafe.h>
|
|
#include <stddef.h> // For size_t.
|
|
#include <wincrypt.h> // For DATA_BLOB.
|
|
|
|
//
|
|
// A class that has a similar public interface as class Burnslib::String, but
|
|
// is represented as an encrypted blob, produced by data protection API.
|
|
//
|
|
// This class can be used to represent sensitive data like password strings
|
|
// in-memory, instead of holding them as cleartext, which is a security hole
|
|
// if the memory pages are swapped to disk, the machine is hibernated, etc.
|
|
//
|
|
// You should convert any cleartext data into an instance of this class, then
|
|
// scribble out your cleartext source copy with SecureZeroMemory.
|
|
|
|
//
|
|
// This implementation has the following requirements of the code in which it
|
|
// is used:
|
|
// a) An ASSERT() macro must be defined.
|
|
// b) It uses functions defined in strsafe.h. This header file deprecates many
|
|
// "unsafe" functions, and your compiler will let you know as much if you have any.
|
|
// If you do not want to change occurrences of unsafe functions in your code you can
|
|
// '#define STRSAFE_NO_DEPRECATE' to get your program to compile cleanly.
|
|
//
|
|
|
|
//
|
|
// Low memory behavior:
|
|
//
|
|
// - Encrypt() will return E_OUTOFMEMORY and the encrypted string will be set to empty.
|
|
// You can check for low memory by checking error code or by calling IsEmpty().
|
|
//
|
|
// - GetClearTextCopy() will return NULL on failure. The encrypted string itself will not
|
|
// be changed.
|
|
//
|
|
// - Copy constructor will set the newly constructed string to empty if there is not enough
|
|
// memory. Check with IsEmpty().
|
|
//
|
|
// - Assignment operator will similarly set the assignee (left hand side string) to empty if
|
|
// there is not enough memory. Check with IsEmpty().
|
|
//
|
|
// - Default constructor does not allocate any memory. If there is no stack memory there
|
|
// will be a stack overflow, and your program will definitely be notified. If there is no
|
|
// heap memory the call to the default constructor will fail before the constructor is
|
|
// entered. Be sure to check for NULL if you are allocating objects on the heap.
|
|
//
|
|
// **Of course if the clear text string is an empty string then the encrypted version will
|
|
// also be empty (IsEmpty() will return true). This is a pretty easy check to make when
|
|
// there would be any ambiguity.
|
|
//
|
|
|
|
//
|
|
// NB:
|
|
//
|
|
// By default this class uses Crypt[Un]ProtectMemory() to handle encryption. These functions
|
|
// are only available as of .NET Server. If your component might be installed on an OS
|
|
// predating .NET Server you can '#define ENCRYPT_WITH_CRYPTPROTECTDATA' to get an
|
|
// EncryptedString that will run on W2K and XP. Define the symbol before you include the
|
|
// header file.
|
|
//
|
|
// CryptProtectMemory() is the more robust API in that it will succeed even if the user's
|
|
// profile cannot be loaded. It is more suited to encrypting temporary buffers in memory.
|
|
// Regardless of which encryption mechanism you enable, be sure to check return values to
|
|
// see if encryption failed. If it fails, you basically have an empty string!
|
|
//
|
|
|
|
//
|
|
// MFC code uses DDX_* functions to perform data binding b/w dialogs and underlying
|
|
// data. Since none of these work with EncryptedString, we define our own. If you
|
|
// need this, just '#define _DDX_ENCRYPTED'.
|
|
//
|
|
// There are also a pair of utility functions to get and set encrypted data in a
|
|
// dialog window.
|
|
//
|
|
|
|
#ifdef _DDX_ENCRYPTED
|
|
|
|
class EncryptedString;
|
|
|
|
// Neither parameter is declared constant b/c (depending on direction of
|
|
// data exchange) either one can be updated.
|
|
inline HRESULT
|
|
DDX_EncryptedText(CDataExchange* pDX, int nIDC, EncryptedString& buff);
|
|
|
|
#include <windows.h>
|
|
|
|
inline HRESULT
|
|
GetEncryptedDlgItemText(HWND parentDialog, int itemResID, EncryptedString& cypher);
|
|
|
|
inline HRESULT
|
|
SetDlgItemText(HWND parentDialog, int itemResID, const EncryptedString& cypher);
|
|
|
|
#endif //_DDX_ENCRYPTED
|
|
|
|
|
|
|
|
class EncryptedString
|
|
{
|
|
public:
|
|
|
|
// constructs an empty string.
|
|
|
|
inline explicit
|
|
EncryptedString(void);
|
|
|
|
|
|
|
|
// constructs a copy of an existing, already encrypted string
|
|
|
|
inline
|
|
EncryptedString(const EncryptedString& rhs);
|
|
|
|
|
|
|
|
// scribbles out the text, and deletes it.
|
|
|
|
~EncryptedString()
|
|
{
|
|
// when the object dies, it had better not have any outstanding
|
|
// cleartext copies.
|
|
|
|
ASSERT(m_cleartextCopyCount == 0);
|
|
|
|
Reset();
|
|
}
|
|
|
|
|
|
|
|
// Scribbles out and de-allocates a clear text copy of the string. The
|
|
// caller is required to balance each call to GetClearTextCopy with a call
|
|
// to DestroyClearTextCopy lest he leak memory and leave cleartext hanging
|
|
// around.
|
|
//
|
|
// cleartext - the same pointer returned by a previous call to
|
|
// GetClearTextCopy on the same instance.
|
|
|
|
inline void
|
|
DestroyClearTextCopy(WCHAR* cleartext) const;
|
|
|
|
|
|
|
|
// Extracts the decoded cleartext representation of the text, including
|
|
// null terminator. The caller must invoke DestroyClearTextCopy on the
|
|
// result, even if the result is null.
|
|
//
|
|
// May return null on error.
|
|
//
|
|
// Example:
|
|
// WCHAR* cleartext = encrypted.GetClearTextCopy();
|
|
// if (cleartext)
|
|
// {
|
|
// // ... use the cleartext
|
|
// }
|
|
// encrypted.DestroyClearTextCopy(cleartext);
|
|
|
|
inline WCHAR*
|
|
GetClearTextCopy() const;
|
|
|
|
|
|
|
|
// Returns true if the string is zero-length, false if not.
|
|
|
|
bool
|
|
IsEmpty() const
|
|
{
|
|
return (GetLength() == 0);
|
|
}
|
|
|
|
|
|
// Sets the contents of self to the encrypted representation of the
|
|
// cleartext, replacing the previous value of self. The encrypted
|
|
// representation will be the same length, in characters, as the
|
|
// cleartext.
|
|
//
|
|
// clearText - in, un-encrypted text to be encrypted. May be empty string,
|
|
// but not a null pointer. The clearText must be null terminated.
|
|
//
|
|
// length - The character length of the clear text, not including null
|
|
// termination. The count should be in unicode characters.
|
|
//
|
|
// Returns S_OK on successful encryption, error code otherwise. If low
|
|
// memory causes failure the code will be E_OUTOFMEMORY.
|
|
|
|
inline HRESULT
|
|
Encrypt(const WCHAR* cleartext, size_t length);
|
|
|
|
inline HRESULT
|
|
Encrypt(const WCHAR* clearText);
|
|
|
|
|
|
|
|
// Returns the length, in unicode characters, of the cleartext, not
|
|
// including the null terminator.
|
|
|
|
inline size_t
|
|
GetLength() const;
|
|
|
|
|
|
void
|
|
Clear()
|
|
{
|
|
// when the object dies, it had better not have any outstanding
|
|
// cleartext copies.
|
|
|
|
ASSERT(m_cleartextCopyCount == 0);
|
|
|
|
Reset();
|
|
}
|
|
|
|
|
|
// Replaces the contents of self with a copy of the contents of rhs.
|
|
// Returns *this.
|
|
|
|
inline const EncryptedString&
|
|
operator= (const EncryptedString& rhs);
|
|
|
|
|
|
|
|
// Compares the cleartext representations of self and rhs, and returns
|
|
// true if they are lexicographically the same: the lengths are the same
|
|
// and all the characters are the same.
|
|
//
|
|
// If the program runs out of memory in this method it will always return
|
|
// false since we will be unable to determine if the strings are equal.
|
|
|
|
inline bool
|
|
operator== (const EncryptedString& rhs) const;
|
|
|
|
|
|
|
|
bool
|
|
operator!= (const EncryptedString& rhs) const
|
|
{
|
|
return !(operator==(rhs));
|
|
}
|
|
|
|
|
|
private:
|
|
|
|
inline HRESULT
|
|
InternalEncrypt(const WCHAR* clearText, size_t length);
|
|
|
|
inline WCHAR*
|
|
InternalDecrypt() const;
|
|
|
|
inline void
|
|
InternalDestroy(WCHAR* cleartext) const;
|
|
|
|
inline void
|
|
Reset();
|
|
|
|
bool
|
|
IsInNullBlobState() const;
|
|
|
|
inline static DWORD CalculateBlobSize(DWORD dataSize);
|
|
inline static HRESULT CopyBlob(DATA_BLOB& dest, const DATA_BLOB& source);
|
|
inline static void DestroyBlob(DATA_BLOB& blob);
|
|
inline static HRESULT EncryptData(const DATA_BLOB& clearText, DATA_BLOB& cypherText);
|
|
inline static HRESULT DecryptData(const DATA_BLOB& cypherText, DATA_BLOB& clearText);
|
|
|
|
|
|
// We deliberately do not implement conversion to or from WCHAR*.
|
|
// This is to force the user of the class to be very deliberate
|
|
// about decoding the string.
|
|
|
|
// deliberately commented out
|
|
//operator WCHAR* ();
|
|
|
|
|
|
size_t m_clearTextLength;
|
|
|
|
|
|
// In the course of en[de]crypting, and assigning to the instance, we
|
|
// may tweak the members of cypherBlob, but logically the string is still
|
|
// "const"
|
|
|
|
mutable DATA_BLOB m_cypherBlob;
|
|
|
|
// In debug versions there will be extra checking to make sure that
|
|
// all of the clear text copies are zeroed and freed.
|
|
#ifdef DBG
|
|
// a logically const instance may have cleartext copies made
|
|
|
|
mutable int m_cleartextCopyCount;
|
|
#endif
|
|
};
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////
|
|
// Implementation
|
|
////////////////////////////////////////////////////
|
|
|
|
inline HRESULT
|
|
GetLastErrorAsHresult(void)
|
|
{
|
|
DWORD err = GetLastError();
|
|
return HRESULT_FROM_WIN32(err);
|
|
}
|
|
|
|
|
|
#ifdef _DDX_ENCRYPTED
|
|
|
|
//
|
|
// Get the text from a dialog into an encrypted string.
|
|
//
|
|
inline HRESULT
|
|
GetEncryptedDlgItemText(HWND parentDialog, int itemResID, EncryptedString& cypher)
|
|
{
|
|
ASSERT(IsWindow(parentDialog));
|
|
ASSERT(itemResID > 0);
|
|
|
|
HRESULT err = S_OK;
|
|
WCHAR* cleartext = 0;
|
|
int length = 0;
|
|
|
|
do
|
|
{
|
|
HWND item = GetDlgItem(parentDialog, itemResID);
|
|
if (!item)
|
|
{
|
|
err = GetLastErrorAsHresult();
|
|
break;
|
|
}
|
|
|
|
length = GetWindowTextLengthW(item);
|
|
if (length < 0)
|
|
{
|
|
err = GetLastErrorAsHresult();
|
|
break;
|
|
}
|
|
|
|
// add 1 to length for null-terminator
|
|
|
|
++length;
|
|
cleartext = new WCHAR[length];
|
|
if (NULL == cleartext)
|
|
{
|
|
err = E_OUTOFMEMORY;
|
|
break;
|
|
}
|
|
|
|
ZeroMemory(cleartext, length * sizeof WCHAR);
|
|
|
|
// length includes space for null terminator
|
|
// which is correct for this call.
|
|
|
|
int result = GetWindowText(item, cleartext, length);
|
|
|
|
ASSERT(result == length - 1);
|
|
|
|
if (result < 0)
|
|
{
|
|
err = GetLastErrorAsHresult();
|
|
break;
|
|
}
|
|
|
|
err = cypher.Encrypt(cleartext);
|
|
}
|
|
while (0);
|
|
|
|
// make sure we scribble out the cleartext.
|
|
|
|
if (cleartext)
|
|
{
|
|
SecureZeroMemory(cleartext, length * sizeof WCHAR);
|
|
delete[] cleartext;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
//
|
|
// Set the text in a dialog from an encrypted string.
|
|
//
|
|
inline HRESULT
|
|
SetDlgItemText(
|
|
HWND parentDialog,
|
|
int itemResID,
|
|
const EncryptedString& cypherText)
|
|
{
|
|
ASSERT(IsWindow(parentDialog));
|
|
ASSERT(itemResID > 0);
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
WCHAR* cleartext = cypherText.GetClearTextCopy();
|
|
|
|
if (NULL != cleartext)
|
|
{
|
|
BOOL succeeded = SetDlgItemText(parentDialog, itemResID, cleartext);
|
|
if (!succeeded)
|
|
{
|
|
hr = GetLastErrorAsHresult();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
|
|
cypherText.DestroyClearTextCopy(cleartext);
|
|
|
|
// This shouldn't fail under any normal circumstances.
|
|
ASSERT(SUCCEEDED(hr));
|
|
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
// DDX_EncryptedText:
|
|
//
|
|
// Function performs data binding between a dialog text control and an
|
|
// encrypted string.
|
|
//
|
|
// For information on DDX_* functions and how they work, see
|
|
// "Technical Note 26" in MSDN documentation.
|
|
//
|
|
// History:
|
|
// 2002-04-04 artm Created.
|
|
//
|
|
inline HRESULT DDX_EncryptedText(CDataExchange* pDX, int nIDC, EncryptedString& buff)
|
|
{
|
|
HRESULT err = S_OK;
|
|
|
|
do { // false while loop
|
|
|
|
// Make sure that we actually have a window with which to
|
|
// exchange data.
|
|
if (NULL == pDX ||
|
|
NULL == pDX->m_pDlgWnd)
|
|
{
|
|
err = E_INVALIDARG;
|
|
break;
|
|
}
|
|
|
|
HWND hWnd = pDX->m_pDlgWnd->GetSafeHwnd();
|
|
if (NULL == hWnd)
|
|
{
|
|
err = E_INVALIDARG;
|
|
break;
|
|
}
|
|
|
|
// Check which direction the data exchange is happening.
|
|
if (pDX->m_bSaveAndValidate)
|
|
{
|
|
//
|
|
// Save the text in the dialog to the encrypted buffer.
|
|
//
|
|
err = GetEncryptedDlgItemText(hWnd, nIDC, buff);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Refresh the text in the dialog from the encrypted buffer.
|
|
//
|
|
err = SetDlgItemText(hWnd, nIDC, buff);
|
|
}
|
|
|
|
} while(false);
|
|
|
|
return err;
|
|
}
|
|
#endif //_DDX_ENCRYPTED
|
|
|
|
|
|
|
|
inline
|
|
EncryptedString::EncryptedString(void)
|
|
:
|
|
m_clearTextLength(0)
|
|
#ifdef DBG
|
|
,m_cleartextCopyCount(0)
|
|
#endif
|
|
{
|
|
::ZeroMemory(&m_cypherBlob, sizeof m_cypherBlob);
|
|
|
|
ASSERT(IsInNullBlobState());
|
|
}
|
|
|
|
|
|
//
|
|
// CalculateBlobSize():
|
|
//
|
|
// Given the size of the data in bytes that must fit in the blob,
|
|
// calculates how large the blob must be to hold that data and be
|
|
// a multiple of CRYPTPROTECTMEMORY_BLOCK_SIZE.
|
|
//
|
|
// History:
|
|
// 2002-06-08 artm Created (NTRAID#NTBUG9-635046)
|
|
//
|
|
inline DWORD
|
|
EncryptedString::CalculateBlobSize(DWORD dataSize)
|
|
{
|
|
// Round the dataSize down to a multiple of the block size.
|
|
DWORD blobSize =
|
|
(dataSize / CRYPTPROTECTMEMORY_BLOCK_SIZE) * CRYPTPROTECTMEMORY_BLOCK_SIZE;
|
|
|
|
// If blobSize cannot hold dataSize, increase blobSize by one memory block.
|
|
if (blobSize < dataSize)
|
|
{
|
|
blobSize += CRYPTPROTECTMEMORY_BLOCK_SIZE;
|
|
}
|
|
|
|
ASSERT(blobSize >= dataSize);
|
|
|
|
return blobSize;
|
|
}
|
|
|
|
|
|
//
|
|
// CopyBlob():
|
|
//
|
|
// Copies source to destination (make sure that you've freed any
|
|
// memory tied to destination before calling). If copy is
|
|
// successful returns S_OK. If memory cannot be allocated then
|
|
// returns E_OUTOFMEMORY and destination is set to an empty blob.
|
|
//
|
|
inline HRESULT
|
|
EncryptedString::CopyBlob(DATA_BLOB& dest, const DATA_BLOB& source)
|
|
{
|
|
HRESULT err = S_OK;
|
|
dest.cbData = source.cbData;
|
|
dest.pbData = 0;
|
|
|
|
if (dest.cbData)
|
|
{
|
|
// copy the blob data. We use LocalAlloc because that's what
|
|
// CryptProtectData does, thus Reset() can use LocalFree no matter
|
|
// how the blob was populated.
|
|
|
|
HLOCAL local = ::LocalAlloc(LPTR, dest.cbData);
|
|
if (local)
|
|
{
|
|
dest.pbData = (BYTE*) local;
|
|
|
|
::CopyMemory(
|
|
dest.pbData,
|
|
source.pbData,
|
|
source.cbData);
|
|
}
|
|
else
|
|
{
|
|
// out of memory means you get an empty instance
|
|
|
|
dest.cbData = 0;
|
|
dest.pbData = 0;
|
|
|
|
err = E_OUTOFMEMORY;
|
|
}
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
inline void
|
|
EncryptedString::DestroyBlob(DATA_BLOB& blob)
|
|
{
|
|
if (blob.cbData)
|
|
{
|
|
ASSERT(blob.pbData);
|
|
|
|
if (blob.pbData)
|
|
{
|
|
// We want to zero out the blob in case it held
|
|
// clear text data.
|
|
SecureZeroMemory(blob.pbData, blob.cbData);
|
|
::LocalFree(blob.pbData);
|
|
}
|
|
|
|
blob.pbData = 0;
|
|
blob.cbData = 0;
|
|
}
|
|
}
|
|
|
|
|
|
inline
|
|
EncryptedString::EncryptedString(const EncryptedString& rhs)
|
|
:
|
|
m_clearTextLength(rhs.m_clearTextLength)
|
|
#ifdef DBG
|
|
// although the rhs instance may have outstanding copies, we don't
|
|
,m_cleartextCopyCount(0)
|
|
#endif
|
|
{
|
|
// Copy the blob, which is already encrypted.
|
|
|
|
HRESULT hr = CopyBlob(m_cypherBlob, rhs.m_cypherBlob);
|
|
|
|
// Can't return error from constructor, but maybe we can detect program
|
|
// bugs during debug build.
|
|
ASSERT(SUCCEEDED(hr));
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
Reset();
|
|
}
|
|
|
|
#ifdef DBG
|
|
if (rhs.IsInNullBlobState())
|
|
{
|
|
ASSERT(IsInNullBlobState());
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
inline const EncryptedString&
|
|
EncryptedString::operator= (const EncryptedString& rhs)
|
|
{
|
|
// don't reset the instance unless you have destroyed all the cleartext
|
|
// copies. We assert this before checking for a=a, because the caller
|
|
// still has a logic error even if the result is "harmless"
|
|
|
|
ASSERT(m_cleartextCopyCount == 0);
|
|
|
|
// handle the a = a case.
|
|
|
|
if (this == &rhs)
|
|
{
|
|
return *this;
|
|
}
|
|
|
|
// dump our old contents
|
|
|
|
Reset();
|
|
|
|
HRESULT hr = CopyBlob(m_cypherBlob, rhs.m_cypherBlob);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
m_clearTextLength = rhs.m_clearTextLength;
|
|
}
|
|
else
|
|
{
|
|
// Copy failed! That means we have an empty blob.
|
|
// Reset() sets length to 0, so we don't need to
|
|
// do it here.
|
|
|
|
// Alert programmer in debug build. This function has
|
|
// no error code, so nothing we can do in release build.
|
|
ASSERT(false);
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
|
|
|
|
// scribbles out and frees the internal string.
|
|
|
|
inline void
|
|
EncryptedString::Reset()
|
|
{
|
|
// don't reset the instance unless you have destroyed all the cleartext
|
|
// copies.
|
|
|
|
ASSERT(m_cleartextCopyCount == 0);
|
|
|
|
DestroyBlob(m_cypherBlob);
|
|
|
|
m_clearTextLength = 0;
|
|
|
|
ASSERT(IsInNullBlobState());
|
|
}
|
|
|
|
|
|
// A word on state:
|
|
//
|
|
// an instance can be in one of two internal states: null blob and non-null
|
|
// blob. These refer to whether or not the instance actually holds any
|
|
// encrypted text.
|
|
//
|
|
// For the sake of the public interface, the null blob state is the same as
|
|
// the non-null blob when the non-null blob holds an empty string. In other
|
|
// words, an instance in the null blob state is an empty string.
|
|
|
|
inline bool
|
|
EncryptedString::IsInNullBlobState() const
|
|
{
|
|
if ( !m_cypherBlob.cbData
|
|
&& !m_cypherBlob.pbData)
|
|
{
|
|
ASSERT(m_clearTextLength == 0);
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
// builds the internal encrypted representation from the cleartext.
|
|
//
|
|
// clearText - in, un-encrypted text to be encrypted. May be empty string, but
|
|
// not a null pointer.
|
|
//
|
|
// length - The character length of the clear text, not including null
|
|
// termination. The count should be in unicode characters.
|
|
//
|
|
// Returns S_OK on successful encryption, error code otherwise. If low
|
|
// memory causes failure the code will be E_OUTOFMEMORY.
|
|
//
|
|
// Note: At this point we trust that length is the _correct_ length of
|
|
// the string.
|
|
|
|
inline HRESULT
|
|
EncryptedString::InternalEncrypt(const WCHAR* clearText, size_t length)
|
|
{
|
|
ASSERT(clearText);
|
|
|
|
// don't reset the instance unless you have destroyed all the cleartext
|
|
// copies.
|
|
|
|
ASSERT(m_cleartextCopyCount == 0);
|
|
|
|
HRESULT err = S_OK;
|
|
|
|
Reset();
|
|
|
|
if (clearText)
|
|
{
|
|
DATA_BLOB dataIn;
|
|
|
|
// Determine size of the data blob, including space for null.
|
|
size_t blobSizeInBytes = (length + 1) * sizeof(WCHAR);
|
|
|
|
dataIn.cbData = (DWORD)blobSizeInBytes;
|
|
dataIn.pbData = (BYTE*)clearText;
|
|
|
|
// Encrypt the clear text. We use a temporary blob so
|
|
// that if the encryption fails, the old cypher blob is
|
|
// left untouched.
|
|
err = EncryptData(dataIn, m_cypherBlob);
|
|
|
|
if (SUCCEEDED(err))
|
|
{
|
|
m_clearTextLength = length;
|
|
}
|
|
else
|
|
{
|
|
// if the encryption bombed, make this the an empty string. One
|
|
// reason it can bomb is out-of-memory.
|
|
|
|
Reset();
|
|
}
|
|
|
|
}
|
|
|
|
ASSERT(m_cleartextCopyCount == 0);
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
|
|
// decrypts the blob and returns a copy of the cleartext, but does not
|
|
// bump up the outstanding copy counter. Result must be freed with
|
|
// LocalFree. May return null (if not enough memory available).
|
|
//
|
|
// Used internally to prevent infinite mutual recursion
|
|
|
|
inline WCHAR*
|
|
EncryptedString::InternalDecrypt() const
|
|
{
|
|
HRESULT hr = S_OK;
|
|
DATA_BLOB clearText;
|
|
::ZeroMemory(&clearText, sizeof(DATA_BLOB));
|
|
WCHAR* copy = NULL;
|
|
|
|
if (IsInNullBlobState())
|
|
{
|
|
// Return an empty string for the clear text since a null
|
|
// blob state should be indistinguishable from an encrypted L"".
|
|
// LocalAlloc() zeroes the memory for us (since we passed LPTR).
|
|
copy = (WCHAR*) ::LocalAlloc(LPTR, sizeof(WCHAR));
|
|
}
|
|
else
|
|
{
|
|
hr = DecryptData(m_cypherBlob, clearText);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
copy = (WCHAR*) clearText.pbData;
|
|
}
|
|
}
|
|
|
|
#ifdef DBG
|
|
if (clearText.pbData)
|
|
{
|
|
// An empty string should consist of at least a null terminator
|
|
|
|
ASSERT(clearText.cbData >= sizeof WCHAR);
|
|
|
|
// check for the null terminator
|
|
|
|
ASSERT(
|
|
( *(clearText.pbData + clearText.cbData - 2) == 0)
|
|
&& ( *(clearText.pbData + clearText.cbData - 1) == 0) );
|
|
|
|
// the decrypted version should be the same length as the encrypted
|
|
// version
|
|
|
|
#ifndef ENCRYPT_WITH_CRYPTPROTECTDATA
|
|
// We need a different version of this check since the CryptProtectMemory()
|
|
// API requires blobs whose sizes are a multiple of a block size.
|
|
size_t decryptedLength = 0;
|
|
|
|
hr = StringCchLength(
|
|
reinterpret_cast<LPCWSTR>(clearText.pbData),
|
|
m_clearTextLength + 1,
|
|
&decryptedLength);
|
|
|
|
ASSERT(SUCCEEDED(hr));
|
|
ASSERT(decryptedLength == m_clearTextLength);
|
|
#else
|
|
ASSERT(clearText.cbData / sizeof WCHAR == m_clearTextLength + 1);
|
|
#endif // ENCRYPT_WITH_CRYPTPROTECTDATA
|
|
|
|
}
|
|
#endif
|
|
|
|
return copy;
|
|
}
|
|
|
|
|
|
|
|
inline void
|
|
EncryptedString::InternalDestroy(WCHAR* cleartext) const
|
|
{
|
|
if (cleartext)
|
|
{
|
|
::SecureZeroMemory(cleartext, m_clearTextLength * sizeof WCHAR);
|
|
::LocalFree(cleartext);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
inline WCHAR*
|
|
EncryptedString::GetClearTextCopy() const
|
|
{
|
|
#ifdef DBG
|
|
// Even if we fail the decryption, we bump the count so that it's easy for
|
|
// the caller to always balance GetClearTextCopy with DestroyClearTextCopy
|
|
++m_cleartextCopyCount;
|
|
#endif
|
|
|
|
WCHAR* copy = NULL;
|
|
copy = InternalDecrypt();
|
|
|
|
return copy;
|
|
}
|
|
|
|
|
|
|
|
inline HRESULT
|
|
EncryptedString::Encrypt(const WCHAR* clearText)
|
|
{
|
|
ASSERT(clearText);
|
|
|
|
// don't reset the instance unless you have destroyed all the cleartext
|
|
// copies.
|
|
|
|
ASSERT(m_cleartextCopyCount == 0);
|
|
|
|
HRESULT err = S_OK;
|
|
|
|
if (NULL != clearText)
|
|
{
|
|
// We need to figure out the length of clearText.
|
|
// Only option is to use an unbounded length function.
|
|
size_t length = wcslen(clearText);
|
|
|
|
err = InternalEncrypt(clearText, length);
|
|
}
|
|
else
|
|
{
|
|
err = E_INVALIDARG;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
inline HRESULT
|
|
EncryptedString::Encrypt(const WCHAR* clearText, size_t length)
|
|
{
|
|
ASSERT(clearText);
|
|
|
|
// don't reset the instance unless you have destroyed all the cleartext
|
|
// copies.
|
|
|
|
ASSERT(m_cleartextCopyCount == 0);
|
|
|
|
HRESULT err = S_OK;
|
|
|
|
if (NULL != clearText)
|
|
{
|
|
// Even though the caller tells us the length of clearText,
|
|
// we don't really trust them and want to make sure that:
|
|
// a) The string really is null terminated, and
|
|
// b) That the null termination happens exactly where they
|
|
// say it does.
|
|
|
|
size_t lengthCheck;
|
|
|
|
err = StringCchLength(
|
|
clearText,
|
|
// Maximum length, including space for null
|
|
(length + 1),
|
|
&lengthCheck);
|
|
|
|
ASSERT(lengthCheck == length);
|
|
|
|
if (lengthCheck == length)
|
|
{
|
|
err = InternalEncrypt(clearText, length);
|
|
}
|
|
else
|
|
{
|
|
err = E_INVALIDARG;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
err = E_INVALIDARG;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
inline bool
|
|
EncryptedString::operator==(const EncryptedString& rhs) const
|
|
{
|
|
// handle the a == a case
|
|
|
|
if (this == &rhs)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (GetLength() != rhs.GetLength())
|
|
{
|
|
// can't be the same if lengths differ...
|
|
|
|
return false;
|
|
}
|
|
|
|
// Two strings are the same if their decoded contents are the same.
|
|
|
|
WCHAR* clearTextThis = GetClearTextCopy();
|
|
WCHAR* clearTextThat = rhs.GetClearTextCopy();
|
|
|
|
// If memory is scarce either clear text copy could be null.
|
|
// In that case we cannot determine if the strings are equal we'll
|
|
// assume that they are not and return false.
|
|
bool result = false;
|
|
if (clearTextThis && clearTextThat)
|
|
{
|
|
result = (wcscmp(clearTextThis, clearTextThat) == 0);
|
|
}
|
|
|
|
DestroyClearTextCopy(clearTextThis);
|
|
rhs.DestroyClearTextCopy(clearTextThat);
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
inline size_t
|
|
EncryptedString::GetLength() const
|
|
{
|
|
|
|
#ifdef DBG
|
|
// we don't use GetClearTextCopy, that may result in infinite recursion
|
|
// since this function is called internally.
|
|
|
|
WCHAR* clearTextThis = InternalDecrypt();
|
|
|
|
size_t len = 0;
|
|
if (clearTextThis)
|
|
{
|
|
HRESULT hr =
|
|
StringCchLength(
|
|
clearTextThis,
|
|
// +1 for the null character
|
|
(m_clearTextLength + 1),
|
|
&len);
|
|
if (FAILED(hr))
|
|
{
|
|
// we should be guaranteeing that the result of GetClearTextCopy
|
|
// is always null-terminated, so a failure here represents a bug
|
|
// in our implementation.
|
|
|
|
ASSERT(false);
|
|
len = 0;
|
|
}
|
|
}
|
|
InternalDestroy(clearTextThis);
|
|
|
|
ASSERT(len == m_clearTextLength);
|
|
#endif
|
|
|
|
return m_clearTextLength;
|
|
}
|
|
|
|
|
|
|
|
inline void
|
|
EncryptedString::DestroyClearTextCopy(WCHAR* cleartext) const
|
|
{
|
|
// we expect that cleartext is usually non-null. It may not be, if
|
|
// GetClearTextCopy failed, however.
|
|
// ASSERT(cleartext);
|
|
|
|
// we should have some outstanding copies. If not, then the caller has
|
|
// called DestroyClearTextCopy more times than he called GetClearTextCopy,
|
|
// and therefore has a bug.
|
|
|
|
ASSERT(m_cleartextCopyCount);
|
|
|
|
InternalDestroy(cleartext);
|
|
#ifdef DBG
|
|
--m_cleartextCopyCount;
|
|
#endif
|
|
}
|
|
|
|
|
|
//
|
|
// EncryptData:
|
|
//
|
|
// Encrypts data in clearText into cypherText using data protection
|
|
// API. This function allocates memory to cypherText so make sure to
|
|
// release any memory tied to cypherText before calling EncryptData().
|
|
// Conversely, free the memory this function allocates by calling
|
|
// LocalFree(cypherText).
|
|
//
|
|
// History:
|
|
// 2002/04/01 artm Created.
|
|
// 2002/06/08 artm Changed implementation to include CryptProtectMemory().
|
|
// NTRAID#NTBUG9-635046
|
|
//
|
|
inline HRESULT
|
|
EncryptedString::EncryptData(const DATA_BLOB& clearText, DATA_BLOB& cypherText)
|
|
{
|
|
ASSERT(clearText.cbData);
|
|
ASSERT(clearText.pbData);
|
|
|
|
::ZeroMemory(&cypherText, sizeof cypherText);
|
|
|
|
HRESULT hr = S_OK;
|
|
BOOL succeeded = FALSE;
|
|
|
|
// Tell compiler that false while loop is okay.
|
|
#pragma warning( push )
|
|
#pragma warning( disable : 4127 )
|
|
|
|
do // false while loop
|
|
{
|
|
#ifndef ENCRYPT_WITH_CRYPTPROTECTDATA
|
|
// Allocate a cypher blob big enough for data and that is
|
|
// a multiple of the block size.
|
|
cypherText.cbData = CalculateBlobSize(clearText.cbData);
|
|
HLOCAL local = ::LocalAlloc(LPTR, cypherText.cbData);
|
|
if (!local)
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
break;
|
|
}
|
|
|
|
// Copy the clear text to the cypher text buffer.
|
|
cypherText.pbData = (BYTE*)local;
|
|
::CopyMemory(
|
|
cypherText.pbData,
|
|
clearText.pbData,
|
|
clearText.cbData);
|
|
|
|
// Encrypt the cypher blob.
|
|
succeeded =
|
|
::CryptProtectMemory(
|
|
cypherText.pbData,
|
|
cypherText.cbData,
|
|
CRYPTPROTECTMEMORY_SAME_PROCESS);
|
|
#else
|
|
succeeded =
|
|
::CryptProtectData(
|
|
const_cast<DATA_BLOB*>(&clearText),
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
CRYPTPROTECT_UI_FORBIDDEN,
|
|
&cypherText);
|
|
#endif
|
|
|
|
if (!succeeded)
|
|
{
|
|
hr = GetLastErrorAsHresult();
|
|
break;
|
|
}
|
|
} while(false);
|
|
|
|
#pragma warning( pop )
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
// Ensure that any clear text copied to the cypher blob is
|
|
// scratched out and then freed.
|
|
DestroyBlob(cypherText);
|
|
|
|
// should have a null result on failure, but just in case...
|
|
ASSERT(!cypherText.cbData && !cypherText.pbData);
|
|
}
|
|
|
|
// we don't assert success, as the API may have run out of memory.
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
//
|
|
// DecryptData:
|
|
//
|
|
// Decrypts cypherText into clearText using data protection API.
|
|
// This function does not release any memory tied to clearText, so
|
|
// check that no memory in clearText needs to be released before calling.
|
|
// To release the memory allocated to clearText in this function call
|
|
// LocalFree().
|
|
//
|
|
// History:
|
|
// 2002/04/01 artm Created.
|
|
// 2002/06/08 artm Changed implementation to include CryptUnprotectMemory().
|
|
// NTRAID#NTBUG9-635046
|
|
//
|
|
inline HRESULT
|
|
EncryptedString::DecryptData(const DATA_BLOB& cypherText, DATA_BLOB& clearText)
|
|
{
|
|
ASSERT(cypherText.cbData);
|
|
ASSERT(cypherText.pbData);
|
|
|
|
::ZeroMemory(&clearText, sizeof clearText);
|
|
|
|
HRESULT hr = S_OK;
|
|
BOOL succeeded = FALSE;
|
|
|
|
// Tell compiler that false while loop is okay.
|
|
#pragma warning( push )
|
|
#pragma warning( disable : 4127 )
|
|
|
|
|
|
do // false while loop
|
|
{
|
|
#ifndef ENCRYPT_WITH_CRYPTPROTECTDATA
|
|
// Our cypher blob should always be a multiple of the block size;
|
|
// if it isn't then it is a bug in the implementation.
|
|
ASSERT( (cypherText.cbData % CRYPTPROTECTMEMORY_BLOCK_SIZE) == 0);
|
|
|
|
hr = CopyBlob(clearText, cypherText);
|
|
if (FAILED(hr))
|
|
{
|
|
break;
|
|
}
|
|
|
|
succeeded =
|
|
::CryptUnprotectMemory(
|
|
clearText.pbData,
|
|
clearText.cbData,
|
|
CRYPTPROTECTMEMORY_SAME_PROCESS);
|
|
#else
|
|
succeeded =
|
|
::CryptUnprotectData(
|
|
const_cast<DATA_BLOB*>(&cypherText),
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
CRYPTPROTECT_UI_FORBIDDEN,
|
|
&clearText);
|
|
#endif
|
|
|
|
if (!succeeded)
|
|
{
|
|
hr = GetLastErrorAsHresult();
|
|
break;
|
|
}
|
|
} while(false);
|
|
|
|
#pragma warning( pop )
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
// Scratch out and free anything we put in clearText.
|
|
DestroyBlob(clearText);
|
|
|
|
// should have a null result on failure, but just in case...
|
|
ASSERT(!clearText.cbData && !clearText.pbData);
|
|
}
|
|
|
|
// we don't assert success, as the API may have run out of memory.
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
#endif // _PUBLIC_ENCRYPTEDSTRING_
|