Passwordfile library: util/openssl.cpp Source File

Passwordfile library 5.2.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
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#include <array>
19#include <cctype>
20#include <cmath>
21#include <iomanip>
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
140 Sha256Sum computeHmacSha256(const unsigned char *key, std::size_t keySize, const unsigned char *data, std::size_t dataSize)
141{
142 auto result = Sha256Sum();
143 unsigned int resultLen = Sha256Sum::size;
144 if (HMAC(EVP_sha256(), key, static_cast<int>(keySize), data, dataSize, result.data, &resultLen) == nullptr) {
145 throw Io::CryptoException("HMAC-SHA256 computation failed.");
146 }
147 return result;
148}
149
153std::uint32_t generateRandomNumber(std::uint32_t min, std::uint32_t max)
154{
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())
160 errorMsg += '\n';
161 errorMsg += ERR_error_string(errorCode, nullptr);
162 }
163 throw Io::CryptoException(std::move(errorMsg));
164 }
165 return min + (val % (max - min + 1));
166}
167
177 TOTP computeTOTP(std::string_view url, CppUtilities::DateTime time)
178{
179 // read parameters from URL
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");
184
185 // encode the counter as a 64-bit big-endian integer as per RFC 6238
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()));
191
192 // create context
193 EVP_MAC *const mac = EVP_MAC_fetch(nullptr, "HMAC", nullptr);
194 if (!mac) {
195 throw Io::CryptoException("EVP_MAC_fetch failed for algorithm=HMAC");
196 }
197 EVP_MAC_CTX *const ctx = EVP_MAC_CTX_new(mac);
198 if (!ctx) {
199 EVP_MAC_free(mac);
200 throw Io::CryptoException("EVP_MAC_CTX_new failed");
201 }
202
203 // init params for specified algorithm
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();
207
208 // supply secret
209 if (EVP_MAC_init(ctx, secret.data(), secret.size(), params) != 1) {
210 EVP_MAC_CTX_free(ctx);
211 EVP_MAC_free(mac);
212 throw Io::CryptoException("EVP_MAC_init failed");
213 }
214
215 // supply counter
216 if (EVP_MAC_update(ctx, counterBytes.data(), counterBytes.size()) != 1) {
217 EVP_MAC_CTX_free(ctx);
218 EVP_MAC_free(mac);
219 throw Io::CryptoException("EVP_MAC_update failed");
220 }
221
222 // get result
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);
227 EVP_MAC_free(mac);
228 throw Io::CryptoException("EVP_MAC_final failed");
229 }
230 EVP_MAC_CTX_free(ctx);
231 EVP_MAC_free(mac);
232
233 // return token digits as string
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));
238 return TOTP{
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)),
242 };
243}
244
245} // namespace OpenSsl
246} // 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:153
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::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:140
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:177
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
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