libclamunrar/secpassword.cpp
01eebc13
 #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;
 }