Skip to content

Commit daa1bd3

Browse files
committed
crypto: add a generic MAC API
Add getMacs() and createMac() to node:crypto as a layer on top of OpenSSL EVP_MAC. Support incremental and streaming operations with provider parameter validation and configurable output sizes. Signed-off-by: Filip Skokan <panva.ip@gmail.com>
1 parent e7a69c4 commit daa1bd3

25 files changed

Lines changed: 2311 additions & 7 deletions

‎benchmark/crypto/mac.js‎

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
'use strict';
2+
3+
const common = require('../common.js');
4+
const { hasOpenSSL } = require('../../test/common/crypto.js');
5+
const assert = require('node:assert');
6+
const {
7+
createHmac,
8+
createMac,
9+
getMacs,
10+
} = require('node:crypto');
11+
12+
if (!hasOpenSSL(3) ||
13+
process.features.openssl_is_boringssl ||
14+
typeof createMac !== 'function' ||
15+
typeof getMacs !== 'function') {
16+
console.log('Skipping: generic MAC API requires OpenSSL >= 3');
17+
process.exit(0);
18+
}
19+
20+
const isTest = process.argv.includes('--test');
21+
const configurations = {
22+
'hmac-sha256': {
23+
algorithm: 'HMAC',
24+
key: Buffer.alloc(32, 0x42),
25+
options: { digest: 'SHA256' },
26+
},
27+
'kmac-128': {
28+
algorithm: 'KMAC-128',
29+
key: Buffer.alloc(32, 0x42),
30+
options: { outputLength: 32 },
31+
},
32+
};
33+
34+
const bench = common.createBenchmark(main, {
35+
operation: [
36+
'get-macs-cold',
37+
'get-macs-warm',
38+
'create-cold',
39+
'create-warm',
40+
'create-hmac',
41+
'update',
42+
'stream',
43+
],
44+
algorithm: Object.keys(configurations),
45+
length: [0, 64, 4096],
46+
n: [1, 10_000, 20_000, 500_000],
47+
}, {
48+
combinationFilter({ operation, algorithm, length, n }) {
49+
if (isTest) {
50+
return algorithm === 'hmac-sha256' && length === 0 && n === 1;
51+
}
52+
53+
if (operation === 'get-macs-cold') {
54+
return algorithm === 'hmac-sha256' && length === 0 && n === 1;
55+
}
56+
if (operation === 'get-macs-warm') {
57+
return algorithm === 'hmac-sha256' && length === 0 && n === 500_000;
58+
}
59+
if (operation === 'create-cold')
60+
return length === 0 && n === 1;
61+
if (operation === 'create-warm')
62+
return length === 0 && n === 20_000;
63+
if (operation === 'create-hmac' && algorithm !== 'hmac-sha256')
64+
return false;
65+
return n === 10_000;
66+
},
67+
test: {
68+
algorithm: ['hmac-sha256'],
69+
length: [0],
70+
n: [1],
71+
},
72+
});
73+
74+
function main({ operation, algorithm, length, n }) {
75+
const configuration = configurations[algorithm];
76+
const data = Buffer.alloc(length, 0x61);
77+
78+
switch (operation) {
79+
case 'get-macs-cold':
80+
measureGetMacs(n, false);
81+
break;
82+
case 'get-macs-warm':
83+
measureGetMacs(n, true);
84+
break;
85+
case 'create-cold':
86+
measureCreate(configuration, n, false);
87+
break;
88+
case 'create-warm':
89+
measureCreate(configuration, n, true);
90+
break;
91+
case 'create-hmac':
92+
measureCreateHmac(configuration, data, n);
93+
break;
94+
case 'update':
95+
measureUpdate(configuration, data, n);
96+
break;
97+
case 'stream':
98+
measureStream(configuration, data, n);
99+
break;
100+
default:
101+
throw new Error(`unknown operation: ${operation}`);
102+
}
103+
}
104+
105+
function measureGetMacs(n, warm) {
106+
if (warm)
107+
getMacs();
108+
109+
let result;
110+
bench.start();
111+
for (let i = 0; i < n; ++i)
112+
result = getMacs();
113+
bench.end(n);
114+
115+
assert(Array.isArray(result));
116+
}
117+
118+
function measureCreate({ algorithm, key, options }, n, warm) {
119+
if (warm)
120+
createMac(algorithm, key, options).final();
121+
122+
const contexts = new Array(n);
123+
bench.start();
124+
for (let i = 0; i < n; ++i)
125+
contexts[i] = createMac(algorithm, key, options);
126+
bench.end(n);
127+
128+
assert.strictEqual(typeof contexts[n - 1], 'object');
129+
}
130+
131+
function measureCreateHmac({ key, options }, data, n) {
132+
createHmac(options.digest, key).update(data).digest();
133+
134+
let result;
135+
bench.start();
136+
for (let i = 0; i < n; ++i)
137+
result = createHmac(options.digest, key).update(data).digest();
138+
bench.end(n);
139+
140+
assert(Buffer.isBuffer(result));
141+
}
142+
143+
function measureUpdate({ algorithm, key, options }, data, n) {
144+
createMac(algorithm, key, options).update(data).final();
145+
146+
let result;
147+
bench.start();
148+
for (let i = 0; i < n; ++i)
149+
result = createMac(algorithm, key, options).update(data).final();
150+
bench.end(n);
151+
152+
assert(Buffer.isBuffer(result));
153+
}
154+
155+
function measureStream({ algorithm, key, options }, data, n) {
156+
const warmup = createMac(algorithm, key, options);
157+
warmup.end(data);
158+
warmup.read();
159+
160+
let result;
161+
bench.start();
162+
for (let i = 0; i < n; ++i) {
163+
const context = createMac(algorithm, key, options);
164+
context.end(data);
165+
result = context.read();
166+
}
167+
bench.end(n);
168+
169+
assert(Buffer.isBuffer(result));
170+
}

