Files
OpenVPNAdapter/Sources/OpenVPN3/openvpn/aws/awsrest.hpp

255 lines
6.5 KiB
C++

// 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-2020 OpenVPN Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License Version 3
// as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program in the COPYING file.
// If not, see <http://www.gnu.org/licenses/>.
// 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 {
namespace 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, &lt))
throw aws_rest_error("gmtime_r failed");
if (!::strftime(buf, sizeof(buf),
"%Y%m%dT%H%M%SZ",
&lt))
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& region_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() {}
};
};
}
}