diff --git a/docs/root/configuration/http_filters/lua_filter.rst b/docs/root/configuration/http_filters/lua_filter.rst index 815e5aef96322..23f4d2a65ad6b 100644 --- a/docs/root/configuration/http_filters/lua_filter.rst +++ b/docs/root/configuration/http_filters/lua_filter.rst @@ -312,6 +312,32 @@ Returns the current request's underlying :repo:`connection `. +importPublicKey() +^^^^^^^^^^^^^^^^^ + +.. code-block:: lua + + pubkey = handle:importPublicKey(keyder, keyderLength) + +Returns public key which is used by :ref:`verifySignature ` to verify digital signature. + +.. _verify_signature: + +verifySignature() +^^^^^^^^^^^^^^^^^ + +.. code-block:: lua + + ok, error = verifySignature(hashFunction, pubkey, signature, signatureLength, data, dataLength) + +Verify signature using provided parameters. *hashFunction* is the variable for hash function which be used +for verifying signature. *SHA1*, *SHA224*, *SHA256*, *SHA384* and *SHA512* are supported. +*pubkey* is the public key. *signature* is the signature to be verified. *signatureLength* is +the length of the signature. *data* is the content which will be hashed. *dataLength* is the length of data. + +The function returns a pair. If the first element is *true*, the second element will be empty +which means signature is verified; otherwise, the second element will store the error message. + .. _config_http_filters_lua_header_wrapper: Header object API diff --git a/docs/root/intro/version_history.rst b/docs/root/intro/version_history.rst index 5666890b05463..90ec364af4d63 100644 --- a/docs/root/intro/version_history.rst +++ b/docs/root/intro/version_history.rst @@ -29,6 +29,7 @@ Version history * listener: added :ref:`source IP ` and :ref:`source port ` filter chain matching. +* lua: exposed functions to Lua to verify digital signature. * original_src filter: added the :ref:`filter`. * rbac: migrated from v2alpha to v2. * redis: add support for Redis cluster custom cluster type. diff --git a/source/common/crypto/utility.cc b/source/common/crypto/utility.cc index 885b4c0bbcaba..fbaff483eb3e2 100644 --- a/source/common/crypto/utility.cc +++ b/source/common/crypto/utility.cc @@ -3,7 +3,9 @@ #include "common/common/assert.h" #include "common/common/stack_array.h" -#include "openssl/evp.h" +#include "absl/strings/ascii.h" +#include "absl/strings/str_cat.h" +#include "openssl/bytestring.h" #include "openssl/hmac.h" #include "openssl/sha.h" @@ -41,6 +43,61 @@ std::vector Utility::getSha256Hmac(const std::vector& key, return hmac; } +const VerificationOutput Utility::verifySignature(absl::string_view hash, EVP_PKEY* pubKey, + const std::vector& signature, + const std::vector& text) { + // Step 1: initialize EVP_MD_CTX + bssl::ScopedEVP_MD_CTX ctx; + + // Step 2: initialize EVP_MD + const EVP_MD* md = Utility::getHashFunction(hash); + + if (md == nullptr) { + return {false, absl::StrCat(hash, " is not supported.")}; + } + + // Step 3: initialize EVP_DigestVerify + int ok = EVP_DigestVerifyInit(ctx.get(), nullptr, md, nullptr, pubKey); + if (!ok) { + return {false, "Failed to initialize digest verify."}; + } + + // Step 4: verify signature + ok = EVP_DigestVerify(ctx.get(), signature.data(), signature.size(), text.data(), text.size()); + + // Step 5: check result + if (ok == 1) { + return {true, ""}; + } + + return {false, absl::StrCat("Failed to verify digest. Error code: ", ok)}; +} + +PublicKeyPtr Utility::importPublicKey(const std::vector& key) { + CBS cbs({key.data(), key.size()}); + return PublicKeyPtr(EVP_parse_public_key(&cbs)); +} + +const EVP_MD* Utility::getHashFunction(absl::string_view name) { + const std::string hash = absl::AsciiStrToLower(name); + + // Hash algorithms set refers + // https://github.com/google/boringssl/blob/master/include/openssl/digest.h + if (hash == "sha1") { + return EVP_sha1(); + } else if (hash == "sha224") { + return EVP_sha224(); + } else if (hash == "sha256") { + return EVP_sha256(); + } else if (hash == "sha384") { + return EVP_sha384(); + } else if (hash == "sha512") { + return EVP_sha512(); + } else { + return nullptr; + } +} + } // namespace Crypto } // namespace Common } // namespace Envoy diff --git a/source/common/crypto/utility.h b/source/common/crypto/utility.h index f755aaf0a7dc4..9649f74b45190 100644 --- a/source/common/crypto/utility.h +++ b/source/common/crypto/utility.h @@ -6,11 +6,27 @@ #include "envoy/buffer/buffer.h" #include "absl/strings/string_view.h" +#include "openssl/evp.h" namespace Envoy { namespace Common { namespace Crypto { +struct VerificationOutput { + /** + * Verification result. If result_ is true, error_message_ is empty. + */ + bool result_; + + /** + * Error message when verification failed. + * TODO(crazyxy): switch to absl::StatusOr when available + */ + std::string error_message_; +}; + +typedef bssl::UniquePtr PublicKeyPtr; + class Utility { public: /** @@ -28,6 +44,29 @@ class Utility { */ static std::vector getSha256Hmac(const std::vector& key, absl::string_view message); + + /** + * Verify cryptographic signatures. + * @param hash hash function(including SHA1, SHA224, SHA256, SHA384, SHA512) + * @param key pointer to public key + * @param signature signature + * @param text clear text + * @return If the result_ is true, the error_message_ is empty; otherwise, + * the error_message_ stores the error message + */ + static const VerificationOutput verifySignature(absl::string_view hash, EVP_PKEY* key, + const std::vector& signature, + const std::vector& text); + + /** + * Import public key. + * @param key key string + * @return pointer to public key + */ + static PublicKeyPtr importPublicKey(const std::vector& key); + +private: + static const EVP_MD* getHashFunction(absl::string_view name); }; } // namespace Crypto diff --git a/source/extensions/filters/http/lua/BUILD b/source/extensions/filters/http/lua/BUILD index de27cd3a70471..646636b28098e 100644 --- a/source/extensions/filters/http/lua/BUILD +++ b/source/extensions/filters/http/lua/BUILD @@ -22,6 +22,7 @@ envoy_cc_library( "//include/envoy/upstream:cluster_manager_interface", "//source/common/buffer:buffer_lib", "//source/common/common:enum_to_int", + "//source/common/crypto:utility_lib", "//source/common/http:message_lib", "//source/extensions/filters/common/lua:lua_lib", "//source/extensions/filters/common/lua:wrappers_lib", @@ -36,6 +37,7 @@ envoy_cc_library( deps = [ "//include/envoy/http:header_map_interface", "//include/envoy/stream_info:stream_info_interface", + "//source/common/crypto:utility_lib", "//source/common/http:utility_lib", "//source/extensions/filters/common/lua:lua_lib", "//source/extensions/filters/common/lua:wrappers_lib", diff --git a/source/extensions/filters/http/lua/lua_filter.cc b/source/extensions/filters/http/lua/lua_filter.cc index 38b997dd27e7e..1dd5edff1fad7 100644 --- a/source/extensions/filters/http/lua/lua_filter.cc +++ b/source/extensions/filters/http/lua/lua_filter.cc @@ -7,6 +7,7 @@ #include "common/buffer/buffer_impl.h" #include "common/common/assert.h" #include "common/common/enum_to_int.h" +#include "common/crypto/utility.h" #include "common/http/message_impl.h" namespace Envoy { @@ -428,6 +429,52 @@ int StreamHandleWrapper::luaLogCritical(lua_State* state) { return 0; } +int StreamHandleWrapper::luaVerifySignature(lua_State* state) { + // Step 1: get hash function + absl::string_view hash = luaL_checkstring(state, 2); + + // Step 2: get key pointer + auto ptr = lua_touserdata(state, 3); + + // Step 3: get signature + const char* signature = luaL_checkstring(state, 4); + int sig_len = luaL_checknumber(state, 5); + const std::vector sig_vec(signature, signature + sig_len); + + // Step 4: get clear text + const char* clear_text = luaL_checkstring(state, 6); + int text_len = luaL_checknumber(state, 7); + const std::vector text_vec(clear_text, clear_text + text_len); + + // Step 5: verify signature + auto output = Common::Crypto::Utility::verifySignature(hash, reinterpret_cast(ptr), + sig_vec, text_vec); + + lua_pushboolean(state, output.result_); + if (output.result_) { + lua_pushnil(state); + } else { + lua_pushlstring(state, output.error_message_.data(), output.error_message_.length()); + } + return 2; +} + +int StreamHandleWrapper::luaImportPublicKey(lua_State* state) { + // Get byte array and the length. + const char* str = luaL_checkstring(state, 2); + int n = luaL_checknumber(state, 3); + std::vector key(str, str + n); + + if (public_key_wrapper_.get() != nullptr) { + public_key_wrapper_.pushStack(); + } else { + public_key_wrapper_.reset( + PublicKeyWrapper::create(state, Common::Crypto::Utility::importPublicKey(key)), true); + } + + return 1; +} + FilterConfig::FilterConfig(const std::string& lua_code, ThreadLocal::SlotAllocator& tls, Upstream::ClusterManager& cluster_manager) : cluster_manager_(cluster_manager), lua_state_(lua_code, tls) { @@ -442,6 +489,7 @@ FilterConfig::FilterConfig(const std::string& lua_code, ThreadLocal::SlotAllocat lua_state_.registerType(); lua_state_.registerType(); lua_state_.registerType(); + lua_state_.registerType(); request_function_slot_ = lua_state_.registerGlobal("envoy_on_request"); if (lua_state_.getGlobalRef(request_function_slot_) == LUA_REFNIL) { diff --git a/source/extensions/filters/http/lua/lua_filter.h b/source/extensions/filters/http/lua/lua_filter.h index c162cf38ad0b9..84da9e013648a 100644 --- a/source/extensions/filters/http/lua/lua_filter.h +++ b/source/extensions/filters/http/lua/lua_filter.h @@ -127,14 +127,23 @@ class StreamHandleWrapper : public Filters::Common::Lua::BaseLuaObject metadata_wrapper_; Filters::Common::Lua::LuaDeathRef stream_info_wrapper_; Filters::Common::Lua::LuaDeathRef connection_wrapper_; + Filters::Common::Lua::LuaDeathRef public_key_wrapper_; State state_{State::Running}; std::function yield_callback_; Http::AsyncClient::Request* http_request_{}; diff --git a/source/extensions/filters/http/lua/wrappers.cc b/source/extensions/filters/http/lua/wrappers.cc index f675d34cfa598..332211d4c8bb0 100644 --- a/source/extensions/filters/http/lua/wrappers.cc +++ b/source/extensions/filters/http/lua/wrappers.cc @@ -174,6 +174,15 @@ int DynamicMetadataMapWrapper::luaPairs(lua_State* state) { return 1; } +int PublicKeyWrapper::luaGet(lua_State* state) { + if (public_key_ == nullptr || public_key_.get() == nullptr) { + lua_pushnil(state); + } else { + lua_pushlightuserdata(state, public_key_.get()); + } + return 1; +} + } // namespace Lua } // namespace HttpFilters } // namespace Extensions diff --git a/source/extensions/filters/http/lua/wrappers.h b/source/extensions/filters/http/lua/wrappers.h index 91f1a400f5b81..e5bc0041d0d80 100644 --- a/source/extensions/filters/http/lua/wrappers.h +++ b/source/extensions/filters/http/lua/wrappers.h @@ -3,6 +3,8 @@ #include "envoy/http/header_map.h" #include "envoy/stream_info/stream_info.h" +#include "common/crypto/utility.h" + #include "extensions/filters/common/lua/lua.h" namespace Envoy { @@ -201,6 +203,24 @@ class StreamInfoWrapper : public Filters::Common::Lua::BaseLuaObject { +public: + PublicKeyWrapper(Envoy::Common::Crypto::PublicKeyPtr key) : public_key_(std::move(key)) {} + static ExportedFunctions exportedFunctions() { return {{"get", static_luaGet}}; } + +private: + /** + * Get a pointer to public key. + * @return pointer to public key. + */ + DECLARE_LUA_FUNCTION(PublicKeyWrapper, luaGet); + + Envoy::Common::Crypto::PublicKeyPtr public_key_; +}; + } // namespace Lua } // namespace HttpFilters } // namespace Extensions diff --git a/test/common/crypto/utility_test.cc b/test/common/crypto/utility_test.cc index bea2f8f9e0d54..5de66a9c884df 100644 --- a/test/common/crypto/utility_test.cc +++ b/test/common/crypto/utility_test.cc @@ -50,6 +50,73 @@ TEST(UtilityTest, TestSha256HmacWithEmptyArguments) { EXPECT_EQ("b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad", Hex::encode(hmac)); } +TEST(UtilityTest, TestImportPublicKey) { + auto key = "30820122300d06092a864886f70d01010105000382010f003082010a0282010100a7471266d01d160308d" + "73409c06f2e8d35c531c458d3e480e9f3191847d062ec5ccff7bc51e949d5f2c3540c189a4eca1e8633a6" + "2cf2d0923101c27e38013e71de9ae91a704849bff7fbe2ce5bf4bd666fd9731102a53193fe5a9a5a50644" + "ff8b1183fa897646598caad22a37f9544510836372b44c58c98586fb7144629cd8c9479592d996d32ff6d" + "395c0b8442ec5aa1ef8051529ea0e375883cefc72c04e360b4ef8f5760650589ca814918f678eee39b884" + "d5af8136a9630a6cc0cde157dc8e00f39540628d5f335b2c36c54c7c8bc3738a6b21acff815405afa28e5" + "183f550dac19abcf1145a7f9ced987db680e4a229cac75dee347ec9ebce1fc3dbbbb0203010001"; + + auto pub_key = Utility::importPublicKey(Hex::decode(key)); + EXPECT_NE(nullptr, pub_key.get()); + + key = "badkey"; + + pub_key = Utility::importPublicKey(Hex::decode(key)); + EXPECT_EQ(nullptr, pub_key.get()); +} + +TEST(UtilityTest, TestVerifySignature) { + auto key = "30820122300d06092a864886f70d01010105000382010f003082010a0282010100a7471266d01d160308d" + "73409c06f2e8d35c531c458d3e480e9f3191847d062ec5ccff7bc51e949d5f2c3540c189a4eca1e8633a6" + "2cf2d0923101c27e38013e71de9ae91a704849bff7fbe2ce5bf4bd666fd9731102a53193fe5a9a5a50644" + "ff8b1183fa897646598caad22a37f9544510836372b44c58c98586fb7144629cd8c9479592d996d32ff6d" + "395c0b8442ec5aa1ef8051529ea0e375883cefc72c04e360b4ef8f5760650589ca814918f678eee39b884" + "d5af8136a9630a6cc0cde157dc8e00f39540628d5f335b2c36c54c7c8bc3738a6b21acff815405afa28e5" + "183f550dac19abcf1145a7f9ced987db680e4a229cac75dee347ec9ebce1fc3dbbbb0203010001"; + auto hash_func = "sha256"; + auto signature = + "345ac3a167558f4f387a81c2d64234d901a7ceaa544db779d2f797b0ea4ef851b740905a63e2f4d5af42cee093a2" + "9c7155db9a63d3d483e0ef948f5ac51ce4e10a3a6606fd93ef68ee47b30c37491103039459122f78e1c7ea71a1a5" + "ea24bb6519bca02c8c9915fe8be24927c91812a13db72dbcb500103a79e8f67ff8cb9e2a631974e0668ab3977bf5" + "70a91b67d1b6bcd5dce84055f21427d64f4256a042ab1dc8e925d53a769f6681a873f5859693a7728fcbe95beace" + "1563b5ffbcd7c93b898aeba31421dafbfadeea50229c49fd6c445449314460f3d19150bd29a91333beaced557ed6" + "295234f7c14fa46303b7e977d2c89ba8a39a46a35f33eb07a332"; + auto data = "hello"; + + auto pub_key = Utility::importPublicKey(Hex::decode(key)); + + std::vector text(data, data + strlen(data)); + + auto sig = Hex::decode(signature); + auto result = Utility::verifySignature(hash_func, pub_key.get(), sig, text); + + EXPECT_EQ(true, result.result_); + EXPECT_EQ("", result.error_message_); + + result = Utility::verifySignature("unknown", pub_key.get(), sig, text); + EXPECT_EQ(false, result.result_); + EXPECT_EQ("unknown is not supported.", result.error_message_); + + result = Utility::verifySignature(hash_func, nullptr, sig, text); + EXPECT_EQ(false, result.result_); + EXPECT_EQ("Failed to initialize digest verify.", result.error_message_); + + data = "baddata"; + text = std::vector(data, data + strlen(data)); + result = Utility::verifySignature(hash_func, pub_key.get(), sig, text); + EXPECT_EQ(false, result.result_); + EXPECT_EQ("Failed to verify digest. Error code: 0", result.error_message_); + + data = "hello"; + text = std::vector(data, data + strlen(data)); + result = Utility::verifySignature(hash_func, pub_key.get(), Hex::decode("000000"), text); + EXPECT_EQ(false, result.result_); + EXPECT_EQ("Failed to verify digest. Error code: 0", result.error_message_); +} + } // namespace } // namespace Crypto } // namespace Common diff --git a/test/extensions/filters/http/lua/lua_filter_test.cc b/test/extensions/filters/http/lua/lua_filter_test.cc index 3a995e9f0c908..f133c1471e4b5 100644 --- a/test/extensions/filters/http/lua/lua_filter_test.cc +++ b/test/extensions/filters/http/lua/lua_filter_test.cc @@ -1577,6 +1577,131 @@ TEST_F(LuaHttpFilterTest, CheckConnection) { EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); } +TEST_F(LuaHttpFilterTest, ImportPublicKey) { + const std::string SCRIPT{R"EOF( + function string.fromhex(str) + return (str:gsub('..', function (cc) + return string.char(tonumber(cc, 16)) + end)) + end + function envoy_on_request(request_handle) + key = "30820122300d06092a864886f70d01010105000382010f003082010a0282010100a7471266d01d160308d73409c06f2e8d35c531c458d3e480e9f3191847d062ec5ccff7bc51e949d5f2c3540c189a4eca1e8633a62cf2d0923101c27e38013e71de9ae91a704849bff7fbe2ce5bf4bd666fd9731102a53193fe5a9a5a50644ff8b1183fa897646598caad22a37f9544510836372b44c58c98586fb7144629cd8c9479592d996d32ff6d395c0b8442ec5aa1ef8051529ea0e375883cefc72c04e360b4ef8f5760650589ca814918f678eee39b884d5af8136a9630a6cc0cde157dc8e00f39540628d5f335b2c36c54c7c8bc3738a6b21acff815405afa28e5183f550dac19abcf1145a7f9ced987db680e4a229cac75dee347ec9ebce1fc3dbbbb0203010001" + raw = key:fromhex() + key = request_handle:importPublicKey(raw, string.len(raw)):get() + + if key == nil then + request_handle:logTrace("failed to import public key") + else + request_handle:logTrace("succeeded to import public key") + end + end + )EOF"}; + + InSequence s; + setup(SCRIPT); + + Http::TestHeaderMapImpl request_headers{{":path", "/"}}; + + EXPECT_CALL(*filter_, scriptLog(spdlog::level::trace, StrEq("succeeded to import public key"))); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); +} + +TEST_F(LuaHttpFilterTest, InvalidPublicKey) { + const std::string SCRIPT{R"EOF( + function string.fromhex(str) + return (str:gsub('..', function (cc) + return string.char(tonumber(cc, 16)) + end)) + end + function envoy_on_request(request_handle) + key = "0000" + raw = key:fromhex() + key = request_handle:importPublicKey(raw, string.len(raw)):get() + + if key == nil then + request_handle:logTrace("failed to import public key") + else + request_handle:logTrace("succeeded to import public key") + end + end + )EOF"}; + + InSequence s; + setup(SCRIPT); + + Http::TestHeaderMapImpl request_headers{{":path", "/"}}; + + EXPECT_CALL(*filter_, scriptLog(spdlog::level::trace, StrEq("failed to import public key"))); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); +} + +TEST_F(LuaHttpFilterTest, SignatureVerify) { + const std::string SCRIPT{R"EOF( + function string.fromhex(str) + return (str:gsub('..', function (cc) + return string.char(tonumber(cc, 16)) + end)) + end + function envoy_on_request(request_handle) + key = "30820122300d06092a864886f70d01010105000382010f003082010a0282010100a7471266d01d160308d73409c06f2e8d35c531c458d3e480e9f3191847d062ec5ccff7bc51e949d5f2c3540c189a4eca1e8633a62cf2d0923101c27e38013e71de9ae91a704849bff7fbe2ce5bf4bd666fd9731102a53193fe5a9a5a50644ff8b1183fa897646598caad22a37f9544510836372b44c58c98586fb7144629cd8c9479592d996d32ff6d395c0b8442ec5aa1ef8051529ea0e375883cefc72c04e360b4ef8f5760650589ca814918f678eee39b884d5af8136a9630a6cc0cde157dc8e00f39540628d5f335b2c36c54c7c8bc3738a6b21acff815405afa28e5183f550dac19abcf1145a7f9ced987db680e4a229cac75dee347ec9ebce1fc3dbbbb0203010001" + hashFunc = "sha256" + signature = "345ac3a167558f4f387a81c2d64234d901a7ceaa544db779d2f797b0ea4ef851b740905a63e2f4d5af42cee093a29c7155db9a63d3d483e0ef948f5ac51ce4e10a3a6606fd93ef68ee47b30c37491103039459122f78e1c7ea71a1a5ea24bb6519bca02c8c9915fe8be24927c91812a13db72dbcb500103a79e8f67ff8cb9e2a631974e0668ab3977bf570a91b67d1b6bcd5dce84055f21427d64f4256a042ab1dc8e925d53a769f6681a873f5859693a7728fcbe95beace1563b5ffbcd7c93b898aeba31421dafbfadeea50229c49fd6c445449314460f3d19150bd29a91333beaced557ed6295234f7c14fa46303b7e977d2c89ba8a39a46a35f33eb07a332" + data = "hello" + + rawkey = key:fromhex() + pubkey = request_handle:importPublicKey(rawkey, string.len(rawkey)):get() + + if pubkey == nil then + request_handle:logTrace("failed to import public key") + return + end + + rawsig = signature:fromhex() + + ok, error = request_handle:verifySignature(hashFunc, pubkey, rawsig, string.len(rawsig), data, string.len(data)) + if ok then + request_handle:logTrace("signature is valid") + else + request_handle:logTrace(error) + end + + ok, error = request_handle:verifySignature("unknown", pubkey, rawsig, string.len(rawsig), data, string.len(data)) + if ok then + request_handle:logTrace("signature is valid") + else + request_handle:logTrace(error) + end + + ok, error = request_handle:verifySignature(hashFunc, pubkey, "0000", 4, data, string.len(data)) + if ok then + request_handle:logTrace("signature is valid") + else + request_handle:logTrace(error) + end + + ok, error = request_handle:verifySignature(hashFunc, pubkey, rawsig, string.len(rawsig), "xxxx", 4) + if ok then + request_handle:logTrace("signature is valid") + else + request_handle:logTrace(error) + end + end + )EOF"}; + + InSequence s; + setup(SCRIPT); + + Http::TestHeaderMapImpl request_headers{{":path", "/"}}; + + EXPECT_CALL(*filter_, scriptLog(spdlog::level::trace, StrEq("signature is valid"))); + EXPECT_CALL(*filter_, scriptLog(spdlog::level::trace, StrEq("unknown is not supported."))); + EXPECT_CALL(*filter_, + scriptLog(spdlog::level::trace, StrEq("Failed to verify digest. Error code: 0"))); + EXPECT_CALL(*filter_, + scriptLog(spdlog::level::trace, StrEq("Failed to verify digest. Error code: 0"))); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); +} + } // namespace } // namespace Lua } // namespace HttpFilters diff --git a/test/extensions/filters/http/lua/lua_integration_test.cc b/test/extensions/filters/http/lua/lua_integration_test.cc index bad8a03574727..4149d43338525 100644 --- a/test/extensions/filters/http/lua/lua_integration_test.cc +++ b/test/extensions/filters/http/lua/lua_integration_test.cc @@ -53,6 +53,8 @@ class LuaIntegrationTest : public testing::TestWithParam0 and '1' or '0') end + return r; + end):gsub('%d%d%d?%d?%d?%d?%d?%d?', function(x) + if (#x ~= 8) then return '' end + local c=0 + for i=1,8 do c=c+(x:sub(i,i)=='1' and 2^(8-i) or 0) end + return string.char(c) + end)) + end + + function envoy_on_request(request_handle) + local metadata = request_handle:metadata():get("keyset") + local keyder = metadata[request_handle:headers():get("keyid")] + + local rawkeyder = dec(keyder) + local pubkey = request_handle:importPublicKey(rawkeyder, string.len(rawkeyder)):get() + + if pubkey == nil then + request_handle:logErr("log test") + request_handle:headers():add("signature_verification", "rejected") + return + end + + local hash = request_handle:headers():get("hash") + local sig = request_handle:headers():get("signature") + local rawsig = sig:fromhex() + local data = request_handle:headers():get("message") + local ok, error = request_handle:verifySignature(hash, pubkey, rawsig, string.len(rawsig), data, string.len(data)) + + if ok then + request_handle:headers():add("signature_verification", "approved") + else + request_handle:logErr(error) + request_handle:headers():add("signature_verification", "rejected") + end + + request_handle:releasePublicKey(pubkey) + end +)EOF"; + + initializeFilter(FILTER_AND_CODE); + + auto signature = + "345ac3a167558f4f387a81c2d64234d901a7ceaa544db779d2f797b0ea4ef851b740905a63e2f4d5af42cee093a2" + "9c7155db9a63d3d483e0ef948f5ac51ce4e10a3a6606fd93ef68ee47b30c37491103039459122f78e1c7ea71a1a5" + "ea24bb6519bca02c8c9915fe8be24927c91812a13db72dbcb500103a79e8f67ff8cb9e2a631974e0668ab3977bf5" + "70a91b67d1b6bcd5dce84055f21427d64f4256a042ab1dc8e925d53a769f6681a873f5859693a7728fcbe95beace" + "1563b5ffbcd7c93b898aeba31421dafbfadeea50229c49fd6c445449314460f3d19150bd29a91333beaced557ed6" + "295234f7c14fa46303b7e977d2c89ba8a39a46a35f33eb07a332"; + + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + Http::TestHeaderMapImpl request_headers{ + {":method", "POST"}, {":path", "/test/long/url"}, {":scheme", "https"}, + {":authority", "host"}, {"x-forwarded-for", "10.0.0.1"}, {"message", "hello"}, + {"keyid", "foo"}, {"signature", signature}, {"hash", "sha256"}}; + + auto response = codec_client_->makeHeaderOnlyRequest(request_headers); + waitForNextUpstreamRequest(); + + EXPECT_EQ("approved", upstream_request_->headers() + .get(Http::LowerCaseString("signature_verification")) + ->value() + .getStringView()); + + upstream_request_->encodeHeaders(default_response_headers_, true); + response->waitForEndStream(); + + EXPECT_TRUE(response->complete()); + EXPECT_EQ("200", response->headers().Status()->value().getStringView()); + + cleanup(); +} + } // namespace } // namespace Envoy