/*
* Project : Managing Secrets
* Content : Data Protection using DPAPI on managed code needed to be done using
* : C++ unmanaged code or by writting some wrapper code as many of us
* : have done. Data Protection is available in VS2005 by using some
* : simple to use static methods located in the
* : "System::Security::Cryptography" namespace. I have taken some
* : time to define a class that I called "Secret" that hides many
* : implementation details with the intention of making working with
* : those methods as simple as it can be.
* File : Secret.h
* Author : Eduardo Sobrino
* Date : Oct/2k5
*/
#pragma once
using namespace System ;
// -----------------------------------------------------------------------------
namespace Security
{
/// <summary>Allow the user to select the kind of protection he/she wants for
/// their memory/data secret</summary>
public enum class ProtectionScope
{
InvalidDataProtectionScope = -2,
InvalidMemoryProtectionScope = -1,
// memory protection
ProtectMemoryCrossProcess = 1,
ProtectMemorySameLogon = 2,
ProtectMemorySameProcess = 3,
// data protection
ProtectDataCurrentUser = 10,
ProtectDataLocalMachine = 11,
Default = 99
} ; // end of ProtectionScope
/// <summary>The following is provided to allow the reporting of the status of
/// the protection reached on the "Secret" class without the nasty exception
/// throw/catch. The user of the "Secret" class should then always check the
/// "Status" property to see how the data was protected (or not)</summary>
public enum class ProtectionStatus
{
Protected = 1,
Unknown = 0,
Unprotected = -1,
// soft exceptions
BufferLengthException = -2,
BufferNullException = -3,
EntropyLengthException = -4,
EntropyNullException = -5,
StreamNullException = -6,
StreamReadNotPermited = -7,
EmptySecret = -97,
InvalidScope = -98,
ExceptionFound = -99
} ; // end of ProtectionStatus
/// <summary>Provide a rich detailed result info rather than merly true, false
/// or a filled buffer (or not). The following class is returned on any
/// invocation of data protection API</summary>
public ref class ProtectionResult
{
public:
ProtectionStatus Status ;
Int32 ResultLength ;
array<Byte> ^Buffer ;
System::Exception ^Exception ;
ProtectionResult()
{
Status = ProtectionStatus::Unknown ;
ResultLength = 0 ;
Buffer = nullptr ;
Exception = nullptr ;
}
} ; // end of ProtectionResult
/// <summary>Provide an interface for managing secrets to allow various
/// alternate implementations</summary>
/// <date>Oct/2k5 (ESob)</date>
interface class ISecret
{
property ProtectionScope Scope;
property ProtectionStatus Status { ProtectionStatus get() ; };
bool ProtectMemory(String ^Message);
bool UnprotectMemory();
bool ProtectDataToFile(
String ^Message, String ^Entropy, System::IO::Stream ^Stream);
bool UnprotectDataFromFile(String ^Entropy, System::IO::Stream ^Stream);
bool ProtectDataToFile(String ^Message, String ^Entropy, String ^FilePath);
bool UnprotectDataFromFile(String ^Entropy, String ^FilePath);
} ; // end of ISecret interface
/// <summary>Protect data using DPAPI. Wrote this class using the MSDN
/// documentation for the "ProtectedMemory" class.</summary>
public ref class ProtectedData
{
public:
#pragma region -- General Support Methods
/// <summary>Create a random entropy</summary>
/// <date>Oct/2k5 (ESob)</date>
static array<Byte> ^CreateRandomEntropy()
{
// Create a byte array to hold the random value.
array<Byte> ^entropy = gcnew array<Byte>(16);
// Create a new instance of the RNGCryptoServiceProvider.
// Fill the array with a random value.
System::Security::Cryptography::RNGCryptoServiceProvider ^rng =
gcnew System::Security::Cryptography::RNGCryptoServiceProvider();
rng->GetBytes(entropy) ;
// Return the array.
return entropy;
} // end of CreateRandomEntropy
#pragma endregion
#pragma region -- Data Protection for In Memory Data
/// <summary>Encrypt given buffer data in memory using DPAPI</summary>
/// <param name="Buffer">Buffer to encrypt</param>
/// <param name="Scope">Protection Scope (CrossProcess, SameLogon,
/// or SameProcess)</param>
/// <returns>returns a summary of the protection result</returns>
/// <date>Oct/2k5 (ESob)</date>
static ProtectionResult ^EncryptInMemoryData(array<Byte> ^Buffer,
System::Security::Cryptography::MemoryProtectionScope Scope)
{
ProtectionResult ^result = gcnew ProtectionResult() ;
if (Buffer == nullptr)
{
result->Status = ProtectionStatus::BufferNullException ;
return(result);
}
if (Buffer->Length <= 0)
{
result->Status = ProtectionStatus::BufferLengthException ;
return(result);
}
// Encrypt the data in memory. The result is stored in the same same
// array as the original data.
System::Security::Cryptography::ProtectedMemory::Protect(Buffer, Scope);
// summarize result
result->Status = ProtectionStatus::Protected ;
result->Buffer = Buffer ;
result->ResultLength = Buffer->Length ;
return(result);
} // end of EncryptInMemoryData
/// <summary>Decrypt given buffer data from memory using DPAPI</summary>
/// <param name="Buffer">Buffer to decrypt</param>
/// <param name="Scope">Protection Scope (CrossProcess, SameLogon,
/// or SameProcess)</param>
/// <returns>returns a summary of the protection result</returns>
/// <date>Oct/2k5 (ESob)</date>
static ProtectionResult ^DecryptInMemoryData(array<Byte> ^Buffer,
System::Security::Cryptography::MemoryProtectionScope Scope)
{
ProtectionResult ^result = gcnew ProtectionResult() ;
if (Buffer == nullptr)
{
result->Status = ProtectionStatus::BufferNullException ;
return(result);
}
if (Buffer->Length <= 0)
{
result->Status = ProtectionStatus::BufferLengthException ;
return(result);
}
// Decrypt the data in memory.
// The result is stored in the same same array as the original data.
System::Security::Cryptography::
ProtectedMemory::Unprotect(Buffer, Scope);
// summarize result
result->Status = ProtectionStatus::Unprotected ;
result->Buffer = Buffer ;
result->ResultLength = Buffer->Length ;
return(result);
} // end of DecryptInMemoryData
#pragma endregion
#pragma region -- Data Protection for Streams
/// <summary>Encrypt given buffer data to a Stream using DPAPI</summary>
/// <param name="Buffer">Buffer of data to encrypt</param>
/// <param name="Entropy">Some user data to salt result</param>
/// <param name="Scope">Protection Scope (CrossProcess, SameLogon,
/// or SameProcess)</param>
/// <param name="Stream">Data stream to persist result too</param>
/// <date>Oct/2k5 (ESob)</date>
static ProtectionResult ^EncryptDataToStream(
array<Byte> ^Buffer, array<Byte> ^Entropy,
System::Security::Cryptography::DataProtectionScope Scope,
System::IO::Stream ^Stream)
{
ProtectionResult ^result = gcnew ProtectionResult() ;
if (Buffer == nullptr)
{
result->Status = ProtectionStatus::BufferNullException ;
return(result);
}
if (Buffer->Length <= 0)
{
result->Status = ProtectionStatus::BufferLengthException ;
return(result);
}
if (Entropy == nullptr)
{
result->Status = ProtectionStatus::EntropyNullException ;
return(result);
}
if (Entropy->Length <= 0)
{
result->Status = ProtectionStatus::EntropyLengthException ;
return(result);
}
if (Stream == nullptr)
{
result->Status = ProtectionStatus::StreamNullException ;
return(result);
}
// Encrypt the data in memory.
// The result is stored in the same same array as the original data.
array<Byte> ^encrptedData =
System::Security::Cryptography::ProtectedData::Protect(
Buffer, Entropy, Scope);
// Write the encrypted data to a stream.
if (Stream->CanWrite && (encrptedData != nullptr))
{
Stream->Write(encrptedData, 0, encrptedData->Length);
result->ResultLength = encrptedData->Length;
result->Status = ProtectionStatus::Protected ;
}
else
result->Status = ProtectionStatus::Unprotected ;
return result ;
} // end of EncryptDataToStream
/// <summary>Decrypt given buffer data from memory using DPAPI</summary>
/// <param name="Buffer">Buffer to store decrypt result</param>
/// <param name="Entropy">Some user data</param>
/// <param name="Scope">Protection Scope (CrossProcess, SameLogon,
/// or SameProcess)</param>
/// <param name="Stream">Data stream to decrypt</param>
/// <date>Oct/2k5 (ESob)</date>
static ProtectionResult ^DecryptDataFromStream(
array<Byte> ^Entropy,
System::Security::Cryptography::DataProtectionScope Scope,
System::IO::Stream ^Stream)
{
ProtectionResult ^result = gcnew ProtectionResult() ;
if (Entropy == nullptr)
{
result->Status = ProtectionStatus::EntropyNullException ;
return(result);
}
if (Entropy->Length <= 0)
{
result->Status = ProtectionStatus::EntropyLengthException ;
return(result);
}
if (Stream == nullptr)
{
result->Status = ProtectionStatus::StreamNullException ;
return(result);
}
Int32 l = 512 ;
array<Byte> ^inBuffer = gcnew array<Byte>(l);
// Read the encrypted data from a stream.
if (Stream->CanRead)
{
Stream->Read(inBuffer, 0, l);
result->Buffer = System::Security::Cryptography::
ProtectedData::Unprotect(inBuffer, Entropy, Scope);
result->ResultLength = result->Buffer->Length ;
result->Status = ProtectionStatus::Unprotected ;
}
else
{
result->Status = ProtectionStatus::StreamReadNotPermited ;
}
return result ;
} // end of DecryptDataFromStream
#pragma endregion
} ; // end of ProtectedData
/// <summary>Provide a simple to use class that does proper handling of secrets
/// </summary>
/// <date>Oct/2k5 (ESob)</date>
public ref class Secret : ISecret
{
#pragma region -- declarations and properties
private:
ProtectionScope scope ;
ProtectionResult ^lastResult ;
public:
property ProtectionScope Scope
{
virtual ProtectionScope get()
{
return(scope);
}
virtual void set(ProtectionScope Value)
{
scope = Value ;
}
} // end of Scope property
property ProtectionStatus Status
{
virtual ProtectionStatus get()
{
if (lastResult == nullptr)
return(ProtectionStatus::Unknown) ;
return(lastResult->Status);
}
} // end of Status property
#pragma endregion
#pragma region -- initialization and cleanup (ctor/dtor)
Secret(String ^Message,ProtectionScope Scope)
{
scope = Scope ;
if (Message != nullptr)
ProtectMemory(Message) ;
} // end of ctor
Secret(ProtectionScope Scope)
{
scope = Scope ;
} // end of ctor
Secret()
{
scope = ProtectionScope::Default ;
} // end of ctor
#pragma endregion
#pragma region -- scope management support functions
private:
/// <summar>The following method returns the memory protection scope. If
/// current setted scope is not a valid "MemoryProtectionScop" the
/// current scope is set as invalid (="InvalidMemoryProtectionScope")
/// </summary>
System::Security::Cryptography::MemoryProtectionScope
GetMemoryProtectionScope()
{
System::Security::Cryptography::MemoryProtectionScope s ;
switch(scope)
{
case ProtectionScope::ProtectMemoryCrossProcess:
s = System::Security::Cryptography::
MemoryProtectionScope::CrossProcess ;
break ;
case ProtectionScope::ProtectMemorySameLogon:
s = System::Security::Cryptography::
MemoryProtectionScope::SameLogon ;
break ;
case ProtectionScope::ProtectMemorySameProcess:
s = System::Security::Cryptography::
MemoryProtectionScope::SameProcess ;
break ;
case ProtectionScope::Default:
s = System::Security::Cryptography::
MemoryProtectionScope::SameProcess ;
break ;
default:
scope = ProtectionScope::InvalidMemoryProtectionScope ;
s = System::Security::Cryptography::
MemoryProtectionScope::SameProcess ;
break ;
}
return(s) ;
} // end of GetMemoryProtectionScope
/// <summar>The following method returns the data protection scope. If
/// current setted scope is not a valid "DataProtectionScop" the
/// current scope is set as invalid (="InvalidDataProtectionScope")
/// </summary>
System::Security::Cryptography::DataProtectionScope GetDataProtectionScope()
{
System::Security::Cryptography::DataProtectionScope s ;
switch(scope)
{
case ProtectionScope::ProtectDataCurrentUser:
s = System::Security::Cryptography::
DataProtectionScope::CurrentUser ;
break ;
case ProtectionScope::ProtectDataLocalMachine:
s = System::Security::Cryptography::
DataProtectionScope::LocalMachine ;
break ;
case ProtectionScope::Default:
s = System::Security::Cryptography::
DataProtectionScope::CurrentUser ;
break ;
default:
scope = ProtectionScope::InvalidDataProtectionScope ;
s = System::Security::Cryptography::
DataProtectionScope::CurrentUser ;
break ;
}
return(s) ;
} // end of GetDataProtectionScope()
#pragma endregion
#pragma region -- memory protection methods
public:
/// <summary>Given a string store it on protected memory</summary>
/// <result>true is returned if mesage is protected else user should check
/// the "Secret.Status" to see what went wrong.</result>
/// <date>Oct/2k5 (ESob)</date>
virtual bool ProtectMemory(String ^Message)
{
System::Security::Cryptography::MemoryProtectionScope s ;
s = GetMemoryProtectionScope() ;
if (scope == ProtectionScope::InvalidMemoryProtectionScope)
{
lastResult = gcnew ProtectionResult() ;
lastResult->Status = ProtectionStatus::InvalidScope ;
return(false) ;
}
// ensure that length of bytes array is a multiple of 16, else an exception
// will be thrown by EncryptInMemoryData. "l" needs to be 0, it is the
// missing number of bytes to be a multiple of 16
Int16 l = Message->Length % 16 ;
System::Text::ASCIIEncoding ^ae = gcnew System::Text::ASCIIEncoding();
array<Byte> ^buffer ;
buffer = ae->GetBytes(Message) ;
// resize buffer to ensure that is a multiple of 16 (if needed)
if (l > 0)
buffer->Resize(buffer,Message->Length + (16 - l)) ;
lastResult = ProtectedData::EncryptInMemoryData(buffer,s) ;
buffer = nullptr ;
return(lastResult->Status == ProtectionStatus::Protected) ;
} // end of Protect
/// <summary>Restore original data into internal buffer</summary>
/// <return>true is returned if mesage is unprotected else user should check
/// the "Secret.Status" to see what went wrong.</return>
/// <date>Oct/2k5 (ESob)</date>
virtual bool UnprotectMemory()
{
if (lastResult == nullptr)
{
lastResult = gcnew ProtectionResult() ;
lastResult->Status = ProtectionStatus::EmptySecret ;
return(false) ;
}
if (lastResult->Status != ProtectionStatus::Protected)
return(false) ;
System::Security::Cryptography::MemoryProtectionScope s ;
s = GetMemoryProtectionScope() ;
if (scope == ProtectionScope::InvalidMemoryProtectionScope)
{
lastResult = gcnew ProtectionResult() ;
lastResult->Status = ProtectionStatus::InvalidScope ;
return(false) ;
}
array<Byte> ^buffer = lastResult->Buffer ;
lastResult = ProtectedData::DecryptInMemoryData(buffer,s) ;
return(lastResult->Status == ProtectionStatus::Unprotected) ;
} // end of Unprotect
#pragma endregion
#pragma region -- data protection methods
/// <summary>Protect info and store result to a file</summary>
/// <param name="Message">String message to protect</param>
/// <param name="Entropy">Some user data to salt protection</param>
/// <param name="Stream">Target stream where protected result will be
/// stored into</param>
/// <return>true is returned if mesage is protected else user should check
/// the "Secret.Status" to see what went wrong.</return>
/// <remarks>the supplied stream is released here, therefore don't ever
/// never use it again</remarks>
/// <date>Oct/2k5 (ESob)</date>
virtual bool ProtectDataToFile(
String ^Message, String ^Entropy, System::IO::Stream ^Stream)
{
System::Security::Cryptography::DataProtectionScope s ;
s = GetDataProtectionScope() ;
if (scope == ProtectionScope::InvalidDataProtectionScope)
{
lastResult = gcnew ProtectionResult() ;
lastResult->Status = ProtectionStatus::InvalidScope ;
return(false) ;
}
System::Text::ASCIIEncoding ^ae = gcnew System::Text::ASCIIEncoding();
array<Byte> ^buffer ;
buffer = ae->GetBytes(Message) ;
array<Byte> ^entropy = ae->GetBytes(Entropy) ;
try
{
lastResult = ProtectedData::EncryptDataToStream(
buffer,entropy,s,Stream) ;
}
catch (System::Exception ^ex)
{
lastResult = gcnew ProtectionResult();
lastResult->Exception = ex ;
lastResult->Status = ProtectionStatus::ExceptionFound ;
}
finally
{
Stream->Close() ;
delete Stream ;
}
return(lastResult->Status == ProtectionStatus::Protected) ;
} // end of ProtectToFile
/// <summary>Protect info and store result to a file</summary>
/// <param name="Message">String message to protect</param>
/// <param name="Entropy">Some user data to salt protection</param>
/// <param name="FilePath">Target file path where protected result will be
/// stored into</param>
/// <return>true is returned if mesage is protected else user should check
/// the "Secret.Status" to see what went wrong.</return>
/// <date>Oct/2k5 (ESob)</date>
virtual bool ProtectDataToFile(
String ^Message, String ^Entropy, String ^FilePath)
{
System::IO::Stream ^stream ;
try
{
stream = gcnew System::IO::FileStream(
FilePath, System::IO::FileMode::OpenOrCreate);
}
catch (System::Exception ^ex)
{
lastResult = gcnew ProtectionResult() ;
lastResult->Exception = ex ;
lastResult->Status = ProtectionStatus::ExceptionFound ;
stream = nullptr ;
}
if (stream == nullptr)
return(false) ;
return(ProtectDataToFile(Message,Entropy,stream)) ;
} // end of ProtectToFile
/// <summary>Unprotect info by reading data from a file</summary>
/// <param name="Entropy">Some user data to salt protection</param>
/// <param name="Stream">Stream from where to read protected
/// data</param>
/// <return>true is returned if mesage is unprotected else user should check
/// the "Secret.Status" to see what went wrong.</return>
/// <remarks>the supplied stream is released here, therefore don't ever
/// never use it again</remarks>
/// <date>Oct/2k5 (ESob)</date>
virtual bool UnprotectDataFromFile(
String ^Entropy, System::IO::Stream ^Stream)
{
System::Security::Cryptography::DataProtectionScope s ;
s = GetDataProtectionScope() ;
if (scope == ProtectionScope::InvalidDataProtectionScope)
{
lastResult = gcnew ProtectionResult() ;
lastResult->Status = ProtectionStatus::InvalidScope ;
return(false) ;
}
System::Text::ASCIIEncoding ^ae = gcnew System::Text::ASCIIEncoding();
array<Byte> ^entropy = ae->GetBytes(Entropy) ;
try
{
lastResult = ProtectedData::DecryptDataFromStream(entropy,s,Stream);
}
catch (System::Exception ^ex)
{
lastResult = gcnew ProtectionResult();
lastResult->Exception = ex ;
lastResult->Status = ProtectionStatus::ExceptionFound ;
}
finally
{
Stream->Close() ;
delete Stream ;
}
return(lastResult->Status == ProtectionStatus::Unprotected) ;
} // end of UnprotectFromFile
/// <summary>Unprotect info by reading data from a file</summary>
/// <param name="Entropy">Some user data to salt protection</param>
/// <param name="FilePath">Source file path from where to read protected
/// data</param>
/// <return>true is returned if mesage is unprotected else user should check
/// the "Secret.Status" to see what went wrong.</return>
/// <date>Oct/2k5 (ESob)</date>
virtual bool UnprotectDataFromFile(String ^Entropy, String ^FilePath)
{
System::IO::Stream ^stream ;
try
{
stream = gcnew System::IO::FileStream(
FilePath, System::IO::FileMode::Open);
}
catch (System::Exception ^ex)
{
lastResult = gcnew ProtectionResult() ;
lastResult->Exception = ex ;
lastResult->Status = ProtectionStatus::ExceptionFound ;
stream = nullptr ;
}
if (stream == nullptr)
return(false) ;
return(UnprotectDataFromFile(Entropy,stream)) ;
} // end of UnprotectFromFile
#pragma endregion
#pragma region -- other support methods
/// <summary>Return the string representation of the internal message
/// </summary>
/// <return>A representation of the internal message buffer is returned
/// </return>
/// <date>Oct/2k5 (ESob)</date>
virtual String ^ToString() override
{
if (lastResult == nullptr)
return(L"(null)") ;
String ^secret = nullptr ;
if (Status == ProtectionStatus::Protected)
{
System::Text::ASCIIEncoding ^ae = gcnew System::Text::ASCIIEncoding();
secret = ae->GetString(lastResult->Buffer,0,lastResult->Buffer->Length);
}
else
if (Status == ProtectionStatus::Unprotected)
{
System::Text::ASCIIEncoding ^ae = gcnew System::Text::ASCIIEncoding();
secret = ae->GetString(lastResult->Buffer,0,lastResult->Buffer->Length);
}
else
return(L"?") ;
return(secret);
} // end of ToString
#pragma endregion
} ; // end of Secret
} // end of namespace Security