Skip to content

Commit eb8c502

Browse files
committed
fixup! crypto: add a generic MAC API
1 parent daa1bd3 commit eb8c502

2 files changed

Lines changed: 228 additions & 3 deletions

File tree

doc/api/crypto.md

Lines changed: 185 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
<!-- source_link=lib/crypto.js -->
88

99
The `node:crypto` module provides cryptographic functionality that includes a
10-
set of wrappers for OpenSSL's hash, HMAC, cipher, decipher, sign, and verify
11-
functions.
10+
set of wrappers for OpenSSL's hash, message authentication code (MAC), cipher,
11+
decipher, sign, verify, and key encapsulation mechanism (KEM) functions.
1212

1313
```mjs
1414
const { createHmac } = await import('node:crypto');
@@ -2543,6 +2543,91 @@ Depending on the type of this `KeyObject`, this property is either
25432543
`'secret'` for secret (symmetric) keys, `'public'` for public (asymmetric) keys
25442544
or `'private'` for private (asymmetric) keys.
25452545

2546+
## Class: `Mac`
2547+
2548+
<!-- YAML
2549+
added: REPLACEME
2550+
-->
2551+
2552+
* Extends: {stream.Transform}
2553+
2554+
The `Mac` class computes message authentication codes using MAC
2555+
implementations supplied by OpenSSL providers. It can be used in one of two
2556+
ways:
2557+
2558+
* As a [stream][] that is both readable and writable, where data is written and
2559+
one authentication tag is produced on the readable side when the writable
2560+
side ends; or
2561+
* By calling [`mac.update()`][] one or more times followed by [`mac.final()`][].
2562+
2563+
Instances of `Mac` are created using [`crypto.createMac()`][]. The `Mac` class
2564+
is not exported directly by the `node:crypto` module.
2565+
2566+
Calling `mac.end()` without first writing data computes the authentication tag
2567+
for an empty message. If the selected MAC produces a zero-byte tag, such as
2568+
when a provider accepts `outputLength: 0`, the readable side ends without
2569+
emitting a data chunk because Node.js streams do not emit zero-length chunks.
2570+
When using `mac.final()` instead, it returns a zero-length [`Buffer`][] or an
2571+
empty encoded string.
2572+
2573+
`mac.end()` and `mac.final()` are alternative terminal operations and must not
2574+
both be called on the same object. A `Mac` object cannot be used again after
2575+
either operation attempts finalization or after an underlying MAC update fails.
2576+
2577+
Example: Using [`mac.update()`][] and [`mac.final()`][]:
2578+
2579+
```mjs
2580+
const { createMac, randomBytes } = await import('node:crypto');
2581+
2582+
const key = randomBytes(16);
2583+
const mac = createMac('CMAC', key, {
2584+
cipher: 'AES-128-CBC',
2585+
});
2586+
2587+
mac.update('some data to authenticate');
2588+
console.log(mac.final('hex'));
2589+
```
2590+
2591+
### `mac.final([outputEncoding])`
2592+
2593+
<!-- YAML
2594+
added: REPLACEME
2595+
-->
2596+
2597+
* `outputEncoding` {string} The [encoding][] of the return value.
2598+
* Returns: {Buffer | string}
2599+
2600+
Completes the MAC computation and returns the authentication tag. If
2601+
`outputEncoding` is omitted or is `'buffer'`, a [`Buffer`][] is returned.
2602+
Otherwise, a string is returned.
2603+
2604+
To verify an authentication tag, compare equal-length [`Buffer`][] values using
2605+
[`crypto.timingSafeEqual()`][].
2606+
2607+
The `Mac` object cannot be used again after finalization is attempted,
2608+
including when finalization fails. Later calls to `mac.update()` or
2609+
`mac.final()` throw `ERR_CRYPTO_MAC_FINALIZED`.
2610+
2611+
### `mac.update(data[, inputEncoding])`
2612+
2613+
<!-- YAML
2614+
added: REPLACEME
2615+
-->
2616+
2617+
* `data` {string|Buffer|TypedArray|DataView}
2618+
* `inputEncoding` {string} The [encoding][] of the `data` string.
2619+
* Returns: {Mac}
2620+
2621+
Updates the MAC with `data` and returns the `Mac` object so that calls can be
2622+
chained. When `data` is a string, `inputEncoding` defaults to `'utf8'`. When
2623+
`data` is a [`Buffer`][], `TypedArray`, or `DataView`, `inputEncoding` is
2624+
ignored.
2625+
2626+
This method can be called multiple times before finalization. If an underlying
2627+
MAC update fails, the `Mac` object cannot be used again. Calling this method
2628+
after a previous underlying MAC update failure or after finalization throws
2629+
`ERR_CRYPTO_MAC_FINALIZED`.
2630+
25462631
## Class: `Sign`
25472632

25482633
<!-- YAML
@@ -4076,6 +4161,65 @@ input.on('readable', () => {
40764161
});
40774162
```
40784163

