QuietUnrar/libunrar/secpassword.cpp

217 lines
5.5 KiB
C++

#include "rar.hpp"
#if defined(_WIN_ALL)
typedef BOOL (WINAPI *CRYPTPROTECTMEMORY)(LPVOID pData,DWORD cbData,DWORD dwFlags);
typedef BOOL (WINAPI *CRYPTUNPROTECTMEMORY)(LPVOID pData,DWORD cbData,DWORD dwFlags);
#ifndef CRYPTPROTECTMEMORY_BLOCK_SIZE
#define CRYPTPROTECTMEMORY_BLOCK_SIZE 16
#define CRYPTPROTECTMEMORY_SAME_PROCESS 0x00
#define CRYPTPROTECTMEMORY_CROSS_PROCESS 0x01
#endif
class CryptLoader
{
private:
HMODULE hCrypt;
bool LoadCalled;
public:
CryptLoader()
{
hCrypt=NULL;
pCryptProtectMemory=NULL;
pCryptUnprotectMemory=NULL;
LoadCalled=false;
}
~CryptLoader()
{
if (hCrypt!=NULL)
FreeLibrary(hCrypt);
hCrypt=NULL;
pCryptProtectMemory=NULL;
pCryptUnprotectMemory=NULL;
};
void Load()
{
if (!LoadCalled)
{
hCrypt = LoadSysLibrary(L"Crypt32.dll");
if (hCrypt != NULL)
{
// Available since Vista.
pCryptProtectMemory = (CRYPTPROTECTMEMORY)GetProcAddress(hCrypt, "CryptProtectMemory");
pCryptUnprotectMemory = (CRYPTUNPROTECTMEMORY)GetProcAddress(hCrypt, "CryptUnprotectMemory");
}
LoadCalled=true;
}
}
CRYPTPROTECTMEMORY pCryptProtectMemory;
CRYPTUNPROTECTMEMORY pCryptUnprotectMemory;
};
// We need to call FreeLibrary when RAR is exiting.
CryptLoader GlobalCryptLoader;
#endif
SecPassword::SecPassword()
{
CrossProcess=false;
Set(L"");
}
SecPassword::~SecPassword()
{
Clean();
}
void SecPassword::Clean()
{
PasswordSet=false;
cleandata(Password,sizeof(Password));
}
// When we call memset in end of function to clean local variables
// for security reason, compiler optimizer can remove such call.
// So we use our own function for this purpose.
void cleandata(void *data,size_t size)
{
if (data==NULL || size==0)
return;
#if defined(_WIN_ALL) && defined(_MSC_VER)
SecureZeroMemory(data,size);
#else
// 'volatile' is required. Otherwise optimizers can remove this function
// if cleaning local variables, which are not used after that.
volatile byte *d = (volatile byte *)data;
for (size_t i=0;i<size;i++)
d[i]=0;
#endif
}
// We got a complain from user that it is possible to create WinRAR dump
// with "Create dump file" command in Windows Task Manager and then easily
// locate Unicode password string in the dump. It is unsecure if several
// people share the same computer and somebody left WinRAR copy with entered
// password. So we decided to obfuscate the password to make it more difficult
// to find it in dump.
void SecPassword::Process(const wchar *Src,size_t SrcSize,wchar *Dst,size_t DstSize,bool Encode)
{
// Source string can be shorter than destination as in case when we process
// -p<pwd> parameter, so we need to take into account both sizes.
memcpy(Dst,Src,Min(SrcSize,DstSize)*sizeof(*Dst));
SecHideData(Dst,DstSize*sizeof(*Dst),Encode,CrossProcess);
}
void SecPassword::Get(wchar *Psw,size_t MaxSize)
{
if (PasswordSet)
{
Process(Password,ASIZE(Password),Psw,MaxSize,false);
Psw[MaxSize-1]=0;
}
else
*Psw=0;
}
void SecPassword::Set(const wchar *Psw)
{
if (*Psw==0)
{
PasswordSet=false;
memset(Password,0,sizeof(Password));
}
else
{
PasswordSet=true;
Process(Psw,wcslen(Psw)+1,Password,ASIZE(Password),true);
}
}
size_t SecPassword::Length()
{
wchar Plain[MAXPASSWORD];
Get(Plain,ASIZE(Plain));
size_t Length=wcslen(Plain);
cleandata(Plain,ASIZE(Plain));
return Length;
}
bool SecPassword::operator == (SecPassword &psw)
{
// We cannot compare encoded data directly, because there is no guarantee
// than encryption function will always produce the same result for same
// data (salt?) and because we do not clean the rest of password buffer
// after trailing zero before encoding password. So we decode first.
wchar Plain1[MAXPASSWORD],Plain2[MAXPASSWORD];
Get(Plain1,ASIZE(Plain1));
psw.Get(Plain2,ASIZE(Plain2));
bool Result=wcscmp(Plain1,Plain2)==0;
cleandata(Plain1,ASIZE(Plain1));
cleandata(Plain2,ASIZE(Plain2));
return Result;
}
void SecHideData(void *Data,size_t DataSize,bool Encode,bool CrossProcess)
{
// CryptProtectMemory is not available in UWP and CryptProtectData
// increases data size not allowing in place conversion.
#if defined(_WIN_ALL)
// Try to utilize the secure Crypt[Un]ProtectMemory if possible.
if (GlobalCryptLoader.pCryptProtectMemory==NULL)
GlobalCryptLoader.Load();
size_t Aligned=DataSize-DataSize%CRYPTPROTECTMEMORY_BLOCK_SIZE;
DWORD Flags=CrossProcess ? CRYPTPROTECTMEMORY_CROSS_PROCESS : CRYPTPROTECTMEMORY_SAME_PROCESS;
if (Encode)
{
if (GlobalCryptLoader.pCryptProtectMemory!=NULL)
{
if (!GlobalCryptLoader.pCryptProtectMemory(Data,DWORD(Aligned),Flags))
{
ErrHandler.GeneralErrMsg(L"CryptProtectMemory failed");
ErrHandler.SysErrMsg();
ErrHandler.Exit(RARX_FATAL);
}
return;
}
}
else
{
if (GlobalCryptLoader.pCryptUnprotectMemory!=NULL)
{
if (!GlobalCryptLoader.pCryptUnprotectMemory(Data,DWORD(Aligned),Flags))
{
ErrHandler.GeneralErrMsg(L"CryptUnprotectMemory failed");
ErrHandler.SysErrMsg();
ErrHandler.Exit(RARX_FATAL);
}
return;
}
}
#endif
// CryptProtectMemory is not available, so only slightly obfuscate data.
uint Key;
#ifdef _WIN_ALL
Key=GetCurrentProcessId();
#elif defined(_UNIX)
Key=getpid();
#else
Key=0; // Just an arbitrary value.
#endif
for (size_t I=0;I<DataSize;I++)
*((byte *)Data+I)^=Key+I+75;
}