Skip to content

Commit dcc2ed9

Browse files
committed
src: move hkdf, scrypto, pbkdf2 impl to ncrypto
PR-URL: #54651 Reviewed-By: Yagiz Nizipli <[email protected]> Reviewed-By: Minwoo Jung <[email protected]>
1 parent 16e6747 commit dcc2ed9

File tree

6 files changed

+231
-103
lines changed

6 files changed

+231
-103
lines changed

deps/ncrypto/ncrypto.cc

Lines changed: 132 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,135 @@ DataPointer DHPointer::stateless(const EVPKeyPointer& ourKey,
12521253
return out;
12531254
}
12541255

1256+
// ============================================================================
1257+
// KDF
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+
ClearErrorOnReturn clearErrorOnReturn;
1279+
1280+
if (!checkHkdfLength(md, length) ||
1281+
info.len > INT_MAX ||
1282+
salt.len > INT_MAX) {
1283+
return {};
1284+
}
1285+
1286+
EVPKeyCtxPointer ctx =
1287+
EVPKeyCtxPointer(EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, nullptr));
1288+
if (!ctx ||
1289+
!EVP_PKEY_derive_init(ctx.get()) ||
1290+
!EVP_PKEY_CTX_set_hkdf_md(ctx.get(), md) ||
1291+
!EVP_PKEY_CTX_add1_hkdf_info(ctx.get(), info.data, info.len)) {
1292+
return {};
1293+
}
1294+
1295+
std::string_view actual_salt;
1296+
static const char default_salt[EVP_MAX_MD_SIZE] = {0};
1297+
if (salt.len > 0) {
1298+
actual_salt = {reinterpret_cast<const char*>(salt.data), salt.len};
1299+
} else {
1300+
actual_salt = {default_salt, static_cast<unsigned>(EVP_MD_size(md))};
1301+
}
1302+
1303+
// We do not use EVP_PKEY_HKDF_MODE_EXTRACT_AND_EXPAND because and instead
1304+
// implement the extraction step ourselves because EVP_PKEY_derive does not
1305+
// handle zero-length keys, which are required for Web Crypto.
1306+
// TODO: Once OpenSSL 1.1.1 support is dropped completely, and once BoringSSL
1307+
// is confirmed to support it, wen can hopefully drop this and use EVP_KDF
1308+
// directly which does support zero length keys.
1309+
unsigned char pseudorandom_key[EVP_MAX_MD_SIZE];
1310+
unsigned pseudorandom_key_len = sizeof(pseudorandom_key);
1311+
1312+
if (HMAC(md,
1313+
actual_salt.data(),
1314+
actual_salt.size(),
1315+
key.data,
1316+
key.len,
1317+
pseudorandom_key,
1318+
&pseudorandom_key_len) == nullptr) {
1319+
return {};
1320+
}
1321+
if (!EVP_PKEY_CTX_hkdf_mode(ctx.get(), EVP_PKEY_HKDEF_MODE_EXPAND_ONLY) ||
1322+
!EVP_PKEY_CTX_set1_hkdf_key(ctx.get(), pseudorandom_key, pseudorandom_key_len)) {
1323+
return {};
1324+
}
1325+
1326+
auto buf = DataPointer::Alloc(length);
1327+
if (!buf) return {};
1328+
1329+
if (EVP_PKEY_derive(ctx.get(), static_cast<unsigned char*>(buf.get()), &length) <= 0) {
1330+
return {};
1331+
}
1332+
1333+
return buf;
1334+
}
1335+
1336+
bool checkScryptParams(uint64_t N, uint64_t r, uint64_t p, uint64_t maxmem) {
1337+
return EVP_PBE_scrypt(nullptr, 0, nullptr, 0, N, r, p, maxmem, nullptr, 0) == 1;
1338+
}
1339+
1340+
DataPointer scrypt(const Buffer<const char>& pass,
1341+
const Buffer<const unsigned char>& salt,
1342+
uint64_t N,
1343+
uint64_t r,
1344+
uint64_t p,
1345+
uint64_t maxmem,
1346+
size_t length) {
1347+
ClearErrorOnReturn clearErrorOnReturn;
1348+
1349+
if (pass.len > INT_MAX ||
1350+
salt.len > INT_MAX) {
1351+
return {};
1352+
}
1353+
1354+
auto dp = DataPointer::Alloc(length);
1355+
if (dp && EVP_PBE_scrypt(
1356+
pass.data, pass.len, salt.data, salt.len, N, r, p, maxmem,
1357+
reinterpret_cast<unsigned char*>(dp.get()), length)) {
1358+
return dp;
1359+
}
1360+
1361+
return {};
1362+
}
1363+
1364+
DataPointer pbkdf2(const EVP_MD* md,
1365+
const Buffer<const char>& pass,
1366+
const Buffer<const unsigned char>& salt,
1367+
uint32_t iterations,
1368+
size_t length) {
1369+
ClearErrorOnReturn clearErrorOnReturn;
1370+
1371+
if (pass.len > INT_MAX ||
1372+
salt.len > INT_MAX ||
1373+
length > INT_MAX) {
1374+
return {};
1375+
}
1376+
1377+
auto dp = DataPointer::Alloc(length);
1378+
if (dp && PKCS5_PBKDF2_HMAC(pass.data, pass.len, salt.data, salt.len,
1379+
iterations, md, length,
1380+
reinterpret_cast<unsigned char*>(dp.get()))) {
1381+
return dp;
1382+
}
1383+
1384+
return {};
1385+
}
1386+
12551387
} // namespace ncrypto

deps/ncrypto/ncrypto.h

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,38 @@ 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+
// KDF
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+
607+
bool checkScryptParams(uint64_t N, uint64_t r, uint64_t p, uint64_t maxmem);
608+
609+
DataPointer scrypt(const Buffer<const char>& pass,
610+
const Buffer<const unsigned char>& salt,
611+
uint64_t N,
612+
uint64_t r,
613+
uint64_t p,
614+
uint64_t maxmem,
615+
size_t length);
616+
617+
DataPointer pbkdf2(const EVP_MD* md,
618+
const Buffer<const char>& pass,
619+
const Buffer<const unsigned char>& salt,
620+
uint32_t iterations,
621+
size_t length);
622+
591623
// ============================================================================
592624
// Version metadata
593625
#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(params.digest,
106+
ncrypto::Buffer<const unsigned char>{
107+
.data = reinterpret_cast<const unsigned char*>(
108+
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

src/crypto/crypto_pbkdf2.cc

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ Maybe<bool> PBKDF2Traits::AdditionalConfig(
102102
}
103103

104104
Utf8Value name(args.GetIsolate(), args[offset + 4]);
105-
params->digest = EVP_get_digestbyname(*name);
105+
params->digest = ncrypto::getDigestByName(name.ToStringView());
106106
if (params->digest == nullptr) {
107107
THROW_ERR_CRYPTO_INVALID_DIGEST(env, "Invalid digest: %s", *name);
108108
return Nothing<bool>();
@@ -111,27 +111,24 @@ Maybe<bool> PBKDF2Traits::AdditionalConfig(
111111
return Just(true);
112112
}
113113

114-
bool PBKDF2Traits::DeriveBits(
115-
Environment* env,
116-
const PBKDF2Config& params,
117-
ByteSource* out) {
118-
ByteSource::Builder buf(params.length);
119-
114+
bool PBKDF2Traits::DeriveBits(Environment* env,
115+
const PBKDF2Config& params,
116+
ByteSource* out) {
120117
// Both pass and salt may be zero length here.
121-
// The generated bytes are stored in buf, which is
122-
// assigned to out on success.
123-
124-
if (PKCS5_PBKDF2_HMAC(params.pass.data<char>(),
125-
params.pass.size(),
126-
params.salt.data<unsigned char>(),
127-
params.salt.size(),
128-
params.iterations,
129-
params.digest,
130-
params.length,
131-
buf.data<unsigned char>()) <= 0) {
132-
return false;
133-
}
134-
*out = std::move(buf).release();
118+
auto dp = ncrypto::pbkdf2(params.digest,
119+
ncrypto::Buffer<const char>{
120+
.data = params.pass.data<const char>(),
121+
.len = params.pass.size(),
122+
},
123+
ncrypto::Buffer<const unsigned char>{
124+
.data = params.salt.data<unsigned char>(),
125+
.len = params.salt.size(),
126+
},
127+
params.iterations,
128+
params.length);
129+
130+
if (!dp) return false;
131+
*out = ByteSource::Allocated(dp.release());
135132
return true;
136133
}
137134

0 commit comments

Comments
 (0)