From: Radoslaw Zarzynski Date: Tue, 23 May 2017 15:32:42 +0000 (+0200) Subject: rgw: switch to Ceph's sstring in AWS signature generation process. X-Git-Tag: v12.1.0~155^2~5 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=c89222850e84d6a1a72646e7a20e47216d533f07;p=ceph.git rgw: switch to Ceph's sstring in AWS signature generation process. Signed-off-by: Radoslaw Zarzynski --- diff --git a/src/rgw/rgw_auth_keystone.cc b/src/rgw/rgw_auth_keystone.cc index 5ab70e945977..4a9685e92fab 100644 --- a/src/rgw/rgw_auth_keystone.cc +++ b/src/rgw/rgw_auth_keystone.cc @@ -417,7 +417,7 @@ rgw::auth::Engine::result_t EC2Engine::authenticate( const boost::string_view& access_key_id, const boost::string_view& signature, const std::string& string_to_sign, - const signature_factory_t& signature_factory, + const signature_factory_t&, const completer_factory_t& completer_factory, /* Passthorugh only! */ const req_state* s) const diff --git a/src/rgw/rgw_auth_keystone.h b/src/rgw/rgw_auth_keystone.h index 53884ebe37f9..18905ba64c04 100644 --- a/src/rgw/rgw_auth_keystone.h +++ b/src/rgw/rgw_auth_keystone.h @@ -94,7 +94,7 @@ class EC2Engine : public rgw::auth::s3::AWSEngine { result_t authenticate(const boost::string_view& access_key_id, const boost::string_view& signature, const std::string& string_to_sign, - const signature_factory_t& signature_factory, + const signature_factory_t&, const completer_factory_t& completer_factory, const req_state* s) const override; public: diff --git a/src/rgw/rgw_auth_s3.cc b/src/rgw/rgw_auth_s3.cc index 88b1c2bd00d2..0bd7f9a33acd 100644 --- a/src/rgw/rgw_auth_s3.cc +++ b/src/rgw/rgw_auth_s3.cc @@ -147,30 +147,6 @@ void rgw_create_s3_canonical_header( dest_str = dest; } -int rgw_get_s3_header_digest(const std::string& auth_hdr, - const std::string& key, - std::string& dest) -{ - if (key.empty()) - return -EINVAL; - - char hmac_sha1[CEPH_CRYPTO_HMACSHA1_DIGESTSIZE]; - calc_hmac_sha1(key.c_str(), key.size(), auth_hdr.c_str(), auth_hdr.size(), hmac_sha1); - - char b64[64]; /* 64 is really enough */ - int ret = ceph_armor(b64, b64 + 64, hmac_sha1, - hmac_sha1 + CEPH_CRYPTO_HMACSHA1_DIGESTSIZE); - if (ret < 0) { - dout(10) << "ceph_armor failed" << dendl; - return ret; - } - b64[ret] = '\0'; - - dest = b64; - - return 0; -} - static inline bool is_base64_for_content_md5(unsigned char c) { return (isalnum(c) || isspace(c) || (c == '+') || (c == '/') || (c == '=')); } @@ -716,7 +692,7 @@ std::string get_v4_string_to_sign(CephContext* const cct, .append(hexed_cr_hash.data(), hexed_cr_hash.size() - 1); ldout(cct, 10) << "string to sign = " - << rgw::crypt_sanitize::log_content{string_to_sign.c_str()} + << rgw::crypt_sanitize::log_content{string_to_sign} << dendl; return string_to_sign; @@ -799,20 +775,53 @@ sha256_digest_t get_v4_signing_key(CephContext* const cct, * http://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html */ -std::array +AWSEngine::VersionAbstractor::server_signature_t get_v4_signature(CephContext* const cct, const sha256_digest_t& signing_key, const boost::string_view& string_to_sign) { - /* The server-side generated signature for comparison. */ - const auto signature = \ - buf_to_hex(calc_hmac_sha256(signing_key, string_to_sign)); + /* The server-side generated digest for comparison. */ + const auto digest = calc_hmac_sha256(signing_key, string_to_sign); + + /* TODO(rzarzynski): I would love to see our sstring having reserve() and + * the non-const data() variant like C++17's std::string. */ + using srv_signature_t = AWSEngine::VersionAbstractor::server_signature_t; + srv_signature_t signature(srv_signature_t::initialized_later(), + digest.size() * 2); + buf_to_hex(digest.data(), digest.size(), signature.begin()); - ldout(cct, 10) << "generated signature = " << signature.data() << dendl; + ldout(cct, 10) << "generated signature = " << signature << dendl; return signature; } +AWSEngine::VersionAbstractor::server_signature_t +get_v2_signature(CephContext* const cct, + const std::string& secret_key, + const std::string& string_to_sign) +{ + if (secret_key.empty()) { + throw -EINVAL; + } + + const auto digest = calc_hmac_sha1(secret_key, string_to_sign); + + /* 64 is really enough */; + char buf[64]; + const int ret = ceph_armor(std::begin(buf), + std::begin(buf) + 64, + std::begin(digest), + std::begin(digest) + digest.size()); + if (ret < 0) { + ldout(cct, 10) << "ceph_armor failed" << dendl; + throw ret; + } else { + buf[ret] = '\0'; + using srv_signature_t = AWSEngine::VersionAbstractor::server_signature_t; + return srv_signature_t(buf, ret); + } +} + bool AWSv4ComplMulti::ChunkMeta::is_new_chunk_in_stream(size_t stream_pos) const { return stream_pos >= (data_offset_in_stream + data_length); diff --git a/src/rgw/rgw_auth_s3.h b/src/rgw/rgw_auth_s3.h index 2cb8c0da6a00..9e59d7f6afc7 100644 --- a/src/rgw/rgw_auth_s3.h +++ b/src/rgw/rgw_auth_s3.h @@ -14,6 +14,7 @@ #include #include +#include "common/sstring.hh" #include "rgw_common.h" #include "rgw_rest_s3.h" @@ -310,10 +311,6 @@ rgw_create_s3_canonical_header(const req_info& info, const bool qsr) { return std::make_tuple(ok, dest, header_time); } -int rgw_get_s3_header_digest(const std::string& auth_hdr, - const std::string& key, - std::string& dest); - namespace rgw { namespace auth { namespace s3 { @@ -426,24 +423,15 @@ get_v4_signing_key(CephContext* const cct, const boost::string_view& credential_scope, const boost::string_view& access_key_secret); -extern std::array +extern AWSEngine::VersionAbstractor::server_signature_t get_v4_signature(CephContext* cct, const sha256_digest_t& signing_key, const boost::string_view& string_to_sign); -static inline -std::string get_v2_signature(CephContext*, - const std::string& secret_key, - const std::string& string_to_sign) { - std::string signature_dest; - const int ret = rgw_get_s3_header_digest(string_to_sign, secret_key, - signature_dest); - if (ret < 0) { - throw ret; - } else { - return signature_dest; - } -} +extern AWSEngine::VersionAbstractor::server_signature_t +get_v2_signature(CephContext*, + const std::string& secret_key, + const std::string& string_to_sign); } /* namespace s3 */ } /* namespace auth */ diff --git a/src/rgw/rgw_common.h b/src/rgw/rgw_common.h index 88d49765dc3c..d041cac85ed0 100644 --- a/src/rgw/rgw_common.h +++ b/src/rgw/rgw_common.h @@ -2223,6 +2223,17 @@ extern std::string url_encode(const std::string& src); /* destination should be CEPH_CRYPTO_HMACSHA1_DIGESTSIZE bytes long */ extern void calc_hmac_sha1(const char *key, int key_len, const char *msg, int msg_len, char *dest); + +using sha1_digest_t = \ + std::array; + +static inline sha1_digest_t +calc_hmac_sha1(const boost::string_view& key, const boost::string_view& msg) { + sha1_digest_t dest; + calc_hmac_sha1(key.data(), key.size(), msg.data(), msg.size(), dest.data()); + return dest; +} + /* destination should be CEPH_CRYPTO_HMACSHA256_DIGESTSIZE bytes long */ extern void calc_hmac_sha256(const char *key, int key_len, const char *msg, int msg_len, diff --git a/src/rgw/rgw_crypt_sanitize.h b/src/rgw/rgw_crypt_sanitize.h index 644e12669baf..628577f90c74 100644 --- a/src/rgw/rgw_crypt_sanitize.h +++ b/src/rgw/rgw_crypt_sanitize.h @@ -4,6 +4,8 @@ #ifndef RGW_RGW_CRYPT_SANITIZE_H_ #define RGW_RGW_CRYPT_SANITIZE_H_ +#include + #include "rgw_common.h" namespace rgw { @@ -54,8 +56,8 @@ struct auth { * Temporary container for suppressing printing if log made from civetweb may contain secret key. */ struct log_content { - const char* buf; - log_content(const char* buf) + const boost::string_view buf; + log_content(const boost::string_view buf) : buf(buf) {} }; diff --git a/src/rgw/rgw_loadgen.cc b/src/rgw/rgw_loadgen.cc index 3c61cf21d79e..edf95cc2ca9c 100644 --- a/src/rgw/rgw_loadgen.cc +++ b/src/rgw/rgw_loadgen.cc @@ -35,14 +35,18 @@ int RGWLoadGenRequestEnv::sign(RGWAccessKey& access_key) sub_resources, canonical_header); - int ret = rgw_get_s3_header_digest(canonical_header, access_key.key, digest); - if (ret < 0) { + headers["HTTP_DATE"] = date_str; + try { + /* FIXME(rzarzynski): kill the dependency on g_ceph_context. */ + const auto signature = static_cast( + rgw::auth::s3::get_v2_signature(g_ceph_context, canonical_header, + access_key.key)); + headers["HTTP_AUTHORIZATION"] = \ + std::string("AWS ") + access_key.id + ":" + signature; + } catch (int ret) { return ret; } - headers["HTTP_DATE"] = date_str; - headers["HTTP_AUTHORIZATION"] = string("AWS ") + access_key.id + ":" + digest; - return 0; } diff --git a/src/rgw/rgw_rest_client.cc b/src/rgw/rgw_rest_client.cc index 1cccbab7d02b..aa3de5fac837 100644 --- a/src/rgw/rgw_rest_client.cc +++ b/src/rgw/rgw_rest_client.cc @@ -130,8 +130,9 @@ int RGWRESTSimpleRequest::execute(RGWAccessKey& key, const char *method, const c canonical_header); string digest; - int ret = rgw_get_s3_header_digest(canonical_header, key.key, digest); - if (ret < 0) { + try { + digest = rgw::auth::s3::get_v2_signature(cct, canonical_header, key.key); + } catch (int ret) { return ret; } @@ -233,8 +234,9 @@ int RGWRESTSimpleRequest::sign_request(RGWAccessKey& key, RGWEnv& env, req_info& ldout(cct, 10) << "generated canonical header: " << canonical_header << dendl; string digest; - int ret = rgw_get_s3_header_digest(canonical_header, key.key, digest); - if (ret < 0) { + try { + digest = rgw::auth::s3::get_v2_signature(cct, canonical_header, key.key); + } catch (int ret) { return ret; } diff --git a/src/rgw/rgw_rest_s3.cc b/src/rgw/rgw_rest_s3.cc index 49895bee710a..1a1dc4337959 100644 --- a/src/rgw/rgw_rest_s3.cc +++ b/src/rgw/rgw_rest_s3.cc @@ -3545,7 +3545,7 @@ null_completer_factory(const boost::optional& secret_key) using AWSVerAbstractor = AWSEngine::VersionAbstractor; std::tuple @@ -3565,12 +3565,15 @@ AWSGeneralAbstractor::get_auth_data(const req_state* const s) const } } -static inline -std::string v4_signature(const boost::string_view& credential_scope, +/* srv_signature_t is an alias over Ceph's basic_sstring. We're using + * it to keep everything within the stack boundaries instead of doing + * dynamic allocations. */ +static inline AWSVerAbstractor::server_signature_t +v4_signature(const boost::string_view& credential_scope, - CephContext* const cct, - const boost::string_view& secret_key, - const boost::string_view& string_to_sign) + CephContext* const cct, + const boost::string_view& secret_key, + const boost::string_view& string_to_sign) { auto signing_key = \ rgw::auth::s3::get_v4_signing_key(cct, credential_scope, secret_key); @@ -3578,12 +3581,11 @@ std::string v4_signature(const boost::string_view& credential_scope, auto server_signature = \ rgw::auth::s3::get_v4_signature(cct, std::move(signing_key), string_to_sign); - - return std::string(server_signature.data(), server_signature.size() - 1); + return server_signature; } std::tuple @@ -3753,7 +3755,7 @@ AWSGeneralAbstractor::get_auth_data_v4(const req_state* const s, std::tuple @@ -3819,7 +3821,7 @@ AWSGeneralAbstractor::get_auth_data_v2(const req_state* const s) const std::tuple @@ -3833,7 +3835,7 @@ AWSBrowserUploadAbstractor::get_auth_data_v2(const req_state* const s) const } std::tuple @@ -3864,7 +3866,7 @@ AWSBrowserUploadAbstractor::get_auth_data_v4(const req_state* const s) const } std::tuple @@ -3938,7 +3940,7 @@ rgw::auth::s3::LDAPEngine::authenticate( const boost::string_view& access_key_id, const boost::string_view& signature, const std::string& string_to_sign, - const signature_factory_t& signature_factory, + const signature_factory_t&, const completer_factory_t& completer_factory, const req_state* const s) const { @@ -3998,7 +4000,8 @@ rgw::auth::s3::LocalEngine::authenticate( //TODO: Uncomment, when we have a migration plan in place. /*else { if (s->user->type != TYPE_RGW) { - ldout(cct, 10) << "ERROR: User id of type: " << s->user->type << " is present" << dendl; + ldout(cct, 10) << "ERROR: User id of type: " << s->user->type + << " is present" << dendl; throw -EPERM; } }*/ @@ -4010,14 +4013,17 @@ rgw::auth::s3::LocalEngine::authenticate( } const RGWAccessKey& k = iter->second; - std::string digest = signature_factory(cct, k.key, string_to_sign); + const VersionAbstractor::server_signature_t server_signature = \ + signature_factory(cct, k.key, string_to_sign); - ldout(cct, 15) << "string_to_sign=" << rgw::crypt_sanitize::log_content{string_to_sign.c_str()} << dendl; - ldout(cct, 15) << "calculated digest=" << digest << dendl; - ldout(cct, 15) << "auth signature=" << signature << dendl; - ldout(cct, 15) << "compare=" << signature.compare(digest) << dendl; + ldout(cct, 15) << "string_to_sign=" + << rgw::crypt_sanitize::log_content{string_to_sign} + << dendl; + ldout(cct, 15) << "server signature=" << server_signature << dendl; + ldout(cct, 15) << "client signature=" << signature << dendl; + ldout(cct, 15) << "compare=" << signature.compare(server_signature) << dendl; - if (signature != digest) { + if (static_cast(server_signature) != signature) { return result_t::deny(-ERR_SIGNATURE_NO_MATCH); } diff --git a/src/rgw/rgw_rest_s3.h b/src/rgw/rgw_rest_s3.h index 0c34d5b66a19..f6fdcd7b0f78 100644 --- a/src/rgw/rgw_rest_s3.h +++ b/src/rgw/rgw_rest_s3.h @@ -10,6 +10,8 @@ #include +#include "common/backport14.h" +#include "common/sstring.hh" #include "rgw_op.h" #include "rgw_rest.h" #include "rgw_http_errors.h" @@ -659,24 +661,33 @@ namespace rgw { namespace auth { namespace s3 { - class AWSEngine : public rgw::auth::Engine { public: class VersionAbstractor { + static constexpr size_t DIGEST_SIZE_V2 = CEPH_CRYPTO_HMACSHA1_DIGESTSIZE; + static constexpr size_t DIGEST_SIZE_V4 = CEPH_CRYPTO_HMACSHA256_DIGESTSIZE; + + /* Knowing the signature max size allows us to employ the sstring, and thus + * avoid dynamic allocations. The multiplier comes from representing digest + * in the base64-encoded form. */ + static constexpr size_t SIGNATURE_MAX_SIZE = \ + ceph::max(DIGEST_SIZE_V2, DIGEST_SIZE_V4) * 2 + sizeof('\0'); + public: virtual ~VersionAbstractor() {}; using access_key_id_t = boost::string_view; - using signature_t = boost::string_view; + using client_signature_t = boost::string_view; + using server_signature_t = basic_sstring; using string_to_sign_t = std::string; /* Transformation for crafting the AWS signature at server side which is * used later to compare with the user-provided one. The methodology for * doing that depends on AWS auth version. */ using signature_factory_t = \ - std::function; + std::function; /* Return an instance of Completer for verifying the payload's fingerprint * if necessary. Otherwise caller gets nullptr. Caller may provide secret @@ -686,7 +697,7 @@ public: const boost::optional& secret_key)>; virtual std::tuple @@ -748,14 +759,14 @@ class AWSGeneralAbstractor : public AWSEngine::VersionAbstractor { const bool qsr) const; std::tuple get_auth_data_v2(const req_state* s) const; std::tuple @@ -767,7 +778,7 @@ public: } std::tuple @@ -782,7 +793,7 @@ class AWSBrowserUploadAbstractor : public AWSEngine::VersionAbstractor { } using auth_data_t = std::tuple; @@ -795,7 +806,7 @@ public: } std::tuple @@ -823,7 +834,7 @@ protected: result_t authenticate(const boost::string_view& access_key_id, const boost::string_view& signature, const std::string& string_to_sign, - const signature_factory_t& signature_factory, + const signature_factory_t&, const completer_factory_t& completer_factory, const req_state* s) const override; public: