3#include <boost/asio/use_awaitable.hpp>
4#include <boost/url/parse.hpp>
5#include <boost/url/url.hpp>
7#include <wolfssl/options.h>
8#include <wolfssl/wolfcrypt/hmac.h>
9#include <wolfssl/wolfcrypt/sha256.h>
19namespace asio = boost::asio;
20namespace urls = boost::urls;
24using Digest = std::array<uint8_t, WC_SHA256_DIGEST_SIZE>;
26std::string toHex(
const uint8_t* bytes,
size_t size) {
27 static constexpr char hex[] =
"0123456789abcdef";
28 std::string out(size * 2,
'0');
29 for (
size_t at = 0; at < size; at++) {
30 out[at * 2] = hex[bytes[at] >> 4];
31 out[at * 2 + 1] = hex[bytes[at] & 0x0F];
36Digest sha256(std::string_view text) {
39 if (wc_InitSha256(&state) != 0)
throw std::runtime_error(
"cannot start sha256");
40 wc_Sha256Update(&state,
reinterpret_cast<const uint8_t*
>(text.data()),
static_cast<word32
>(text.size()));
41 wc_Sha256Final(&state, out.data());
42 wc_Sha256Free(&state);
46Digest hmac(
const uint8_t* key,
size_t keySize, std::string_view text) {
49 if (wc_HmacInit(&state,
nullptr, INVALID_DEVID) != 0)
throw std::runtime_error(
"cannot start hmac");
50 wc_HmacSetKey(&state, WC_SHA256, key,
static_cast<word32
>(keySize));
51 wc_HmacUpdate(&state,
reinterpret_cast<const uint8_t*
>(text.data()),
static_cast<word32
>(text.size()));
52 wc_HmacFinal(&state, out.data());
57Digest hmac(
const Digest& key, std::string_view text) {
58 return hmac(key.data(), key.size(), text);
61std::string env(
const char* name) {
62 const char* value = std::getenv(name);
63 return value ? value : std::string{};
73std::string encodePath(std::string_view path) {
74 static constexpr char hex[] =
"0123456789ABCDEF";
76 out.reserve(path.size());
78 for (
const unsigned char c : path) {
79 const bool plain = (c >=
'A' && c <=
'Z') || (c >=
'a' && c <=
'z') || (c >=
'0' && c <=
'9')
80 || c ==
'-' || c ==
'_' || c ==
'.' || c ==
'~' || c ==
'/';
82 out.push_back(
static_cast<char>(c));
85 out.push_back(hex[c >> 4]);
86 out.push_back(hex[c & 0x0F]);
98 const auto clock = std::time(
nullptr);
100 gmtime_r(&clock, &parts);
104 std::strftime(date,
sizeof(date),
"%Y%m%d", &parts);
105 std::strftime(moment,
sizeof(moment),
"%Y%m%dT%H%M%SZ", &parts);
106 return Stamp{date, moment};
113std::string
awsSignatureV4(std::string_view secret, std::string_view date, std::string_view region,
114 std::string_view service, std::string_view stringToSign) {
115 const std::string seed =
"AWS4" + std::string(secret);
117 hmac(hmac(hmac(hmac(
reinterpret_cast<const uint8_t*
>(seed.data()), seed.size(), date),
118 region), service),
"aws4_request");
119 return toHex(hmac(signing, stringToSign).data(), WC_SHA256_DIGEST_SIZE);
131 const auto endpoint = urls::parse_uri_reference(bucket.
url);
133 std::cerr <<
"invalid s3 endpoint: " << bucket.
url <<
"\n";
137 const std::string host{endpoint->has_port() ? std::string(endpoint->host()) +
":" + std::string(endpoint->port())
138 : std::string(endpoint->host())};
141 std::string prefix{endpoint->path()};
142 while (!prefix.empty() && prefix.back() ==
'/') prefix.pop_back();
144 auto resource = prefix + (path.empty() || path.front() ==
'/' ?
"" :
"/") + path;
145 if (resource.empty() || resource.front() !=
'/') resource.insert(resource.begin(),
'/');
147 const auto canonicalPath = encodePath(resource);
148 const auto target =
"https://" + host + canonicalPath;
151 const auto key = bucket.
accessKey.empty() ? env(
"AWS_ACCESS_KEY_ID") : bucket.
accessKey;
152 const auto secret = bucket.
secretKey.empty() ? env(
"AWS_SECRET_ACCESS_KEY") : bucket.
secretKey;
153 if (key.empty() || secret.empty()) {
155 co_return co_await fetchUrlWith(target, std::move(path), {});
158 const auto stamp = nowUtc();
162 const auto payload = toHex(sha256(
"").data(), WC_SHA256_DIGEST_SIZE);
164 std::string signedHeaders =
"host;x-amz-content-sha256;x-amz-date";
165 std::string canonicalHeaders =
"host:" + host +
"\n"
166 +
"x-amz-content-sha256:" + payload +
"\n"
167 +
"x-amz-date:" + stamp.moment +
"\n";
168 if (!token.empty()) {
170 signedHeaders +=
";x-amz-security-token";
171 canonicalHeaders +=
"x-amz-security-token:" + token +
"\n";
174 const auto canonicalRequest =
"GET\n" + canonicalPath +
"\n\n" + canonicalHeaders +
"\n"
175 + signedHeaders +
"\n" + payload;
177 const auto scope = stamp.date +
"/" + bucket.
region +
"/s3/aws4_request";
178 const auto toSign =
"AWS4-HMAC-SHA256\n" + stamp.moment +
"\n" + scope +
"\n"
179 + toHex(sha256(canonicalRequest).data(), WC_SHA256_DIGEST_SIZE);
184 {
"x-amz-content-sha256", payload},
185 {
"x-amz-date", stamp.moment},
186 {
"authorization",
"AWS4-HMAC-SHA256 Credential=" + key +
"/" + scope
187 +
", SignedHeaders=" + signedHeaders +
", Signature=" + signature},
189 if (!token.empty()) headers.emplace(
"x-amz-security-token", token);
191 co_return co_await fetchUrlWith(target, std::move(path), std::move(headers));
std::map< std::string, std::string > Headers
boost::asio::awaitable< Blob > fetchS3(const BucketConfig &bucket, std::string path)
boost::asio::awaitable< Blob > fetchUrlWith(std::string url, std::string pathForFormat, Headers headers)
std::string awsSignatureV4(std::string_view secret, std::string_view date, std::string_view region, std::string_view service, std::string_view stringToSign)
Failure(Haio::ErrorCode why, const std::string &what)