4164+
### `crypto.createMac(algorithm, key[, options])`
4165+
4166+
<!-- YAML
4167+
added: REPLACEME
4168+
-->
4169+
4170+
> Stability: 1.2 - Release candidate
4171+
4172+
* `algorithm` {string} The name of the MAC algorithm.
4173+
* `key` {ArrayBuffer|Buffer|TypedArray|DataView|KeyObject}
4174+
* `options` {Object} [`stream.transform` options][]
4175+
* `digest` {string} The digest used by a MAC such as HMAC.
4176+
* `cipher` {string} The cipher used by a MAC such as CMAC or GMAC.
4177+
* `iv` {ArrayBuffer|Buffer|TypedArray|DataView} The initialization vector for
4178+
a MAC such as GMAC.
4179+
* `customization` {ArrayBuffer|Buffer|TypedArray|DataView} A customization
4180+
byte string for MACs that support it, such as KMAC.
4181+
* `salt` {ArrayBuffer|Buffer|TypedArray|DataView} A salt byte string for MACs
4182+
that support it, such as BLAKE2 MACs.
4183+
* `outputLength` {number} The requested provider output size in bytes. Must be
4184+
an unsigned 32-bit integer. Provider-specific restrictions also apply.
4185+
* Returns: {Mac}
4186+
4187+
`algorithm` must be a non-empty provider MAC name. The MAC-specific properties
4188+
listed above are extensions to the standard [`stream.transform` options][] and
4189+
are passed only when the selected provider implementation advertises the
4190+
corresponding parameter with the expected type. A supplied MAC-specific option
4191+
that the selected implementation does not support causes an error.
4192+
4193+
The following table summarizes the MAC-specific options accepted by MAC
4194+
implementations in OpenSSL's built-in providers. The `key` argument is required
4195+
for every MAC. The table lists only MAC-specific options; standard
4196+
[`stream.transform` options][] remain available for every family.
4197+
4198+
| MAC family | Required options | Optional options | Notes |
4199+
| ---------- | --------------------------------------- | --------------------------------------- | ---------------------------------------------------------------------- |
4200+
| HMAC | `digest` | None | |
4201+
| CMAC | `cipher` using CBC mode | None | |
4202+
| GMAC | `cipher` using GCM mode, non-empty `iv` | None | Requires a unique IV for every message authenticated with a given key. |
4203+
| KMAC | None | `customization`, `outputLength` | |
4204+
| BLAKE2 MAC | None | `customization`, `salt`, `outputLength` | |
4205+
| Poly1305 | None | None | Each key must be used for only one message. |
4206+
| SipHash | None | `outputLength` | |
4207+
4208+
`outputLength` configures the output size of the provider MAC. It is never
4209+
implemented by computing a longer tag and truncating it. A value of `0` is
4210+
passed to the provider and is accepted only when that provider can initialize
4211+
and finalize the MAC with a zero-byte output. When `outputLength` is omitted,
4212+
the provider's default output size is used and must be nonzero.
4213+
4214+
The `key` must contain bytes or be a [`KeyObject`][] of type `secret`. Key
4215+
length and other key requirements are determined by the selected provider
4216+
implementation.
4217+
4218+
Available algorithms and their accepted parameters depend on the OpenSSL
4219+
version, loaded providers, and active default property query. Use
4220+
[`crypto.getMacs()`][] to list fetchable MAC names. A listed name can still
4221+
require options or a key with provider-specific properties.
4222+
40794223
### `crypto.createPrivateKey(key)`
40804224

