Passwordfile library: util/openssl.cpp Source File

Passwordfile library 5.2.1 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
3#include "../io/cryptoexception.h"
4
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>
9
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>
17
18#ifdef OPENSSL_VERSION_MAJOR
19#if OPENSSL_VERSION_MAJOR >= 3
20#define PASSWORD_FILE_USE_OPENSSL_PROVIDER_API
21#include <openssl/provider.h>
22#endif
23#endif
24
25#include <array>
26#include <cctype>
27#include <cmath>
28#include <iomanip>
29#include <iostream>
30#include <sstream>
31#include <vector>
32
36namespace Util {
37
41namespace OpenSsl {
42
43static_assert(Sha256Sum::size == SHA256_DIGEST_LENGTH, "SHA-256 sum fits into Sha256Sum struct");
44
45namespace {
50static std::vector<std::uint8_t> decodeBase32(std::string_view input)
51{
52 auto result = std::vector<std::uint8_t>();
53 result.reserve((input.size() * 5 + 7) / 8);
54 auto buffer = std::uint32_t();
55 auto bitsLeft = 0;
56 for (char c : input) {
57 int value;
58 if (c >= 'A' && c <= 'Z') {
59 value = c - 'A';
60 } else if (c >= 'a' && c <= 'z') {
61 value = c - 'a';
62 } else if (c >= '2' && c <= '7') {
63 value = c - '2' + 26;
64 } else if (c == '=') {
65 break;
66 } else if (std::isspace(static_cast<unsigned char>(c))) {
67 continue;
68 } else {
69 throw CppUtilities::ConversionException("Base32 encoded secret contains invalid character");
70 }
71 buffer = (buffer << 5) | static_cast<std::uint32_t>(value);
72 bitsLeft += 5;
73 if (bitsLeft >= 8) {
74 result.push_back(static_cast<std::uint8_t>((buffer >> (bitsLeft - 8)) & 0xFF));
75 bitsLeft -= 8;
76 }
77 }
78 return result;
79}
80
86static std::string_view getQueryParam(std::string_view url, std::string_view param, std::string_view fallback = std::string_view())
87{
88 const auto queryStart = url.find('?');
89 if (queryStart == std::string_view::npos) {
90 if (fallback.empty()) {
91 throw CppUtilities::ConversionException("query parameters missing");
92 }
93 return fallback;
94 }
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);
103 }
104 pos = nextPos == std::string_view::npos ? nextPos : nextPos + 1;
105 }
106 if (fallback.empty()) {
107 throw CppUtilities::ConversionException(CppUtilities::argsToString(param, " is empty/missing"));
108 }
109 return fallback;
110}
111} // namespace
112
113#ifdef PASSWORD_FILE_USE_OPENSSL_PROVIDER_API
114static OSSL_PROVIDER *provider = nullptr;
115#endif
116
120 void init()
121{
122 // load the human readable error strings for libcrypto (for compatibility with OpenSSL < 1.1.0)
123 ERR_load_crypto_strings();
124
125 // load all digest and cipher algorithms (for compatibility with OpenSSL < 1.1.0)
126 OpenSSL_add_all_algorithms();
127
128 // ensure the default provider is loaded
129 // note: Other libraries like the Qt Network plugin might configure their own provider contexts
130 // explicitly. This explicit configuration disables the automatic fallback for loading the
131 // "default" provider globally - which we therefore need to do explicitly as well.
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";
135 }
136#endif
137}
138
142 void clean()
143{
144 // removes all digests and ciphers (for compatibility with OpenSSL < 1.1.0)
145 EVP_cleanup();
146
147 // remove error strings (for compatibility with OpenSSL < 1.1.0)
148 ERR_free_strings();
149
150 // unload default provider
151#ifdef PASSWORD_FILE_USE_OPENSSL_PROVIDER_API
152 if (provider) {
153 OSSL_PROVIDER_unload(provider);
154 }
155#endif
156}
157
161 Sha256Sum computeSha256Sum(const unsigned char *buffer, std::size_t size)
162{
163 auto hash = Sha256Sum();
164 SHA256(buffer, size, hash.data);
165 return hash;
166}
167
171 Sha256Sum computeHmacSha256(const unsigned char *key, std::size_t keySize, const unsigned char *data, std::size_t dataSize)
172{
173 auto result = Sha256Sum();
174 unsigned int resultLen = Sha256Sum::size;
175 if (HMAC(EVP_sha256(), key, static_cast<int>(keySize), data, dataSize, result.data, &resultLen) == nullptr) {
176 throw Io::CryptoException("HMAC-SHA256 computation failed.");
177 }
178 return result;
179}
180
184std::uint32_t generateRandomNumber(std::uint32_t min, std::uint32_t max)
185{
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())
191 errorMsg += '\n';
192 errorMsg += ERR_error_string(errorCode, nullptr);
193 }
194 throw Io::CryptoException(std::move(errorMsg));
195 }
196 return min + (val % (max - min + 1));
197}
198
208 TOTP computeTOTP(std::string_view url, CppUtilities::DateTime time)
209{
210 // read parameters from URL
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");
217 }
218
219 // encode the counter as a 64-bit big-endian integer as per RFC 6238
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()));
225
226 // create context
227 EVP_MAC *const mac = EVP_MAC_fetch(nullptr, "HMAC", nullptr);
228 if (!mac) {
229 throw Io::CryptoException("EVP_MAC_fetch failed for algorithm=HMAC");
230 }
231 EVP_MAC_CTX *const ctx = EVP_MAC_CTX_new(mac);
232 if (!ctx) {
233 EVP_MAC_free(mac);
234 throw Io::CryptoException("EVP_MAC_CTX_new failed");
235 }
236
237 // init params for specified algorithm
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();
241
242 // supply secret
243 if (EVP_MAC_init(ctx, secret.data(), secret.size(), params) != 1) {
244 EVP_MAC_CTX_free(ctx);
245 EVP_MAC_free(mac);
246 throw Io::CryptoException("EVP_MAC_init failed");
247 }
248
249 // supply counter
250 if (EVP_MAC_update(ctx, counterBytes.data(), counterBytes.size()) != 1) {
251 EVP_MAC_CTX_free(ctx);
252 EVP_MAC_free(mac);
253 throw Io::CryptoException("EVP_MAC_update failed");
254 }
255
256 // get result
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);
261 EVP_MAC_free(mac);
262 throw Io::CryptoException("EVP_MAC_final failed");
263 }
264 EVP_MAC_CTX_free(ctx);
265 EVP_MAC_free(mac);
266
267 // return token digits as string
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));
272 return TOTP{
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)),
276 };
277}
278
279} // namespace OpenSsl
280} // namespace Util
Io::CryptoException
The exception that is thrown when an encryption/decryption error occurs.
Definition cryptoexception.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:184
Util::OpenSsl::init
PASSWORD_FILE_EXPORT void init()
Initializes OpenSSL.
Definition openssl.cpp:120
Util::OpenSsl::clean
PASSWORD_FILE_EXPORT void clean()
Cleans resources of OpenSSL.
Definition openssl.cpp:142
Util::OpenSsl::computeHmacSha256
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.
Definition openssl.cpp:171
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:208
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:161
Util
Contains utility classes and functions.
Definition openssl.h:17
openssl.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