Skip to content
File

Blob: src/workerd/api/crypto/pbkdf2.c++

5.7 KB
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#include "impl.h"
6#include "kdf.h"
7 
8#include <ncrypto.h>
9 
10namespace workerd::api {
11namespace {
12 
13// The underlying implementation of PBKDF2 for WebCrypto.
14// The CryptoKey::Impl here is used only for web crypto uses.
15class Pbkdf2Key final: public CryptoKey::Impl {
16 public:
17 explicit Pbkdf2Key(kj::Array<kj::byte> keyData,
18 CryptoKey::KeyAlgorithm keyAlgorithm,
19 bool extractable,
20 CryptoKeyUsageSet usages)
21 : CryptoKey::Impl(extractable, usages),
22 keyData(kj::mv(keyData)),
23 keyAlgorithm(kj::mv(keyAlgorithm)) {}
24 
25 kj::StringPtr jsgGetMemoryName() const override {
26 return "Pbkdf2Key";
27 }
28 size_t jsgGetMemorySelfSize() const override {
29 return sizeof(Pbkdf2Key);
30 }
31 void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const override {
32 tracker.trackFieldWithSize("keyData", keyData.size());
33 tracker.trackField("keyAlgorithm", keyAlgorithm);
34 }
35 
36 private:
37 jsg::JsArrayBuffer deriveBits(jsg::Lock& js,
38 SubtleCrypto::DeriveKeyAlgorithm&& algorithm,
39 kj::Maybe<uint32_t> maybeLength) const override {
40 kj::StringPtr hashName = api::getAlgorithmName(
41 JSG_REQUIRE_NONNULL(algorithm.hash, TypeError, "Missing field \"hash\" in \"algorithm\"."));
42 auto hashType = lookupDigestAlgorithm(hashName).second;
43 auto saltHandle =
44 JSG_REQUIRE_NONNULL(algorithm.salt, TypeError, "Missing field \"salt\" in \"algorithm\".")
45 .getHandle(js);
46 kj::ArrayPtr<kj::byte> salt = saltHandle.asArrayPtr();
47 int iterations = JSG_REQUIRE_NONNULL(
48 algorithm.iterations, TypeError, "Missing field \"iterations\" in \"algorithm\".");
49 
50 uint32_t length = JSG_REQUIRE_NONNULL(
51 maybeLength, DOMOperationError, "PBKDF2 cannot derive a key with null length.");
52 
53 JSG_REQUIRE(length % 8 == 0, DOMOperationError,
54 "PBKDF2 requires a derived key length that is a multiple of eight (requested ", length,
55 ").");
56 
57 JSG_REQUIRE(iterations > 0, DOMOperationError,
58 "PBKDF2 requires a positive iteration count (requested ", iterations, ").");
59 
60 // Note: The user could DoS us by selecting a very high iteration count. Our dead man's switch
61 // would kick in, resulting in a process restart. We guard against this by limiting the
62 // maximum iteration count a user can select -- this is an intentional non-conformity.
63 // Another approach might be to fork OpenSSL's PKCS5_PBKDF2_HMAC() function and insert a
64 // check for v8::Isolate::IsExecutionTerminating() in the loop, but for now a hard cap seems
65 // wisest.
66 checkPbkdfLimits(js, iterations);
67 
68 return JSG_REQUIRE_NONNULL(pbkdf2(js, length / 8, iterations, hashType, keyData, salt), Error,
69 "PBKDF2 deriveBits failed.");
70 }
71 
72 // TODO(bug): Possibly by mistake, PBKDF2 was historically not on the allow list of
73 // algorithms in exportKey(). Later, the allow list was removed, instead assuming that any
74 // algorithm which implemented this method must be allowed. To maintain exactly the
75 // preexisting behavior, then, this implementation had to be commented out. If disallowing this
76 // was a mistake, we can un-comment this method, but we would need to make sure to add tests
77 // when we do.
78 // SubtleCrypto::ExportKeyData exportKey(kj::StringPtr format) const override {
79 // JSG_REQUIRE(format == "raw", DOMNotSupportedError,
80 // "Unimplemented key export format \"", format, "\".");
81 // return kj::heapArray(keyData.asPtr());
82 // }
83 
84 kj::StringPtr getAlgorithmName() const override {
85 return "PBKDF2";
86 }
87 CryptoKey::AlgorithmVariant getAlgorithm(jsg::Lock& js) const override {
88 return keyAlgorithm;
89 }
90 
91 bool equals(const CryptoKey::Impl& other) const override final {
92 return this == &other || (other.getType() == "secret"_kj && other.equals(keyData));
93 }
94 
95 bool equals(const kj::Array<kj::byte>& other) const override final {
96 return keyData.size() == other.size() &&
97 CRYPTO_memcmp(keyData.begin(), other.begin(), keyData.size()) == 0;
98 }
99 
100 ZeroOnFree keyData;
101 CryptoKey::KeyAlgorithm keyAlgorithm;
102};
103} // namespace
104 
105kj::Maybe<jsg::JsArrayBuffer> pbkdf2(jsg::Lock& js,
106 size_t length,
107 size_t iterations,
108 const EVP_MD* digest,
109 kj::ArrayPtr<const kj::byte> password,
110 kj::ArrayPtr<const kj::byte> salt) {
111 ncrypto::ClearErrorOnReturn clearErrorOnReturn;
112 auto buf = jsg::JsArrayBuffer::create(js, length);
113 auto ncBuf = ToNcryptoBuffer(buf.asArrayPtr());
114 if (ncrypto::pbkdf2Into(digest, ToNcryptoBuffer(password.asChars()), ToNcryptoBuffer(salt),
115 iterations, length, &ncBuf)) {
116 return kj::mv(buf);
117 }
118 return kj::none;
119}
120 
121kj::Own<CryptoKey::Impl> CryptoKey::Impl::importPbkdf2(jsg::Lock& js,
122 kj::StringPtr normalizedName,
123 kj::StringPtr format,
124 SubtleCrypto::ImportKeyData keyData,
125 SubtleCrypto::ImportKeyAlgorithm&& algorithm,
126 bool extractable,
127 kj::ArrayPtr<const kj::String> keyUsages) {
128 auto usages = CryptoKeyUsageSet::validate(normalizedName,
129 CryptoKeyUsageSet::Context::importSecret, keyUsages, CryptoKeyUsageSet::derivationKeyMask());
130 
131 JSG_REQUIRE(!extractable, DOMSyntaxError, "PBKDF2 key cannot be extractable.");
132 JSG_REQUIRE(format == "raw", DOMNotSupportedError,
133 "PBKDF2 key must be imported in \"raw\" format (requested \"", format, "\").");
134 
135 // NOTE: Checked in SubtleCrypto::importKey().
136 auto keyDataArray = kj::mv(keyData.get<kj::Array<kj::byte>>());
137 
138 auto keyAlgorithm = CryptoKey::KeyAlgorithm{normalizedName};
139 return kj::heap<Pbkdf2Key>(kj::mv(keyDataArray), kj::mv(keyAlgorithm), extractable, usages);
140}
141 
142} // namespace workerd::api