Skip to content

Commit

Permalink
Merge pull request envoyproxy#82 from duderino/jblatt_1_2_lua_symbols
Browse files Browse the repository at this point in the history
cherry-pick into 1.2:  filter: exposed functions to Lua to verify digital signature (envoyproxy#7050)
  • Loading branch information
Joshua Blatt authored Jun 12, 2019
2 parents f504c56 + efc9d6f commit 4f5b5e1
Show file tree
Hide file tree
Showing 12 changed files with 535 additions and 9 deletions.
26 changes: 26 additions & 0 deletions docs/root/configuration/http_filters/lua_filter.rst
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,32 @@ Returns the current request's underlying :repo:`connection <include/envoy/networ

Returns a :ref:`connection object <config_http_filters_lua_connection_wrapper>`.

importPublicKey()
^^^^^^^^^^^^^^^^^

.. code-block:: lua
pubkey = handle:importPublicKey(keyder, keyderLength)
Returns public key which is used by :ref:`verifySignature <verify_signature>` 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
Expand Down
6 changes: 6 additions & 0 deletions docs/root/intro/version_history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ Version history
* http: fixed a crashing bug where gRPC local replies would cause segfaults when upstream access logging was on.
* http: mitigated a race condition with the :ref:`delayed_close_timeout<envoy_api_field_config.filter.network.http_connection_manager.v2.HttpConnectionManager.delayed_close_timeout>` where it could trigger while actively flushing a pending write buffer for a downstream connection.
* jwt_authn: make filter's parsing of JWT more flexible, allowing syntax like ``jwt=eyJhbGciOiJS...ZFnFIw,extra=7,realm=123``
* listener: added :ref:`source IP <envoy_api_field_listener.FilterChainMatch.source_prefix_ranges>`
and :ref:`source port <envoy_api_field_listener.FilterChainMatch.source_ports>` filter
chain matching.
* lua: exposed functions to Lua to verify digital signature.
* original_src filter: added the :ref:`filter<config_http_filters_original_src>`.
* rbac: migrated from v2alpha to v2.
* redis: add support for Redis cluster custom cluster type.
* redis: added :ref:`prefix routing <envoy_api_field_config.filter.network.redis_proxy.v2.RedisProxy.prefix_routes>` to enable routing commands based on their key's prefix to different upstream.
* redis: add support for zpopmax and zpopmin commands.
Expand Down
59 changes: 58 additions & 1 deletion source/common/crypto/utility.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -41,6 +43,61 @@ std::vector<uint8_t> Utility::getSha256Hmac(const std::vector<uint8_t>& key,
return hmac;
}

const VerificationOutput Utility::verifySignature(absl::string_view hash, EVP_PKEY* pubKey,
const std::vector<uint8_t>& signature,
const std::vector<uint8_t>& 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<uint8_t>& 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
39 changes: 39 additions & 0 deletions source/common/crypto/utility.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<EVP_PKEY> PublicKeyPtr;

class Utility {
public:
/**
Expand All @@ -28,6 +44,29 @@ class Utility {
*/
static std::vector<uint8_t> getSha256Hmac(const std::vector<uint8_t>& 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<uint8_t>& signature,
const std::vector<uint8_t>& text);

/**
* Import public key.
* @param key key string
* @return pointer to public key
*/
static PublicKeyPtr importPublicKey(const std::vector<uint8_t>& key);

private:
static const EVP_MD* getHashFunction(absl::string_view name);
};

} // namespace Crypto
Expand Down
2 changes: 2 additions & 0 deletions source/extensions/filters/http/lua/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down
48 changes: 48 additions & 0 deletions source/extensions/filters/http/lua/lua_filter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<uint8_t> 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<uint8_t> text_vec(clear_text, clear_text + text_len);

// Step 5: verify signature
auto output = Common::Crypto::Utility::verifySignature(hash, reinterpret_cast<EVP_PKEY*>(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<uint8_t> 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) {
Expand All @@ -442,6 +489,7 @@ FilterConfig::FilterConfig(const std::string& lua_code, ThreadLocal::SlotAllocat
lua_state_.registerType<DynamicMetadataMapWrapper>();
lua_state_.registerType<DynamicMetadataMapIterator>();
lua_state_.registerType<StreamHandleWrapper>();
lua_state_.registerType<PublicKeyWrapper>();

request_function_slot_ = lua_state_.registerGlobal("envoy_on_request");
if (lua_state_.getGlobalRef(request_function_slot_) == LUA_REFNIL) {
Expand Down
48 changes: 40 additions & 8 deletions source/extensions/filters/http/lua/lua_filter.h
Original file line number Diff line number Diff line change
Expand Up @@ -127,14 +127,23 @@ class StreamHandleWrapper : public Filters::Common::Lua::BaseLuaObject<StreamHan
}

static ExportedFunctions exportedFunctions() {
return {{"headers", static_luaHeaders}, {"body", static_luaBody},
{"bodyChunks", static_luaBodyChunks}, {"trailers", static_luaTrailers},
{"metadata", static_luaMetadata}, {"logTrace", static_luaLogTrace},
{"logDebug", static_luaLogDebug}, {"logInfo", static_luaLogInfo},
{"logWarn", static_luaLogWarn}, {"logErr", static_luaLogErr},
{"logCritical", static_luaLogCritical}, {"httpCall", static_luaHttpCall},
{"respond", static_luaRespond}, {"streamInfo", static_luaStreamInfo},
{"connection", static_luaConnection}};
return {{"headers", static_luaHeaders},
{"body", static_luaBody},
{"bodyChunks", static_luaBodyChunks},
{"trailers", static_luaTrailers},
{"metadata", static_luaMetadata},
{"logTrace", static_luaLogTrace},
{"logDebug", static_luaLogDebug},
{"logInfo", static_luaLogInfo},
{"logWarn", static_luaLogWarn},
{"logErr", static_luaLogErr},
{"logCritical", static_luaLogCritical},
{"httpCall", static_luaHttpCall},
{"respond", static_luaRespond},
{"streamInfo", static_luaStreamInfo},
{"connection", static_luaConnection},
{"importPublicKey", static_luaImportPublicKey},
{"verifySignature", static_luaVerifySignature}};
}

private:
Expand Down Expand Up @@ -209,6 +218,27 @@ class StreamHandleWrapper : public Filters::Common::Lua::BaseLuaObject<StreamHan
DECLARE_LUA_FUNCTION(StreamHandleWrapper, luaLogErr);
DECLARE_LUA_FUNCTION(StreamHandleWrapper, luaLogCritical);

/**
* Verify cryptographic signatures.
* @param 1 (string) hash function(including SHA1, SHA224, SHA256, SHA384, SHA512)
* @param 2 (void*) pointer to public key
* @param 3 (string) signature
* @param 4 (int) length of signature
* @param 5 (string) clear text
* @param 6 (int) length of clear text
* @return (bool, string) If the first element is true, the second element is empty; otherwise,
* the second element stores the error message
*/
DECLARE_LUA_FUNCTION(StreamHandleWrapper, luaVerifySignature);

/**
* Import public key.
* @param 1 (string) keyder string
* @param 2 (int) length of keyder string
* @return pointer to public key
*/
DECLARE_LUA_FUNCTION(StreamHandleWrapper, luaImportPublicKey);

/**
* This is the closure/iterator returned by luaBodyChunks() above.
*/
Expand All @@ -226,6 +256,7 @@ class StreamHandleWrapper : public Filters::Common::Lua::BaseLuaObject<StreamHan
metadata_wrapper_.reset();
stream_info_wrapper_.reset();
connection_wrapper_.reset();
public_key_wrapper_.reset();
}

// Http::AsyncClient::Callbacks
Expand All @@ -247,6 +278,7 @@ class StreamHandleWrapper : public Filters::Common::Lua::BaseLuaObject<StreamHan
Filters::Common::Lua::LuaDeathRef<Filters::Common::Lua::MetadataMapWrapper> metadata_wrapper_;
Filters::Common::Lua::LuaDeathRef<StreamInfoWrapper> stream_info_wrapper_;
Filters::Common::Lua::LuaDeathRef<Filters::Common::Lua::ConnectionWrapper> connection_wrapper_;
Filters::Common::Lua::LuaDeathRef<PublicKeyWrapper> public_key_wrapper_;
State state_{State::Running};
std::function<void()> yield_callback_;
Http::AsyncClient::Request* http_request_{};
Expand Down
9 changes: 9 additions & 0 deletions source/extensions/filters/http/lua/wrappers.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 20 additions & 0 deletions source/extensions/filters/http/lua/wrappers.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -201,6 +203,24 @@ class StreamInfoWrapper : public Filters::Common::Lua::BaseLuaObject<StreamInfoW
friend class DynamicMetadataMapWrapper;
};

/**
* Lua wrapper for EVP_PKEY.
*/
class PublicKeyWrapper : public Filters::Common::Lua::BaseLuaObject<PublicKeyWrapper> {
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
Expand Down
Loading

0 comments on commit 4f5b5e1

Please sign in to comment.