‎deps/ncrypto/ncrypto.cc‎

Lines changed: 100 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7200,6 +7200,79 @@ EVPMacPointer EVPMacPointer::Fetch(const char* algorithm) {
72007200
return EVPMacPointer(EVP_MAC_fetch(nullptr, algorithm, nullptr));
72017201
}
72027202

7203+
MacKind MacCache::GetKind(EVP_MAC* mac) {
7204+
if (EVP_MAC_is_a(mac, OSSL_MAC_NAME_HMAC)) return MacKind::kHmac;
7205+
if (EVP_MAC_is_a(mac, OSSL_MAC_NAME_CMAC)) return MacKind::kCmac;
7206+
if (EVP_MAC_is_a(mac, OSSL_MAC_NAME_GMAC)) return MacKind::kGmac;
7207+
return MacKind::kOther;
7208+
}
7209+
7210+
MacCache::Result MacCache::lookup(const char* name, uint64_t generation) const {
7211+
if (generation_ != generation || name == nullptr) return {};
7212+
const auto it = aliases_.find(name);
7213+
if (it == aliases_.end()) return {};
7214+
return lookup(it->second, generation);
7215+
}
7216+
7217+
MacCache::Result MacCache::insert(const char* name,
7218+
EVPMacPointer&& mac,
7219+
uint64_t generation) {
7220+
if (generation_ != generation || generation != getFipsStateGeneration() ||
7221+
name == nullptr || mac == nullptr) {
7222+
return {};
7223+
}
7224+
7225+
const char* canonical_name = EVP_MAC_get0_name(mac.get());
7226+
const OSSL_PROVIDER* provider = EVP_MAC_get0_provider(mac.get());
7227+
if (canonical_name == nullptr || provider == nullptr) return {};
7228+
7229+
for (size_t index = 0; index < macs_.size(); index++) {
7230+
EVP_MAC* cached = macs_[index].mac.get();
7231+
if (cached == nullptr) continue;
7232+
const char* cached_name = EVP_MAC_get0_name(cached);
7233+
if (EVP_MAC_get0_provider(cached) == provider && cached_name != nullptr &&
7234+
CaseInsensitiveNameEqual()(cached_name, canonical_name)) {
7235+
if (generation != getFipsStateGeneration()) return {};
7236+
const int32_t id = static_cast<int32_t>(first_id_ + index);
7237+
aliases_.insert_or_assign(name, id);
7238+
return {cached, id, macs_[index].kind};
7239+
}
7240+
}
7241+
7242+
if (next_id_ == UINT32_MAX) return {};
7243+
7244+
std::vector<std::string> aliases;
7245+
{
7246+
MarkPopErrorOnReturn mark_pop_error_on_return;
7247+
if (EVP_MAC_names_do_all(mac.get(), PushAlgorithmAlias, &aliases) != 1) {
7248+
return {};
7249+
}
7250+
}
7251+
if (generation != getFipsStateGeneration()) return {};
7252+
7253+
const MacKind kind = GetKind(mac.get());
7254+
macs_.push_back({std::move(mac), kind});
7255+
const int32_t id = static_cast<int32_t>(next_id_++);
7256+
const size_t index = macs_.size() - 1;
7257+
7258+
for (const std::string& alias : aliases) aliases_.emplace(alias, id);
7259+
aliases_.insert_or_assign(name, id);
7260+
7261+
return {macs_[index].mac.get(), id, kind};
7262+
}
7263+
7264+
void MacCache::reset(uint64_t generation) {
7265+
if (generation_ == generation) return;
7266+
aliases_.clear();
7267+
macs_.clear();
7268+
first_id_ = next_id_;
7269+
generation_ = generation;
7270+
}
7271+
7272+
const MacCache::AliasMap& MacCache::aliases() const {
7273+
return aliases_;
7274+
}
7275+
72037276
EVPMacCtxPointer::EVPMacCtxPointer(EVP_MAC_CTX* ctx) : ctx_(ctx) {}
72047277

