Skip to content

Commit 8e603cb

Browse files
authored
wasm: register sign as foreign function (envoyproxy#41032)
Signed-off-by: shane-yuan <[email protected]>
1 parent ac5fd98 commit 8e603cb

File tree

22 files changed

+1286
-145
lines changed

22 files changed

+1286
-145
lines changed

changelogs/current.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,5 +35,9 @@ removed_config_or_runtime:
3535
Removed runtime guard ``envoy.reloadable_features.original_src_fix_port_exhaustion`` and legacy code paths.
3636
3737
new_features:
38+
- area: wasm
39+
change: |
40+
Added ``sign`` foreign function to create cryptographic signatures.
41+
See :ref:`Wasm foreign functions <arch_overview_wasm_foreign_functions>` for more details.
3842
3943
deprecated:

docs/root/intro/arch_overview/advanced/wasm.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,15 @@ Wasm ABI exposes Envoy-specific host attributes via the dedicated `proxy_get_pro
6666
standard :ref:`attributes <arch_overview_attributes>` and the values are returned via the type-specific binary
6767
serialization.
6868

69+
.. _arch_overview_wasm_foreign_functions:
70+
6971
Foreign functions
7072
-----------------
7173

7274
Envoy offers additional functionality over the Proxy-Wasm ABI via `proxy_call_foreign_function
7375
<https://github.com/proxy-wasm/spec/tree/main/abi-versions/v0.2.1#proxy_call_foreign_function>`_ binary interface:
7476

77+
* ``sign`` creates cryptographic signatures.
7578
* ``verify_signature`` verifies cryptographic signatures.
7679
* ``compress`` applies ``zlib`` compression.
7780
* ``uncompress`` applies ``zlib`` decompression.

source/common/crypto/crypto_impl.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ namespace Envoy {
44
namespace Common {
55
namespace Crypto {
66

7-
EVP_PKEY* PublicKeyObject::getEVP_PKEY() const { return pkey_.get(); }
7+
EVP_PKEY* PKeyObject::getEVP_PKEY() const { return pkey_.get(); }
88

9-
void PublicKeyObject::setEVP_PKEY(EVP_PKEY* pkey) { pkey_.reset(pkey); }
9+
void PKeyObject::setEVP_PKEY(EVP_PKEY* pkey) { pkey_.reset(pkey); }
1010

1111
} // namespace Crypto
1212
} // namespace Common

source/common/crypto/crypto_impl.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,19 @@ namespace Envoy {
99
namespace Common {
1010
namespace Crypto {
1111

12-
class PublicKeyObject : public Envoy::Common::Crypto::CryptoObject {
12+
class PKeyObject : public Envoy::Common::Crypto::CryptoObject {
1313
public:
14-
PublicKeyObject() = default;
15-
PublicKeyObject(EVP_PKEY* pkey) : pkey_(pkey) {}
16-
PublicKeyObject(const PublicKeyObject& pkey_wrapper);
14+
PKeyObject() = default;
15+
PKeyObject(EVP_PKEY* pkey) : pkey_(pkey) {}
1716
EVP_PKEY* getEVP_PKEY() const;
1817
void setEVP_PKEY(EVP_PKEY* pkey);
1918

2019
private:
2120
bssl::UniquePtr<EVP_PKEY> pkey_;
2221
};
2322

23+
using PKeyObjectPtr = std::unique_ptr<PKeyObject>;
24+
2425
} // namespace Crypto
2526
} // namespace Common
2627
} // namespace Envoy

source/common/crypto/utility.h

Lines changed: 52 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,18 @@
44
#include <vector>
55

66
#include "envoy/buffer/buffer.h"
7-
#include "envoy/common/crypto/crypto.h"
87

8+
#include "source/common/crypto/crypto_impl.h"
99
#include "source/common/singleton/threadsafe_singleton.h"
1010

11+
#include "absl/status/statusor.h"
1112
#include "absl/strings/string_view.h"
13+
#include "absl/types/span.h"
1214

1315
namespace Envoy {
1416
namespace Common {
1517
namespace Crypto {
1618

17-
struct VerificationOutput {
18-
/**
19-
* Verification result. If result_ is true, error_message_ is empty.
20-
*/
21-
bool result_;
22-
23-
/**
24-
* Error message when verification failed.
25-
* TODO(crazyxy): switch to absl::StatusOr when available
26-
*/
27-
std::string error_message_;
28-
};
29-
3019
class Utility {
3120
public:
3221
virtual ~Utility() = default;
@@ -44,28 +33,64 @@ class Utility {
4433
* @param message string_view message data for the HMAC function.
4534
* @return a vector of bytes for the computed HMAC.
4635
*/
47-
virtual std::vector<uint8_t> getSha256Hmac(const std::vector<uint8_t>& key,
36+
virtual std::vector<uint8_t> getSha256Hmac(absl::Span<const uint8_t> key,
4837
absl::string_view message) PURE;
4938

5039
/**
5140
* Verify cryptographic signatures.
52-
* @param hash hash function(including SHA1, SHA224, SHA256, SHA384, SHA512)
53-
* @param key pointer to EVP_PKEY public key
54-
* @param signature signature
55-
* @param text clear text
56-
* @return If the result_ is true, the error_message_ is empty; otherwise,
57-
* the error_message_ stores the error message
41+
* @param hash_function hash function name (including SHA1, SHA224, SHA256, SHA384, SHA512)
42+
* @param key CryptoObject containing EVP_PKEY public key (must be imported via importPublicKey())
43+
* @param signature signature bytes to verify
44+
* @param text clear text that was signed
45+
* @return absl::Status containing Ok if verification succeeds, or error status on failure
46+
* @note The key must be imported using importPublicKey() which supports both DER and PEM formats
47+
* @note Works with public keys imported from DER (PKCS#1) or PEM (PKCS#1/PKCS#8) formats
48+
*/
49+
virtual absl::Status verifySignature(absl::string_view hash_function, PKeyObject& key,
50+
absl::Span<const uint8_t> signature,
51+
absl::Span<const uint8_t> text) PURE;
52+
53+
/**
54+
* Sign data with a private key.
55+
* @param hash_function hash function name (including SHA1, SHA224, SHA256, SHA384, SHA512)
56+
* @param key CryptoObject containing EVP_PKEY private key (must be imported via
57+
* importPrivateKey())
58+
* @param text clear text to sign
59+
* @return absl::StatusOr<std::vector<uint8_t>> containing the signature on success, or error
60+
* status on failure
61+
* @note The key must be imported using importPrivateKey() which supports both DER and PEM formats
62+
* @note Works with private keys imported from DER (PKCS#8) or PEM (PKCS#1/PKCS#8) formats
5863
*/
59-
virtual const VerificationOutput verifySignature(absl::string_view hash, CryptoObject& key,
60-
const std::vector<uint8_t>& signature,
61-
const std::vector<uint8_t>& text) PURE;
64+
virtual absl::StatusOr<std::vector<uint8_t>>
65+
sign(absl::string_view hash_function, PKeyObject& key, absl::Span<const uint8_t> text) PURE;
6266

6367
/**
64-
* Import public key.
65-
* @param key key string
68+
* Import public key from PEM format.
69+
* @param key Public key in PEM format
6670
* @return pointer to EVP_PKEY public key
6771
*/
68-
virtual CryptoObjectPtr importPublicKey(const std::vector<uint8_t>& key) PURE;
72+
virtual PKeyObjectPtr importPublicKeyPEM(absl::string_view key) PURE;
73+
74+
/**
75+
* Import public key from DER format.
76+
* @param key Public key in DER format
77+
* @return pointer to EVP_PKEY public key
78+
*/
79+
virtual PKeyObjectPtr importPublicKeyDER(absl::Span<const uint8_t> key) PURE;
80+
81+
/**
82+
* Import private key from PEM format.
83+
* @param key Private key in PEM format
84+
* @return pointer to EVP_PKEY private key
85+
*/
86+
virtual PKeyObjectPtr importPrivateKeyPEM(absl::string_view key) PURE;
87+
88+
/**
89+
* Import private key from DER format.
90+
* @param key Private key in DER format
91+
* @return pointer to EVP_PKEY private key
92+
*/
93+
virtual PKeyObjectPtr importPrivateKeyDER(absl::Span<const uint8_t> key) PURE;
6994
};
7095

7196
using UtilitySingleton = InjectableSingleton<Utility>;

source/common/crypto/utility_impl.cc

Lines changed: 84 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33
#include "source/common/common/assert.h"
44
#include "source/common/crypto/crypto_impl.h"
55

6-
#include "absl/container/fixed_array.h"
76
#include "absl/strings/ascii.h"
87
#include "absl/strings/str_cat.h"
8+
#include "absl/types/span.h"
9+
#include "openssl/pem.h"
910

1011
namespace Envoy {
1112
namespace Common {
@@ -25,7 +26,7 @@ std::vector<uint8_t> UtilityImpl::getSha256Digest(const Buffer::Instance& buffer
2526
return digest;
2627
}
2728

28-
std::vector<uint8_t> UtilityImpl::getSha256Hmac(const std::vector<uint8_t>& key,
29+
std::vector<uint8_t> UtilityImpl::getSha256Hmac(absl::Span<const uint8_t> key,
2930
absl::string_view message) {
3031
std::vector<uint8_t> hmac(SHA256_DIGEST_LENGTH);
3132
const auto ret =
@@ -35,46 +36,109 @@ std::vector<uint8_t> UtilityImpl::getSha256Hmac(const std::vector<uint8_t>& key,
3536
return hmac;
3637
}
3738

38-
const VerificationOutput UtilityImpl::verifySignature(absl::string_view hash, CryptoObject& key,
39-
const std::vector<uint8_t>& signature,
40-
const std::vector<uint8_t>& text) {
41-
// Step 1: initialize EVP_MD_CTX
39+
absl::Status UtilityImpl::verifySignature(absl::string_view hash_function, PKeyObject& key,
40+
absl::Span<const uint8_t> signature,
41+
absl::Span<const uint8_t> text) {
4242
bssl::ScopedEVP_MD_CTX ctx;
4343

44-
// Step 2: initialize EVP_MD
45-
const EVP_MD* md = getHashFunction(hash);
44+
const EVP_MD* md = getHashFunction(hash_function);
4645

4746
if (md == nullptr) {
48-
return {false, absl::StrCat(hash, " is not supported.")};
47+
return absl::InvalidArgumentError(absl::StrCat(hash_function, " is not supported."));
4948
}
50-
// Step 3: initialize EVP_DigestVerify
51-
auto pkey_wrapper = Common::Crypto::Access::getTyped<Common::Crypto::PublicKeyObject>(key);
52-
EVP_PKEY* pkey = pkey_wrapper->getEVP_PKEY();
49+
EVP_PKEY* pkey = key.getEVP_PKEY();
5350

5451
if (pkey == nullptr) {
55-
return {false, "Failed to initialize digest verify."};
52+
return absl::InternalError("Failed to initialize digest verify.");
5653
}
5754

5855
int ok = EVP_DigestVerifyInit(ctx.get(), nullptr, md, nullptr, pkey);
5956
if (!ok) {
60-
return {false, "Failed to initialize digest verify."};
57+
return absl::InternalError("Failed to initialize digest verify.");
6158
}
6259

63-
// Step 4: verify signature
6460
ok = EVP_DigestVerify(ctx.get(), signature.data(), signature.size(), text.data(), text.size());
6561

66-
// Step 5: check result
6762
if (ok == 1) {
68-
return {true, ""};
63+
return absl::OkStatus();
6964
}
7065

71-
return {false, absl::StrCat("Failed to verify digest. Error code: ", ok)};
66+
return absl::InternalError(absl::StrCat("Failed to verify digest. Error code: ", ok));
7267
}
7368

74-
CryptoObjectPtr UtilityImpl::importPublicKey(const std::vector<uint8_t>& key) {
69+
absl::StatusOr<std::vector<uint8_t>> UtilityImpl::sign(absl::string_view hash_function,
70+
PKeyObject& key,
71+
absl::Span<const uint8_t> text) {
72+
bssl::ScopedEVP_MD_CTX ctx;
73+
74+
const EVP_MD* md = getHashFunction(hash_function);
75+
76+
if (md == nullptr) {
77+
return absl::InvalidArgumentError(absl::StrCat(hash_function, " is not supported."));
78+
}
79+
80+
EVP_PKEY* pkey = key.getEVP_PKEY();
81+
82+
if (pkey == nullptr) {
83+
return absl::InternalError("Invalid key type: private key required for signing operation.");
84+
}
85+
86+
int ok = EVP_DigestSignInit(ctx.get(), nullptr, md, nullptr, pkey);
87+
if (!ok) {
88+
return absl::InternalError("Invalid private key: key data is corrupted or malformed.");
89+
}
90+
91+
size_t sig_len = 0;
92+
ok = EVP_DigestSign(ctx.get(), nullptr, &sig_len, text.data(), text.size());
93+
if (!ok) {
94+
return absl::InternalError("Failed to get signature length.");
95+
}
96+
97+
std::vector<uint8_t> signature(sig_len);
98+
ok = EVP_DigestSign(ctx.get(), signature.data(), &sig_len, text.data(), text.size());
99+
if (!ok) {
100+
return absl::InternalError("Failed to create signature.");
101+
}
102+
103+
RELEASE_ASSERT(signature.size() >= sig_len, "signature.size() >= sig_len");
104+
signature.resize(sig_len);
105+
return signature;
106+
}
107+
108+
namespace {
109+
// Template helper for importing keys with different formats and types
110+
template <typename KeyObjectType, typename ParseFunction>
111+
PKeyObjectPtr importKeyPEM(absl::string_view key, ParseFunction parse_func) {
112+
// PEM format: Use PEM parsing which automatically handles both PKCS#1 and PKCS#8 formats
113+
bssl::UniquePtr<BIO> bio(BIO_new_mem_buf(key.data(), key.size()));
114+
if (!bio) {
115+
return std::make_unique<KeyObjectType>(nullptr);
116+
}
117+
return std::make_unique<KeyObjectType>(parse_func(bio.get(), nullptr, nullptr, nullptr));
118+
}
119+
120+
template <typename KeyObjectType, typename ParseFunction>
121+
PKeyObjectPtr importKeyDER(absl::Span<const uint8_t> key, ParseFunction parse_func) {
122+
// DER format: Use DER parsing
75123
CBS cbs({key.data(), key.size()});
124+
return std::make_unique<KeyObjectType>(parse_func(&cbs));
125+
}
126+
} // namespace
127+
128+
PKeyObjectPtr UtilityImpl::importPublicKeyPEM(absl::string_view key) {
129+
return importKeyPEM<PKeyObject>(key, PEM_read_bio_PUBKEY);
130+
}
131+
132+
PKeyObjectPtr UtilityImpl::importPublicKeyDER(absl::Span<const uint8_t> key) {
133+
return importKeyDER<PKeyObject>(key, EVP_parse_public_key);
134+
}
135+
136+
PKeyObjectPtr UtilityImpl::importPrivateKeyPEM(absl::string_view key) {
137+
return importKeyPEM<PKeyObject>(key, PEM_read_bio_PrivateKey);
138+
}
76139

77-
return std::make_unique<PublicKeyObject>(EVP_parse_public_key(&cbs));
140+
PKeyObjectPtr UtilityImpl::importPrivateKeyDER(absl::Span<const uint8_t> key) {
141+
return importKeyDER<PKeyObject>(key, EVP_parse_private_key);
78142
}
79143

80144
const EVP_MD* UtilityImpl::getHashFunction(absl::string_view name) {

source/common/crypto/utility_impl.h

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
#pragma once
22

3+
#include "source/common/crypto/crypto_impl.h"
34
#include "source/common/crypto/utility.h"
45

6+
#include "absl/types/span.h"
7+
#include "openssl/bio.h"
58
#include "openssl/bytestring.h"
69
#include "openssl/hmac.h"
710
#include "openssl/sha.h"
@@ -13,12 +16,17 @@ namespace Crypto {
1316
class UtilityImpl : public Envoy::Common::Crypto::Utility {
1417
public:
1518
std::vector<uint8_t> getSha256Digest(const Buffer::Instance& buffer) override;
16-
std::vector<uint8_t> getSha256Hmac(const std::vector<uint8_t>& key,
19+
std::vector<uint8_t> getSha256Hmac(absl::Span<const uint8_t> key,
1720
absl::string_view message) override;
18-
const VerificationOutput verifySignature(absl::string_view hash, CryptoObject& key,
19-
const std::vector<uint8_t>& signature,
20-
const std::vector<uint8_t>& text) override;
21-
CryptoObjectPtr importPublicKey(const std::vector<uint8_t>& key) override;
21+
absl::Status verifySignature(absl::string_view hash_function, PKeyObject& key,
22+
absl::Span<const uint8_t> signature,
23+
absl::Span<const uint8_t> text) override;
24+
absl::StatusOr<std::vector<uint8_t>> sign(absl::string_view hash_function, PKeyObject& key,
25+
absl::Span<const uint8_t> text) override;
26+
PKeyObjectPtr importPublicKeyPEM(absl::string_view key) override;
27+
PKeyObjectPtr importPublicKeyDER(absl::Span<const uint8_t> key) override;
28+
PKeyObjectPtr importPrivateKeyPEM(absl::string_view key) override;
29+
PKeyObjectPtr importPrivateKeyDER(absl::Span<const uint8_t> key) override;
2230

2331
private:
2432
const EVP_MD* getHashFunction(absl::string_view name);

source/extensions/common/wasm/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ envoy_cc_extension(
111111
"//source/extensions/common/wasm/ext:declare_property_cc_proto",
112112
"//source/extensions/common/wasm/ext:envoy_null_vm_wasm_api",
113113
"//source/extensions/common/wasm/ext:set_envoy_filter_state_cc_proto",
114+
"//source/extensions/common/wasm/ext:sign_cc_proto",
114115
"//source/extensions/common/wasm/ext:verify_signature_cc_proto",
115116
"//source/extensions/filters/common/expr:context_lib",
116117
"@com_google_absl//absl/base",

source/extensions/common/wasm/ext/BUILD

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ envoy_cc_library(
3333
":declare_property_cc_proto",
3434
":set_envoy_filter_state_cc_proto",
3535
":verify_signature_cc_proto",
36+
":sign_cc_proto",
3637
"//source/common/grpc:async_client_lib",
3738
"@envoy_api//envoy/config/core/v3:pkg_cc_proto",
3839
],
@@ -50,6 +51,7 @@ cc_library(
5051
":node_subset_cc_proto",
5152
":set_envoy_filter_state_cc_proto",
5253
":verify_signature_cc_proto",
54+
":sign_cc_proto",
5355
"@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics",
5456
],
5557
alwayslink = 1,
@@ -117,3 +119,15 @@ cc_proto_library(
117119
name = "set_envoy_filter_state_cc_proto",
118120
deps = [":set_envoy_filter_state_proto"],
119121
)
122+
123+
# NB: this target is compiled both to native code and to Wasm. Hence the generic rule.
124+
proto_library(
125+
name = "sign_proto",
126+
srcs = ["sign.proto"],
127+
)
128+
129+
# NB: this target is compiled both to native code and to Wasm. Hence the generic rule.
130+
cc_proto_library(
131+
name = "sign_cc_proto",
132+
deps = [":sign_proto"],
133+
)

0 commit comments

Comments
 (0)