1919
2020#ifdef PROXY_WASM_VERIFY_WITH_ED25519_PUBKEY
2121#include < openssl/evp.h>
22- #include < openssl/sha.h>
2322#endif
2423
2524#include " include/proxy-wasm/bytecode_util.h"
@@ -53,6 +52,42 @@ template <size_t N> constexpr std::array<uint8_t, N> hex2pubkey(const char (&hex
5352
5453} // namespace
5554
55+ namespace {
56+
57+ #ifdef PROXY_WASM_VERIFY_WITH_ED25519_PUBKEY
58+
59+ // Helper function to parse a varint from the payload
60+ bool parseVarint (const char *&pos, const char *end, uint32_t &ret) {
61+ uint32_t shift = 0 ;
62+ uint32_t total = 0 ;
63+ uint32_t v;
64+ char b;
65+ while (pos < end) {
66+ if (pos + 1 > end) {
67+ return false ;
68+ }
69+ b = *pos++;
70+ v = (b & 0x7f );
71+ if (shift == 28 && v > 3 ) {
72+ return false ;
73+ }
74+ total += v << shift;
75+ if ((b & 0x80 ) == 0 ) {
76+ ret = total;
77+ return true ;
78+ }
79+ shift += 7 ;
80+ if (shift > 28 ) {
81+ return false ;
82+ }
83+ }
84+ return false ;
85+ }
86+
87+ #endif
88+
89+ } // namespace
90+
5691namespace proxy_wasm {
5792
5893bool SignatureUtil::verifySignature (std::string_view bytecode, std::string &message) {
@@ -61,50 +96,200 @@ bool SignatureUtil::verifySignature(std::string_view bytecode, std::string &mess
6196
6297 /*
6398 * Ed25519 signature generated using https://github.com/wasm-signatures/wasmsign2
99+ * Format specification: https://github.com/WebAssembly/tool-conventions/blob/main/Signatures.md
64100 */
65101
66- std::string_view payload ;
67- if (!BytecodeUtil::getCustomSection (bytecode, " signature" , payload )) {
102+ std::string_view signature_payload ;
103+ if (!BytecodeUtil::getCustomSection (bytecode, " signature" , signature_payload )) {
68104 message = " Failed to parse corrupted Wasm module" ;
69105 return false ;
70106 }
71107
72- if (payload .empty ()) {
108+ if (signature_payload .empty ()) {
73109 message = " Custom Section \" signature\" not found" ;
74110 return false ;
75111 }
76112
77- if (bytecode.data () + bytecode.size () != payload.data () + payload.size ()) {
78- message = " Custom Section \" signature\" not at the end of Wasm module" ;
113+ // In wasmsign2 v2, the signature section must be FIRST, not last
114+ // Check if the signature section is at the beginning (after the WASM header)
115+ // The signature section should start at bytecode offset 8 (after magic + version)
116+ const char *sig_section_start = bytecode.data () + 8 ;
117+
118+ // Verify the signature section is at the beginning by checking if its custom section (type 0)
119+ if (sig_section_start >= bytecode.data () + bytecode.size () || *sig_section_start != 0 ) {
120+ message = " Custom Section \" signature\" not at the beginning of Wasm module" ;
79121 return false ;
80122 }
81123
82- if (payload.size () != 68 ) {
83- message = " Signature has a wrong size (want: 68, is: " + std::to_string (payload.size ()) + " )" ;
124+ // Parse wasmsign2 v2 format:
125+ // spec_version (byte), content_type (byte), hash_fn (byte),
126+ // signed_hashes_count (varint), then for each signed_hash:
127+ // hashes_count (varint), hashes (32 bytes each for SHA-256),
128+ // signature_count (varint), then for each signature:
129+ // key_id_len (varint), key_id (bytes), signature_id (byte),
130+ // signature_len (varint), signature (bytes)
131+
132+ const char *pos = signature_payload.data ();
133+ const char *end = signature_payload.data () + signature_payload.size ();
134+
135+ if (pos + 3 > end) {
136+ message = " Signature payload too short" ;
84137 return false ;
85138 }
86139
87- uint32_t alg_id ;
88- std::memcpy (&alg_id, payload. data (), sizeof ( uint32_t ) );
89- alg_id = wasmtoh (alg_id, true );
140+ uint8_t spec_version = static_cast < uint8_t >(*pos++) ;
141+ uint8_t content_type = static_cast < uint8_t >(*pos++ );
142+ uint8_t hash_fn = static_cast < uint8_t >(*pos++ );
90143
91- if (alg_id != 2 ) {
92- message = " Signature has a wrong alg_id (want: 2, is: " + std::to_string (alg_id) + " ) " ;
144+ if (spec_version != 0x01 ) {
145+ message = " Unsupported signature spec version: " + std::to_string (spec_version) ;
93146 return false ;
94147 }
95148
96- const auto *signature = reinterpret_cast <const uint8_t *>(payload.data ()) + sizeof (uint32_t );
149+ if (content_type != 0x01 ) {
150+ message = " Unsupported content type: " + std::to_string (content_type);
151+ return false ;
152+ }
153+
154+ if (hash_fn != 0x01 ) {
155+ message = " Unsupported hash function: " + std::to_string (hash_fn) + " (only SHA-256 supported)" ;
156+ return false ;
157+ }
97158
98- SHA512_CTX ctx;
99- SHA512_Init (&ctx);
100- SHA512_Update (&ctx, " WasmSignature" , sizeof (" WasmSignature" ) - 1 );
101- const uint32_t ad_len = 0 ;
102- SHA512_Update (&ctx, &ad_len, sizeof (uint32_t ));
103- const size_t section_len = 3 + sizeof (" signature" ) - 1 + 68 ;
104- SHA512_Update (&ctx, bytecode.data (), bytecode.size () - section_len);
105- uint8_t hash[SHA512_DIGEST_LENGTH];
106- SHA512_Final (hash, &ctx);
159+ uint32_t signed_hashes_count = 0 ;
160+ if (!parseVarint (pos, end, signed_hashes_count) || signed_hashes_count == 0 ) {
161+ message = " Invalid or zero signed_hashes_count" ;
162+ return false ;
163+ }
164+
165+ // We only verify the first signed_hash set
166+ uint32_t hashes_count = 0 ;
167+ if (!parseVarint (pos, end, hashes_count) || hashes_count == 0 ) {
168+ message = " Invalid or zero hashes_count" ;
169+ return false ;
170+ }
171+
172+ // For simplicity, we only support single-hash verification (no partial verification)
173+ if (hashes_count != 1 ) {
174+ message = " Only single-hash signatures are supported (found " + std::to_string (hashes_count) + " hashes)" ;
175+ return false ;
176+ }
177+
178+ if (pos + 32 > end) {
179+ message = " Signature payload too short for hash" ;
180+ return false ;
181+ }
182+
183+ // Extract the expected hash
184+ const uint8_t *expected_hash = reinterpret_cast <const uint8_t *>(pos);
185+ pos += 32 ;
186+
187+ uint32_t signatures_count = 0 ;
188+ if (!parseVarint (pos, end, signatures_count) || signatures_count == 0 ) {
189+ message = " Invalid or zero signatures_count" ;
190+ return false ;
191+ }
192+
193+ // We only verify the first signature
194+ uint32_t key_id_len = 0 ;
195+ if (!parseVarint (pos, end, key_id_len)) {
196+ message = " Invalid key_id_len" ;
197+ return false ;
198+ }
199+
200+ // Skip the key_id
201+ if (key_id_len > 0 ) {
202+ if (pos + key_id_len > end) {
203+ message = " Signature payload too short for key_id" ;
204+ return false ;
205+ }
206+ pos += key_id_len;
207+ }
208+
209+ if (pos + 1 > end) {
210+ message = " Signature payload too short for signature_id" ;
211+ return false ;
212+ }
213+
214+ uint8_t signature_id = static_cast <uint8_t >(*pos++);
215+ if (signature_id != 0x01 ) {
216+ message = " Unsupported signature algorithm: " + std::to_string (signature_id) + " (only Ed25519 supported)" ;
217+ return false ;
218+ }
219+
220+ uint32_t signature_len = 0 ;
221+ if (!parseVarint (pos, end, signature_len)) {
222+ message = " Invalid signature_len" ;
223+ return false ;
224+ }
225+
226+ if (signature_len != 64 ) {
227+ message = " Invalid Ed25519 signature length: " + std::to_string (signature_len) + " (expected 64)" ;
228+ return false ;
229+ }
230+
231+ if (pos + signature_len > end) {
232+ message = " Signature payload too short for signature data" ;
233+ return false ;
234+ }
235+
236+ const uint8_t *signature = reinterpret_cast <const uint8_t *>(pos);
237+
238+ // Compute the hash of the module content (everything after the signature section)
239+ // We need to find where the signature section ends in the original bytecode
240+ // The signature section structure in WASM is:
241+ // section_type (1 byte) + section_len (varint) + name_len (varint) + name + payload
242+
243+ // Find the end of the signature section
244+ const char *sig_section_pos = sig_section_start;
245+ sig_section_pos++; // skip section type (0)
246+
247+ uint32_t section_len = 0 ;
248+ if (!parseVarint (sig_section_pos, bytecode.data () + bytecode.size (), section_len)) {
249+ message = " Failed to parse signature section length" ;
250+ return false ;
251+ }
252+
253+ uint32_t name_len = 0 ;
254+ const char *section_data_start = sig_section_pos;
255+ if (!parseVarint (sig_section_pos, bytecode.data () + bytecode.size (), name_len)) {
256+ message = " Failed to parse signature section name length" ;
257+ return false ;
258+ }
259+
260+ // The content to hash starts after the signature section
261+ const char *content_start = section_data_start + section_len;
262+ size_t content_len = bytecode.size () - (content_start - bytecode.data ());
263+
264+ // Compute SHA-256 hash of the content
265+ EVP_MD_CTX *hash_ctx = EVP_MD_CTX_new ();
266+ if (hash_ctx == nullptr ) {
267+ message = " Failed to allocate memory for hash context" ;
268+ return false ;
269+ }
270+
271+ uint8_t computed_hash[32 ]; // SHA-256 produces 32 bytes
272+ unsigned int hash_len = 0 ;
273+
274+ bool hash_ok =
275+ (EVP_DigestInit_ex (hash_ctx, EVP_sha256 (), nullptr ) != 0 ) &&
276+ (EVP_DigestUpdate (hash_ctx, content_start, content_len) != 0 ) &&
277+ (EVP_DigestFinal_ex (hash_ctx, computed_hash, &hash_len) != 0 );
278+
279+ EVP_MD_CTX_free (hash_ctx);
280+
281+ if (!hash_ok || hash_len != 32 ) {
282+ message = " Failed to compute SHA-256 hash" ;
283+ return false ;
284+ }
285+
286+ // Verify the computed hash matches the expected hash
287+ if (std::memcmp (computed_hash, expected_hash, 32 ) != 0 ) {
288+ message = " Hash mismatch" ;
289+ return false ;
290+ }
107291
292+ // Verify the signature
108293 static const auto ed25519_pubkey = hex2pubkey<32 >(PROXY_WASM_VERIFY_WITH_ED25519_PUBKEY);
109294
110295 EVP_PKEY *pubkey = EVP_PKEY_new_raw_public_key (EVP_PKEY_ED25519, nullptr , ed25519_pubkey.data (),
@@ -123,7 +308,7 @@ bool SignatureUtil::verifySignature(std::string_view bytecode, std::string &mess
123308
124309 bool ok =
125310 (EVP_DigestVerifyInit (mdctx, nullptr , nullptr , nullptr , pubkey) != 0 ) &&
126- (EVP_DigestVerify (mdctx, signature, 64 /* ED25519_SIGNATURE_LEN */ , hash, sizeof (hash) ) != 0 );
311+ (EVP_DigestVerify (mdctx, signature, 64 /* ED25519_SIGNATURE_LEN */ , expected_hash, 32 ) != 0 );
127312
128313 EVP_MD_CTX_free (mdctx);
129314 EVP_PKEY_free (pubkey);
0 commit comments