Skip to content

Commit 9c8efab

Browse files
Jake ChampionJakeChampion
authored andcommitted
feat: Add MD5 support into crypto.subtle.digest
MD5 is not part of the WebCrypto API standard as a recommended algorithm to implement but we are adding support for it, which is allowed by the Web Crypto API. The reason we are adding it is because some customers use legacy systems that require MD5 and it is better for performance if we implement it in native code rather than the customer implementing it is JS
1 parent cdf2fee commit 9c8efab

File tree

8 files changed

+8367
-134
lines changed

8 files changed

+8367
-134
lines changed

c-dependencies/js-compute-runtime/builtins/crypto-algorithm.cpp

Lines changed: 72 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ const EVP_MD *createDigestAlgorithm(JSContext *cx, JS::HandleObject key) {
3232
}
3333

3434
std::string_view name(name_chars.get(), name_length);
35-
if (name == "SHA-1") {
35+
if (name == "MD5") {
36+
return EVP_md5();
37+
} else if (name == "SHA-1") {
3638
return EVP_sha1();
3739
} else if (name == "SHA-224") {
3840
return EVP_sha224();
@@ -282,62 +284,6 @@ std::unique_ptr<CryptoKeyRSAComponents> createRSAPrivateKeyFromJWK(JSContext *cx
282284
return privateKeyComponents;
283285
}
284286

285-
const char *algorithmName(CryptoAlgorithmIdentifier algorithm) {
286-
switch (algorithm) {
287-
case CryptoAlgorithmIdentifier::RSASSA_PKCS1_v1_5: {
288-
return "RSASSA-PKCS1-v1_5";
289-
}
290-
case CryptoAlgorithmIdentifier::RSA_PSS: {
291-
return "RSA-PSS";
292-
}
293-
case CryptoAlgorithmIdentifier::RSA_OAEP: {
294-
return "RSA-OAEP";
295-
}
296-
case CryptoAlgorithmIdentifier::ECDSA: {
297-
return "ECDSA";
298-
}
299-
case CryptoAlgorithmIdentifier::ECDH: {
300-
return "ECDH";
301-
}
302-
case CryptoAlgorithmIdentifier::AES_CTR: {
303-
return "AES-CTR";
304-
}
305-
case CryptoAlgorithmIdentifier::AES_CBC: {
306-
return "AES-CBC";
307-
}
308-
case CryptoAlgorithmIdentifier::AES_GCM: {
309-
return "AES-GCM";
310-
}
311-
case CryptoAlgorithmIdentifier::AES_KW: {
312-
return "AES-KW";
313-
}
314-
case CryptoAlgorithmIdentifier::HMAC: {
315-
return "HMAC";
316-
}
317-
case CryptoAlgorithmIdentifier::SHA_1: {
318-
return "SHA-1";
319-
}
320-
case CryptoAlgorithmIdentifier::SHA_256: {
321-
return "SHA-256";
322-
}
323-
case CryptoAlgorithmIdentifier::SHA_384: {
324-
return "SHA-384";
325-
}
326-
case CryptoAlgorithmIdentifier::SHA_512: {
327-
return "SHA-512";
328-
}
329-
case CryptoAlgorithmIdentifier::HKDF: {
330-
return "HKDF";
331-
}
332-
case CryptoAlgorithmIdentifier::PBKDF2: {
333-
return "PBKDF2";
334-
}
335-
default: {
336-
MOZ_ASSERT_UNREACHABLE("Unknown `CryptoAlgorithmIdentifier` value");
337-
}
338-
}
339-
}
340-
341287
// Web Crypto API uses DOMExceptions to indicate errors
342288
// We are adding the fields which are tested for in Web Platform Tests
343289
// TODO: Implement DOMExceptions class and use that instead of duck-typing on an Error instance
@@ -445,6 +391,8 @@ JS::Result<CryptoAlgorithmIdentifier> normalizeIdentifier(JSContext *cx, JS::Han
445391
return CryptoAlgorithmIdentifier::AES_KW;
446392
} else if (algorithm == "HMAC") {
447393
return CryptoAlgorithmIdentifier::HMAC;
394+
} else if (algorithm == "MD5") {
395+
return CryptoAlgorithmIdentifier::MD5;
448396
} else if (algorithm == "SHA-1") {
449397
return CryptoAlgorithmIdentifier::SHA_1;
450398
} else if (algorithm == "SHA-256") {
@@ -465,6 +413,65 @@ JS::Result<CryptoAlgorithmIdentifier> normalizeIdentifier(JSContext *cx, JS::Han
465413
}
466414
} // namespace
467415

416+
const char *algorithmName(CryptoAlgorithmIdentifier algorithm) {
417+
switch (algorithm) {
418+
case CryptoAlgorithmIdentifier::RSASSA_PKCS1_v1_5: {
419+
return "RSASSA-PKCS1-v1_5";
420+
}
421+
case CryptoAlgorithmIdentifier::RSA_PSS: {
422+
return "RSA-PSS";
423+
}
424+
case CryptoAlgorithmIdentifier::RSA_OAEP: {
425+
return "RSA-OAEP";
426+
}
427+
case CryptoAlgorithmIdentifier::ECDSA: {
428+
return "ECDSA";
429+
}
430+
case CryptoAlgorithmIdentifier::ECDH: {
431+
return "ECDH";
432+
}
433+
case CryptoAlgorithmIdentifier::AES_CTR: {
434+
return "AES-CTR";
435+
}
436+
case CryptoAlgorithmIdentifier::AES_CBC: {
437+
return "AES-CBC";
438+
}
439+
case CryptoAlgorithmIdentifier::AES_GCM: {
440+
return "AES-GCM";
441+
}
442+
case CryptoAlgorithmIdentifier::AES_KW: {
443+
return "AES-KW";
444+
}
445+
case CryptoAlgorithmIdentifier::HMAC: {
446+
return "HMAC";
447+
}
448+
case CryptoAlgorithmIdentifier::MD5: {
449+
return "MD5";
450+
}
451+
case CryptoAlgorithmIdentifier::SHA_1: {
452+
return "SHA-1";
453+
}
454+
case CryptoAlgorithmIdentifier::SHA_256: {
455+
return "SHA-256";
456+
}
457+
case CryptoAlgorithmIdentifier::SHA_384: {
458+
return "SHA-384";
459+
}
460+
case CryptoAlgorithmIdentifier::SHA_512: {
461+
return "SHA-512";
462+
}
463+
case CryptoAlgorithmIdentifier::HKDF: {
464+
return "HKDF";
465+
}
466+
case CryptoAlgorithmIdentifier::PBKDF2: {
467+
return "PBKDF2";
468+
}
469+
default: {
470+
MOZ_ASSERT_UNREACHABLE("Unknown `CryptoAlgorithmIdentifier` value");
471+
}
472+
}
473+
}
474+
468475
// clang-format off
469476
/// This table is from https://w3c.github.io/webcrypto/#h-note-15
470477
// | Algorithm | encrypt | decrypt | sign | verify | digest | generateKey | deriveKey | deriveBits | importKey | exportKey | wrapKey | unwrapKey |
@@ -499,9 +506,13 @@ std::unique_ptr<CryptoAlgorithmDigest> CryptoAlgorithmDigest::normalize(JSContex
499506

500507
// The table listed at https://w3c.github.io/webcrypto/#h-note-15 is what defines which algorithms support which operations
501508
// SHA-1, SHA-256, SHA-384, and SHA-512 are the only algorithms which support the digest operation
509+
// We also support MD5 as an extra implementor defined algorithm
502510

503511
// Note: The specification states that none of the SHA algorithms take any parameters -- https://w3c.github.io/webcrypto/#sha-registration
504512
switch (identifier) {
513+
case CryptoAlgorithmIdentifier::MD5: {
514+
return std::make_unique<CryptoAlgorithmMD5>();
515+
}
505516
case CryptoAlgorithmIdentifier::SHA_1: {
506517
return std::make_unique<CryptoAlgorithmSHA1>();
507518
}
@@ -991,7 +1002,7 @@ JSObject *CryptoAlgorithmRSASSA_PKCS1_v1_5_Import::toObject(JSContext *cx) {
9911002
// Set the hash attribute of algorithm to the hash member of normalizedAlgorithm.
9921003
JS::RootedObject hash(cx, JS_NewObject(cx, nullptr));
9931004

994-
auto hash_name = JS_NewStringCopyZ(cx, algorithmName(this->hashIdentifier));
1005+
auto hash_name = JS_NewStringCopyZ(cx, builtins::algorithmName(this->hashIdentifier));
9951006
if (!hash_name) {
9961007
return nullptr;
9971008
}
@@ -1006,6 +1017,9 @@ JSObject *CryptoAlgorithmRSASSA_PKCS1_v1_5_Import::toObject(JSContext *cx) {
10061017
return algorithm;
10071018
}
10081019

1020+
JSObject *CryptoAlgorithmMD5::digest(JSContext *cx, std::span<uint8_t> data) {
1021+
return ::builtins::digest(cx, data, EVP_md5(), MD5_DIGEST_LENGTH);
1022+
}
10091023
JSObject *CryptoAlgorithmSHA1::digest(JSContext *cx, std::span<uint8_t> data) {
10101024
return ::builtins::digest(cx, data, EVP_sha1(), SHA_DIGEST_LENGTH);
10111025
}

c-dependencies/js-compute-runtime/builtins/crypto-algorithm.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "crypto-key.h"
77
#include "json-web-key.h"
88
#include "openssl/evp.h"
9+
#include <openssl/md5.h>
910

1011
namespace builtins {
1112

@@ -22,6 +23,7 @@ enum class CryptoAlgorithmIdentifier : uint8_t {
2223
AES_GCM,
2324
AES_KW,
2425
HMAC,
26+
MD5,
2527
SHA_1,
2628
SHA_256,
2729
SHA_384,
@@ -30,6 +32,8 @@ enum class CryptoAlgorithmIdentifier : uint8_t {
3032
PBKDF2
3133
};
3234

35+
const char *algorithmName(CryptoAlgorithmIdentifier algorithm);
36+
3337
/// The base class that all algorithm implementations should derive from.
3438
class CryptoAlgorithm {
3539
public:
@@ -104,6 +108,13 @@ class CryptoAlgorithmDigest : public CryptoAlgorithm {
104108
static std::unique_ptr<CryptoAlgorithmDigest> normalize(JSContext *cx, JS::HandleValue value);
105109
};
106110

111+
class CryptoAlgorithmMD5 final : public CryptoAlgorithmDigest {
112+
public:
113+
const char *name() const noexcept override { return "MD5"; };
114+
CryptoAlgorithmIdentifier identifier() override { return CryptoAlgorithmIdentifier::MD5; };
115+
JSObject *digest(JSContext *cx, std::span<uint8_t>) override;
116+
};
117+
107118
class CryptoAlgorithmSHA1 final : public CryptoAlgorithmDigest {
108119
public:
109120
const char *name() const noexcept override { return "SHA-1"; };

c-dependencies/js-compute-runtime/builtins/crypto-key.cpp

Lines changed: 0 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -6,82 +6,6 @@
66

77
namespace builtins {
88

9-
namespace {
10-
const char *algorithmName(CryptoAlgorithmIdentifier algorithm) {
11-
const char *result = nullptr;
12-
switch (algorithm) {
13-
case CryptoAlgorithmIdentifier::RSASSA_PKCS1_v1_5: {
14-
result = "RSASSA-PKCS1-v1_5";
15-
break;
16-
}
17-
case CryptoAlgorithmIdentifier::RSA_PSS: {
18-
result = "RSA-PSS";
19-
break;
20-
}
21-
case CryptoAlgorithmIdentifier::RSA_OAEP: {
22-
result = "RSA-OAEP";
23-
break;
24-
}
25-
case CryptoAlgorithmIdentifier::ECDSA: {
26-
result = "ECDSA";
27-
break;
28-
}
29-
case CryptoAlgorithmIdentifier::ECDH: {
30-
result = "ECDH";
31-
break;
32-
}
33-
case CryptoAlgorithmIdentifier::AES_CTR: {
34-
result = "AES-CTR";
35-
break;
36-
}
37-
case CryptoAlgorithmIdentifier::AES_CBC: {
38-
result = "AES-CBC";
39-
break;
40-
}
41-
case CryptoAlgorithmIdentifier::AES_GCM: {
42-
result = "AES-GCM";
43-
break;
44-
}
45-
case CryptoAlgorithmIdentifier::AES_KW: {
46-
result = "AES-KW";
47-
break;
48-
}
49-
case CryptoAlgorithmIdentifier::HMAC: {
50-
result = "HMAC";
51-
break;
52-
}
53-
case CryptoAlgorithmIdentifier::SHA_1: {
54-
result = "SHA-1";
55-
break;
56-
}
57-
case CryptoAlgorithmIdentifier::SHA_256: {
58-
result = "SHA-256";
59-
break;
60-
}
61-
case CryptoAlgorithmIdentifier::SHA_384: {
62-
result = "SHA-384";
63-
break;
64-
}
65-
case CryptoAlgorithmIdentifier::SHA_512: {
66-
result = "SHA-512";
67-
break;
68-
}
69-
case CryptoAlgorithmIdentifier::HKDF: {
70-
result = "HKDF";
71-
break;
72-
}
73-
case CryptoAlgorithmIdentifier::PBKDF2: {
74-
result = "PBKDF2";
75-
break;
76-
}
77-
default: {
78-
MOZ_ASSERT_UNREACHABLE("Unknown `CryptoAlgorithmIdentifier` value");
79-
}
80-
}
81-
return result;
82-
}
83-
} // namespace
84-
859
CryptoKeyUsages::CryptoKeyUsages(uint8_t mask) { this->mask = mask; };
8610
CryptoKeyUsages::CryptoKeyUsages(bool encrypt, bool decrypt, bool sign, bool verify,
8711
bool derive_key, bool derive_bits, bool wrap_key,

documentation/docs/globals/SubtleCrypto/prototype/digest.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ digest(algorithm, data)
2525

2626
- `algorithm`
2727
- : This may be a string or an object with a single property `name` that is a string. The string names the hash function to use. Supported values are:
28+
- `"MD5"` (but don't use this in cryptographic applications)
2829
- `"SHA-1"` (but don't use this in cryptographic applications)
2930
- `"SHA-256"`
3031
- `"SHA-384"`

integration-tests/js-compute/fixtures/crypto/bin/index.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,14 @@ routes.set("/crypto.subtle", async () => {
562562
}
563563
// happy paths
564564
{
565+
// "MD5"
566+
routes.set("/crypto.subtle.digest/md5", async () => {
567+
const result = new Uint8Array(await crypto.subtle.digest("md5", new Uint8Array));
568+
const expected = new Uint8Array([212, 29, 140, 217, 143, 0, 178, 4, 233, 128, 9, 152, 236, 248, 66, 126]);
569+
error = assert(result, expected, "result deep equals expected");
570+
if (error) { return error; }
571+
return pass();
572+
});
565573
// "SHA-1"
566574
routes.set("/crypto.subtle.digest/sha-1", async () => {
567575
const result = new Uint8Array(await crypto.subtle.digest("sha-1", new Uint8Array));

0 commit comments

Comments
 (0)