Skip to content

Commit

Permalink
filter: exposed functions to Lua to verify digital signature (envoypr…
Browse files Browse the repository at this point in the history
  • Loading branch information
yxue committed Jun 6, 2019
1 parent ad33805 commit d09ae4d
Show file tree
Hide file tree
Showing 12 changed files with 593 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
64 changes: 64 additions & 0 deletions docs/root/intro/version_history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,70 @@ Version history

1.11.0 (Pending)
================
* access log: added a new field for downstream TLS session ID to file and gRPC access logger.
* access log: added a new field for route name to file and gRPC access logger.
* access log: added a new field for response code details in :ref:`file access logger<config_access_log_format_response_code_details>` and :ref:`gRPC access logger<envoy_api_field_data.accesslog.v2.HTTPResponseProperties.response_code_details>`.
* access log: added several new variables for exposing information about the downstream TLS connection to :ref:`file access logger<config_access_log_format_response_code_details>` and :ref:`gRPC access logger<envoy_api_field_data.accesslog.v2.AccessLogCommon.tls_properties>`.
* admin: the administration interface now includes a :ref:`/ready endpoint <operations_admin_interface>` for easier readiness checks.
* admin: extend :ref:`/runtime_modify endpoint <operations_admin_interface_runtime_modify>` to support parameters within the request body.
* api: track and report requests issued since last load report.
* build: releases are built with Clang and linked with LLD.
* dubbo_proxy: support the :ref:`Dubbo proxy filter <config_network_filters_dubbo_proxy>`.
* eds: added support to specify max time for which endpoints can be used :ref:`gRPC filter <envoy_api_msg_ClusterLoadAssignment.Policy>`.
* event: added :ref:`loop duration and poll delay statistics <operations_performance>`.
* ext_authz: added a `x-envoy-auth-partial-body` metadata header set to `false|true` indicating if there is a partial body sent in the authorization request message.
* ext_authz: added configurable status code that allows customizing HTTP responses on filter check status errors.
* ext_authz: added option to `ext_authz` that allows the filter clearing route cache.
* grpc-json: added support for :ref:`auto mapping
<envoy_api_field_config.filter.http.transcoder.v2.GrpcJsonTranscoder.auto_mapping>`.
* health check: added :ref:`initial jitter <envoy_api_field_core.HealthCheck.initial_jitter>` to add jitter to the first health check in order to prevent thundering herd on Envoy startup.
* hot restart: stats are no longer shared between hot restart parent/child via shared memory, but rather by RPC. Hot restart version incremented to 11.
* http: fixed a bug where large unbufferable responses were not tracked in stats and logs correctly.
* 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.
* http: changed `sendLocalReply` to send percent-encoded `GrpcMessage`.
* 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.
* redis: added
:ref:`max_buffer_size_before_flush <envoy_api_field_config.filter.network.redis_proxy.v2.RedisProxy.ConnPoolSettings.max_buffer_size_before_flush>` to batch commands together until the encoder buffer hits a certain size, and
:ref:`buffer_flush_timeout <envoy_api_field_config.filter.network.redis_proxy.v2.RedisProxy.ConnPoolSettings.buffer_flush_timeout>` to control how quickly the buffer is flushed if it is not full.
* redis: added auth support :ref:`downstream_auth_password <envoy_api_field_config.filter.network.redis_proxy.v2.RedisProxy.downstream_auth_password>` for downstream client authentication, and :ref:`auth_password <envoy_api_field_config.filter.network.redis_proxy.v2.RedisProtocolOptions.auth_password>` to configure authentication passwords for upstream server clusters.
* router: add support for configuring a :ref:`grpc timeout offset <envoy_api_field_route.RouteAction.grpc_timeout_offset>` on incoming requests.
* router: added ability to control retry back-off intervals via :ref:`retry policy <envoy_api_msg_route.RetryPolicy.RetryBackOff>`.
* router: added ability to issue a hedged retry in response to a per try timeout via a :ref:`hedge policy <envoy_api_msg_route.HedgePolicy>`.
* router: added a route name field to each http route in route.Route list
* router: added several new variables for exposing information about the downstream TLS connection via :ref:`header
formatters <config_http_conn_man_headers_custom_request_headers>`.
* router: per try timeouts will no longer start before the downstream request has been received
in full by the router. This ensures that the per try timeout does not account for slow
downstreams and that will not start before the global timeout.
* runtime: added support for :ref:`flexible layering configuration
<envoy_api_field_config.bootstrap.v2.Bootstrap.layered_runtime>`.
* runtime: added support for statically :ref:`specifying the runtime in the bootstrap configuration
<envoy_api_field_config.bootstrap.v2.Runtime.base>`.
* sandbox: added :ref:`CSRF sandbox <install_sandboxes_csrf>`.
* server: ``--define manual_stamp=manual_stamp`` was added to allow server stamping outside of binary rules.
more info in the `bazel docs <https://github.com/envoyproxy/envoy/blob/master/bazel/README.md#enabling-optional-features>`_.
* tool: added :repo:`proto <test/tools/router_check/validation.proto>` support for :ref:`router check tool <install_tools_route_table_check_tool>` tests.
* tracing: add trace sampling configuration to the route, to override the route level.
* upstream: added :ref:`upstream_cx_pool_overflow <config_cluster_manager_cluster_stats>` for the connection pool circuit breaker.
* upstream: an EDS management server can now force removal of a host that is still passing active
health checking by first marking the host as failed via EDS health check and subsequently removing
it in a future update. This is a mechanism to work around a race condition in which an EDS
implementation may remove a host before it has stopped passing active HC, thus causing the host
to become stranded until a future update.
* upstream: added :ref:`an option <envoy_api_field_Cluster.CommonLbConfig.ignore_new_hosts_until_first_hc>`
that allows ignoring new hosts for the purpose of load balancing calculations until they have
been health checked for the first time.
* upstream: added runtime error checking to prevent setting dns type to STRICT_DNS or LOGICAL_DNS when custom resolver name is specified.

1.10.0 (Apr 5, 2019)
====================
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 @@ -426,6 +427,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 @@ -440,6 +487,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
Loading

0 comments on commit d09ae4d

Please sign in to comment.