40814225
<!-- YAML
@@ -5095,6 +5239,40 @@ const {
50955239
console.log(getHashes()); // ['DSA', 'DSA-SHA', 'DSA-SHA1', ...]
50965240
```
50975241

5242+
### `crypto.getMacs()`
5243+
5244+
<!-- YAML
5245+
added: REPLACEME
5246+
-->
5247+
5248+
> Stability: 1.2 - Release candidate
5249+
5250+
* Returns: {string\[]} A fresh array containing the sorted, lowercase names
5251+
and aliases of fetchable MAC implementations.
5252+
5253+
Returns MAC names exposed by loaded OpenSSL providers that match the active
5254+
default property query. Duplicate names and numeric OID aliases are omitted.
5255+
On builds without OpenSSL `EVP_MAC` support, this function returns an empty
5256+
array.
5257+
5258+
The returned names describe implementations that OpenSSL can fetch. They do not
5259+
guarantee that [`crypto.createMac()`][] can initialize the MAC without
5260+
additional options. A provider can require additional parameters or a key with
5261+
algorithm-specific properties, and it can expose parameters that this API does
5262+
not support.
5263+
5264+
After a successful FIPS mode change made with [`crypto.setFips()`][], subsequent
5265+
calls reflect the new mode, and newly created `Mac` objects use it. Existing
5266+
`Mac` objects continue using the provider implementation selected when they
5267+
were created.
5268+
5269+
```mjs
5270+
const { getMacs } = await import('node:crypto');
5271+
5272+
console.log(getMacs());
5273+
// ['blake2bmac', 'blake2smac', 'cmac', 'gmac', 'hmac', ...]
5274+
```
5275+
50985276
### `crypto.getRandomValues(typedArray)`
50995277

51005278
<!-- YAML
@@ -7456,6 +7634,7 @@ See the [list of SSL OP Flags][] for details.
74567634
[`crypto.createECDH()`]: #cryptocreateecdhcurvename
74577635
[`crypto.createHash()`]: #cryptocreatehashalgorithm-options
74587636
[`crypto.createHmac()`]: #cryptocreatehmacalgorithm-key-options
7637+
[`crypto.createMac()`]: #cryptocreatemacalgorithm-key-options
74597638
[`crypto.createPrivateKey()`]: #cryptocreateprivatekeykey
74607639
[`crypto.createPublicKey()`]: #cryptocreatepublickeykey
74617640
[`crypto.createSecretKey()`]: #cryptocreatesecretkeykey-encoding
@@ -7468,6 +7647,7 @@ See the [list of SSL OP Flags][] for details.
74687647
[`crypto.getDiffieHellman()`]: #cryptogetdiffiehellmangroupname
74697648
[`crypto.getFips()`]: #cryptogetfips
74707649
[`crypto.getHashes()`]: #cryptogethashes
7650+
[`crypto.getMacs()`]: #cryptogetmacs
74717651
[`crypto.hash()`]: #cryptohashalgorithm-data-options
74727652
[`crypto.privateDecrypt()`]: #cryptoprivatedecryptprivatekey-buffer
74737653
[`crypto.privateEncrypt()`]: #cryptoprivateencryptprivatekey-buffer
@@ -7477,6 +7657,7 @@ See the [list of SSL OP Flags][] for details.
74777657
[`crypto.randomFill()`]: #cryptorandomfillbuffer-offset-size-callback
74787658
[`crypto.setFips()`]: #cryptosetfipsbool
74797659
[`crypto.sign()`]: #cryptosignalgorithm-data-key-callback
7660+
[`crypto.timingSafeEqual()`]: #cryptotimingsafeequala-b
74807661
[`crypto.verify()`]: #cryptoverifyalgorithm-data-key-signature-callback
74817662
[`crypto.webcrypto.getRandomValues()`]: webcrypto.md#cryptogetrandomvaluestypedarray
74827663
[`crypto.webcrypto.subtle`]: webcrypto.md#class-subtlecrypto
@@ -7493,6 +7674,8 @@ See the [list of SSL OP Flags][] for details.
74937674
[`hmac.update()`]: #hmacupdatedata-inputencoding
74947675
[`import()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import
74957676
[`keyObject.export()`]: #keyobjectexportoptions
7677+
[`mac.final()`]: #macfinaloutputencoding
7678+
[`mac.update()`]: #macupdatedata-inputencoding
74967679
[`postMessage()`]: worker_threads.md#portpostmessagevalue-transferlist
74977680
[`sign.sign()`]: #signsignprivatekey-outputencoding
74987681
[`sign.update()`]: #signupdatedata-inputencoding

doc/api/errors.md

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -936,7 +936,7 @@ be called no more than one time per instance of a `Hash` object.
936936

937937
### `ERR_CRYPTO_HASH_UPDATE_FAILED`
938938

939-
[`hash.update()`][] failed for any reason. This should rarely, if ever, happen.
939+
[`hash.update()`][] failed for an unspecified reason.
940940

941941
<a id="ERR_CRYPTO_INCOMPATIBLE_KEY"></a>
942942

@@ -1052,6 +1052,16 @@ An invalid key type was provided.
10521052

10531053
The given crypto key object's type is invalid for the attempted operation.
10541054

1055+
<a id="ERR_CRYPTO_INVALID_MAC"></a>
1056+
1057+
### `ERR_CRYPTO_INVALID_MAC`
1058+
1059+
<!-- YAML
1060+
added: REPLACEME
1061+
-->
1062+
1063+
An invalid MAC algorithm was specified.
1064+
10551065
<a id="ERR_CRYPTO_INVALID_MESSAGELEN"></a>
10561066

10571067
### `ERR_CRYPTO_INVALID_MESSAGELEN`
@@ -1125,6 +1135,37 @@ added: v24.7.0
11251135
Attempted to use KEM operations while Node.js was not compiled with
11261136
OpenSSL with KEM support.
11271137

1138+
<a id="ERR_CRYPTO_MAC_FINALIZED"></a>
1139+
1140+
### `ERR_CRYPTO_MAC_FINALIZED`
1141+
1142+
<!-- YAML
1143+
added: REPLACEME
1144+
-->
1145+
1146+
An operation was attempted on a `Mac` object after finalization was attempted
1147+
or an underlying MAC update failed.
1148+
1149+
<a id="ERR_CRYPTO_MAC_NOT_SUPPORTED"></a>
1150+
1151+
### `ERR_CRYPTO_MAC_NOT_SUPPORTED`
1152+
1153+
<!-- YAML
1154+
added: REPLACEME
1155+
-->
1156+
1157+
Node.js was built without support for the OpenSSL `EVP_MAC` API.
1158+
1159+
<a id="ERR_CRYPTO_MAC_UPDATE_FAILED"></a>
1160+
1161+
### `ERR_CRYPTO_MAC_UPDATE_FAILED`
1162+
1163+
<!-- YAML
1164+
added: REPLACEME
1165+
-->
1166+
1167+
[`mac.update()`][] failed for an unspecified reason.
1168+
11281169
<a id="ERR_CRYPTO_OPERATION_FAILED"></a>
11291170

11301171
### `ERR_CRYPTO_OPERATION_FAILED`
@@ -4693,6 +4734,7 @@ An error occurred trying to allocate memory. This should never happen.
46934734
[`http`]: http.md
46944735
[`https`]: https.md
46954736
[`libuv Error handling`]: https://docs.libuv.org/en/v1.x/errors.html
4737+
[`mac.update()`]: crypto.md#macupdatedata-inputencoding
46964738
[`net.Server`]: net.md#class-netserver
46974739
[`net.Socket.write()`]: net.md#socketwritedata-encoding-callback
46984740
[`net.Socket`]: net.md#class-netsocket

0 commit comments

Comments
 (0)