Skip to content
File

Blob: src/workerd/api/crypto/impl.h

cpp464 lines
1// Copyright (c) 2017-2022 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4 
5#pragma once
6// INTERNAL CRYPTO IMPLEMENTATION FILE
7//
8// Don't include this file unless your name is "crypto*.c++".
9 
10#include "crypto.h"
11 
12#include <workerd/api/util.h>
13#include <workerd/jsg/jsvalue.h>
14 
15#include <ncrypto.h>
16#include <openssl/base.h>
17#include <openssl/bn.h>
18#include <openssl/err.h>
19 
20#include <kj/encoding.h>
21 
22using BIGNUM = struct bignum_st;
23 
24// Wrap calls to OpenSSL's EVP_* interface (and similar APIs) in this macro to
25// deal with errors.
26#define OSSLCALL(...) \
27 if ((__VA_ARGS__) != 1) ::workerd::api::throwOpensslError(__FILE__, __LINE__, #__VA_ARGS__)
28 
29#define UNWRAP_JWK_BIGNUM(value, ...) \
30 JSG_REQUIRE_NONNULL(decodeBase64Url(JSG_REQUIRE_NONNULL((value), __VA_ARGS__)), __VA_ARGS__)
31 
32namespace workerd::api {
33 
34struct OpensslUntranslatedError {
35 kj::StringPtr library;
36 kj::StringPtr reasonName;
37};
38 
39// Call to throw an exception based on the OpenSSL error code. Usually, you should wrap your call
40// in OSSLCALL() to have this invoked automatically.
41//
42// Some error codes are translated into application-visible errors of type
43// `DOMException(OperationError)`, but most errors are considered internal errors.
44KJ_NORETURN(void throwOpensslError(const char* file, int line, kj::StringPtr code));
45 
46// Consumes the entire OpenSSL error queue & converts it either into friendly names or the raw
47// (unfriendly) name that OpenSSL gives the error code.
48kj::Vector<kj::OneOf<kj::StringPtr, OpensslUntranslatedError>> consumeAllOpensslErrors();
49 
50// Returns a description of the OpenSSL errors (starting with ": ") in the stack & clears them if
51// there are any. The expected usage is something like:
52// JSG_REQUIRE(<some OpenSSL call succeeds>, OperationError, "This thing went wrong",
53// tryDescribeOpensslErrors());
54// This way if there are any OpenSSL errors to describe it will get rendered as:
55// "jsg.DOMException(OperationError): This thing went wrong: <description>."
56// and if there aren't, then this will get rendered as:
57// "jsg.DOMException(OperationError): This thing went wrong."
58kj::String tryDescribeOpensslErrors(kj::StringPtr defaultIfNoError = nullptr);
59 
60// Like tryDescribeOpensslErrors but dumps all OpenSSL errors even if not user-facing. This is for
61// use with `Internal` errors passed to JSG which automagically strip all contextual information so
62// that these errors only end up in Sentry.
63kj::String internalDescribeOpensslErrors();
64 
65// Helper for implementing `sign()`, `digest()` and `importKey()`. Returns a pair containing a
66// StringPtr to the normalized name of the given algorithm and the EVP_MD type to use with
67// OpenSSL's EVP interface.
68//
69// Throws if the given algorithm isn't supported.
70std::pair<kj::StringPtr, const EVP_MD*> lookupDigestAlgorithm(kj::StringPtr algorithm);
71 
72// kj::decodeBase64 doesn't know how to parse URL-encoded variants.
73// https://en.wikipedia.org/wiki/Base64#URL_applications
74// Due to this, the input string is modified prior to passing to kj::decodeBase64. The mutation
75// isn't actually required & it's possible that a non-mutating variant could be written. That's more
76// complex to implement outside of kj::decodeBase64 though and the mutating variant is easier to
77// implement as a wrapper. Could be sufficient to just add a "urlEncoded" boolean so that
78// kj::decodeBase64 can do this in-situ for both cases.
79kj::EncodingResult<kj::Array<kj::byte>> decodeBase64Url(kj::String text);
80 
81// WebCrypto likes to allow algorithms to be specified as a simple string name, or as a struct
82// containing a `name` field and possibly other fields. This helper collapses that.
83template <typename T>
84T interpretAlgorithmParam(kj::OneOf<kj::String, T>&& param) {
85 if (param.template is<kj::String>()) {
86 T result;
87 result.name = kj::mv(param.template get<kj::String>());
88 return result;
89 } else {
90 return kj::mv(param.template get<T>());
91 }
92}
93 
94// Like `interpretAlgorithmParam` but just get the algorithm name. Works with const input.
95template <typename T>
96kj::StringPtr getAlgorithmName(const kj::OneOf<kj::String, T>& param) {
97 if (param.template is<kj::String>()) {
98 return param.template get<kj::String>();
99 } else {
100 return param.template get<T>().name;
101 }
102}
103 
104class CryptoKey::Impl {
105 public:
106 // C++ API
107 
108 using ImportFunc = kj::Own<Impl>(jsg::Lock& js,
109 kj::StringPtr normalizedName,
110 kj::StringPtr format,
111 SubtleCrypto::ImportKeyData keyData,
112 SubtleCrypto::ImportKeyAlgorithm&& algorithm,
113 bool extractable,
114 kj::ArrayPtr<const kj::String> keyUsages);
115 
116 static ImportFunc importAes;
117 static ImportFunc importHmac;
118 static ImportFunc importPbkdf2;
119 static ImportFunc importHkdf;
120 static ImportFunc importRsa;
121 static ImportFunc importEcdsa;
122 static ImportFunc importEcdh;
123 static ImportFunc importEddsa;
124 static ImportFunc importRsaRaw;
125 
126 using GenerateFunc = kj::OneOf<jsg::Ref<CryptoKey>, CryptoKeyPair>(jsg::Lock& js,
127 kj::StringPtr normalizedName,
128 SubtleCrypto::GenerateKeyAlgorithm&& algorithm,
129 bool extractable,
130 kj::ArrayPtr<const kj::String> keyUsages);
131 
132 static GenerateFunc generateAes;
133 static GenerateFunc generateHmac;
134 static GenerateFunc generateRsa;
135 static GenerateFunc generateEcdsa;
136 static GenerateFunc generateEcdh;
137 static GenerateFunc generateEddsa;
138 
139 Impl(bool extractable, CryptoKeyUsageSet usages): extractable(extractable), usages(usages) {}
140 
141 static kj::Own<CryptoKey::Impl> from(jsg::Lock& js, kj::Own<EVP_PKEY> key);
142 
143 bool isExtractable() const {
144 return extractable;
145 }
146 CryptoKeyUsageSet getUsages() const {
147 return usages;
148 }
149 
150 virtual jsg::JsArrayBuffer encrypt(jsg::Lock& js,
151 SubtleCrypto::EncryptAlgorithm&& algorithm,
152 kj::ArrayPtr<const kj::byte> plainText) const {
153 JSG_FAIL_REQUIRE(DOMNotSupportedError, "The encrypt operation is not implemented for \"",
154 getAlgorithmName(), "\".");
155 }
156 virtual jsg::JsArrayBuffer decrypt(jsg::Lock& js,
157 SubtleCrypto::EncryptAlgorithm&& algorithm,
158 kj::ArrayPtr<const kj::byte> cipherText) const {
159 JSG_FAIL_REQUIRE(DOMNotSupportedError, "The decrypt operation is not implemented for \"",
160 getAlgorithmName(), "\".");
161 }
162 
163 virtual jsg::JsArrayBuffer sign(jsg::Lock& js,
164 SubtleCrypto::SignAlgorithm&& algorithm,
165 kj::ArrayPtr<const kj::byte> data) const {
166 JSG_FAIL_REQUIRE(DOMNotSupportedError, "The sign operation is not implemented for \"",
167 getAlgorithmName(), "\".");
168 }
169 virtual bool verify(jsg::Lock& js,
170 SubtleCrypto::SignAlgorithm&& algorithm,
171 kj::ArrayPtr<const kj::byte> signature,
172 kj::ArrayPtr<const kj::byte> data) const {
173 JSG_FAIL_REQUIRE(DOMNotSupportedError, "The verify operation is not implemented for \"",
174 getAlgorithmName(), "\".");
175 }
176 
177 virtual jsg::JsArrayBuffer deriveBits(jsg::Lock& js,
178 SubtleCrypto::DeriveKeyAlgorithm&& algorithm,
179 kj::Maybe<uint32_t> length) const {
180 JSG_FAIL_REQUIRE(DOMNotSupportedError,
181 "The deriveKey and deriveBits operations are not implemented for \"", getAlgorithmName(),
182 "\".");
183 }
184 
185 virtual jsg::JsArrayBuffer wrapKey(jsg::Lock& js,
186 SubtleCrypto::EncryptAlgorithm&& algorithm,
187 kj::ArrayPtr<const kj::byte> unwrappedKey) const {
188 // For many algorithms, wrapKey() is the same as encrypt(), so as a convenience the default
189 // implementation just forwards to it.
190 return encrypt(js, kj::mv(algorithm), unwrappedKey);
191 }
192 
193 virtual jsg::JsArrayBuffer unwrapKey(jsg::Lock& js,
194 SubtleCrypto::EncryptAlgorithm&& algorithm,
195 kj::ArrayPtr<const kj::byte> wrappedKey) const {
196 // For many algorithms, unwrapKey() is the same as decrypt(), so as a convenience the default
197 // implementation just forwards to it.
198 return decrypt(js, kj::mv(algorithm), wrappedKey);
199 }
200 
201 virtual SubtleCrypto::ExportKeyData exportKey(jsg::Lock& js, kj::StringPtr format) const {
202 JSG_FAIL_REQUIRE(DOMNotSupportedError, "Unrecognized or unsupported export of \"",
203 getAlgorithmName(), "\" requested.");
204 }
205 
206 // The exportKeyExt variant is used by the Node.js crypto module. It allows the caller to
207 // specify a broader range of export formats and types that are not supported by Web
208 // Crypto. For instance, Web Crypto limits the export of public keys to only the spki or
209 // jwk formats, while Node.js allows pkcs1 or spki formatted as either pem, der, or jwk.
210 // For private keys, Node.js allows optionally encrypting the private key using a given
211 // cipher and passphrase.
212 // Rather than modify the existing exportKey API, we add this new variant to support the
213 // Node.js implementation without risking breaking the Web Crypto impl.
214 virtual jsg::JsUint8Array exportKeyExt(jsg::Lock& js,
215 kj::StringPtr format,
216 kj::StringPtr type,
217 jsg::Optional<kj::String> cipher = kj::none,
218 jsg::Optional<kj::Array<kj::byte>> passphrase = kj::none) const {
219 JSG_FAIL_REQUIRE(DOMNotSupportedError, "Unrecognized or unsupported export of \"",
220 getAlgorithmName(), "\" requested.");
221 }
222 
223 virtual kj::StringPtr getAlgorithmName() const = 0;
224 
225 virtual CryptoKey::AsymmetricKeyDetails getAsymmetricKeyDetail(jsg::Lock& js) const {
226 JSG_FAIL_REQUIRE(DOMNotSupportedError,
227 "The getAsymmetricKeyDetail operation is not implemented for \"", getAlgorithmName(),
228 "\".");
229 }
230 
231 // JS API implementation
232 
233 virtual AlgorithmVariant getAlgorithm(jsg::Lock& js) const = 0;
234 virtual kj::StringPtr getType() const {
235 return "secret"_kj;
236 }
237 
238 virtual bool equals(const Impl& other) const = 0;
239 virtual bool equals(const kj::Array<kj::byte>& other) const;
240 
241 virtual kj::StringPtr jsgGetMemoryName() const {
242 return "CryptoKey::Impl";
243 }
244 virtual size_t jsgGetMemorySelfSize() const {
245 return sizeof(Impl);
246 }
247 virtual void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const {}
248 
249 virtual bool verifyX509Public(const X509* cert) const {
250 return false;
251 }
252 virtual bool verifyX509Private(const X509* cert) const {
253 return false;
254 }
255 
256 virtual void visitForGc(jsg::GcVisitor& visitor) {
257 // By default, nothing to visit.
258 }
259 
260 private:
261 const bool extractable;
262 const CryptoKeyUsageSet usages;
263};
264 
265struct CryptoAlgorithm {
266 // Name, in canonical (all-uppercase) format.
267 kj::StringPtr name;
268 
269 // Functions to import / generate keys for this algorithm. If nullptr, the respective
270 // operation isn't allowed.
271 CryptoKey::Impl::ImportFunc* importFunc = nullptr;
272 
273 // Functions to import / generate keys for this algorithm. If nullptr, the respective
274 // operation isn't allowed.
275 CryptoKey::Impl::GenerateFunc* generateFunc = nullptr;
276 // TODO(cleanup): I have these as pointers instead of maybe-references because the references
277 // would have to be const in order to enable const-copying, but it turns out you cannot specify
278 // `const` on a reference-to-function (the compiler ignores it as "redundant", but then
279 // template metaprogramming cannot recognize it as const). Maybe we can fix this in KJ, by
280 // making `RemoveConstOrDisable` recognize function references are inherently const.
281 
282 // Allow comparison by name, case-insensitive. This is a convenience for placing in an std::set.
283 inline bool operator==(const CryptoAlgorithm& other) const {
284 return strcasecmp(name.cStr(), other.name.cStr()) == 0;
285 }
286 // Allow comparison by name, case-insensitive. This is a convenience for placing in an std::set.
287 inline bool operator<(const CryptoAlgorithm& other) const {
288 return strcasecmp(name.cStr(), other.name.cStr()) < 0;
289 }
290 // TODO(cleanup): I'd rather use kj::Table with HashIndex but we need a case-insensitive hash
291 // function, which seemed slightly too annoying to implement now.
292};
293 
294class SslArrayDisposer: public kj::ArrayDisposer {
295 public:
296 static const SslArrayDisposer INSTANCE;
297 
298 void disposeImpl(void* firstElement,
299 size_t elementSize,
300 size_t elementCount,
301 size_t capacity,
302 void (*destroyElement)(void*)) const override;
303};
304 
305template <typename T, void (*sslFree)(T*)>
306class SslDisposer: public kj::Disposer {
307 public:
308 static const SslDisposer INSTANCE;
309 
310 protected:
311 void disposeImpl(void* pointer) const override {
312 sslFree(reinterpret_cast<T*>(pointer));
313 }
314};
315 
316template <typename T, void (*sslFree)(T*)>
317const SslDisposer<T, sslFree> SslDisposer<T, sslFree>::INSTANCE;
318 
319#define OSSLCALL_OWN(T, code, ...) \
320 ({ \
321 T* result = code; \
322 JSG_REQUIRE(result != nullptr, ##__VA_ARGS__); \
323 kj::Own<T>(result, workerd::api::SslDisposer<T, &T##_free>::INSTANCE); \
324 })
325 
326#define OSSL_NEW(T, ...) \
327 OSSLCALL_OWN(T, T##_new(__VA_ARGS__), InternalDOMOperationError, "Error allocating crypto")
328 
329#define BIGNUM_new BN_new
330#define BIGNUM_free BN_clear_free
331// BIGNUM obnoxiously doesn't follow the naming convention...
332// Using BN_clear_free here ensures that any potentially sensitive information in the
333// BIGNUM is also cleansed when it is freed.
334 
335using UniqueBignum = std::unique_ptr<BIGNUM, void (*)(BIGNUM*)>;
336kj::Maybe<kj::Own<BIGNUM>> toBignum(kj::ArrayPtr<const kj::byte> data);
337BIGNUM* toBignumUnowned(kj::ArrayPtr<const kj::byte> data);
338// Like toBignumUnowned but returns a UniqueBignum for RAII. Use .release() to transfer
339// ownership to RSA_set0_key etc.
340UniqueBignum toBignumOwned(kj::ArrayPtr<const kj::byte> data);
341kj::Maybe<kj::Array<kj::byte>> bignumToArray(const BIGNUM& bignum);
342kj::Maybe<kj::Array<kj::byte>> bignumToArrayPadded(const BIGNUM& bignum);
343kj::Maybe<kj::Array<kj::byte>> bignumToArrayPadded(const BIGNUM& bignum, size_t paddedLength);
344kj::Maybe<jsg::JsUint8Array> bignumToArray(jsg::Lock& js, const BIGNUM& bignum);
345kj::Maybe<jsg::JsUint8Array> bignumToArrayPadded(jsg::Lock& js, const BIGNUM& bignum);
346kj::Maybe<jsg::JsUint8Array> bignumToArrayPadded(
347 jsg::Lock& js, const BIGNUM& bignum, size_t paddedLength);
348kj::Own<BIGNUM> newBignum();
349 
350#define OSSL_BIO_MEM() \
351 ({ \
352 BIO* result = BIO_new(BIO_s_mem()); \
353 JSG_REQUIRE(result != nullptr, InternalDOMOperationError, "Error allocating crypto"); \
354 kj::Own<BIO>(result, workerd::api::SslDisposer<BIO, &BIO_free_all>::INSTANCE); \
355 })
356 
357// Adopted from Node.js' crypto implementation. the MarkPopErrorOnReturn
358// and ClearErrorOnReturn mechanisms make working with the openssl error
359// stack a bit easier...
360struct MarkPopErrorOnReturn {
361 MarkPopErrorOnReturn() {
362 ERR_set_mark();
363 }
364 ~MarkPopErrorOnReturn() {
365 ERR_pop_to_mark();
366 }
367 KJ_DISALLOW_COPY_AND_MOVE(MarkPopErrorOnReturn);
368};
369 
370struct ClearErrorOnReturn {
371 ClearErrorOnReturn() {
372 ERR_clear_error();
373 }
374 ~ClearErrorOnReturn() {
375 ERR_clear_error();
376 }
377 KJ_DISALLOW_COPY_AND_MOVE(ClearErrorOnReturn);
378 
379 uint32_t peekError() {
380 return ERR_peek_error();
381 }
382 uint32_t consumeError() {
383 return ERR_get_error();
384 }
385};
386 
387// Returns ceil(a / b) for integers (std::ceil always returns a floating point result).
388template <typename T>
389static inline T integerCeilDivision(T a, T b) {
390 static_assert(std::is_unsigned_v<T>);
391 return a == 0 ? 0 : 1 + (a - 1) / b;
392}
393 
394// A wrapper for kj::Array<kj::byte> that will ensure the memory is overwritten
395// with zeroes when destroyed.
396class ZeroOnFree {
397 public:
398 inline ZeroOnFree(kj::Array<kj::byte>&& inner): inner(kj::mv(inner)) {}
399 ~ZeroOnFree() noexcept(false);
400 
401 inline size_t size() const {
402 return inner.size();
403 }
404 inline const kj::byte* begin() const {
405 return inner.begin();
406 }
407 inline operator kj::ArrayPtr<const kj::byte>() const {
408 return inner.asPtr();
409 }
410 inline operator const kj::Array<kj::byte>&() const {
411 return inner;
412 }
413 inline kj::ArrayPtr<kj::byte> asPtr() {
414 return inner.asPtr();
415 }
416 inline kj::ArrayPtr<const kj::byte> asPtr() const {
417 return inner.asPtr();
418 }
419 
420 private:
421 kj::Array<kj::byte> inner;
422};
423 
424// Check that the requested number of iterations for a key-derivation function
425// is acceptable. If the requested iterations is not acceptable, a JS error will
426// be thrown. Otherwise the method will return normally.
427void checkPbkdfLimits(jsg::Lock& js, size_t iterations);
428 
429// Either succeeds with exactly |length| bytes of cryptographically
430// strong pseudo-random data, or fails. This function may block.
431// Don't assume anything about the contents of |buffer| on error.
432// As a special case, |length == 0| can be used to check if the CSPRNG
433// is properly seeded without consuming entropy.
434bool CSPRNG(kj::ArrayPtr<kj::byte> buffer);
435 
436kj::Own<CryptoKey::Impl> fromRsaKey(jsg::Lock& js, kj::Own<EVP_PKEY> key);
437kj::Own<CryptoKey::Impl> fromEcKey(kj::Own<EVP_PKEY> key);
438kj::Own<CryptoKey::Impl> fromEd25519Key(kj::Own<EVP_PKEY> key);
439 
440// If the input bytes are a valid ASN.1 sequence, return them minus the prefix.
441kj::Maybe<kj::ArrayPtr<const kj::byte>> tryGetAsn1Sequence(kj::ArrayPtr<const kj::byte> data);
442 
443template <typename T = const kj::byte>
444ncrypto::Buffer<T> ToNcryptoBuffer(kj::ArrayPtr<T> array) {
445 return ncrypto::Buffer<T>(array.begin(), array.size());
446}
447 
448kj::Maybe<kj::Array<kj::byte>> simdutfBase64UrlDecode(kj::StringPtr input);
449kj::Maybe<jsg::JsUint8Array> simdutfBase64UrlDecode(jsg::Lock& js, kj::StringPtr input);
450jsg::JsUint8Array simdutfBase64UrlDecodeChecked(
451 jsg::Lock& js, kj::StringPtr input, kj::StringPtr error);
452 
453} // namespace workerd::api
454 
455KJ_DECLARE_NON_POLYMORPHIC(DH);
456KJ_DECLARE_NON_POLYMORPHIC(EC_KEY);
457KJ_DECLARE_NON_POLYMORPHIC(EC_POINT);
458KJ_DECLARE_NON_POLYMORPHIC(EC_GROUP);
459KJ_DECLARE_NON_POLYMORPHIC(BN_CTX);
460KJ_DECLARE_NON_POLYMORPHIC(EVP_PKEY);
461KJ_DECLARE_NON_POLYMORPHIC(EVP_PKEY_CTX);
462KJ_DECLARE_NON_POLYMORPHIC(RSA);
463// Tell KJ that these OpenSSL types are non-polymorphic so that they can be wrapped in kj::Own.