Skip to content

Commit 27447ca

Browse files
committed
src: move hkdf impl to ncrypto
1 parent ff5ef70 commit 27447ca

File tree

3 files changed

+115
-51
lines changed

3 files changed

+115
-51
lines changed

deps/ncrypto/ncrypto.cc

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include <openssl/dh.h>
55
#include <openssl/bn.h>
66
#include <openssl/evp.h>
7+
#include <openssl/hmac.h>
78
#include <openssl/pkcs12.h>
89
#include <openssl/x509v3.h>
910
#if OPENSSL_VERSION_MAJOR >= 3
@@ -1252,4 +1253,82 @@ DataPointer DHPointer::stateless(const EVPKeyPointer& ourKey,
12521253
return out;
12531254
}
12541255

1256+
// ============================================================================
1257+
// HKDF
1258+
1259+
const EVP_MD* getDigestByName(const std::string_view name) {
1260+
return EVP_get_digestbyname(name.data());
1261+
}
1262+
1263+
bool checkHkdfLength(const EVP_MD* md, size_t length) {
1264+
// HKDF-Expand computes up to 255 HMAC blocks, each having as many bits as
1265+
// the output of the hash function. 255 is a hard limit because HKDF appends
1266+
// an 8-bit counter to each HMAC'd message, starting at 1.
1267+
static constexpr size_t kMaxDigestMultiplier = 255;
1268+
size_t max_length = EVP_MD_size(md) * kMaxDigestMultiplier;
1269+
if (length > max_length) return false;
1270+
return true;
1271+
}
1272+
1273+
DataPointer hkdf(const EVP_MD* md,
1274+
const Buffer<const unsigned char>& key,
1275+
const Buffer<const unsigned char>& info,
1276+
const Buffer<const unsigned char>& salt,
1277+
size_t length) {
1278+
if (!checkHkdfLength(md, length) ||
1279+
info.len > INT_MAX ||
1280+
salt.len > INT_MAX) {
1281+
return {};
1282+
}
1283+
1284+
EVPKeyCtxPointer ctx =
1285+
EVPKeyCtxPointer(EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, nullptr));
1286+
if (!ctx ||
1287+
!EVP_PKEY_derive_init(ctx.get()) ||
1288+
!EVP_PKEY_CTX_set_hkdf_md(ctx.get(), md) ||
1289+
!EVP_PKEY_CTX_add1_hkdf_info(ctx.get(), info.data, info.len)) {
1290+
return {};
1291+
}
1292+
1293+
std::string_view actual_salt;
1294+
static const char default_salt[EVP_MAX_MD_SIZE] = {0};
1295+
if (salt.len > 0) {
1296+
actual_salt = {reinterpret_cast<const char*>(salt.data), salt.len};
1297+
} else {
1298+
actual_salt = {default_salt, static_cast<unsigned>(EVP_MD_size(md))};
1299+
}
1300+
1301+
// We do not use EVP_PKEY_HKDF_MODE_EXTRACT_AND_EXPAND because and instead
1302+
// implement the extraction step ourselves because EVP_PKEY_derive does not
1303+
// handle zero-length keys, which are required for Web Crypto.
1304+
// TODO: Once OpenSSL 1.1.1 support is dropped completely, and once BoringSSL
1305+
// is confirmed to support it, wen can hopefully drop this and use EVP_KDF
1306+
// directly which does support zero length keys.
1307+
unsigned char pseudorandom_key[EVP_MAX_MD_SIZE];
1308+
unsigned pseudorandom_key_len = sizeof(pseudorandom_key);
1309+
1310+
if (HMAC(md,
1311+
actual_salt.data(),
1312+
actual_salt.size(),
1313+
key.data,
1314+
key.len,
1315+
pseudorandom_key,
1316+
&pseudorandom_key_len) == nullptr) {
1317+
return {};
1318+
}
1319+
if (!EVP_PKEY_CTX_hkdf_mode(ctx.get(), EVP_PKEY_HKDEF_MODE_EXPAND_ONLY) ||
1320+
!EVP_PKEY_CTX_set1_hkdf_key(ctx.get(), pseudorandom_key, pseudorandom_key_len)) {
1321+
return {};
1322+
}
1323+
1324+
auto buf = DataPointer::Alloc(length);
1325+
if (!buf) return {};
1326+
1327+
if (EVP_PKEY_derive(ctx.get(), static_cast<unsigned char*>(buf.get()), &length) <= 0) {
1328+
return {};
1329+
}
1330+
1331+
return buf;
1332+
}
1333+
12551334
} // namespace ncrypto

deps/ncrypto/ncrypto.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,22 @@ BIOPointer ExportPublicKey(const char* input, size_t length);
588588
// The caller takes ownership of the returned Buffer<char>
589589
Buffer<char> ExportChallenge(const char* input, size_t length);
590590

591+
// ============================================================================
592+
// HKDF
593+
594+
const EVP_MD* getDigestByName(const std::string_view name);
595+
596+
// Verify that the specified HKDF output length is valid for the given digest.
597+
// The maximum length for HKDF output for a given digest is 255 times the
598+
// hash size for the given digest algorithm.
599+
bool checkHkdfLength(const EVP_MD* md, size_t length);
600+
601+
DataPointer hkdf(const EVP_MD* md,
602+
const Buffer<const unsigned char>& key,
603+
const Buffer<const unsigned char>& info,
604+
const Buffer<const unsigned char>& salt,
605+
size_t length);
606+
591607
// ============================================================================
592608
// Version metadata
593609
#define NCRYPTO_VERSION "0.0.1"

src/crypto/crypto_hkdf.cc

Lines changed: 20 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ Maybe<bool> HKDFTraits::AdditionalConfig(
5656
CHECK(args[offset + 4]->IsUint32()); // Length
5757

5858
Utf8Value hash(env->isolate(), args[offset]);
59-
params->digest = EVP_get_digestbyname(*hash);
59+
params->digest = ncrypto::getDigestByName(hash.ToStringView());
6060
if (params->digest == nullptr) {
6161
THROW_ERR_CRYPTO_INVALID_DIGEST(env, "Invalid digest: %s", *hash);
6262
return Nothing<bool>();
@@ -90,9 +90,7 @@ Maybe<bool> HKDFTraits::AdditionalConfig(
9090
// HKDF-Expand computes up to 255 HMAC blocks, each having as many bits as the
9191
// output of the hash function. 255 is a hard limit because HKDF appends an
9292
// 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) {
93+
if (!ncrypto::checkHkdfLength(params->digest, params->length)) {
9694
THROW_ERR_CRYPTO_INVALID_KEYLEN(env);
9795
return Nothing<bool>();
9896
}
@@ -104,53 +102,24 @@ bool HKDFTraits::DeriveBits(
104102
Environment* env,
105103
const HKDFConfig& params,
106104
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();
105+
auto dp = ncrypto::hkdf(
106+
params.digest,
107+
ncrypto::Buffer<const unsigned char> {
108+
.data = reinterpret_cast<const unsigned char*>(params.key->GetSymmetricKey()),
109+
.len = params.key->GetSymmetricKeySize(),
110+
},
111+
ncrypto::Buffer<const unsigned char> {
112+
.data = params.info.data<const unsigned char>(),
113+
.len = params.info.size(),
114+
},
115+
ncrypto::Buffer<const unsigned char> {
116+
.data = params.salt.data<const unsigned char>(),
117+
.len = params.salt.size(),
118+
},
119+
params.length);
120+
if (!dp) return false;
121+
122+
*out = ByteSource::Allocated(dp.release());
154123
return true;
155124
}
156125

0 commit comments

Comments
 (0)