Skip to content
File

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

4.6 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 <workerd/jsg/jsvalue.h>
9 
10#include <ncrypto.h>
11 
12namespace workerd::api {
13namespace {
14 
15// The underlying implementation of HKDF for WebCrypto.
16// The CryptoKey::Impl here is used only for web crypto uses.
17class HkdfKey final: public CryptoKey::Impl {
18 public:
19 explicit HkdfKey(kj::Array<kj::byte> keyData,
20 CryptoKey::KeyAlgorithm keyAlgorithm,
21 bool extractable,
22 CryptoKeyUsageSet usages)
23 : CryptoKey::Impl(extractable, usages),
24 keyData(kj::mv(keyData)),
25 keyAlgorithm(kj::mv(keyAlgorithm)) {}
26 
27 kj::StringPtr jsgGetMemoryName() const override {
28 return "HkdfKey";
29 }
30 size_t jsgGetMemorySelfSize() const override {
31 return sizeof(HkdfKey);
32 }
33 void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const override {
34 tracker.trackFieldWithSize("keyData", keyData.size());
35 tracker.trackField("keyAlgorithm", keyAlgorithm);
36 }
37 
38 private:
39 jsg::JsArrayBuffer deriveBits(jsg::Lock& js,
40 SubtleCrypto::DeriveKeyAlgorithm&& algorithm,
41 kj::Maybe<uint32_t> maybeLength) const override {
42 kj::StringPtr hashName = api::getAlgorithmName(
43 JSG_REQUIRE_NONNULL(algorithm.hash, TypeError, "Missing field \"hash\" in \"algorithm\"."));
44 const EVP_MD* hashType = lookupDigestAlgorithm(hashName).second;
45 
46 auto saltHandle =
47 JSG_REQUIRE_NONNULL(algorithm.salt, TypeError, "Missing field \"salt\" in \"algorithm\".")
48 .getHandle(js);
49 const auto& salt = saltHandle.asArrayPtr();
50 auto infoHandle =
51 JSG_REQUIRE_NONNULL(algorithm.info, TypeError, "Missing field \"info\" in \"algorithm\".")
52 .getHandle(js);
53 const auto& info = infoHandle.asArrayPtr();
54 
55 uint32_t length = JSG_REQUIRE_NONNULL(
56 maybeLength, DOMOperationError, "HKDF cannot derive a key with null length.");
57 
58 JSG_REQUIRE(length % 8 == 0, DOMOperationError,
59 "HKDF requires a derived key length that is a multiple of eight (requested ", length, ").");
60 
61 auto derivedLengthBytes = length / 8;
62 
63 return JSG_REQUIRE_NONNULL(hkdf(js, derivedLengthBytes, hashType, keyData, salt, info),
64 DOMOperationError, "HKDF deriveBits failed.");
65 }
66 
67 kj::StringPtr getAlgorithmName() const override {
68 return "HKDF";
69 }
70 CryptoKey::AlgorithmVariant getAlgorithm(jsg::Lock& js) const override {
71 return keyAlgorithm;
72 }
73 
74 bool equals(const CryptoKey::Impl& other) const override final {
75 return this == &other || (other.getType() == "secret"_kj && other.equals(keyData));
76 }
77 
78 bool equals(const kj::Array<kj::byte>& other) const override final {
79 return keyData.size() == other.size() &&
80 CRYPTO_memcmp(keyData.begin(), other.begin(), keyData.size()) == 0;
81 }
82 
83 ZeroOnFree keyData;
84 CryptoKey::KeyAlgorithm keyAlgorithm;
85};
86} // namespace
87 
88kj::Maybe<jsg::JsArrayBuffer> hkdf(jsg::Lock& js,
89 size_t length,
90 const EVP_MD* digest,
91 kj::ArrayPtr<const kj::byte> key,
92 kj::ArrayPtr<const kj::byte> salt,
93 kj::ArrayPtr<const kj::byte> info) {
94 // Because we want to be using the v8 sandbox, we need to allocate the result
95 // buffer in the v8 isolate heap then generate the HKDF result into that.
96 ncrypto::ClearErrorOnReturn clearErrorOnReturn;
97 auto buf = jsg::JsArrayBuffer::create(js, length);
98 auto ncBuf = ToNcryptoBuffer(buf.asArrayPtr());
99 if (ncrypto::hkdfInfo(digest, ToNcryptoBuffer(key), ToNcryptoBuffer(info), ToNcryptoBuffer(salt),
100 length, &ncBuf)) {
101 return kj::mv(buf);
102 }
103 
104 return kj::none;
105}
106 
107kj::Own<CryptoKey::Impl> CryptoKey::Impl::importHkdf(jsg::Lock& js,
108 kj::StringPtr normalizedName,
109 kj::StringPtr format,
110 SubtleCrypto::ImportKeyData keyData,
111 SubtleCrypto::ImportKeyAlgorithm&& algorithm,
112 bool extractable,
113 kj::ArrayPtr<const kj::String> keyUsages) {
114 auto usages = CryptoKeyUsageSet::validate(normalizedName,
115 CryptoKeyUsageSet::Context::importSecret, keyUsages, CryptoKeyUsageSet::derivationKeyMask());
116 
117 JSG_REQUIRE(!extractable, DOMSyntaxError, "HKDF key cannot be extractable.");
118 JSG_REQUIRE(format == "raw", DOMNotSupportedError,
119 "HKDF key must be imported "
120 "in \"raw\" format (requested \"",
121 format, "\")");
122 
123 // NOTE: Checked in SubtleCrypto::importKey().
124 auto keyDataArray = kj::mv(keyData.get<kj::Array<kj::byte>>());
125 
126 auto keyAlgorithm = CryptoKey::KeyAlgorithm{normalizedName};
127 return kj::heap<HkdfKey>(kj::mv(keyDataArray), kj::mv(keyAlgorithm), extractable, usages);
128}
129 
130} // namespace workerd::api