Passwordfile library: util/openssl.cpp Source File

Passwordfile library 5.1.0 C++ library to read/write passwords from/to encrypted files

Loading...
Searching...
No Matches
openssl.cpp
Go to the documentation of this file.
1#include "./openssl.h"
2#include "./opensslrandomdevice.h"
3
4#include "../io/cryptoexception.h"
5
6#include <c++utilities/chrono/datetime.h>
7#include <c++utilities/conversion/binaryconversion.h>
8#include <c++utilities/conversion/stringbuilder.h>
9#include <c++utilities/conversion/stringconversion.h>
10
11#include <openssl/core_names.h>
12#include <openssl/err.h>
13#include <openssl/evp.h>
14#include <openssl/params.h>
15#include <openssl/sha.h>
16
17#include <array>
18#include <cctype>
19#include <cmath>
20#include <iomanip>
21#include <random>
22#include <sstream>
23#include <vector>
24
28namespace Util {
29
33namespace OpenSsl {
34
35static_assert(Sha256Sum::size == SHA256_DIGEST_LENGTH, "SHA-256 sum fits into Sha256Sum struct");
36
37namespace {
42static std::vector<std::uint8_t> decodeBase32(std::string_view input)
43{
44 auto result = std::vector<std::uint8_t>();
45 result.reserve((input.size() * 5 + 7) / 8);
46 auto buffer = std::uint32_t();
47 auto bitsLeft = 0;
48 for (char c : input) {
49 int value;
50 if (c >= 'A' && c <= 'Z') {
51 value = c - 'A';
52 } else if (c >= 'a' && c <= 'z') {
53 value = c - 'a';
54 } else if (c >= '2' && c <= '7') {
55 value = c - '2' + 26;
56 } else if (c == '=') {
57 break;
58 } else if (std::isspace(static_cast<unsigned char>(c))) {
59 continue;
60 } else {
61 throw CppUtilities::ConversionException("Base32 encoded secret contains invalid character");
62 }
63 buffer = (buffer << 5) | static_cast<std::uint32_t>(value);
64 bitsLeft += 5;
65 if (bitsLeft >= 8) {
66 result.push_back(static_cast<std::uint8_t>((buffer >> (bitsLeft - 8)) & 0xFF));
67 bitsLeft -= 8;
68 }
69 }
70 return result;
71}
72
78static std::string_view getQueryParam(std::string_view url, std::string_view param, std::string_view fallback = std::string_view())
79{
80 const auto queryStart = url.find('?');
81 if (queryStart == std::string_view::npos) {
82 if (fallback.empty()) {
83 throw CppUtilities::ConversionException("query parameters missing");
84 }
85 return fallback;
86 }
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);
95 }
96 pos = nextPos == std::string_view::npos ? nextPos : nextPos + 1;
97 }
98 if (fallback.empty()) {
99 throw CppUtilities::ConversionException(CppUtilities::argsToString(param, " is empty/missing"));
100 }
101 return fallback;
102}
103} // namespace
104
108 void init()
109{
110 // load the human readable error strings for libcrypto
111 ERR_load_crypto_strings();
112 // load all digest and cipher algorithms
113 OpenSSL_add_all_algorithms();
114}
115
119 void clean()
120{
121 // removes all digests and ciphers
122 EVP_cleanup();
123 // remove error strings
124 ERR_free_strings();
125}
126
130 Sha256Sum computeSha256Sum(const unsigned char *buffer, std::size_t size)
131{
132 auto hash = Sha256Sum();
133 SHA256(buffer, size, hash.data);
134 return hash;
135}
136
140std::uint32_t generateRandomNumber(std::uint32_t min, std::uint32_t max)
141{
142 OpenSslRandomDevice dev;
143 std::default_random_engine rng(dev());
144 std::uniform_int_distribution<std::uint32_t> dist(min, max);
145 return dist(rng);
146}
147
157 TOTP computeTOTP(std::string_view url, CppUtilities::DateTime time)
158{
159 // read parameters from URL
160 const auto secret = decodeBase32(getQueryParam(url, "secret"));
161 const auto period = CppUtilities::stringToNumber<std::uint64_t>(getQueryParam(url, "period", "30"));
162 const auto digits = CppUtilities::stringToNumber<int>(getQueryParam(url, "digits", "6"));
163 const auto algo = getQueryParam(url, "algorithm", "SHA1");
164
165 // encode the counter as a 64-bit big-endian integer as per RFC 6238
166 auto timeStamp = static_cast<std::uint64_t>(time.toTimeStamp());
167 auto counter = timeStamp / period;
168 auto remaining = period - (timeStamp % period);
169 auto counterBytes = std::array<unsigned char, 8>();
170 CppUtilities::BE::getBytes(counter, reinterpret_cast<char *>(counterBytes.data()));
171
172 // create context
173 EVP_MAC *const mac = EVP_MAC_fetch(nullptr, "HMAC", nullptr);
174 if (!mac) {
175 throw Io::CryptoException("EVP_MAC_fetch failed for algorithm=HMAC");
176 }
177 EVP_MAC_CTX *const ctx = EVP_MAC_CTX_new(mac);
178 if (!ctx) {
179 EVP_MAC_free(mac);
180 throw Io::CryptoException("EVP_MAC_CTX_new failed");
181 }
182
183 // init params for specified algorithm
184 OSSL_PARAM params[2];
185 params[0] = OSSL_PARAM_construct_utf8_string(OSSL_MAC_PARAM_DIGEST, const_cast<char *>(algo.data()), 0);
186 params[1] = OSSL_PARAM_construct_end();
187
188 // supply secret
189 if (EVP_MAC_init(ctx, secret.data(), secret.size(), params) != 1) {
190 EVP_MAC_CTX_free(ctx);
191 EVP_MAC_free(mac);
192 throw Io::CryptoException("EVP_MAC_init failed");
193 }
194
195 // supply counter
196 if (EVP_MAC_update(ctx, counterBytes.data(), counterBytes.size()) != 1) {
197 EVP_MAC_CTX_free(ctx);
198 EVP_MAC_free(mac);
199 throw Io::CryptoException("EVP_MAC_update failed");
200 }
201
202 // get result
203 auto out = std::array<unsigned char, EVP_MAX_MD_SIZE>();
204 auto outLen = std::size_t();
205 if (EVP_MAC_final(ctx, out.data(), &outLen, out.size()) != 1) {
206 EVP_MAC_CTX_free(ctx);
207 EVP_MAC_free(mac);
208 throw Io::CryptoException("EVP_MAC_final failed");
209 }
210 EVP_MAC_CTX_free(ctx);
211 EVP_MAC_free(mac);
212
213 // return token digits as string
214 const auto offset = static_cast<std::size_t>(out[outLen - 1] & 0x0F);
215 const auto truncatedHash = (static_cast<std::uint32_t>(out[offset] & 0x7F) << 24) | (static_cast<std::uint32_t>(out[offset + 1] & 0xFF) << 16)
216 | (static_cast<std::uint32_t>(out[offset + 2] & 0xFF) << 8) | static_cast<std::uint32_t>(out[offset + 3] & 0xFF);
217 const auto otp = truncatedHash % static_cast<std::uint32_t>(std::pow(10, digits));
218 return TOTP{
219 .digits = (std::ostringstream() << std::setfill('0') << std::setw(digits) << otp).str(),
220 .period = CppUtilities::TimeSpan::fromSeconds(static_cast<double>(period)),
221 .remaining = CppUtilities::TimeSpan::fromSeconds(static_cast<double>(remaining)),
222 };
223}
224
225} // namespace OpenSsl
226} // namespace Util
Io::CryptoException
The exception that is thrown when an encryption/decryption error occurs.
Definition cryptoexception.h:11
Util::OpenSslRandomDevice
Provides a random device using the OpenSSL function RAND_bytes().
Definition opensslrandomdevice.h:11
cryptoexception.h
Util::OpenSsl
Contains functions utilizing the usage of OpenSSL.
Definition openssl.h:19
Util::OpenSsl::generateRandomNumber
PASSWORD_FILE_EXPORT std::uint32_t generateRandomNumber(std::uint32_t min, std::uint32_t max)
Generates a random number using OpenSSL.
Definition openssl.cpp:140
Util::OpenSsl::init
PASSWORD_FILE_EXPORT void init()
Initializes OpenSSL.
Definition openssl.cpp:108
Util::OpenSsl::clean
PASSWORD_FILE_EXPORT void clean()
Cleans resources of OpenSSL.
Definition openssl.cpp:119
Util::OpenSsl::computeTOTP
PASSWORD_FILE_EXPORT TOTP computeTOTP(std::string_view url, CppUtilities::DateTime time)
Compute a token following the TOTP standard (RFC 6238).
Definition openssl.cpp:157
Util::OpenSsl::computeSha256Sum
PASSWORD_FILE_EXPORT Sha256Sum computeSha256Sum(const unsigned char *buffer, std::size_t size)
Computes a SHA-256 sum using OpenSSL.
Definition openssl.cpp:130
Util
Contains utility classes and functions.
Definition openssl.h:17
openssl.h
opensslrandomdevice.h
Util::OpenSsl::Sha256Sum
Definition openssl.h:21
Util::OpenSsl::Sha256Sum::size
static constexpr std::size_t size
Definition openssl.h:22
Util::OpenSsl::TOTP
Definition openssl.h:26