1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238
|
// OpenVPN -- An application to securely tunnel IP networks
// over a single port, with support for SSL/TLS-based
// session authentication and key exchange,
// packet encryption, packet authentication, and
// packet compression.
//
// Copyright (C) 2012- OpenVPN Inc.
//
// SPDX-License-Identifier: MPL-2.0 OR AGPL-3.0-only WITH openvpn3-openssl-exception
//
// AWS REST API query utilities such as query signing
#pragma once
#include <string>
#include <vector>
#include <cstdint> // for std::uint8_t
#include <algorithm>
#include <utility>
#include <time.h>
#include <openvpn/common/exception.hpp>
#include <openvpn/common/hexstr.hpp>
#include <openvpn/http/urlencode.hpp>
#include <openvpn/crypto/digestapi.hpp>
#include <openvpn/aws/awscreds.hpp>
namespace openvpn::AWS {
class REST
{
public:
OPENVPN_EXCEPTION(aws_rest_error);
// 20130524T000000Z
static std::string amz_date()
{
struct tm lt;
char buf[64];
const time_t t = ::time(nullptr);
if (!::gmtime_r(&t, <))
throw aws_rest_error("gmtime_r failed");
if (!::strftime(buf, sizeof(buf), "%Y%m%dT%H%M%SZ", <))
throw aws_rest_error("strftime failed");
return std::string(buf);
}
struct SHA256
{
std::string to_hex() const
{
return render_hex(hash, sizeof(hash));
}
std::uint8_t hash[32];
};
static SHA256 hmac_sha256(DigestFactory &digest_factory, const std::string &data, const std::string &key)
{
SHA256 ret;
HMACInstance::Ptr hi(digest_factory.new_hmac(CryptoAlgs::SHA256, (const std::uint8_t *)key.c_str(), key.length()));
hi->update((const std::uint8_t *)data.c_str(), data.length());
hi->final(ret.hash);
return ret;
}
static SHA256 hmac_sha256(DigestFactory &digest_factory, const std::string &data, const SHA256 &key)
{
SHA256 ret;
HMACInstance::Ptr hi(digest_factory.new_hmac(CryptoAlgs::SHA256, key.hash, sizeof(key.hash)));
hi->update((const std::uint8_t *)data.c_str(), data.length());
hi->final(ret.hash);
return ret;
}
static SHA256 sha256(DigestFactory &digest_factory, const std::string &data)
{
SHA256 ret;
DigestInstance::Ptr di(digest_factory.new_digest(CryptoAlgs::SHA256));
di->update((const std::uint8_t *)data.c_str(), data.length());
di->final(ret.hash);
return ret;
}
static SHA256 signing_key(DigestFactory &df,
const std::string &key,
const std::string &date_stamp,
const std::string ®ion_name,
const std::string &service_name)
{
const SHA256 h1 = hmac_sha256(df, date_stamp, "AWS4" + key);
const SHA256 h2 = hmac_sha256(df, region_name, h1);
const SHA256 h3 = hmac_sha256(df, service_name, h2);
const SHA256 h4 = hmac_sha256(df, "aws4_request", h3);
return h4;
}
struct KeyValue
{
KeyValue(std::string key_arg, std::string value_arg)
: key(std::move(key_arg)),
value(std::move(value_arg))
{
}
bool operator<(const KeyValue &rhs) const
{
return key < rhs.key;
}
std::string uri_encode() const
{
return URL::encode(key) + '=' + URL::encode(value);
}
std::string key;
std::string value;
};
struct Query : public std::vector<KeyValue>
{
std::string canonical_query_string() const
{
bool first = true;
std::string ret;
for (auto &p : *this)
{
if (!first)
ret += '&';
ret += p.uri_encode();
first = false;
}
return ret;
}
void sort()
{
std::sort(begin(), end());
}
};
struct QueryBuilder
{
std::string date; // such as "20130524T000000Z"
unsigned int expires = 300; // request expiration in seconds
std::string region; // such as "us-east-1"
std::string service; // such as "s3"
std::string method; // such as "GET"
std::string host; // such as "ec2.us-west-2.amazonaws.com"
std::string uri; // such as "/"
Query parms;
std::string uri_query() const
{
return uri + '?' + parms.canonical_query_string();
}
std::string url_query() const
{
return "https://" + host + uri_query();
}
void add_amz_parms(const Creds &creds)
{
parms.emplace_back("X-Amz-Algorithm", "AWS4-HMAC-SHA256");
parms.emplace_back("X-Amz-Credential", creds.access_key + '/' + amz_credential());
parms.emplace_back("X-Amz-Date", date);
parms.emplace_back("X-Amz-Expires", std::to_string(expires));
parms.emplace_back("X-Amz-SignedHeaders", amz_signed_headers());
if (!creds.token.empty())
parms.emplace_back("X-Amz-Security-Token", creds.token);
}
void sort_parms()
{
parms.sort();
}
void add_amz_signature(DigestFactory &digest_factory, const Creds &creds)
{
parms.emplace_back("X-Amz-Signature", signature(digest_factory, creds));
}
std::string signature(DigestFactory &digest_factory, const Creds &creds) const
{
const SHA256 sk = signing_key(digest_factory,
creds.secret_key,
date.substr(0, 8),
region,
service);
return hmac_sha256(digest_factory, string_to_sign(digest_factory), sk).to_hex();
}
virtual std::string content_hash() const
{
// SHA256 of empty string
return "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
}
std::string canonical_request() const
{
std::string ret = method + '\n'
+ uri + '\n'
+ parms.canonical_query_string() + '\n'
+ "host:" + host + '\n'
+ '\n'
+ amz_signed_headers() + '\n';
if (service == "s3")
ret += "UNSIGNED-PAYLOAD";
else
ret += content_hash();
return ret;
}
std::string amz_signed_headers() const
{
std::string signed_headers = "host";
return signed_headers;
}
std::string string_to_sign(DigestFactory &digest_factory) const
{
return "AWS4-HMAC-SHA256\n"
+ date + '\n'
+ amz_credential() + "\n"
+ sha256(digest_factory, canonical_request()).to_hex();
}
std::string amz_credential() const
{
return date.substr(0, 8) + '/' + region + '/' + service + "/aws4_request";
}
virtual ~QueryBuilder() = default;
};
};
} // namespace openvpn::AWS
|