1
+ #include " openssl/sha.h"
2
+ #include < iostream>
3
+ #include < span>
4
+
5
+ #include " crypto-algorithm.h"
6
+
7
+ namespace builtins {
8
+
9
+ namespace {
10
+
11
+ // Web Crypto API uses DOMExceptions to indicate errors
12
+ // We are adding the fields which are tested for in Web Platform Tests
13
+ // TODO: Implement DOMExceptions class and use that instead of duck-typing on an Error instance
14
+ void convertErrorToNotSupported (JSContext *cx) {
15
+ MOZ_ASSERT (JS_IsExceptionPending (cx));
16
+ JS::RootedValue exn (cx);
17
+ if (!JS_GetPendingException (cx, &exn)) {
18
+ return ;
19
+ }
20
+ MOZ_ASSERT (exn.isObject ());
21
+ JS::RootedObject error (cx, &exn.toObject ());
22
+ JS::RootedValue name (cx, JS::StringValue (JS_NewStringCopyZ (cx, " NotSupportedError" )));
23
+ JS_SetProperty (cx, error, " name" , name);
24
+ JS::RootedValue code (cx, JS::NumberValue (9 ));
25
+ JS_SetProperty (cx, error, " code" , code);
26
+ }
27
+
28
+ // This implements the first section of
29
+ // https://w3c.github.io/webcrypto/#algorithm-normalization-normalize-an-algorithm which is shared
30
+ // across all the diffent algorithms, but importantly does not implement the parts to do with the
31
+ // chosen `op` (operation) the `op` parts are handled in the specialized `normalize` functions on
32
+ // the concrete classes which derive from CryptoAlgorithm such as CryptoAlgorithmDigest.
33
+ JS::Result<CryptoAlgorithmIdentifier> normalizeIdentifier (JSContext *cx, JS::HandleValue value) {
34
+
35
+ // The specification states:
36
+ // --------
37
+ // If alg is an instance of a DOMString:
38
+ // Return the result of running the normalize an algorithm algorithm,
39
+ // with the alg set to a new Algorithm dictionary whose name attribute
40
+ // is alg, and with the op set to op.
41
+ // --------
42
+ // Instead of doing that, we operate on the string and not the dictionary.
43
+ // If we see a dictionary (JSObject), we pull the name attribute out
44
+ // and coerce it's value to a String.
45
+ // The reason we chose this direct is because we only need this one field
46
+ // from the provided dictionary, so we store the field on it's own and not
47
+ // in a JSObject which would take up more memory.
48
+
49
+ // 1. Let registeredAlgorithms be the associative container stored at the op key of
50
+ // supportedAlgorithms.
51
+ // 2. Let initialAlg be the result of converting the ECMAScript object represented by alg to the
52
+ // IDL dictionary type Algorithm, as defined by [WebIDL].
53
+ // 3. If an error occurred, return the error and terminate this algorithm.
54
+ // 4. Let algName be the value of the name attribute of initialAlg.
55
+ JS::Rooted<JSString *> algName (cx);
56
+ if (value.isObject ()) {
57
+ JS::Rooted<JSObject *> params (cx, &value.toObject ());
58
+ JS::Rooted<JS::Value> name_val (cx);
59
+ if (!JS_GetProperty (cx, params, " name" , &name_val)) {
60
+ return JS::Result<CryptoAlgorithmIdentifier>(JS::Error ());
61
+ }
62
+ algName.set (JS::ToString (cx, name_val));
63
+ } else {
64
+ algName.set (JS::ToString (cx, value));
65
+ }
66
+ // If `algName` is falsey, it means the call to JS::ToString failed.
67
+ // In that scenario, we should already have an exception, which is why we are not creating our own
68
+ // one.
69
+ if (!algName) {
70
+ return JS::Result<CryptoAlgorithmIdentifier>(JS::Error ());
71
+ }
72
+
73
+ // TODO: We convert from JSString to std::string quite a lot in the codebase, should we pull this
74
+ // logic out into a new function?
75
+ size_t algorithmLen;
76
+ JS::UniqueChars algorithmChars = encode (cx, algName, &algorithmLen);
77
+ if (!algorithmChars) {
78
+ return JS::Result<CryptoAlgorithmIdentifier>(JS::Error ());
79
+ }
80
+ std::string algorithm (algorithmChars.get (), algorithmLen);
81
+
82
+ // 5. If registeredAlgorithms contains a key that is a case-insensitive string match for algName:
83
+ // 5.1 Set algName to the value of the matching key.
84
+ // 5.2 Let desiredType be the IDL dictionary type stored at algName in registeredAlgorithms.
85
+ // Note: We do not implement 5.2 here, it is instead implemented in the specialized `normalize`
86
+ // functions.
87
+ std::transform (algorithm.begin (), algorithm.end (), algorithm.begin (),
88
+ [](unsigned char c) { return std::toupper (c); });
89
+ if (algorithm == " RSASSA-PKCS1-V1_5" ) {
90
+ return CryptoAlgorithmIdentifier::RSASSA_PKCS1_v1_5;
91
+ } else if (algorithm == " RSA-PSS" ) {
92
+ return CryptoAlgorithmIdentifier::RSA_PSS;
93
+ } else if (algorithm == " RSA-OAEP" ) {
94
+ return CryptoAlgorithmIdentifier::RSA_OAEP;
95
+ } else if (algorithm == " ECDSA" ) {
96
+ return CryptoAlgorithmIdentifier::ECDSA;
97
+ } else if (algorithm == " ECDH" ) {
98
+ return CryptoAlgorithmIdentifier::ECDH;
99
+ } else if (algorithm == " AES-CTR" ) {
100
+ return CryptoAlgorithmIdentifier::AES_CTR;
101
+ } else if (algorithm == " AES-CBC" ) {
102
+ return CryptoAlgorithmIdentifier::AES_CBC;
103
+ } else if (algorithm == " AES-GCM" ) {
104
+ return CryptoAlgorithmIdentifier::AES_GCM;
105
+ } else if (algorithm == " AES-KW" ) {
106
+ return CryptoAlgorithmIdentifier::AES_KW;
107
+ } else if (algorithm == " HMAC" ) {
108
+ return CryptoAlgorithmIdentifier::HMAC;
109
+ } else if (algorithm == " SHA-1" ) {
110
+ return CryptoAlgorithmIdentifier::SHA_1;
111
+ } else if (algorithm == " SHA-256" ) {
112
+ return CryptoAlgorithmIdentifier::SHA_256;
113
+ } else if (algorithm == " SHA-384" ) {
114
+ return CryptoAlgorithmIdentifier::SHA_384;
115
+ } else if (algorithm == " SHA-512" ) {
116
+ return CryptoAlgorithmIdentifier::SHA_512;
117
+ } else if (algorithm == " HKDF" ) {
118
+ return CryptoAlgorithmIdentifier::HKDF;
119
+ } else if (algorithm == " PBKDF2" ) {
120
+ return CryptoAlgorithmIdentifier::PBKDF2;
121
+ } else {
122
+ // Otherwise: Return a new NotSupportedError and terminate this algorithm.
123
+ JS_ReportErrorUTF8 (cx, " Algorithm: Unrecognized name" );
124
+ return JS::Result<CryptoAlgorithmIdentifier>(JS::Error ());
125
+ }
126
+ }
127
+ } // namespace
128
+
129
+ // clang-format off
130
+ // / This table is from https://w3c.github.io/webcrypto/#h-note-15
131
+ // | Algorithm | encrypt | decrypt | sign | verify | digest | generateKey | deriveKey | deriveBits | importKey | exportKey | wrapKey | unwrapKey |
132
+ // | RSASSA-PKCS1-v1_5 | | | ✔ | ✔ | | ✔ | | | ✔ | ✔ | | |
133
+ // | RSA-PSS | | | ✔ | ✔ | | ✔ | | | ✔ | ✔ | | |
134
+ // | RSA-OAEP | ✔ | ✔ | | | | ✔ | | | ✔ | ✔ | ✔ | ✔ |
135
+ // | ECDSA | | | ✔ | ✔ | | ✔ | | | ✔ | ✔ | | |
136
+ // | ECDH | | | | | | ✔ | ✔ | ✔ | ✔ | ✔ | | |
137
+ // | AES-CTR | ✔ | ✔ | | | | ✔ | | | ✔ | ✔ | ✔ | ✔ |
138
+ // | AES-CBC | ✔ | ✔ | | | | ✔ | | | ✔ | ✔ | ✔ | ✔ |
139
+ // | AES-GCM | ✔ | ✔ | | | | ✔ | | | ✔ | ✔ | ✔ | ✔ |
140
+ // | AES-KW | | | | | | ✔ | | | ✔ | ✔ | ✔ | ✔ |
141
+ // | HMAC | | | ✔ | ✔ | | ✔ | | | ✔ | ✔ | | |
142
+ // | SHA-1 | | | | | ✔ | | | | | | | |
143
+ // | SHA-256 | | | | | ✔ | | | | | | | |
144
+ // | SHA-384 | | | | | ✔ | | | | | | | |
145
+ // | SHA-512 | | | | | ✔ | | | | | | | |
146
+ // | HKDF | | | | | | | ✔ | ✔ | ✔ | | | |
147
+ // | PBKDF2 | | | | | | | ✔ | ✔ | ✔ | | | |
148
+ // clang-format on
149
+
150
+ std::unique_ptr<CryptoAlgorithmDigest> CryptoAlgorithmDigest::normalize (JSContext *cx,
151
+ JS::HandleValue value) {
152
+ // Do steps 1 through 5.1 of https://w3c.github.io/webcrypto/#algorithm-normalization-normalize-an-algorithm
153
+ auto identifierResult = normalizeIdentifier (cx, value);
154
+ if (identifierResult.isErr ()) {
155
+ // If we are here, this means either the identifier could not be coerced to a String or was not recognized
156
+ // In both those scenarios an exception will have already been created, which is why we are not creating one here.
157
+ return nullptr ;
158
+ }
159
+ auto identifier = identifierResult.unwrap ();
160
+
161
+ // The table listed at https://w3c.github.io/webcrypto/#h-note-15 is what defines which algorithms support which operations
162
+ // SHA-1, SHA-256, SHA-384, and SHA-512 are the only algorithms which support the digest operation
163
+
164
+ // Note: The specification states that none of the SHA algorithms take any parameters -- https://w3c.github.io/webcrypto/#sha-registration
165
+ switch (identifier) {
166
+ case CryptoAlgorithmIdentifier::SHA_1: {
167
+ return std::make_unique<CryptoAlgorithmSHA1>();
168
+ }
169
+ case CryptoAlgorithmIdentifier::SHA_256: {
170
+ return std::make_unique<CryptoAlgorithmSHA256>();
171
+ }
172
+ case CryptoAlgorithmIdentifier::SHA_384: {
173
+ return std::make_unique<CryptoAlgorithmSHA384>();
174
+ }
175
+ case CryptoAlgorithmIdentifier::SHA_512: {
176
+ return std::make_unique<CryptoAlgorithmSHA512>();
177
+ }
178
+ default : {
179
+ JS_ReportErrorASCII (cx, " Supplied algorithm does not support the digest operation" );
180
+ convertErrorToNotSupported (cx);
181
+ return nullptr ;
182
+ }
183
+ }
184
+ };
185
+
186
+ namespace {
187
+ // This implements https://w3c.github.io/webcrypto/#sha-operations for all
188
+ // the SHA algorithms that we support.
189
+ JSObject *digest (JSContext *cx, std::span<uint8_t > data, const EVP_MD * algorithm, size_t buffer_size) {
190
+ unsigned int size;
191
+ auto buf = static_cast <unsigned char *>(JS_malloc (cx, buffer_size));
192
+ if (!buf) {
193
+ JS_ReportOutOfMemory (cx);
194
+ return nullptr ;
195
+ }
196
+ if (!EVP_Digest (data.data (), data.size (), buf, &size, algorithm, NULL )) {
197
+ // 2. If performing the operation results in an error, then throw an OperationError.
198
+ // TODO: Change to an OperationError DOMException
199
+ JS_ReportErrorUTF8 (cx, " SubtleCrypto.digest: failed to create digest" );
200
+ return nullptr ;
201
+ }
202
+ // 3. Return a new ArrayBuffer containing result.
203
+ JS::RootedObject array_buffer (cx);
204
+ array_buffer.set (JS::NewArrayBufferWithContents (cx, size, buf));
205
+ if (!array_buffer) {
206
+ JS_ReportOutOfMemory (cx);
207
+ return nullptr ;
208
+ }
209
+ return array_buffer;
210
+ };
211
+ }
212
+
213
+ JSObject *CryptoAlgorithmSHA1::digest (JSContext *cx, std::span<uint8_t > data) {
214
+ return ::builtins::digest (cx, data, EVP_sha1 (), SHA_DIGEST_LENGTH);
215
+ }
216
+ JSObject *CryptoAlgorithmSHA256::digest (JSContext *cx, std::span<uint8_t > data) {
217
+ return ::builtins::digest (cx, data, EVP_sha256 (), SHA256_DIGEST_LENGTH);
218
+ }
219
+ JSObject *CryptoAlgorithmSHA384::digest (JSContext *cx, std::span<uint8_t > data) {
220
+ return ::builtins::digest (cx, data, EVP_sha384 (), SHA384_DIGEST_LENGTH);
221
+ }
222
+ JSObject *CryptoAlgorithmSHA512::digest (JSContext *cx, std::span<uint8_t > data) {
223
+ return ::builtins::digest (cx, data, EVP_sha512 (), SHA512_DIGEST_LENGTH);
224
+ }
225
+
226
+ } // namespace builtins
0 commit comments