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>
35static_assert(
Sha256Sum::size == SHA256_DIGEST_LENGTH,
"SHA-256 sum fits into Sha256Sum struct");
42static std::vector<std::uint8_t> decodeBase32(std::string_view input)
44 auto result = std::vector<std::uint8_t>();
45 result.reserve((input.size() * 5 + 7) / 8);
46 auto buffer = std::uint32_t();
48 for (
char c : input) {
50 if (c >=
'A' && c <=
'Z') {
52 }
else if (c >=
'a' && c <=
'z') {
54 }
else if (c >=
'2' && c <=
'7') {
56 }
else if (c ==
'=') {
58 }
else if (std::isspace(
static_cast<unsigned char>(c))) {
61 throw CppUtilities::ConversionException(
"Base32 encoded secret contains invalid character");
63 buffer = (buffer << 5) | static_cast<std::uint32_t>(value);
66 result.push_back(
static_cast<std::uint8_t
>((buffer >> (bitsLeft - 8)) & 0xFF));
78static std::string_view getQueryParam(std::string_view url, std::string_view param, std::string_view fallback = std::string_view())
80 const auto queryStart = url.find(
'?');
81 if (queryStart == std::string_view::npos) {
82 if (fallback.empty()) {
83 throw CppUtilities::ConversionException(
"query parameters missing");
87 const auto query = url.substr(queryStart + 1);
88 auto pos = std::size_t();
89 while (pos != std::string_view::npos) {
90 const auto nextPos = query.find(
'&', pos);
91 const auto pair = query.substr(pos, nextPos == std::string_view::npos ? nextPos : nextPos - pos);
92 const auto eqPos = pair.find(
'=');
93 if (eqPos != std::string_view::npos && pair.substr(0, eqPos) == param) {
94 return pair.substr(eqPos + 1);
96 pos = nextPos == std::string_view::npos ? nextPos : nextPos + 1;
98 if (fallback.empty()) {
99 throw CppUtilities::ConversionException(CppUtilities::argsToString(param,
" is empty/missing"));
111 ERR_load_crypto_strings();
113 OpenSSL_add_all_algorithms();
133 SHA256(buffer, size, hash.data);
144 if (HMAC(EVP_sha256(), key,
static_cast<int>(keySize), data, dataSize, result.data, &resultLen) ==
nullptr) {
155 auto val = std::uint32_t();
156 if (RAND_bytes(
reinterpret_cast<unsigned char *
>(&val),
sizeof(val)) != 1) {
157 auto errorMsg = std::string();
158 while (
unsigned long errorCode = ERR_get_error()) {
159 if (!errorMsg.empty())
161 errorMsg += ERR_error_string(errorCode,
nullptr);
165 return min + (val % (max - min + 1));
180 const auto secret = decodeBase32(getQueryParam(url,
"secret"));
181 const auto period = CppUtilities::stringToNumber<std::uint64_t>(getQueryParam(url,
"period",
"30"));
182 const auto digits = CppUtilities::stringToNumber<int>(getQueryParam(url,
"digits",
"6"));
183 const auto algo = getQueryParam(url,
"algorithm",
"SHA1");
186 auto timeStamp =
static_cast<std::uint64_t
>(time.toTimeStamp());
187 auto counter = timeStamp / period;
188 auto remaining = period - (timeStamp % period);
189 auto counterBytes = std::array<unsigned char, 8>();
190 CppUtilities::BE::getBytes(counter,
reinterpret_cast<char *
>(counterBytes.data()));
193 EVP_MAC *
const mac = EVP_MAC_fetch(
nullptr,
"HMAC",
nullptr);
197 EVP_MAC_CTX *
const ctx = EVP_MAC_CTX_new(mac);
204 OSSL_PARAM params[2];
205 params[0] = OSSL_PARAM_construct_utf8_string(OSSL_MAC_PARAM_DIGEST,
const_cast<char *
>(algo.data()), 0);
206 params[1] = OSSL_PARAM_construct_end();
209 if (EVP_MAC_init(ctx, secret.data(), secret.size(), params) != 1) {
210 EVP_MAC_CTX_free(ctx);
216 if (EVP_MAC_update(ctx, counterBytes.data(), counterBytes.size()) != 1) {
217 EVP_MAC_CTX_free(ctx);
223 auto out = std::array<unsigned char, EVP_MAX_MD_SIZE>();
224 auto outLen = std::size_t();
225 if (EVP_MAC_final(ctx, out.data(), &outLen, out.size()) != 1) {
226 EVP_MAC_CTX_free(ctx);
230 EVP_MAC_CTX_free(ctx);
234 const auto offset =
static_cast<std::size_t
>(out[outLen - 1] & 0x0F);
235 const auto truncatedHash = (
static_cast<std::uint32_t
>(out[offset] & 0x7F) << 24) | (
static_cast<std::uint32_t
>(out[offset + 1] & 0xFF) << 16)
236 | (
static_cast<std::uint32_t
>(out[offset + 2] & 0xFF) << 8) |
static_cast<std::uint32_t
>(out[offset + 3] & 0xFF);
237 const auto otp = truncatedHash %
static_cast<std::uint32_t
>(std::pow(10, digits));
239 .digits = (std::ostringstream() << std::setfill(
'0') << std::setw(digits) << otp).str(),
240 .period = CppUtilities::TimeSpan::fromSeconds(
static_cast<double>(period)),
241 .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