Azure C++ Utils 1.5.2+3
Azure REST API Helpers for Modern C++
C:/Users/maas/source/repos/siddiqsoft/azure-cpp-utils/src/encryption-utils.hpp
Go to the documentation of this file.
1/*
2 azure-cpp-utils : Azure REST API Utilities for Modern C++
3
4 BSD 3-Clause License
5
6 Copyright (c) 2021, Siddiq Software LLC
7 All rights reserved.
8
9 Redistribution and use in source and binary forms, with or without
10 modification, are permitted provided that the following conditions are met:
11
12 1. Redistributions of source code must retain the above copyright notice, this
13 list of conditions and the following disclaimer.
14
15 2. Redistributions in binary form must reproduce the above copyright notice,
16 this list of conditions and the following disclaimer in the documentation
17 and/or other materials provided with the distribution.
18
19 3. Neither the name of the copyright holder nor the names of its
20 contributors may be used to endorse or promote products derived from
21 this software without specific prior written permission.
22
23 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
27 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
29 SERVICES; LOSS OF USE, d_, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
30 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
31 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
32 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 */
34
35#pragma once
36
37#ifndef ENCRYPTION_UTILS_HPP
38#define ENCRYPTION_UTILS_HPP
39
40
41#include <algorithm>
42#include <iostream>
43#include <chrono>
44#include <string>
45#include <functional>
46#include <memory>
47#include <ranges>
48#include <concepts>
49#include <format>
50
51#if defined(_WIN64) || defined(WIN64) || defined(WIN32) || defined(_WIN32)
52#include <Windows.h>
53#include <wincrypt.h>
54#include <bcrypt.h>
55#pragma comment(lib, "bcrypt")
56#pragma comment(lib, "crypt32")
57#endif
58
59
60#include "base64-utils.hpp"
61#include "url-utils.hpp"
62#include "siddiqsoft/RunOnEnd.hpp"
63
64
66namespace siddiqsoft
67{
73 {
77 template <typename T = char>
78 requires std::same_as<T, char> || std::same_as<T, wchar_t>
79 static std::string MD5(const std::basic_string<T>& source)
80 {
81 constexpr char rgbDigits[] {"0123456789abcdef"};
82
83 if constexpr (std::is_same_v<T, char>) {
84 HCRYPTPROV hProv {};
85 HCRYPTHASH hHash {};
86 RunOnEnd cleanUpOnEnd {[&hProv, &hHash] {
87 // Cleanup on exit of this function scope
88 if (hHash != NULL) CryptDestroyHash(hHash);
89 if (hProv != NULL) CryptReleaseContext(hProv, 0);
90 }};
91
92 // Get handle to the crypto provider
93 if (TRUE == CryptAcquireContext(&hProv, nullptr, nullptr, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)) {
94 // Get the hash library, choose MD5..
95 if (TRUE == CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash)) {
96 // Hash the source..
97 if (TRUE ==
98 CryptHashData(
99 hHash, reinterpret_cast<const BYTE*>(source.data()), static_cast<DWORD>(source.length()), 0)) {
100 BYTE rgbHash[sizeof(rgbDigits)] {};
101 DWORD rgbHashSize = sizeof(rgbDigits);
102 // Fetch the results using the gethashparam call..
103 if (TRUE == CryptGetHashParam(hHash, HP_HASHVAL, rgbHash, &rgbHashSize, 0)) {
104 std::string result {};
105
106 for (DWORD i = 0; i < rgbHashSize; i++) {
107 std::format_to(std::back_inserter(result),
108 "{}{}",
109 rgbDigits[rgbHash[i] >> 4],
110 rgbDigits[rgbHash[i] & 0xf]);
111 }
112
113 return result;
114 }
115 }
116 }
117 }
118 }
119 else {
120 // The MD5 result is a "binary" so we must not try and convert.
121 return MD5<char>(ConversionUtils::utf8FromWide(source));
122 }
123
124 // Fall-through failure
125 return {};
126 }
127
128
134 template <typename T = char>
135 requires std::same_as<T, char> || std::same_as<T, wchar_t>
136 static std::string HMAC(const std::basic_string<T>& message, const std::string& key)
137 {
138 if constexpr (std::is_same_v<T, char>) {
139 BCRYPT_ALG_HANDLE hAlg {};
140 BCRYPT_HASH_HANDLE hHash {};
141 NTSTATUS status {0};
142 RunOnEnd cleanupOnEnd {[&hAlg, &hHash] {
143 // All handles we allocate are cleaned up when this function returns to caller
144 if (hAlg) BCryptCloseAlgorithmProvider(hAlg, 0);
145 if (hHash) BCryptDestroyHash(hHash);
146 }};
147
148
149 if (status = BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_SHA256_ALGORITHM, nullptr, BCRYPT_ALG_HANDLE_HMAC_FLAG);
150 status == 0) {
151 // Set the key for the hash function..
152 // Passing NULL, 0 to the pbHashObject and cbHashObject asks the method to allocate
153 // memory on our behalf.
154 if (status = BCryptCreateHash(hAlg,
155 &hHash,
156 nullptr,
157 0,
158 reinterpret_cast<UCHAR*>(const_cast<char*>(key.data())),
159 static_cast<DWORD>(key.length()),
160 0);
161 status == 0)
162 {
163 // Let's hash our message!
164 if (status = BCryptHashData(hHash,
165 reinterpret_cast<UCHAR*>(const_cast<char*>(message.data())),
166 static_cast<DWORD>(message.length()),
167 0);
168 status == 0)
169 {
170 // Get the size of the hash so we can fetch it..
171 DWORD cbHash {0};
172 ULONG cbData {0};
173 if (status = BCryptGetProperty(
174 hAlg, BCRYPT_HASH_LENGTH, reinterpret_cast<UCHAR*>(&cbHash), sizeof(DWORD), &cbData, 0);
175 status == 0) {
176 std::vector<BYTE> pbHash(cbHash);
177 // Fetch the hash value
178 if (status = BCryptFinishHash(hHash, reinterpret_cast<UCHAR*>(pbHash.data()), cbHash, 0);
179 status == 0) {
180 // Return the HMAC as a raw binary..client must choose to encode or leave as-is
181 return std::string {reinterpret_cast<char*>(pbHash.data()), cbHash};
182 }
183 }
184 }
185 }
186 }
187 }
188 else {
189 // The HMAC result is "binary" and must not be treated as wstring
190 return HMAC<char>(ConversionUtils::asciiFromWide(message), key);
191 }
192
193 // Fall-through is failure
194 return {};
195 }
196
197
203 template <typename T = char>
204 requires std::same_as<T, char> || std::same_as<T, wchar_t>
205 static std::basic_string<T>
206 JWTHMAC256(const std::string& key, const std::basic_string<T>& header, const std::basic_string<T>& payload)
207 {
208 if constexpr (std::is_same_v<T, char>) {
209 auto s1 = Base64Utils::urlEscape<char>(Base64Utils::encode<char>(header));
210 auto s2 = Base64Utils::urlEscape<char>(Base64Utils::encode<char>(payload));
211 auto a3 = std::format("{}.{}", s1, s2);
212 auto a4 = HMAC<char>(a3, key);
213 auto signature = Base64Utils::urlEscape<char>(Base64Utils::encode<char>(a4));
214
215 return std::format("{}.{}.{}", s1, s2, signature);
216 }
217 else {
218 // Delegate to the narrow version; conversion at the edge
220 JWTHMAC256<char>(key, ConversionUtils::utf8FromWide(header), ConversionUtils::utf8FromWide(payload)));
221 }
222 }
223
224
232 template <typename T = char>
233 requires std::same_as<T, char> || std::same_as<T, wchar_t>
234 static std::basic_string<T> SASToken(const std::string& key,
235 const std::basic_string<T>& url,
236 const std::basic_string<T>& keyName,
237 const std::chrono::seconds& timeout)
238 {
239 time_t epoch {};
240
241 time(&epoch); // number of seconds since 1970-1-1
242
243 if constexpr (std::is_same_v<T, wchar_t>) {
244 return SASToken<wchar_t>(key, url, keyName, std::to_wstring(int64_t(epoch) + timeout.count()));
245 }
246 else if constexpr (std::is_same_v<T, char>) {
247 return SASToken<char>(key, url, keyName, std::to_string(int64_t(epoch) + timeout.count()));
248 }
249 }
250
251
259 template <typename T = char>
260 requires std::same_as<T, char> || std::same_as<T, wchar_t>
261 static std::basic_string<T> SASToken(const std::string& key,
262 const std::basic_string<T>& url,
263 const std::basic_string<T>& keyName,
264 const std::basic_string<T>& expiry)
265 {
266 if (url.empty()) throw std::invalid_argument("SASToken: url may not be empty");
267 if (keyName.empty()) throw std::invalid_argument("SASToken: keyName may not be empty");
268 if (key.empty()) throw std::invalid_argument("SASToken: key may not be empty");
269 if (expiry.empty()) throw std::invalid_argument("SASToken: expiry may not be empty");
270
271 if constexpr (std::is_same_v<T, char>) {
272 auto s1 = UrlUtils::encode<char>(url, true); // lowercase
273 auto sig = HMAC<char>(std::format("{}\n{}", s1, expiry), key);
274 auto esign = Base64Utils::encode<char>(sig);
275 esign = UrlUtils::encode<char>(esign, true); // lowercase
276
277 return std::format("SharedAccessSignature sr={}&sig={}&se={}&skn={}", s1, esign, expiry, keyName);
278 }
279 else {
280 // Delegate to the narrow version and convert at the edges.
281 return ConversionUtils::wideFromUtf8(SASToken<char>(key,
285 }
286 }
287
288
296 template <typename T = char>
297 requires std::same_as<T, char> || std::same_as<T, wchar_t>
298 static std::basic_string<T> CosmosToken(const std::string& key,
299 const std::basic_string<T>& verb,
300 const std::basic_string<T>& type,
301 const std::basic_string<T>& resourceLink,
302 const std::basic_string<T>& date)
303 {
304 if (key.empty()) throw std::invalid_argument("CosmosToken: key may not be empty");
305 if (date.empty()) throw std::invalid_argument("CosmosToken: date may not be empty");
306 if (verb.empty()) throw std::invalid_argument("CosmosToken: verb may not be empty");
307
308 if constexpr (std::is_same_v<T, char>) {
309 // The formula is expressed as per
310 // https://docs.microsoft.com/en-us/rest/api/documentdb/access-control-on-documentdb-resources?redirectedfrom=MSDN
311 std::string strToHash {};
312
313 std::ranges::transform(verb, std::back_inserter(strToHash), [](auto& ch) { return std::tolower(ch); });
314 std::format_to(std::back_inserter(strToHash), "\n");
315 std::ranges::transform(type, std::back_inserter(strToHash), [](auto& ch) { return std::tolower(ch); });
316 std::format_to(std::back_inserter(strToHash), "\n{}\n", resourceLink);
317 std::ranges::transform(date, std::back_inserter(strToHash), [](auto& ch) { return std::tolower(ch); });
318 std::format_to(std::back_inserter(strToHash), "\n\n");
319
320 if (!strToHash.empty()) {
321 // Sign using SHA256 using the master key and base64 encode. force lowercase
322 if (auto hmacBase64UrlEscaped = UrlUtils::encode<char>(
323 Base64Utils::encode<char>(EncryptionUtils::HMAC<char>(strToHash, key)), true);
324 !hmacBase64UrlEscaped.empty())
325 {
326 return std::format("type%3dmaster%26ver%3d1.0%26sig%3d{}", hmacBase64UrlEscaped);
327 }
328 }
329 }
330 else {
331 // Delegate to the narrow version, conversion at the edges.
332 return ConversionUtils::wideFromAscii(CosmosToken<char>(key,
335 ConversionUtils::asciiFromWide(resourceLink),
337 }
338
339 // Fall-through failure
340 return {};
341 }
342 };
343} // namespace siddiqsoft
344
345#endif // !AZURECPPUTILS_HPP
SiddiqSoft.
static std::wstring wideFromAscii(const std::string &src)
Given an ascii encoded string returns a utf-16 in std::wstring.
static std::wstring wideFromUtf8(const std::string &src)
Given a utf-8 encoded string returns a utf-16 in std::wstring.
static std::string utf8FromWide(const std::wstring &src)
Convert given wide string to utf8 encoded string.
static std::string asciiFromWide(const std::wstring &src)
Convert given wide string to ascii encoded string.
Encryption utility functions for ServiceBus, Cosmos, EventGrid, EventHub Implementation Note!...
static std::basic_string< T > SASToken(const std::string &key, const std::basic_string< T > &url, const std::basic_string< T > &keyName, const std::chrono::seconds &timeout)
Create a Shared Access Signature for Azure storage https://docs.microsoft.com/en-us/rest/api/eventhub...
static std::basic_string< T > SASToken(const std::string &key, const std::basic_string< T > &url, const std::basic_string< T > &keyName, const std::basic_string< T > &expiry)
Create a Shared Access Signature for Azure storage https://docs.microsoft.com/en-us/rest/api/eventhub...
static std::string MD5(const std::basic_string< T > &source)
Create a MD5 hash for the given source as a string.
static std::basic_string< T > JWTHMAC256(const std::string &key, const std::basic_string< T > &header, const std::basic_string< T > &payload)
Create a JsonWebToken authorization with HMAC 256.
static std::string HMAC(const std::basic_string< T > &message, const std::string &key)
Returns binary HMAC using SHA-256. https://www.liavaag.org/English/SHA-Generator/HMAC/.
static std::basic_string< T > CosmosToken(const std::string &key, const std::basic_string< T > &verb, const std::basic_string< T > &type, const std::basic_string< T > &resourceLink, const std::basic_string< T > &date)
Create the Cosmos Authorization Token using the Key for this connection.