1 #include "crypto/crypto_hkdf.h"
2 #include "async_wrap-inl.h"
3 #include "base_object-inl.h"
4 #include "crypto/crypto_keys.h"
5 #include "env-inl.h"
6 #include "memory_tracker-inl.h"
7 #include "threadpoolwork-inl.h"
8 #include "v8.h"
9
10 namespace node {
11
12 using v8::FunctionCallbackInfo;
13 using v8::Just;
14 using v8::Maybe;
15 using v8::Nothing;
16 using v8::Uint32;
17 using v8::Value;
18
19 namespace crypto {
HKDFConfig(HKDFConfig && other)20 HKDFConfig::HKDFConfig(HKDFConfig&& other) noexcept
21 : mode(other.mode),
22 length(other.length),
23 digest(other.digest),
24 key(other.key),
25 salt(std::move(other.salt)),
26 info(std::move(other.info)) {}
27
operator =(HKDFConfig && other)28 HKDFConfig& HKDFConfig::operator=(HKDFConfig&& other) noexcept {
29 if (&other == this) return *this;
30 this->~HKDFConfig();
31 return *new (this) HKDFConfig(std::move(other));
32 }
33
EncodeOutput(Environment * env,const HKDFConfig & params,ByteSource * out,v8::Local<v8::Value> * result)34 Maybe<bool> HKDFTraits::EncodeOutput(
35 Environment* env,
36 const HKDFConfig& params,
37 ByteSource* out,
38 v8::Local<v8::Value>* result) {
39 *result = out->ToArrayBuffer(env);
40 return Just(!result->IsEmpty());
41 }
42
AdditionalConfig(CryptoJobMode mode,const FunctionCallbackInfo<Value> & args,unsigned int offset,HKDFConfig * params)43 Maybe<bool> HKDFTraits::AdditionalConfig(
44 CryptoJobMode mode,
45 const FunctionCallbackInfo<Value>& args,
46 unsigned int offset,
47 HKDFConfig* params) {
48 Environment* env = Environment::GetCurrent(args);
49
50 params->mode = mode;
51
52 CHECK(args[offset]->IsString()); // Hash
53 CHECK(args[offset + 1]->IsObject()); // Key
54 CHECK(IsAnyByteSource(args[offset + 2])); // Salt
55 CHECK(IsAnyByteSource(args[offset + 3])); // Info
56 CHECK(args[offset + 4]->IsUint32()); // Length
57
58 Utf8Value hash(env->isolate(), args[offset]);
59 params->digest = EVP_get_digestbyname(*hash);
60 if (params->digest == nullptr) {
61 THROW_ERR_CRYPTO_INVALID_DIGEST(env, "Invalid digest: %s", *hash);
62 return Nothing<bool>();
63 }
64
65 KeyObjectHandle* key;
66 ASSIGN_OR_RETURN_UNWRAP(&key, args[offset + 1], Nothing<bool>());
67 params->key = key->Data();
68
69 ArrayBufferOrViewContents<char> salt(args[offset + 2]);
70 ArrayBufferOrViewContents<char> info(args[offset + 3]);
71
72 if (UNLIKELY(!salt.CheckSizeInt32())) {
73 THROW_ERR_OUT_OF_RANGE(env, "salt is too big");
74 return Nothing<bool>();
75 }
76 if (UNLIKELY(!info.CheckSizeInt32())) {
77 THROW_ERR_OUT_OF_RANGE(env, "info is too big");
78 return Nothing<bool>();
79 }
80
81 params->salt = mode == kCryptoJobAsync
82 ? salt.ToCopy()
83 : salt.ToByteSource();
84
85 params->info = mode == kCryptoJobAsync
86 ? info.ToCopy()
87 : info.ToByteSource();
88
89 params->length = args[offset + 4].As<Uint32>()->Value();
90 // HKDF-Expand computes up to 255 HMAC blocks, each having as many bits as the
91 // output of the hash function. 255 is a hard limit because HKDF appends an
92 // 8-bit counter to each HMAC'd message, starting at 1.
93 constexpr size_t kMaxDigestMultiplier = 255;
94 size_t max_length = EVP_MD_size(params->digest) * kMaxDigestMultiplier;
95 if (params->length > max_length) {
96 THROW_ERR_CRYPTO_INVALID_KEYLEN(env);
97 return Nothing<bool>();
98 }
99
100 return Just(true);
101 }
102
DeriveBits(Environment * env,const HKDFConfig & params,ByteSource * out)103 bool HKDFTraits::DeriveBits(
104 Environment* env,
105 const HKDFConfig& params,
106 ByteSource* out) {
107 EVPKeyCtxPointer ctx =
108 EVPKeyCtxPointer(EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, nullptr));
109 if (!ctx || !EVP_PKEY_derive_init(ctx.get()) ||
110 !EVP_PKEY_CTX_set_hkdf_md(ctx.get(), params.digest) ||
111 !EVP_PKEY_CTX_add1_hkdf_info(
112 ctx.get(), params.info.data<unsigned char>(), params.info.size())) {
113 return false;
114 }
115
116 // TODO(panva): Once support for OpenSSL 1.1.1 is dropped the whole
117 // of HKDFTraits::DeriveBits can be refactored to use
118 // EVP_KDF which does handle zero length key.
119
120 std::string_view salt;
121 if (params.salt.size() != 0) {
122 salt = {params.salt.data<char>(), params.salt.size()};
123 } else {
124 static const char default_salt[EVP_MAX_MD_SIZE] = {0};
125 salt = {default_salt, static_cast<unsigned>(EVP_MD_size(params.digest))};
126 }
127
128 // We do not use EVP_PKEY_HKDEF_MODE_EXTRACT_AND_EXPAND and instead implement
129 // the extraction step ourselves because EVP_PKEY_derive does not handle
130 // zero-length keys, which are required for Web Crypto.
131 unsigned char pseudorandom_key[EVP_MAX_MD_SIZE];
132 unsigned int prk_len = sizeof(pseudorandom_key);
133 if (HMAC(
134 params.digest,
135 salt.data(),
136 salt.size(),
137 reinterpret_cast<const unsigned char*>(params.key->GetSymmetricKey()),
138 params.key->GetSymmetricKeySize(),
139 pseudorandom_key,
140 &prk_len) == nullptr) {
141 return false;
142 }
143 if (!EVP_PKEY_CTX_hkdf_mode(ctx.get(), EVP_PKEY_HKDEF_MODE_EXPAND_ONLY) ||
144 !EVP_PKEY_CTX_set1_hkdf_key(ctx.get(), pseudorandom_key, prk_len)) {
145 return false;
146 }
147
148 size_t length = params.length;
149 ByteSource::Builder buf(length);
150 if (EVP_PKEY_derive(ctx.get(), buf.data<unsigned char>(), &length) <= 0)
151 return false;
152
153 *out = std::move(buf).release();
154 return true;
155 }
156
MemoryInfo(MemoryTracker * tracker) const157 void HKDFConfig::MemoryInfo(MemoryTracker* tracker) const {
158 tracker->TrackField("key", key);
159 // If the job is sync, then the HKDFConfig does not own the data
160 if (mode == kCryptoJobAsync) {
161 tracker->TrackFieldWithSize("salt", salt.size());
162 tracker->TrackFieldWithSize("info", info.size());
163 }
164 }
165
166 } // namespace crypto
167 } // namespace node
168