72057278
EVPMacCtxPointer::EVPMacCtxPointer(EVPMacCtxPointer&& other) noexcept
@@ -7227,22 +7300,42 @@ EVP_MAC_CTX* EVPMacCtxPointer::release() {
72277300
bool EVPMacCtxPointer::init(const Buffer<const void>& key,
72287301
const OSSL_PARAM* params) {
72297302
if (!ctx_) return false;
7230-
return EVP_MAC_init(ctx_.get(),
7231-
static_cast<const unsigned char*>(key.data),
7232-
key.len,
7233-
params) == 1;
7303+
7304+
static constexpr unsigned char kEmptyKey = 0;
7305+
const unsigned char* key_data = static_cast<const unsigned char*>(key.data);
7306+
if (key_data == nullptr) {
7307+
if (key.len != 0) return false;
7308+
key_data = &kEmptyKey;
7309+
}
7310+
7311+
return EVP_MAC_init(ctx_.get(), key_data, key.len, params) == 1;
72347312
}
72357313

72367314
bool EVPMacCtxPointer::update(const Buffer<const void>& data) {
72377315
if (!ctx_) return false;
7316+
if (data.len == 0) return true;
7317+
if (data.data == nullptr) return false;
72387318
return EVP_MAC_update(ctx_.get(),
72397319
static_cast<const unsigned char*>(data.data),
72407320
data.len) == 1;
72417321
}
72427322

7323+
size_t EVPMacCtxPointer::getSize() const {
7324+
return ctx_ ? EVP_MAC_CTX_get_mac_size(ctx_.get()) : 0;
7325+
}
7326+
7327+
const OSSL_PARAM* EVPMacCtxPointer::getSettableParams() const {
7328+
return ctx_ ? EVP_MAC_CTX_settable_params(ctx_.get()) : nullptr;
7329+
}
7330+
72437331
DataPointer EVPMacCtxPointer::final(size_t length) {
72447332
if (!ctx_) return {};
7245-
auto buf = DataPointer::Alloc(length);
7333+
7334+
// DataPointer uses a null allocation to represent failure. Retain a
7335+
// one-byte allocation for a successful zero-length result while passing the
7336+
// requested zero capacity to OpenSSL. A non-null output pointer is required
7337+
// to actually finalize; nullptr only queries the output length.
7338+
auto buf = DataPointer::Alloc(length == 0 ? 1 : length);
72467339
if (!buf) return {};
72477340

72487341
size_t result_len = length;
@@ -7252,8 +7345,9 @@ DataPointer EVPMacCtxPointer::final(size_t length) {
72527345
length) != 1) {
72537346
return {};
72547347
}
7348+
if (result_len > length) return {};
72557349

7256-
return buf;
7350+
return buf.resize(result_len);
72577351
}
72587352

72597353
EVPMacCtxPointer EVPMacCtxPointer::New(EVP_MAC* mac) {

0 commit comments

Comments
 (0)