diff --git a/include/cryptorand.hpp b/include/cryptorand.hpp new file mode 100644 index 0000000..cd1d17b --- /dev/null +++ b/include/cryptorand.hpp @@ -0,0 +1,476 @@ +/* +Cryptographically Secure Pseudo-Random Number Generator. Choice of public domain or MIT-0. See license statements at the end of this file. + +David Reid - mackron@gmail.com +*/ + +/* +This uses the operating system's random number generation. If you're looking for a CSPRNG from +scratch you'll need to look elsewhere. + +Supported generation methods are Win32's BCryptGenRandom() with CryptGenRandom() as a fallback. On +platforms that support /dev/urandom, that will be used. OpenBSD will use arc4random(). + +There is no need to link to anything with this library. You can use CRYPTORAND_IMPLEMENTATION to +define the implementation section, or you can use cryptorand.c if you prefer a traditional +header/source pair. + +There's only three functions, all of which should be self explanatory and easy to figure out: + + ``` + cryptorand_result cryptorand_init(cryptorand* pRNG); + void cryptorand_uninit(cryptorand* pRNG); + cryptorand_result cryptorand_generate(cryptorand* pRNG, void* pBufferOut, size_t byteCount); + ``` + +Call `cryptorand_init()` to initialize the random number generator. On Windows, this is where +libraries are linked at runtime so avoid calling this in high performance scenarios. It's best to +just create one instance and then read from it multiple times. + +To generate random bytes you need only call `cryptorand_generate()`. You just specify a pointer to +a buffer that will receive the random data and the number of bytes you want. If this fails, the +content of the buffer will be cleared to zero. + +Uninitialize the random number generator with `cryptorand_uninit()`. + +Thread safety depends on the backend. +*/ + +#ifndef cryptorand_h +#define cryptorand_h + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* Win32. */ +#if defined(_WIN32) +#define CRYPTORAND_WIN32 +#endif + +/* /dev/urandom. Add platforms to this list as required. */ +#if defined(__linux__) || defined(__APPLE__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__ANDROID__) +#define CRYPTORAND_URANDOM +#endif + +/* +OpenBSD recommends using arc4random() over /dev/urandom: + + The urandom device is intended to be used in scripts. In C programs, use the arc4random(3) + family of functions instead ... +*/ +#if defined(__OpenBSD__) +#define CRYPTORAND_ARC4RANDOM +#endif + +#include /* For size_t. */ + +#if !defined(CRYPTORAND_API) +#define CRYPTORAND_API +#endif + + typedef enum + { + CRYPTORAND_SUCCESS = 0, + CRYPTORAND_ERROR = -1, + CRYPTORAND_INVALID_ARGS = -2, + CRYPTORAND_INVALID_OPERATION = -3, + CRYPTORAND_TOO_BIG = -11, + CRYPTORAND_NOT_IMPLEMENTED = -29 + } cryptorand_result; + + typedef void (*cryptorand_proc)(void); + + typedef struct + { +#if defined(CRYPTORAND_WIN32) + struct + { + void *hBcryptDLL; /* If set, using BCryptGenRandom() */ + cryptorand_proc BCryptOpenAlgorithmProvider; + cryptorand_proc BCryptCloseAlgorithmProvider; + cryptorand_proc BCryptGenRandom; + void *hAlgorithm; /* Used with BCryptGenRandom() */ + + void *hAdvapiDLL; /* If set, using CryptGenRandom() */ + cryptorand_proc CryptAcquireContextW; + cryptorand_proc CryptReleaseContext; + cryptorand_proc CryptGenRandom; + void *hProvider; /* Used with CryptGenRandom() */ + } win32; +#endif +#if defined(CRYPTORAND_URANDOM) + struct + { + /*FILE**/ void *pFile; /* The file handle returned by open(). */ + } urandom; +#endif +#if defined(CRYPTORAND_ARC4RANDOM) + struct + { + int __unused; + } arc4; +#endif + } cryptorand; + + CRYPTORAND_API cryptorand_result cryptorand_init(cryptorand *pRNG); + CRYPTORAND_API void cryptorand_uninit(cryptorand *pRNG); + CRYPTORAND_API cryptorand_result cryptorand_generate(cryptorand *pRNG, void *pBufferOut, size_t byteCount); + +#ifdef __cplusplus +} +#endif +#endif /* cryptorand_h */ +#if defined(CRYPTORAND_IMPLEMENTATION) +#ifndef cryptorand_c +#define cryptorand_c + +#include +#define CRYPTORAND_ZERO_MEMORY(p, sz) memset((p), 0, (sz)) +#define CRYPTORAND_ZERO_OBJECT(o) CRYPTORAND_ZERO_MEMORY((o), sizeof(*o)) + +#if defined(CRYPTORAND_WIN32) +#include /* For LoadLibrary(). */ + +typedef LONG(WINAPI *CRYPTORAND_PFN_BCryptOpenAlgorithmProvider)(void **phAlgorithm, LPCWSTR pszAlgId, LPCWSTR pszImplementation, ULONG dwFlags); +typedef LONG(WINAPI *CRYPTORAND_PFN_BCryptCloseAlgorithmProvider)(void *hAlgorithm, ULONG dwFlags); +typedef LONG(WINAPI *CRYPTORAND_PFN_BCryptGenRandom)(void *hAlgorithm, unsigned char *pbBuffer, ULONG cbBuffer, ULONG dwFlags); + +#define CRYPTORAND_BCRYPT_RNG_ALGORITHM L"RNG" + +typedef BOOL(WINAPI *CRYPTORAND_PFN_CryptAcquireContextW)(void **phProv, LPCWSTR szContainer, LPCWSTR szProvider, DWORD dwProvType, DWORD dwFlags); +typedef BOOL(WINAPI *CRYPTORAND_PFN_CryptReleaseContext)(void *hProv, DWORD dwFlags); +typedef BOOL(WINAPI *CRYPTORAND_PFN_CryptGenRandom)(void *hProv, DWORD dwLen, BYTE *pbBuffer); + +#define CRYPTORAND_PROV_RSA_FULL 1 +#define CRYPTORAND_CRYPT_VERIFYCONTEXT 0xF0000000 +#define CRYPTORAND_CRYPT_SILENT 0x00000040 + +static cryptorand_result cryptorand_init__win32(cryptorand *pRNG) +{ + /* + We first need to try using BCrypt which is the most modern version. If this fails it might mean + we're running on Windows XP in which case we'll fall back to CryptGenRandom(). + */ + CRYPTORAND_ZERO_OBJECT(&pRNG->win32); /* For safety. */ + { + HMODULE hBcryptDLL; + + hBcryptDLL = LoadLibraryW(L"bcrypt.dll"); + if (hBcryptDLL != NULL) + { + pRNG->win32.hBcryptDLL = (void *)hBcryptDLL; + pRNG->win32.BCryptOpenAlgorithmProvider = (cryptorand_proc)GetProcAddress(hBcryptDLL, "BCryptOpenAlgorithmProvider"); + pRNG->win32.BCryptCloseAlgorithmProvider = (cryptorand_proc)GetProcAddress(hBcryptDLL, "BCryptCloseAlgorithmProvider"); + pRNG->win32.BCryptGenRandom = (cryptorand_proc)GetProcAddress(hBcryptDLL, "BCryptGenRandom"); + + if (pRNG->win32.BCryptOpenAlgorithmProvider != NULL && pRNG->win32.BCryptCloseAlgorithmProvider != NULL && pRNG->win32.BCryptGenRandom != NULL) + { + if (((CRYPTORAND_PFN_BCryptOpenAlgorithmProvider)pRNG->win32.BCryptOpenAlgorithmProvider)(&pRNG->win32.hAlgorithm, CRYPTORAND_BCRYPT_RNG_ALGORITHM, NULL, 0) == 0) + { + return CRYPTORAND_SUCCESS; + } + else + { + /* Failed to open provider. */ + } + } + else + { + /* Failed to retrieve function addresses.*/ + } + } + else + { + /* Failed to load DLL. */ + } + } + + /* Getting here means we're falling back to the old method. */ + CRYPTORAND_ZERO_OBJECT(&pRNG->win32); /* For safety. */ + { + HMODULE hAdvapiDLL; + + hAdvapiDLL = LoadLibraryW(L"advapi32.dll"); + if (hAdvapiDLL != NULL) + { + pRNG->win32.hAdvapiDLL = (void *)hAdvapiDLL; + pRNG->win32.CryptAcquireContextW = (cryptorand_proc)GetProcAddress(hAdvapiDLL, "CryptAcquireContextW"); + pRNG->win32.CryptReleaseContext = (cryptorand_proc)GetProcAddress(hAdvapiDLL, "CryptReleaseContext"); + pRNG->win32.CryptGenRandom = (cryptorand_proc)GetProcAddress(hAdvapiDLL, "CryptGenRandom"); + + if (pRNG->win32.CryptAcquireContextW != NULL && pRNG->win32.CryptReleaseContext != NULL && pRNG->win32.CryptGenRandom != NULL) + { + if (((CRYPTORAND_PFN_CryptAcquireContextW)pRNG->win32.CryptAcquireContextW)(&pRNG->win32.hProvider, NULL, NULL, CRYPTORAND_PROV_RSA_FULL, CRYPTORAND_CRYPT_VERIFYCONTEXT | CRYPTORAND_CRYPT_SILENT)) + { + return CRYPTORAND_SUCCESS; + } + else + { + /* Failed to acquire context. */ + } + } + else + { + /* Failed to retrieve function addresses.*/ + } + } + else + { + /* Failed to load DLL. */ + } + } + + /* Getting here means both BCryptGenRandom() and CryptGenRandom() are unusable. */ + CRYPTORAND_ZERO_OBJECT(&pRNG->win32); + return CRYPTORAND_ERROR; +} + +static void cryptorand_uninit__win32(cryptorand *pRNG) +{ + if (pRNG->win32.hAlgorithm != NULL) + { + ((CRYPTORAND_PFN_BCryptCloseAlgorithmProvider)pRNG->win32.BCryptCloseAlgorithmProvider)(pRNG->win32.hAlgorithm, 0); + } + else if (pRNG->win32.hProvider != NULL) + { + ((CRYPTORAND_PFN_CryptReleaseContext)pRNG->win32.CryptReleaseContext)(pRNG->win32.hProvider, 0); + } + + if (pRNG->win32.hBcryptDLL != NULL) + { + FreeLibrary((HMODULE)pRNG->win32.hBcryptDLL); + } + if (pRNG->win32.hAdvapiDLL != NULL) + { + FreeLibrary((HMODULE)pRNG->win32.hAdvapiDLL); + } +} + +static cryptorand_result cryptorand_generate__win32(cryptorand *pRNG, void *pBufferOut, size_t byteCount) +{ + if (byteCount > 0xFFFFFFFF) + { + return CRYPTORAND_TOO_BIG; /* TODO: Maybe handle this better by running in a loop. */ + } + + if (pRNG->win32.hAlgorithm != NULL) + { + LONG result = ((CRYPTORAND_PFN_BCryptGenRandom)pRNG->win32.BCryptGenRandom)(pRNG->win32.hAlgorithm, (unsigned char *)pBufferOut, (ULONG)byteCount, 0); + if (result != 0) + { + return CRYPTORAND_ERROR; + } + } + else if (pRNG->win32.hProvider != NULL) + { + if (!((CRYPTORAND_PFN_CryptGenRandom)pRNG->win32.CryptGenRandom)(pRNG->win32.hProvider, (DWORD)byteCount, (unsigned char *)pBufferOut)) + { + return CRYPTORAND_ERROR; + } + } + + return CRYPTORAND_SUCCESS; +} +#endif + +#if defined(CRYPTORAND_URANDOM) +#include + +static cryptorand_result cryptorand_init__urandom(cryptorand *pRNG) +{ + pRNG->urandom.pFile = fopen("/dev/urandom", "rb"); + if (pRNG->urandom.pFile == NULL) + { + return CRYPTORAND_ERROR; + } + + return CRYPTORAND_SUCCESS; +} + +static void cryptorand_uninit__urandom(cryptorand *pRNG) +{ + if (pRNG->urandom.pFile == NULL) + { + return; + } + + fclose((FILE *)pRNG->urandom.pFile); +} + +static cryptorand_result cryptorand_generate__urandom(cryptorand *pRNG, void *pBufferOut, size_t byteCount) +{ + size_t bytesRead; + + if (pRNG->urandom.pFile == NULL) + { + return CRYPTORAND_INVALID_OPERATION; + } + + bytesRead = fread(pBufferOut, 1, byteCount, (FILE *)pRNG->urandom.pFile); + if (bytesRead < byteCount) + { + return CRYPTORAND_ERROR; /* Wasn't able to read all the data. Should never happen. */ + } + + return CRYPTORAND_SUCCESS; +} +#endif + +#if defined(CRYPTORAND_ARC4RANDOM) +#include + +static cryptorand_result cryptorand_init__arc4random(cryptorand *pRNG) +{ + (void)pRNG; + return CRYPTORAND_SUCCESS; +} + +static void cryptorand_uninit__arc4random(cryptorand *pRNG) +{ + (void)pRNG; +} + +static cryptorand_result cryptorand_generate__arc4random(cryptorand *pRNG, void *pBufferOut, size_t byteCount) +{ + /* The arc4random() family is always successful. */ + arc4random_buf(pBufferOut, byteCount); + + (void)pRNG; + return CRYPTORAND_SUCCESS; +} +#endif + +CRYPTORAND_API cryptorand_result cryptorand_init(cryptorand *pRNG) +{ + cryptorand_result result; + + if (pRNG == NULL) + { + return CRYPTORAND_INVALID_ARGS; + } + + CRYPTORAND_ZERO_OBJECT(pRNG); + +#if defined(CRYPTORAND_WIN32) + result = cryptorand_init__win32(pRNG); +#elif defined(CRYPTORAND_URANDOM) + result = cryptorand_init__urandom(pRNG); +#elif defined(CRYPTORAND_ARC4RANDOM) + result = cryptorand_init__arc4random(pRNG); +#else + result = CRYPTORAND_NOT_IMPLEMENTED; +#endif + + if (result != CRYPTORAND_SUCCESS) + { + CRYPTORAND_ZERO_OBJECT(pRNG); /* Make sure the caller is given a blank object on failure. */ + } + + return result; +} + +CRYPTORAND_API void cryptorand_uninit(cryptorand *pRNG) +{ + if (pRNG == NULL) + { + return; + } + +#if defined(CRYPTORAND_WIN32) + cryptorand_uninit__win32(pRNG); +#elif defined(CRYPTORAND_URANDOM) + cryptorand_uninit__urandom(pRNG); +#elif defined(CRYPTORAND_ARC4RANDOM) + cryptorand_uninit__arc4random(pRNG); +#else + /* Not implemented. */ +#endif + + CRYPTORAND_ZERO_OBJECT(pRNG); +} + +CRYPTORAND_API cryptorand_result cryptorand_generate(cryptorand *pRNG, void *pBufferOut, size_t byteCount) +{ + cryptorand_result result; + + if (pRNG == NULL || pBufferOut == NULL) + { + return CRYPTORAND_INVALID_ARGS; + } + +#if defined(CRYPTORAND_WIN32) + result = cryptorand_generate__win32(pRNG, pBufferOut, byteCount); +#elif defined(CRYPTORAND_URANDOM) + result = cryptorand_generate__urandom(pRNG, pBufferOut, byteCount); +#elif defined(CRYPTORAND_ARC4RANDOM) + result = cryptorand_generate__arc4random(pRNG, pBufferOut, byteCount); +#else + result = CRYPTORAND_NOT_IMPLEMENTED; +#endif + + /* + If an error occurred, make sure everything is cleared to zero to make it clear to the caller that + the content in the buffer is not valid. + */ + if (result != CRYPTORAND_SUCCESS) + { + CRYPTORAND_ZERO_MEMORY(pBufferOut, byteCount); + } + + return result; +} + +#endif /* cryptorand_c */ +#endif /* CRYPTORAND_IMPLEMENTATION */ + +/* +This software is available as a choice of the following licenses. Choose +whichever you prefer. + +=============================================================================== +ALTERNATIVE 1 - Public Domain (www.unlicense.org) +=============================================================================== +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. + +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to + +=============================================================================== +ALTERNATIVE 2 - MIT No Attribution +=============================================================================== +Copyright 2022 David Reid + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ diff --git a/include/func.hpp b/include/func.hpp index ad84348..073ec47 100644 --- a/include/func.hpp +++ b/include/func.hpp @@ -34,6 +34,6 @@ void add_logininfo_to_buffer(Buffer &buffer, const char *label, const char *user LoginInfoPointer get_logininfo_pointer_from_buffer(Buffer &buffer, int index_of_pass); -void generate_password(std::string &password, int len); +bool generate_password(std::string &password, int len); #endif \ No newline at end of file diff --git a/source/arg_func.cpp b/source/arg_func.cpp index e7a2425..9777f0f 100644 --- a/source/arg_func.cpp +++ b/source/arg_func.cpp @@ -142,7 +142,11 @@ std::optional arg_new_password(Buffer &decrypted_buffer, Buffe length = default_length; } - generate_password(password, length); + if (!generate_password(password, length)) + { + printf("error generating password\n"); + return {}; + } } else { diff --git a/source/func.cpp b/source/func.cpp index 3a13050..2db0a56 100644 --- a/source/func.cpp +++ b/source/func.cpp @@ -1,10 +1,12 @@ -#include #include #include "func.hpp" #include "glob.hpp" #include "buffer.hpp" +#define CRYPTORAND_IMPLEMENTATION +#include "cryptorand.hpp" + int find_logininfo_in_buffer(Buffer &buffer, const char *label) { Index *index = (Index *)buffer.buffer; @@ -73,12 +75,26 @@ LoginInfoPointer get_logininfo_pointer_from_buffer(Buffer &buffer, int index_of_ return ret; } -void generate_password(std::string &password, int len) +bool generate_password(std::string &password, int len) { - srand(time(NULL)); + + cryptorand pRNG; + cryptorand_result res = cryptorand_init(&pRNG); + if (res != CRYPTORAND_SUCCESS) + return false; + + int buff; + char characters[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789~!@#$%^&*()_-+={[}]|:;<,>.?"; for (int i = 0; i < len; i++) { - password += characters[rand() % (sizeof(characters) - 1)]; + res = cryptorand_generate(&pRNG, &buff, sizeof(buff)); + if (res != CRYPTORAND_SUCCESS) + return false; + password += characters[buff % (sizeof(characters) - 1)]; } + + cryptorand_uninit(&pRNG); + + return true; }