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