5#include <c++utilities/chrono/datetime.h>
6#include <c++utilities/conversion/binaryconversion.h>
7#include <c++utilities/conversion/stringbuilder.h>
8#include <c++utilities/conversion/stringconversion.h>
10#include <openssl/core_names.h>
11#include <openssl/err.h>
12#include <openssl/evp.h>
13#include <openssl/hmac.h>
14#include <openssl/params.h>
15#include <openssl/rand.h>
16#include <openssl/sha.h>
18#ifdef OPENSSL_VERSION_MAJOR
19#if OPENSSL_VERSION_MAJOR >= 3
20#define PASSWORD_FILE_USE_OPENSSL_PROVIDER_API
21#include <openssl/provider.h>
43static_assert(
Sha256Sum::size == SHA256_DIGEST_LENGTH,
"SHA-256 sum fits into Sha256Sum struct");
50static std::vector<std::uint8_t> decodeBase32(std::string_view input)
52 auto result = std::vector<std::uint8_t>();
53 result.reserve((input.size() * 5 + 7) / 8);
54 auto buffer = std::uint32_t();
56 for (
char c : input) {
58 if (c >=
'A' && c <=
'Z') {
60 }
else if (c >=
'a' && c <=
'z') {
62 }
else if (c >=
'2' && c <=
'7') {
64 }
else if (c ==
'=') {
66 }
else if (std::isspace(
static_cast<unsigned char>(c))) {
69 throw CppUtilities::ConversionException(
"Base32 encoded secret contains invalid character");
71 buffer = (buffer << 5) | static_cast<std::uint32_t>(value);
74 result.push_back(
static_cast<std::uint8_t
>((buffer >> (bitsLeft - 8)) & 0xFF));
86static std::string_view getQueryParam(std::string_view url, std::string_view param, std::string_view fallback = std::string_view())
88 const auto queryStart = url.find(
'?');
89 if (queryStart == std::string_view::npos) {
90 if (fallback.empty()) {
91 throw CppUtilities::ConversionException(
"query parameters missing");
95 const auto query = url.substr(queryStart + 1);
96 auto pos = std::size_t();
97 while (pos != std::string_view::npos) {
98 const auto nextPos = query.find(
'&', pos);
99 const auto pair = query.substr(pos, nextPos == std::string_view::npos ? nextPos : nextPos - pos);
100 const auto eqPos = pair.find(
'=');
101 if (eqPos != std::string_view::npos && pair.substr(0, eqPos) == param) {
102 return pair.substr(eqPos + 1);
104 pos = nextPos == std::string_view::npos ? nextPos : nextPos + 1;
106 if (fallback.empty()) {
107 throw CppUtilities::ConversionException(CppUtilities::argsToString(param,
" is empty/missing"));
113#ifdef PASSWORD_FILE_USE_OPENSSL_PROVIDER_API
114static OSSL_PROVIDER *provider =
nullptr;
123 ERR_load_crypto_strings();
126 OpenSSL_add_all_algorithms();
132#ifdef PASSWORD_FILE_USE_OPENSSL_PROVIDER_API
133 if (!(provider = OSSL_PROVIDER_load(
nullptr,
"default"))) {
134 std::cerr <<
"Unable to load default OpenSSL provider.\n";
151#ifdef PASSWORD_FILE_USE_OPENSSL_PROVIDER_API
153 OSSL_PROVIDER_unload(provider);
164 SHA256(buffer, size, hash.data);
175 if (HMAC(EVP_sha256(), key,
static_cast<int>(keySize), data, dataSize, result.data, &resultLen) ==
nullptr) {
186 auto val = std::uint32_t();
187 if (RAND_bytes(
reinterpret_cast<unsigned char *
>(&val),
sizeof(val)) != 1) {
188 auto errorMsg = std::string();
189 while (
unsigned long errorCode = ERR_get_error()) {
190 if (!errorMsg.empty())
192 errorMsg += ERR_error_string(errorCode,
nullptr);
196 return min + (val % (max - min + 1));
211 const auto secret = decodeBase32(getQueryParam(url,
"secret"));
212 const auto period = CppUtilities::stringToNumber<std::uint64_t>(getQueryParam(url,
"period",
"30"));
213 const auto digits = CppUtilities::stringToNumber<int>(getQueryParam(url,
"digits",
"6"));
214 const auto algo = getQueryParam(url,
"algorithm",
"SHA1");
215 if (period < 1 || digits < 1) {
216 throw CppUtilities::ConversionException(
"period and digits must be >= 1");
220 auto timeStamp =
static_cast<std::uint64_t
>(time.toTimeStamp());
221 auto counter = timeStamp / period;
222 auto remaining = period - (timeStamp % period);
223 auto counterBytes = std::array<unsigned char, 8>();
224 CppUtilities::BE::getBytes(counter,
reinterpret_cast<char *
>(counterBytes.data()));
227 EVP_MAC *
const mac = EVP_MAC_fetch(
nullptr,
"HMAC",
nullptr);
231 EVP_MAC_CTX *
const ctx = EVP_MAC_CTX_new(mac);
238 OSSL_PARAM params[2];
239 params[0] = OSSL_PARAM_construct_utf8_string(OSSL_MAC_PARAM_DIGEST,
const_cast<char *
>(algo.data()), 0);
240 params[1] = OSSL_PARAM_construct_end();
243 if (EVP_MAC_init(ctx, secret.data(), secret.size(), params) != 1) {
244 EVP_MAC_CTX_free(ctx);
250 if (EVP_MAC_update(ctx, counterBytes.data(), counterBytes.size()) != 1) {
251 EVP_MAC_CTX_free(ctx);
257 auto out = std::array<unsigned char, EVP_MAX_MD_SIZE>();
258 auto outLen = std::size_t();
259 if (EVP_MAC_final(ctx, out.data(), &outLen, out.size()) != 1) {
260 EVP_MAC_CTX_free(ctx);
264 EVP_MAC_CTX_free(ctx);
268 const auto offset =
static_cast<std::size_t
>(out[outLen - 1] & 0x0F);
269 const auto truncatedHash = (
static_cast<std::uint32_t
>(out[offset] & 0x7F) << 24) | (
static_cast<std::uint32_t
>(out[offset + 1] & 0xFF) << 16)
270 | (
static_cast<std::uint32_t
>(out[offset + 2] & 0xFF) << 8) |
static_cast<std::uint32_t
>(out[offset + 3] & 0xFF);
271 const auto otp = truncatedHash %
static_cast<std::uint32_t
>(std::pow(10, digits));
273 .digits = (std::ostringstream() << std::setfill(
'0') << std::setw(digits) << otp).str(),
274 .period = CppUtilities::TimeSpan::fromSeconds(
static_cast<double>(period)),
275 .remaining = CppUtilities::TimeSpan::fromSeconds(
static_cast<double>(remaining)),
The exception that is thrown when an encryption/decryption error occurs.
Contains functions utilizing the usage of OpenSSL.
PASSWORD_FILE_EXPORT std::uint32_t generateRandomNumber(std::uint32_t min, std::uint32_t max)
Generates a random number using OpenSSL.
PASSWORD_FILE_EXPORT void init()
Initializes OpenSSL.
PASSWORD_FILE_EXPORT void clean()
Cleans resources of OpenSSL.
PASSWORD_FILE_EXPORT Sha256Sum computeHmacSha256(const unsigned char *key, std::size_t keySize, const unsigned char *data, std::size_t dataSize)
Computes an HMAC-SHA256 using OpenSSL.
PASSWORD_FILE_EXPORT TOTP computeTOTP(std::string_view url, CppUtilities::DateTime time)
Compute a token following the TOTP standard (RFC 6238).
PASSWORD_FILE_EXPORT Sha256Sum computeSha256Sum(const unsigned char *buffer, std::size_t size)
Computes a SHA-256 sum using OpenSSL.
Contains utility classes and functions.
static constexpr std::size_t size