crypto: add KMAC Web Cryptography algorithms

PR-URL: https://github.com/nodejs/node/pull/59647
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
Filip Skokan 2025-09-07 00:43:15 +02:00 committed by GitHub
parent a7fde8a86f
commit 14c68e3b53
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 1402 additions and 79 deletions

View File

@ -4413,6 +4413,96 @@ HMACCtxPointer HMACCtxPointer::New() {
return HMACCtxPointer(HMAC_CTX_new()); return HMACCtxPointer(HMAC_CTX_new());
} }
#if OPENSSL_VERSION_MAJOR >= 3
EVPMacPointer::EVPMacPointer(EVP_MAC* mac) : mac_(mac) {}
EVPMacPointer::EVPMacPointer(EVPMacPointer&& other) noexcept
: mac_(std::move(other.mac_)) {}
EVPMacPointer& EVPMacPointer::operator=(EVPMacPointer&& other) noexcept {
if (this == &other) return *this;
mac_ = std::move(other.mac_);
return *this;
}
EVPMacPointer::~EVPMacPointer() {
mac_.reset();
}
void EVPMacPointer::reset(EVP_MAC* mac) {
mac_.reset(mac);
}
EVP_MAC* EVPMacPointer::release() {
return mac_.release();
}
EVPMacPointer EVPMacPointer::Fetch(const char* algorithm) {
return EVPMacPointer(EVP_MAC_fetch(nullptr, algorithm, nullptr));
}
EVPMacCtxPointer::EVPMacCtxPointer(EVP_MAC_CTX* ctx) : ctx_(ctx) {}
EVPMacCtxPointer::EVPMacCtxPointer(EVPMacCtxPointer&& other) noexcept
: ctx_(std::move(other.ctx_)) {}
EVPMacCtxPointer& EVPMacCtxPointer::operator=(
EVPMacCtxPointer&& other) noexcept {
if (this == &other) return *this;
ctx_ = std::move(other.ctx_);
return *this;
}
EVPMacCtxPointer::~EVPMacCtxPointer() {
ctx_.reset();
}
void EVPMacCtxPointer::reset(EVP_MAC_CTX* ctx) {
ctx_.reset(ctx);
}
EVP_MAC_CTX* EVPMacCtxPointer::release() {
return ctx_.release();
}
bool EVPMacCtxPointer::init(const Buffer<const void>& key,
const OSSL_PARAM* params) {
if (!ctx_) return false;
return EVP_MAC_init(ctx_.get(),
static_cast<const unsigned char*>(key.data),
key.len,
params) == 1;
}
bool EVPMacCtxPointer::update(const Buffer<const void>& data) {
if (!ctx_) return false;
return EVP_MAC_update(ctx_.get(),
static_cast<const unsigned char*>(data.data),
data.len) == 1;
}
DataPointer EVPMacCtxPointer::final(size_t length) {
if (!ctx_) return {};
auto buf = DataPointer::Alloc(length);
if (!buf) return {};
size_t result_len = length;
if (EVP_MAC_final(ctx_.get(),
static_cast<unsigned char*>(buf.get()),
&result_len,
length) != 1) {
return {};
}
return buf;
}
EVPMacCtxPointer EVPMacCtxPointer::New(EVP_MAC* mac) {
if (!mac) return EVPMacCtxPointer();
return EVPMacCtxPointer(EVP_MAC_CTX_new(mac));
}
#endif // OPENSSL_VERSION_MAJOR >= 3
DataPointer hashDigest(const Buffer<const unsigned char>& buf, DataPointer hashDigest(const Buffer<const unsigned char>& buf,
const EVP_MD* md) { const EVP_MD* md) {
if (md == nullptr) return {}; if (md == nullptr) return {};

View File

@ -229,6 +229,8 @@ class DataPointer;
class DHPointer; class DHPointer;
class ECKeyPointer; class ECKeyPointer;
class EVPKeyPointer; class EVPKeyPointer;
class EVPMacCtxPointer;
class EVPMacPointer;
class EVPMDCtxPointer; class EVPMDCtxPointer;
class SSLCtxPointer; class SSLCtxPointer;
class SSLPointer; class SSLPointer;
@ -1451,6 +1453,56 @@ class HMACCtxPointer final {
DeleteFnPtr<HMAC_CTX, HMAC_CTX_free> ctx_; DeleteFnPtr<HMAC_CTX, HMAC_CTX_free> ctx_;
}; };
#if OPENSSL_VERSION_MAJOR >= 3
class EVPMacPointer final {
public:
EVPMacPointer() = default;
explicit EVPMacPointer(EVP_MAC* mac);
EVPMacPointer(EVPMacPointer&& other) noexcept;
EVPMacPointer& operator=(EVPMacPointer&& other) noexcept;
NCRYPTO_DISALLOW_COPY(EVPMacPointer)
~EVPMacPointer();
inline bool operator==(std::nullptr_t) noexcept { return mac_ == nullptr; }
inline operator bool() const { return mac_ != nullptr; }
inline EVP_MAC* get() const { return mac_.get(); }
inline operator EVP_MAC*() const { return mac_.get(); }
void reset(EVP_MAC* mac = nullptr);
EVP_MAC* release();
static EVPMacPointer Fetch(const char* algorithm);
private:
DeleteFnPtr<EVP_MAC, EVP_MAC_free> mac_;
};
class EVPMacCtxPointer final {
public:
EVPMacCtxPointer() = default;
explicit EVPMacCtxPointer(EVP_MAC_CTX* ctx);
EVPMacCtxPointer(EVPMacCtxPointer&& other) noexcept;
EVPMacCtxPointer& operator=(EVPMacCtxPointer&& other) noexcept;
NCRYPTO_DISALLOW_COPY(EVPMacCtxPointer)
~EVPMacCtxPointer();
inline bool operator==(std::nullptr_t) noexcept { return ctx_ == nullptr; }
inline operator bool() const { return ctx_ != nullptr; }
inline EVP_MAC_CTX* get() const { return ctx_.get(); }
inline operator EVP_MAC_CTX*() const { return ctx_.get(); }
void reset(EVP_MAC_CTX* ctx = nullptr);
EVP_MAC_CTX* release();
bool init(const Buffer<const void>& key, const OSSL_PARAM* params = nullptr);
bool update(const Buffer<const void>& data);
DataPointer final(size_t length);
static EVPMacCtxPointer New(EVP_MAC* mac);
private:
DeleteFnPtr<EVP_MAC_CTX, EVP_MAC_CTX_free> ctx_;
};
#endif // OPENSSL_VERSION_MAJOR >= 3
#ifndef OPENSSL_NO_ENGINE #ifndef OPENSSL_NO_ENGINE
class EnginePointer final { class EnginePointer final {
public: public:

View File

@ -2,6 +2,9 @@
<!-- YAML <!-- YAML
changes: changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/59647
description: KMAC algorithms are now supported.
- version: REPLACEME - version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/59544 pr-url: https://github.com/nodejs/node/pull/59544
description: Argon2 algorithms are now supported. description: Argon2 algorithms are now supported.
@ -117,6 +120,8 @@ Algorithms:
* `'ChaCha20-Poly1305'` * `'ChaCha20-Poly1305'`
* `'cSHAKE128'` * `'cSHAKE128'`
* `'cSHAKE256'` * `'cSHAKE256'`
* `'KMAC128'`[^openssl30]
* `'KMAC256'`[^openssl30]
* `'ML-DSA-44'`[^openssl35] * `'ML-DSA-44'`[^openssl35]
* `'ML-DSA-65'`[^openssl35] * `'ML-DSA-65'`[^openssl35]
* `'ML-DSA-87'`[^openssl35] * `'ML-DSA-87'`[^openssl35]
@ -522,6 +527,8 @@ implementation and the APIs supported for each:
| `'Ed448'`[^secure-curves] | ✔ | ✔ | ✔ | ✔ | | `'Ed448'`[^secure-curves] | ✔ | ✔ | ✔ | ✔ |
| `'HKDF'` | | | ✔ | | | `'HKDF'` | | | ✔ | |
| `'HMAC'` | ✔ | ✔ | ✔ | | | `'HMAC'` | ✔ | ✔ | ✔ | |
| `'KMAC128'`[^modern-algos] | ✔ | ✔ | ✔ | |
| `'KMAC256'`[^modern-algos] | ✔ | ✔ | ✔ | |
| `'ML-DSA-44'`[^modern-algos] | ✔ | ✔ | ✔ | ✔ | | `'ML-DSA-44'`[^modern-algos] | ✔ | ✔ | ✔ | ✔ |
| `'ML-DSA-65'`[^modern-algos] | ✔ | ✔ | ✔ | ✔ | | `'ML-DSA-65'`[^modern-algos] | ✔ | ✔ | ✔ | ✔ |
| `'ML-DSA-87'`[^modern-algos] | ✔ | ✔ | ✔ | ✔ | | `'ML-DSA-87'`[^modern-algos] | ✔ | ✔ | ✔ | ✔ |
@ -566,6 +573,8 @@ implementation and the APIs supported for each:
| `'Ed448'`[^secure-curves] | | ✔ | | | | | | `'Ed448'`[^secure-curves] | | ✔ | | | | |
| `'HKDF'` | | | ✔ | | | | | `'HKDF'` | | | ✔ | | | |
| `'HMAC'` | | ✔ | | | | | | `'HMAC'` | | ✔ | | | | |
| `'KMAC128'`[^modern-algos] | | ✔ | | | | |
| `'KMAC256'`[^modern-algos] | | ✔ | | | | |
| `'ML-DSA-44'`[^modern-algos] | | ✔ | | | | | | `'ML-DSA-44'`[^modern-algos] | | ✔ | | | | |
| `'ML-DSA-65'`[^modern-algos] | | ✔ | | | | | | `'ML-DSA-65'`[^modern-algos] | | ✔ | | | | |
| `'ML-DSA-87'`[^modern-algos] | | ✔ | | | | | | `'ML-DSA-87'`[^modern-algos] | | ✔ | | | | |
@ -648,7 +657,7 @@ added: v15.0.0
<!--lint disable maximum-line-length remark-lint--> <!--lint disable maximum-line-length remark-lint-->
* Type: {KeyAlgorithm|RsaHashedKeyAlgorithm|EcKeyAlgorithm|AesKeyAlgorithm|HmacKeyAlgorithm} * Type: {KeyAlgorithm|RsaHashedKeyAlgorithm|EcKeyAlgorithm|AesKeyAlgorithm|HmacKeyAlgorithm|KmacKeyAlgorithm}
<!--lint enable maximum-line-length remark-lint--> <!--lint enable maximum-line-length remark-lint-->
@ -736,6 +745,8 @@ Valid key usages depend on the key algorithm (identified by
| `'Ed448'`[^secure-curves] | | ✔ | | | | | `'Ed448'`[^secure-curves] | | ✔ | | | |
| `'HDKF'` | | | ✔ | | | | `'HDKF'` | | | ✔ | | |
| `'HMAC'` | | ✔ | | | | | `'HMAC'` | | ✔ | | | |
| `'KMAC128'`[^modern-algos] | | ✔ | | | |
| `'KMAC256'`[^modern-algos] | | ✔ | | | |
| `'ML-DSA-44'`[^modern-algos] | | ✔ | | | | | `'ML-DSA-44'`[^modern-algos] | | ✔ | | | |
| `'ML-DSA-65'`[^modern-algos] | | ✔ | | | | | `'ML-DSA-65'`[^modern-algos] | | ✔ | | | |
| `'ML-DSA-87'`[^modern-algos] | | ✔ | | | | | `'ML-DSA-87'`[^modern-algos] | | ✔ | | | |
@ -831,7 +842,7 @@ added: v24.7.0
* `decapsulationAlgorithm` {string|Algorithm} * `decapsulationAlgorithm` {string|Algorithm}
* `decapsulationKey` {CryptoKey} * `decapsulationKey` {CryptoKey}
* `ciphertext` {ArrayBuffer|TypedArray|DataView|Buffer} * `ciphertext` {ArrayBuffer|TypedArray|DataView|Buffer}
* `sharedKeyAlgorithm` {string|Algorithm|HmacImportParams|AesDerivedKeyParams} * `sharedKeyAlgorithm` {string|Algorithm|HmacImportParams|AesDerivedKeyParams|KmacImportParams}
* `extractable` {boolean} * `extractable` {boolean}
* `usages` {string\[]} See [Key usages][]. * `usages` {string\[]} See [Key usages][].
* Returns: {Promise} Fulfills with {CryptoKey} upon success. * Returns: {Promise} Fulfills with {CryptoKey} upon success.
@ -946,7 +957,7 @@ changes:
* `algorithm` {EcdhKeyDeriveParams|HkdfParams|Pbkdf2Params|Argon2Params} * `algorithm` {EcdhKeyDeriveParams|HkdfParams|Pbkdf2Params|Argon2Params}
* `baseKey` {CryptoKey} * `baseKey` {CryptoKey}
* `derivedKeyAlgorithm` {string|Algorithm|HmacImportParams|AesDerivedKeyParams} * `derivedKeyAlgorithm` {string|Algorithm|HmacImportParams|AesDerivedKeyParams|KmacImportParams}
* `extractable` {boolean} * `extractable` {boolean}
* `keyUsages` {string\[]} See [Key usages][]. * `keyUsages` {string\[]} See [Key usages][].
* Returns: {Promise} Fulfills with a {CryptoKey} upon success. * Returns: {Promise} Fulfills with a {CryptoKey} upon success.
@ -1037,7 +1048,7 @@ added: v24.7.0
* `encapsulationAlgorithm` {string|Algorithm} * `encapsulationAlgorithm` {string|Algorithm}
* `encapsulationKey` {CryptoKey} * `encapsulationKey` {CryptoKey}
* `sharedKeyAlgorithm` {string|Algorithm|HmacImportParams|AesDerivedKeyParams} * `sharedKeyAlgorithm` {string|Algorithm|HmacImportParams|AesDerivedKeyParams|KmacImportParams}
* `extractable` {boolean} * `extractable` {boolean}
* `usages` {string\[]} See [Key usages][]. * `usages` {string\[]} See [Key usages][].
* Returns: {Promise} Fulfills with {EncapsulatedKey} upon success. * Returns: {Promise} Fulfills with {EncapsulatedKey} upon success.
@ -1085,6 +1096,9 @@ The algorithms currently supported include:
<!-- YAML <!-- YAML
added: v15.0.0 added: v15.0.0
changes: changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/59647
description: KMAC algorithms are now supported.
- version: v24.7.0 - version: v24.7.0
pr-url: https://github.com/nodejs/node/pull/59569 pr-url: https://github.com/nodejs/node/pull/59569
description: ML-KEM algorithms are now supported. description: ML-KEM algorithms are now supported.
@ -1135,6 +1149,8 @@ specification.
| `'Ed25519'` | ✔ | ✔ | ✔ | ✔ | | ✔ | | | `'Ed25519'` | ✔ | ✔ | ✔ | ✔ | | ✔ | |
| `'Ed448'`[^secure-curves] | ✔ | ✔ | ✔ | ✔ | | ✔ | | | `'Ed448'`[^secure-curves] | ✔ | ✔ | ✔ | ✔ | | ✔ | |
| `'HMAC'` | | | ✔ | ✔ | ✔ | | | | `'HMAC'` | | | ✔ | ✔ | ✔ | | |
| `'KMAC128'`[^modern-algos] | | | ✔ | | ✔ | | |
| `'KMAC256'`[^modern-algos] | | | ✔ | | ✔ | | |
| `'ML-DSA-44'`[^modern-algos] | ✔ | ✔ | ✔ | | | ✔ | ✔ | | `'ML-DSA-44'`[^modern-algos] | ✔ | ✔ | ✔ | | | ✔ | ✔ |
| `'ML-DSA-65'`[^modern-algos] | ✔ | ✔ | ✔ | | | ✔ | ✔ | | `'ML-DSA-65'`[^modern-algos] | ✔ | ✔ | ✔ | | | ✔ | ✔ |
| `'ML-DSA-87'`[^modern-algos] | ✔ | ✔ | ✔ | | | ✔ | ✔ | | `'ML-DSA-87'`[^modern-algos] | ✔ | ✔ | ✔ | | | ✔ | ✔ |
@ -1164,6 +1180,9 @@ Derives the public key from a given private key.
<!-- YAML <!-- YAML
added: v15.0.0 added: v15.0.0
changes: changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/59647
description: KMAC algorithms are now supported.
- version: v24.7.0 - version: v24.7.0
pr-url: https://github.com/nodejs/node/pull/59569 pr-url: https://github.com/nodejs/node/pull/59569
description: ML-KEM algorithms are now supported. description: ML-KEM algorithms are now supported.
@ -1177,7 +1196,7 @@ changes:
<!--lint disable maximum-line-length remark-lint--> <!--lint disable maximum-line-length remark-lint-->
* `algorithm` {string|Algorithm|RsaHashedKeyGenParams|EcKeyGenParams|HmacKeyGenParams|AesKeyGenParams} * `algorithm` {string|Algorithm|RsaHashedKeyGenParams|EcKeyGenParams|HmacKeyGenParams|AesKeyGenParams|KmacKeyGenParams}
<!--lint enable maximum-line-length remark-lint--> <!--lint enable maximum-line-length remark-lint-->
@ -1217,12 +1236,17 @@ The {CryptoKey} (secret key) generating algorithms supported include:
* `'AES-OCB'`[^modern-algos] * `'AES-OCB'`[^modern-algos]
* `'ChaCha20-Poly1305'`[^modern-algos] * `'ChaCha20-Poly1305'`[^modern-algos]
* `'HMAC'` * `'HMAC'`
* `'KMAC128'`[^modern-algos]
* `'KMAC256'`[^modern-algos]
### `subtle.importKey(format, keyData, algorithm, extractable, keyUsages)` ### `subtle.importKey(format, keyData, algorithm, extractable, keyUsages)`
<!-- YAML <!-- YAML
added: v15.0.0 added: v15.0.0
changes: changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/59647
description: KMAC algorithms are now supported.
- version: v24.7.0 - version: v24.7.0
pr-url: https://github.com/nodejs/node/pull/59569 pr-url: https://github.com/nodejs/node/pull/59569
description: ML-KEM algorithms are now supported. description: ML-KEM algorithms are now supported.
@ -1249,7 +1273,7 @@ changes:
<!--lint disable maximum-line-length remark-lint--> <!--lint disable maximum-line-length remark-lint-->
* `algorithm` {string|Algorithm|RsaHashedImportParams|EcKeyImportParams|HmacImportParams} * `algorithm` {string|Algorithm|RsaHashedImportParams|EcKeyImportParams|HmacImportParams|KmacImportParams}
<!--lint enable maximum-line-length remark-lint--> <!--lint enable maximum-line-length remark-lint-->
@ -1283,6 +1307,8 @@ The algorithms currently supported include:
| `'Ed448'`[^secure-curves] | ✔ | ✔ | ✔ | ✔ | | ✔ | | | `'Ed448'`[^secure-curves] | ✔ | ✔ | ✔ | ✔ | | ✔ | |
| `'HDKF'` | | | | ✔ | ✔ | | | | `'HDKF'` | | | | ✔ | ✔ | | |
| `'HMAC'` | | | ✔ | ✔ | ✔ | | | | `'HMAC'` | | | ✔ | ✔ | ✔ | | |
| `'KMAC128'`[^modern-algos] | | | ✔ | | ✔ | | |
| `'KMAC256'`[^modern-algos] | | | ✔ | | ✔ | | |
| `'ML-DSA-44'`[^modern-algos] | ✔ | ✔ | ✔ | | | ✔ | ✔ | | `'ML-DSA-44'`[^modern-algos] | ✔ | ✔ | ✔ | | | ✔ | ✔ |
| `'ML-DSA-65'`[^modern-algos] | ✔ | ✔ | ✔ | | | ✔ | ✔ | | `'ML-DSA-65'`[^modern-algos] | ✔ | ✔ | ✔ | | | ✔ | ✔ |
| `'ML-DSA-87'`[^modern-algos] | ✔ | ✔ | ✔ | | | ✔ | ✔ | | `'ML-DSA-87'`[^modern-algos] | ✔ | ✔ | ✔ | | | ✔ | ✔ |
@ -1301,6 +1327,9 @@ The algorithms currently supported include:
<!-- YAML <!-- YAML
added: v15.0.0 added: v15.0.0
changes: changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/59647
description: KMAC algorithms are now supported.
- version: v24.7.0 - version: v24.7.0
pr-url: https://github.com/nodejs/node/pull/59365 pr-url: https://github.com/nodejs/node/pull/59365
description: ML-DSA algorithms are now supported. description: ML-DSA algorithms are now supported.
@ -1313,7 +1342,7 @@ changes:
<!--lint disable maximum-line-length remark-lint--> <!--lint disable maximum-line-length remark-lint-->
* `algorithm` {string|Algorithm|RsaPssParams|EcdsaParams|Ed448Params|ContextParams} * `algorithm` {string|Algorithm|RsaPssParams|EcdsaParams|Ed448Params|ContextParams|KmacParams}
* `key` {CryptoKey} * `key` {CryptoKey}
* `data` {ArrayBuffer|TypedArray|DataView|Buffer} * `data` {ArrayBuffer|TypedArray|DataView|Buffer}
* Returns: {Promise} Fulfills with an {ArrayBuffer} upon success. * Returns: {Promise} Fulfills with an {ArrayBuffer} upon success.
@ -1331,6 +1360,8 @@ The algorithms currently supported include:
* `'Ed25519'` * `'Ed25519'`
* `'Ed448'`[^secure-curves] * `'Ed448'`[^secure-curves]
* `'HMAC'` * `'HMAC'`
* `'KMAC128'`[^modern-algos]
* `'KMAC256'`[^modern-algos]
* `'ML-DSA-44'`[^modern-algos] * `'ML-DSA-44'`[^modern-algos]
* `'ML-DSA-65'`[^modern-algos] * `'ML-DSA-65'`[^modern-algos]
* `'ML-DSA-87'`[^modern-algos] * `'ML-DSA-87'`[^modern-algos]
@ -1358,7 +1389,7 @@ changes:
<!--lint disable maximum-line-length remark-lint--> <!--lint disable maximum-line-length remark-lint-->
* `unwrapAlgo` {string|Algorithm|RsaOaepParams|AesCtrParams|AesCbcParams|AeadParams} * `unwrapAlgo` {string|Algorithm|RsaOaepParams|AesCtrParams|AesCbcParams|AeadParams}
* `unwrappedKeyAlgo` {string|Algorithm|RsaHashedImportParams|EcKeyImportParams|HmacImportParams} * `unwrappedKeyAlgo` {string|Algorithm|RsaHashedImportParams|EcKeyImportParams|HmacImportParams|KmacImportParams}
<!--lint enable maximum-line-length remark-lint--> <!--lint enable maximum-line-length remark-lint-->
@ -1398,6 +1429,8 @@ The unwrapped key algorithms supported include:
* `'Ed25519'` * `'Ed25519'`
* `'Ed448'`[^secure-curves] * `'Ed448'`[^secure-curves]
* `'HMAC'` * `'HMAC'`
* `'KMAC128'`[^secure-curves]
* `'KMAC256'`[^secure-curves]
* `'ML-DSA-44'`[^modern-algos] * `'ML-DSA-44'`[^modern-algos]
* `'ML-DSA-65'`[^modern-algos] * `'ML-DSA-65'`[^modern-algos]
* `'ML-DSA-87'`[^modern-algos] * `'ML-DSA-87'`[^modern-algos]
@ -1415,6 +1448,9 @@ The unwrapped key algorithms supported include:
<!-- YAML <!-- YAML
added: v15.0.0 added: v15.0.0
changes: changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/59647
description: KMAC algorithms are now supported.
- version: v24.7.0 - version: v24.7.0
pr-url: https://github.com/nodejs/node/pull/59365 pr-url: https://github.com/nodejs/node/pull/59365
description: ML-DSA algorithms are now supported. description: ML-DSA algorithms are now supported.
@ -1446,6 +1482,8 @@ The algorithms currently supported include:
* `'Ed25519'` * `'Ed25519'`
* `'Ed448'`[^secure-curves] * `'Ed448'`[^secure-curves]
* `'HMAC'` * `'HMAC'`
* `'KMAC128'`[^secure-curves]
* `'KMAC256'`[^secure-curves]
* `'ML-DSA-44'`[^modern-algos] * `'ML-DSA-44'`[^modern-algos]
* `'ML-DSA-65'`[^modern-algos] * `'ML-DSA-65'`[^modern-algos]
* `'ML-DSA-87'`[^modern-algos] * `'ML-DSA-87'`[^modern-algos]
@ -2271,6 +2309,115 @@ added: v15.0.0
* Type: {string} * Type: {string}
### Class: `KmacImportParams`
<!-- YAML
added: REPLACEME
-->
#### `kmacImportParams.length`
<!-- YAML
added: REPLACEME
-->
* Type: {number}
The optional number of bits in the KMAC key. This is optional and should
be omitted for most cases.
#### `kmacImportParams.name`
<!-- YAML
added: REPLACEME
-->
* Type: {string} Must be `'KMAC128'` or `'KMAC256'`.
### Class: `KmacKeyAlgorithm`
<!-- YAML
added: REPLACEME
-->
#### `kmacKeyAlgorithm.length`
<!-- YAML
added: REPLACEME
-->
* Type: {number}
The length of the KMAC key in bits.
#### `kmacKeyAlgorithm.name`
<!-- YAML
added: REPLACEME
-->
* Type: {string}
### Class: `KmacKeyGenParams`
<!-- YAML
added: REPLACEME
-->
#### `kmacKeyGenParams.length`
<!-- YAML
added: REPLACEME
-->
* Type: {number}
The number of bits to generate for the KMAC key. If omitted,
the length will be determined by the KMAC algorithm used.
This is optional and should be omitted for most cases.
#### `kmacKeyGenParams.name`
<!-- YAML
added: REPLACEME
-->
* Type: {string} Must be `'KMAC128'` or `'KMAC256'`.
### Class: `KmacParams`
<!-- YAML
added: REPLACEME
-->
#### `kmacParams.algorithm`
<!-- YAML
added: REPLACEME
-->
* Type: {string} Must be `'KMAC128'` or `'KMAC256'`.
#### `kmacParams.customization`
<!-- YAML
added: REPLACEME
-->
* Type: {ArrayBuffer|TypedArray|DataView|Buffer|undefined}
The `customization` member represents the optional customization string.
#### `kmacParams.length`
<!-- YAML
added: REPLACEME
-->
* Type: {number}
The length of the output in bytes. This must be a positive integer.
### Class: `Pbkdf2Params` ### Class: `Pbkdf2Params`
<!-- YAML <!-- YAML

View File

@ -189,8 +189,12 @@ const {
let result; let result;
switch (algorithm.name) { switch (algorithm.name) {
case 'HMAC': case 'HMAC':
// Fall through
case 'KMAC128':
// Fall through
case 'KMAC256':
result = require('internal/crypto/mac') result = require('internal/crypto/mac')
.hmacImportKey('KeyObject', this, algorithm, extractable, keyUsages); .macImportKey('KeyObject', this, algorithm, extractable, keyUsages);
break; break;
case 'AES-CTR': case 'AES-CTR':
// Fall through // Fall through

View File

@ -3,11 +3,13 @@
const { const {
ArrayFrom, ArrayFrom,
SafeSet, SafeSet,
StringPrototypeSubstring,
} = primordials; } = primordials;
const { const {
HmacJob, HmacJob,
KeyObjectHandle, KeyObjectHandle,
KmacJob,
kCryptoJobAsync, kCryptoJobAsync,
kSignJobModeSign, kSignJobModeSign,
kSignJobModeVerify, kSignJobModeVerify,
@ -32,6 +34,13 @@ const {
generateKey: _generateKey, generateKey: _generateKey,
} = require('internal/crypto/keygen'); } = require('internal/crypto/keygen');
const {
randomBytes: _randomBytes,
} = require('internal/crypto/random');
const randomBytes = promisify(_randomBytes);
const { const {
InternalCryptoKey, InternalCryptoKey,
SecretKeyObject, SecretKeyObject,
@ -42,11 +51,11 @@ const {
const generateKey = promisify(_generateKey); const generateKey = promisify(_generateKey);
async function hmacGenerateKey(algorithm, extractable, keyUsages) { async function hmacGenerateKey(algorithm, extractable, keyUsages) {
const { hash, name } = algorithm; const {
let { length } = algorithm; hash,
name,
if (length === undefined) length = getBlockSize(hash.name),
length = getBlockSize(hash.name); } = algorithm;
const usageSet = new SafeSet(keyUsages); const usageSet = new SafeSet(keyUsages);
if (hasAnyNotIn(usageSet, ['sign', 'verify'])) { if (hasAnyNotIn(usageSet, ['sign', 'verify'])) {
@ -68,17 +77,49 @@ async function hmacGenerateKey(algorithm, extractable, keyUsages) {
extractable); extractable);
} }
function hmacImportKey( async function kmacGenerateKey(algorithm, extractable, keyUsages) {
const {
name,
length = {
__proto__: null,
KMAC128: 128,
KMAC256: 256,
}[name],
} = algorithm;
const usageSet = new SafeSet(keyUsages);
if (hasAnyNotIn(usageSet, ['sign', 'verify'])) {
throw lazyDOMException(
`Unsupported key usage for ${name} key`,
'SyntaxError');
}
const keyData = await randomBytes(length / 8).catch((err) => {
throw lazyDOMException(
'The operation failed for an operation-specific reason' +
`[${err.message}]`,
{ name: 'OperationError', cause: err });
});
return new InternalCryptoKey(
createSecretKey(keyData),
{ name, length },
ArrayFrom(usageSet),
extractable);
}
function macImportKey(
format, format,
keyData, keyData,
algorithm, algorithm,
extractable, extractable,
keyUsages, keyUsages,
) { ) {
const isHmac = algorithm.name === 'HMAC';
const usagesSet = new SafeSet(keyUsages); const usagesSet = new SafeSet(keyUsages);
if (hasAnyNotIn(usagesSet, ['sign', 'verify'])) { if (hasAnyNotIn(usagesSet, ['sign', 'verify'])) {
throw lazyDOMException( throw lazyDOMException(
'Unsupported key usage for an HMAC key', `Unsupported key usage for ${algorithm.name} key`,
'SyntaxError'); 'SyntaxError');
} }
let keyObject; let keyObject;
@ -87,7 +128,11 @@ function hmacImportKey(
keyObject = keyData; keyObject = keyData;
break; break;
} }
case 'raw-secret':
case 'raw': { case 'raw': {
if (format === 'raw' && !isHmac) {
return undefined;
}
keyObject = createSecretKey(keyData); keyObject = createSecretKey(keyData);
break; break;
} }
@ -115,8 +160,9 @@ function hmacImportKey(
} }
if (keyData.alg !== undefined) { if (keyData.alg !== undefined) {
const expected = const expected = isHmac ?
normalizeHashName(algorithm.hash.name, normalizeHashName.kContextJwkHmac); normalizeHashName(algorithm.hash.name, normalizeHashName.kContextJwkHmac) :
`K${StringPrototypeSubstring(algorithm.name, 4)}`;
if (expected && keyData.alg !== expected) if (expected && keyData.alg !== expected)
throw lazyDOMException( throw lazyDOMException(
'JWK "alg" does not match the requested algorithm', 'JWK "alg" does not match the requested algorithm',
@ -147,12 +193,18 @@ function hmacImportKey(
throw lazyDOMException('Invalid key length', 'DataError'); throw lazyDOMException('Invalid key length', 'DataError');
} }
const algorithmObject = {
name: algorithm.name,
length,
};
if (isHmac) {
algorithmObject.hash = algorithm.hash;
}
return new InternalCryptoKey( return new InternalCryptoKey(
keyObject, { keyObject,
name: 'HMAC', algorithmObject,
hash: algorithm.hash,
length,
},
keyUsages, keyUsages,
extractable); extractable);
} }
@ -168,8 +220,23 @@ function hmacSignVerify(key, data, algorithm, signature) {
signature)); signature));
} }
function kmacSignVerify(key, data, algorithm, signature) {
const mode = signature === undefined ? kSignJobModeSign : kSignJobModeVerify;
return jobPromise(() => new KmacJob(
kCryptoJobAsync,
mode,
key[kKeyObject][kHandle],
algorithm.name,
algorithm.customization,
algorithm.length / 8,
data,
signature));
}
module.exports = { module.exports = {
hmacImportKey, macImportKey,
hmacGenerateKey, hmacGenerateKey,
hmacSignVerify, hmacSignVerify,
kmacGenerateKey,
kmacSignVerify,
}; };

View File

@ -41,6 +41,7 @@ const {
EVP_PKEY_ML_KEM_1024, EVP_PKEY_ML_KEM_1024,
kKeyVariantAES_OCB_128: hasAesOcbMode, kKeyVariantAES_OCB_128: hasAesOcbMode,
Argon2Job, Argon2Job,
KmacJob,
} = internalBinding('crypto'); } = internalBinding('crypto');
const { getOptionValue } = require('internal/options'); const { getOptionValue } = require('internal/options');
@ -283,6 +284,22 @@ const kAlgorithmDefinitions = {
'verify': null, 'verify': null,
'get key length': 'HmacImportParams', 'get key length': 'HmacImportParams',
}, },
'KMAC128': {
'generateKey': 'KmacKeyGenParams',
'exportKey': null,
'importKey': 'KmacImportParams',
'sign': 'KmacParams',
'verify': 'KmacParams',
'get key length': 'KmacImportParams',
},
'KMAC256': {
'generateKey': 'KmacKeyGenParams',
'exportKey': null,
'importKey': 'KmacImportParams',
'sign': 'KmacParams',
'verify': 'KmacParams',
'get key length': 'KmacImportParams',
},
'ML-DSA-44': { 'ML-DSA-44': {
'generateKey': null, 'generateKey': null,
'exportKey': null, 'exportKey': null,
@ -386,6 +403,8 @@ const conditionalAlgorithms = {
'cSHAKE256': !process.features.openssl_is_boringssl || 'cSHAKE256': !process.features.openssl_is_boringssl ||
ArrayPrototypeIncludes(getHashes(), 'shake256'), ArrayPrototypeIncludes(getHashes(), 'shake256'),
'Ed448': !process.features.openssl_is_boringssl, 'Ed448': !process.features.openssl_is_boringssl,
'KMAC128': !!KmacJob,
'KMAC256': !!KmacJob,
'ML-DSA-44': !!EVP_PKEY_ML_DSA_44, 'ML-DSA-44': !!EVP_PKEY_ML_DSA_44,
'ML-DSA-65': !!EVP_PKEY_ML_DSA_65, 'ML-DSA-65': !!EVP_PKEY_ML_DSA_65,
'ML-DSA-87': !!EVP_PKEY_ML_DSA_87, 'ML-DSA-87': !!EVP_PKEY_ML_DSA_87,
@ -411,6 +430,8 @@ const experimentalAlgorithms = [
'cSHAKE128', 'cSHAKE128',
'cSHAKE256', 'cSHAKE256',
'Ed448', 'Ed448',
'KMAC128',
'KMAC256',
'ML-DSA-44', 'ML-DSA-44',
'ML-DSA-65', 'ML-DSA-65',
'ML-DSA-87', 'ML-DSA-87',
@ -488,6 +509,9 @@ const simpleAlgorithmDictionaries = {
nonce: 'BufferSource', nonce: 'BufferSource',
secretValue: 'BufferSource', secretValue: 'BufferSource',
}, },
KmacParams: {
customization: 'BufferSource',
},
}; };
function validateMaxBufferLength(data, name) { function validateMaxBufferLength(data, name) {

View File

@ -185,6 +185,13 @@ async function generateKey(
result = await require('internal/crypto/ml_kem') result = await require('internal/crypto/ml_kem')
.mlKemGenerateKey(algorithm, extractable, keyUsages); .mlKemGenerateKey(algorithm, extractable, keyUsages);
break; break;
case 'KMAC128':
// Fall through
case 'KMAC256':
resultType = 'CryptoKey';
result = await require('internal/crypto/mac')
.kmacGenerateKey(algorithm, extractable, keyUsages);
break;
default: default:
throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError'); throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError');
} }
@ -278,6 +285,13 @@ function getKeyLength({ name, length, hash }) {
} }
throw lazyDOMException('Invalid key length', 'OperationError'); throw lazyDOMException('Invalid key length', 'OperationError');
case 'KMAC128':
case 'KMAC256':
if (typeof length === 'number') {
return length;
}
return name === 'KMAC128' ? 128 : 256;
case 'HKDF': case 'HKDF':
case 'PBKDF2': case 'PBKDF2':
case 'Argon2d': case 'Argon2d':
@ -533,6 +547,10 @@ async function exportKeyRawSecret(key, format) {
return key[kKeyObject][kHandle].export().buffer; return key[kKeyObject][kHandle].export().buffer;
case 'AES-OCB': case 'AES-OCB':
// Fall through // Fall through
case 'KMAC128':
// Fall through
case 'KMAC256':
// Fall through
case 'ChaCha20-Poly1305': case 'ChaCha20-Poly1305':
if (format === 'raw-secret') { if (format === 'raw-secret') {
return key[kKeyObject][kHandle].export().buffer; return key[kKeyObject][kHandle].export().buffer;
@ -611,6 +629,13 @@ async function exportKeyJWK(key) {
if (alg) parameters.alg = alg; if (alg) parameters.alg = alg;
break; break;
} }
case 'KMAC128':
parameters.alg = 'K128';
break;
case 'KMAC256': {
parameters.alg = 'K256';
break;
}
default: default:
return undefined; return undefined;
} }
@ -772,9 +797,12 @@ async function importKey(
.cfrgImportKey(format, keyData, algorithm, extractable, keyUsages); .cfrgImportKey(format, keyData, algorithm, extractable, keyUsages);
break; break;
case 'HMAC': case 'HMAC':
format = aliasKeyFormat(format); // Fall through
case 'KMAC128':
// Fall through
case 'KMAC256':
result = require('internal/crypto/mac') result = require('internal/crypto/mac')
.hmacImportKey(format, keyData, algorithm, extractable, keyUsages); .macImportKey(format, keyData, algorithm, extractable, keyUsages);
break; break;
case 'AES-CTR': case 'AES-CTR':
// Fall through // Fall through
@ -785,9 +813,6 @@ async function importKey(
case 'AES-KW': case 'AES-KW':
// Fall through // Fall through
case 'AES-OCB': case 'AES-OCB':
if (algorithm.name !== 'AES-OCB') {
format = aliasKeyFormat(format);
}
result = require('internal/crypto/aes') result = require('internal/crypto/aes')
.aesImportKey(algorithm, format, keyData, extractable, keyUsages); .aesImportKey(algorithm, format, keyData, extractable, keyUsages);
break; break;
@ -1024,6 +1049,11 @@ function signVerify(algorithm, key, data, signature) {
case 'ML-DSA-87': case 'ML-DSA-87':
return require('internal/crypto/ml_dsa') return require('internal/crypto/ml_dsa')
.mlDsaSignVerify(key, data, algorithm, signature); .mlDsaSignVerify(key, data, algorithm, signature);
case 'KMAC128':
// Fall through
case 'KMAC256':
return require('internal/crypto/mac')
.kmacSignVerify(key, data, algorithm, signature);
} }
throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError'); throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError');
} }

View File

@ -461,32 +461,6 @@ converters.AesKeyGenParams = createDictionaryConverter(
}, },
]); ]);
converters.HmacKeyGenParams = createDictionaryConverter(
'HmacKeyGenParams', [
...new SafeArrayIterator(dictAlgorithm),
{
key: 'hash',
converter: converters.HashAlgorithmIdentifier,
validator: (V, dict) => ensureSHA(V, 'HmacKeyGenParams'),
required: true,
},
{
key: 'length',
converter: (V, opts) =>
converters['unsigned long'](V, { ...opts, enforceRange: true }),
validator: (V, dict) => validateHmacKeyAlgorithm(V),
},
]);
function validateHmacKeyAlgorithm(length) {
if (length === 0)
throw lazyDOMException('Zero-length key is not supported', 'DataError');
// The Web Crypto spec allows for key lengths that are not multiples of 8. We don't.
if (length % 8)
throw lazyDOMException('Unsupported algorithm.length', 'NotSupportedError');
}
function validateZeroLength(parameterName) { function validateZeroLength(parameterName) {
return (V, dict) => { return (V, dict) => {
if (V.byteLength) { if (V.byteLength) {
@ -527,22 +501,24 @@ converters.EcdsaParams = createDictionaryConverter(
}, },
]); ]);
converters.HmacImportParams = createDictionaryConverter( for (const { 0: name, 1: zeroError } of [['HmacKeyGenParams', 'OperationError'], ['HmacImportParams', 'DataError']]) {
'HmacImportParams', [ converters[name] = createDictionaryConverter(
...new SafeArrayIterator(dictAlgorithm), name, [
{ ...new SafeArrayIterator(dictAlgorithm),
key: 'hash', {
converter: converters.HashAlgorithmIdentifier, key: 'hash',
validator: (V, dict) => ensureSHA(V, 'HmacImportParams'), converter: converters.HashAlgorithmIdentifier,
required: true, validator: (V, dict) => ensureSHA(V, name),
}, required: true,
{ },
key: 'length', {
converter: (V, opts) => key: 'length',
converters['unsigned long'](V, { ...opts, enforceRange: true }), converter: (V, opts) =>
validator: (V, dict) => validateHmacKeyAlgorithm(V), converters['unsigned long'](V, { ...opts, enforceRange: true }),
}, validator: validateMacKeyLength(`${name}.length`, zeroError),
]); },
]);
}
const simpleDomStringKey = (key) => ({ key, converter: converters.DOMString }); const simpleDomStringKey = (key) => ({ key, converter: converters.DOMString });
@ -867,6 +843,50 @@ converters.Argon2Params = createDictionaryConverter(
}, },
]); ]);
function validateMacKeyLength(parameterName, zeroError) {
return (V) => {
if (V === 0)
throw lazyDOMException(`${parameterName} cannot be 0`, zeroError);
// The Web Crypto spec allows for key lengths that are not multiples of 8. We don't.
if (V % 8)
throw lazyDOMException(`Unsupported ${parameterName}`, 'NotSupportedError');
};
}
for (const { 0: name, 1: zeroError } of [['KmacKeyGenParams', 'OperationError'], ['KmacImportParams', 'DataError']]) {
converters[name] = createDictionaryConverter(
name, [
...new SafeArrayIterator(dictAlgorithm),
{
key: 'length',
converter: (V, opts) =>
converters['unsigned long'](V, { ...opts, enforceRange: true }),
validator: validateMacKeyLength(`${name}.length`, zeroError),
},
]);
}
converters.KmacParams = createDictionaryConverter(
'KmacParams', [
...new SafeArrayIterator(dictAlgorithm),
{
key: 'length',
converter: (V, opts) =>
converters['unsigned long'](V, { ...opts, enforceRange: true }),
validator: (V, opts) => {
// The Web Crypto spec allows for KMAC output length that are not multiples of 8. We don't.
if (V % 8)
throw lazyDOMException('Unsupported KmacParams length', 'NotSupportedError');
},
required: true,
},
{
key: 'customization',
converter: converters.BufferSource,
},
]);
module.exports = { module.exports = {
converters, converters,
requiredArguments, requiredArguments,

View File

@ -345,6 +345,7 @@
'src/crypto/crypto_ml_dsa.cc', 'src/crypto/crypto_ml_dsa.cc',
'src/crypto/crypto_kem.cc', 'src/crypto/crypto_kem.cc',
'src/crypto/crypto_hmac.cc', 'src/crypto/crypto_hmac.cc',
'src/crypto/crypto_kmac.cc',
'src/crypto/crypto_random.cc', 'src/crypto/crypto_random.cc',
'src/crypto/crypto_rsa.cc', 'src/crypto/crypto_rsa.cc',
'src/crypto/crypto_spkac.cc', 'src/crypto/crypto_spkac.cc',
@ -362,6 +363,7 @@
'src/crypto/crypto_clienthello-inl.h', 'src/crypto/crypto_clienthello-inl.h',
'src/crypto/crypto_dh.h', 'src/crypto/crypto_dh.h',
'src/crypto/crypto_hmac.h', 'src/crypto/crypto_hmac.h',
'src/crypto/crypto_kmac.h',
'src/crypto/crypto_rsa.h', 'src/crypto/crypto_rsa.h',
'src/crypto/crypto_spkac.h', 'src/crypto/crypto_spkac.h',
'src/crypto/crypto_util.h', 'src/crypto/crypto_util.h',

220
src/crypto/crypto_kmac.cc Normal file
View File

@ -0,0 +1,220 @@
#include "crypto/crypto_kmac.h"
#include "async_wrap-inl.h"
#include "node_internals.h"
#include "threadpoolwork-inl.h"
#if OPENSSL_VERSION_MAJOR >= 3
#include <openssl/core_names.h>
#include <openssl/params.h>
#include "crypto/crypto_keys.h"
#include "crypto/crypto_sig.h"
#include "ncrypto.h"
namespace node::crypto {
using ncrypto::EVPMacCtxPointer;
using ncrypto::EVPMacPointer;
using node::Utf8Value;
using v8::Boolean;
using v8::FunctionCallbackInfo;
using v8::JustVoid;
using v8::Local;
using v8::Maybe;
using v8::MaybeLocal;
using v8::Nothing;
using v8::Object;
using v8::Uint32;
using v8::Value;
KmacConfig::KmacConfig(KmacConfig&& other) noexcept
: job_mode(other.job_mode),
mode(other.mode),
key(std::move(other.key)),
data(std::move(other.data)),
signature(std::move(other.signature)),
customization(std::move(other.customization)),
variant(other.variant),
length(other.length) {}
KmacConfig& KmacConfig::operator=(KmacConfig&& other) noexcept {
if (&other == this) return *this;
this->~KmacConfig();
return *new (this) KmacConfig(std::move(other));
}
void KmacConfig::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackField("key", key);
// If the job is sync, then the KmacConfig does not own the data.
if (job_mode == kCryptoJobAsync) {
tracker->TrackFieldWithSize("data", data.size());
tracker->TrackFieldWithSize("signature", signature.size());
tracker->TrackFieldWithSize("customization", customization.size());
}
}
Maybe<void> KmacTraits::AdditionalConfig(
CryptoJobMode mode,
const FunctionCallbackInfo<Value>& args,
unsigned int offset,
KmacConfig* params) {
Environment* env = Environment::GetCurrent(args);
params->job_mode = mode;
CHECK(args[offset]->IsUint32()); // SignConfiguration::Mode
params->mode =
static_cast<SignConfiguration::Mode>(args[offset].As<Uint32>()->Value());
CHECK(args[offset + 1]->IsObject()); // Key
KeyObjectHandle* key;
ASSIGN_OR_RETURN_UNWRAP(&key, args[offset + 1], Nothing<void>());
params->key = key->Data().addRef();
CHECK(args[offset + 2]->IsString()); // Algorithm name
Utf8Value algorithm_name(env->isolate(), args[offset + 2]);
std::string_view algorithm_str = algorithm_name.ToStringView();
// Convert string to enum and validate
if (algorithm_str == OSSL_MAC_NAME_KMAC128) {
params->variant = KmacVariant::KMAC128;
} else if (algorithm_str == OSSL_MAC_NAME_KMAC256) {
params->variant = KmacVariant::KMAC256;
} else {
UNREACHABLE();
}
// Customization string (may be empty or undefined).
if (!args[offset + 3]->IsUndefined()) {
ArrayBufferOrViewContents<char> customization(args[offset + 3]);
if (!customization.CheckSizeInt32()) [[unlikely]] {
THROW_ERR_OUT_OF_RANGE(env, "customization is too big");
return Nothing<void>();
}
params->customization = mode == kCryptoJobAsync
? customization.ToCopy()
: customization.ToByteSource();
}
// If undefined, params->customization remains uninitialized (size 0).
CHECK(args[offset + 4]->IsUint32()); // Length
params->length = args[offset + 4].As<Uint32>()->Value();
ArrayBufferOrViewContents<char> data(args[offset + 5]);
if (!data.CheckSizeInt32()) [[unlikely]] {
THROW_ERR_OUT_OF_RANGE(env, "data is too big");
return Nothing<void>();
}
params->data = mode == kCryptoJobAsync ? data.ToCopy() : data.ToByteSource();
if (!args[offset + 6]->IsUndefined()) {
ArrayBufferOrViewContents<char> signature(args[offset + 6]);
if (!signature.CheckSizeInt32()) [[unlikely]] {
THROW_ERR_OUT_OF_RANGE(env, "signature is too big");
return Nothing<void>();
}
params->signature =
mode == kCryptoJobAsync ? signature.ToCopy() : signature.ToByteSource();
}
return JustVoid();
}
bool KmacTraits::DeriveBits(Environment* env,
const KmacConfig& params,
ByteSource* out,
CryptoJobMode mode) {
if (params.length == 0) {
*out = ByteSource();
return true;
}
// Get the key data.
const void* key_data = params.key.GetSymmetricKey();
size_t key_size = params.key.GetSymmetricKeySize();
if (key_size == 0) {
return false;
}
// Fetch the KMAC algorithm
auto mac = EVPMacPointer::Fetch((params.variant == KmacVariant::KMAC128)
? OSSL_MAC_NAME_KMAC128
: OSSL_MAC_NAME_KMAC256);
if (!mac) {
return false;
}
// Create MAC context
auto mac_ctx = EVPMacCtxPointer::New(mac.get());
if (!mac_ctx) {
return false;
}
// Set up parameters.
OSSL_PARAM params_array[3]; // Max 3: size + customization + end
size_t params_count = 0;
// Set output length (always required for KMAC).
size_t outlen = params.length;
params_array[params_count++] =
OSSL_PARAM_construct_size_t(OSSL_MAC_PARAM_SIZE, &outlen);
// Set customization if provided.
if (params.customization.size() > 0) {
params_array[params_count++] = OSSL_PARAM_construct_octet_string(
OSSL_MAC_PARAM_CUSTOM,
const_cast<void*>(params.customization.data()),
params.customization.size());
}
params_array[params_count] = OSSL_PARAM_construct_end();
// Initialize the MAC context.
if (!mac_ctx.init(ncrypto::Buffer<const void>(key_data, key_size),
params_array)) {
return false;
}
// Update with data.
if (!mac_ctx.update(ncrypto::Buffer<const void>(params.data.data(),
params.data.size()))) {
return false;
}
// Finalize and get the result.
auto result = mac_ctx.final(params.length);
if (!result) {
return false;
}
auto buffer = result.release();
*out = ByteSource::Allocated(buffer.data, buffer.len);
return true;
}
MaybeLocal<Value> KmacTraits::EncodeOutput(Environment* env,
const KmacConfig& params,
ByteSource* out) {
switch (params.mode) {
case SignConfiguration::Mode::Sign:
return out->ToArrayBuffer(env);
case SignConfiguration::Mode::Verify:
return Boolean::New(
env->isolate(),
out->size() > 0 && out->size() == params.signature.size() &&
memcmp(out->data(), params.signature.data(), out->size()) == 0);
}
UNREACHABLE();
}
void Kmac::Initialize(Environment* env, Local<Object> target) {
KmacJob::Initialize(env, target);
}
void Kmac::RegisterExternalReferences(ExternalReferenceRegistry* registry) {
KmacJob::RegisterExternalReferences(registry);
}
} // namespace node::crypto
#endif

79
src/crypto/crypto_kmac.h Normal file
View File

@ -0,0 +1,79 @@
#ifndef SRC_CRYPTO_CRYPTO_KMAC_H_
#define SRC_CRYPTO_CRYPTO_KMAC_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include <string>
#include "crypto/crypto_keys.h"
#include "crypto/crypto_sig.h"
#include "crypto/crypto_util.h"
namespace node::crypto {
// KMAC (Keccak Message Authentication Code) is available since OpenSSL 3.0.
#if OPENSSL_VERSION_MAJOR >= 3
enum class KmacVariant { KMAC128, KMAC256 };
struct KmacConfig final : public MemoryRetainer {
CryptoJobMode job_mode;
SignConfiguration::Mode mode;
KeyObjectData key;
ByteSource data;
ByteSource signature;
ByteSource customization;
KmacVariant variant;
uint32_t length; // Output length in bytes
KmacConfig() = default;
explicit KmacConfig(KmacConfig&& other) noexcept;
KmacConfig& operator=(KmacConfig&& other) noexcept;
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(KmacConfig)
SET_SELF_SIZE(KmacConfig)
};
struct KmacTraits final {
using AdditionalParameters = KmacConfig;
static constexpr const char* JobName = "KmacJob";
static constexpr AsyncWrap::ProviderType Provider =
AsyncWrap::PROVIDER_SIGNREQUEST;
static v8::Maybe<void> AdditionalConfig(
CryptoJobMode mode,
const v8::FunctionCallbackInfo<v8::Value>& args,
unsigned int offset,
KmacConfig* params);
static bool DeriveBits(Environment* env,
const KmacConfig& params,
ByteSource* out,
CryptoJobMode mode);
static v8::MaybeLocal<v8::Value> EncodeOutput(Environment* env,
const KmacConfig& params,
ByteSource* out);
};
using KmacJob = DeriveBitsJob<KmacTraits>;
namespace Kmac {
void Initialize(Environment* env, v8::Local<v8::Object> target);
void RegisterExternalReferences(ExternalReferenceRegistry* registry);
} // namespace Kmac
#else
// If there is no KMAC support, provide empty namespace functions.
namespace Kmac {
void Initialize(Environment* env, v8::Local<v8::Object> target) {}
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {}
} // namespace Kmac
#endif
} // namespace node::crypto
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CRYPTO_CRYPTO_KMAC_H_

View File

@ -66,11 +66,13 @@ namespace crypto {
#define ARGON2_NAMESPACE_LIST(V) #define ARGON2_NAMESPACE_LIST(V)
#endif // !OPENSSL_NO_ARGON2 && OpenSSL >= 3.2 #endif // !OPENSSL_NO_ARGON2 && OpenSSL >= 3.2
// KEM functionality requires OpenSSL 3.0.0 or later // KEM and KMAC functionality requires OpenSSL 3.0.0 or later
#if OPENSSL_VERSION_MAJOR >= 3 #if OPENSSL_VERSION_MAJOR >= 3
#define KEM_NAMESPACE_LIST(V) V(KEM) #define KEM_NAMESPACE_LIST(V) V(KEM)
#define KMAC_NAMESPACE_LIST(V) V(Kmac)
#else #else
#define KEM_NAMESPACE_LIST(V) #define KEM_NAMESPACE_LIST(V)
#define KMAC_NAMESPACE_LIST(V)
#endif #endif
#ifdef OPENSSL_NO_SCRYPT #ifdef OPENSSL_NO_SCRYPT
@ -83,6 +85,7 @@ namespace crypto {
CRYPTO_NAMESPACE_LIST_BASE(V) \ CRYPTO_NAMESPACE_LIST_BASE(V) \
ARGON2_NAMESPACE_LIST(V) \ ARGON2_NAMESPACE_LIST(V) \
KEM_NAMESPACE_LIST(V) \ KEM_NAMESPACE_LIST(V) \
KMAC_NAMESPACE_LIST(V) \
SCRYPT_NAMESPACE_LIST(V) SCRYPT_NAMESPACE_LIST(V)
void Initialize(Local<Object> target, void Initialize(Local<Object> target,

View File

@ -42,6 +42,7 @@
#include "crypto/crypto_hmac.h" #include "crypto/crypto_hmac.h"
#if OPENSSL_VERSION_MAJOR >= 3 #if OPENSSL_VERSION_MAJOR >= 3
#include "crypto/crypto_kem.h" #include "crypto/crypto_kem.h"
#include "crypto/crypto_kmac.h"
#endif #endif
#include "crypto/crypto_keygen.h" #include "crypto/crypto_keygen.h"
#include "crypto/crypto_keys.h" #include "crypto/crypto_keys.h"

120
test/fixtures/crypto/kmac.js vendored Normal file
View File

@ -0,0 +1,120 @@
'use strict';
module.exports = function() {
// KMAC test vectors from https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/KMAC_samples.pdf
const vectors = [
{
// Sample #1 - KMAC128, no customization
algorithm: 'KMAC128',
key: Buffer.from([
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b,
0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
]),
data: Buffer.from([0x00, 0x01, 0x02, 0x03]),
customization: undefined,
length: 256,
expected: Buffer.from([
0xe5, 0x78, 0x0b, 0x0d, 0x3e, 0xa6, 0xf7, 0xd3, 0xa4, 0x29, 0xc5, 0x70,
0x6a, 0xa4, 0x3a, 0x00, 0xfa, 0xdb, 0xd7, 0xd4, 0x96, 0x28, 0x83, 0x9e,
0x31, 0x87, 0x24, 0x3f, 0x45, 0x6e, 0xe1, 0x4e,
]),
},
{
// Sample #2 - KMAC128, with customization
algorithm: 'KMAC128',
key: Buffer.from([
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b,
0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
]),
data: Buffer.from([0x00, 0x01, 0x02, 0x03]),
customization: Buffer.from('My Tagged Application'),
length: 256,
expected: Buffer.from([
0x3b, 0x1f, 0xba, 0x96, 0x3c, 0xd8, 0xb0, 0xb5, 0x9e, 0x8c, 0x1a, 0x6d,
0x71, 0x88, 0x8b, 0x71, 0x43, 0x65, 0x1a, 0xf8, 0xba, 0x0a, 0x70, 0x70,
0xc0, 0x97, 0x9e, 0x28, 0x11, 0x32, 0x4a, 0xa5,
]),
},
{
// Sample #3 - KMAC128, large data, with customization
algorithm: 'KMAC128',
key: Buffer.from([
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b,
0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
]),
data: Buffer.from(Array.from({ length: 200 }, (_, i) => i)), // 0x00-0xC7
customization: Buffer.from('My Tagged Application'),
length: 256,
expected: Buffer.from([
0x1f, 0x5b, 0x4e, 0x6c, 0xca, 0x02, 0x20, 0x9e, 0x0d, 0xcb, 0x5c, 0xa6,
0x35, 0xb8, 0x9a, 0x15, 0xe2, 0x71, 0xec, 0xc7, 0x60, 0x07, 0x1d, 0xfd,
0x80, 0x5f, 0xaa, 0x38, 0xf9, 0x72, 0x92, 0x30,
]),
},
{
// Sample #4 - KMAC256, with customization, 512-bit output
algorithm: 'KMAC256',
key: Buffer.from([
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b,
0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
]),
data: Buffer.from([0x00, 0x01, 0x02, 0x03]),
customization: Buffer.from('My Tagged Application'),
length: 512,
expected: Buffer.from([
0x20, 0xc5, 0x70, 0xc3, 0x13, 0x46, 0xf7, 0x03, 0xc9, 0xac, 0x36, 0xc6,
0x1c, 0x03, 0xcb, 0x64, 0xc3, 0x97, 0x0d, 0x0c, 0xfc, 0x78, 0x7e, 0x9b,
0x79, 0x59, 0x9d, 0x27, 0x3a, 0x68, 0xd2, 0xf7, 0xf6, 0x9d, 0x4c, 0xc3,
0xde, 0x9d, 0x10, 0x4a, 0x35, 0x16, 0x89, 0xf2, 0x7c, 0xf6, 0xf5, 0x95,
0x1f, 0x01, 0x03, 0xf3, 0x3f, 0x4f, 0x24, 0x87, 0x10, 0x24, 0xd9, 0xc2,
0x77, 0x73, 0xa8, 0xdd,
]),
},
{
// Sample #5 - KMAC256, large data, no customization, 512-bit output
algorithm: 'KMAC256',
key: Buffer.from([
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b,
0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
]),
data: Buffer.from(Array.from({ length: 200 }, (_, i) => i)), // 0x00-0xC7
customization: undefined,
length: 512,
expected: Buffer.from([
0x75, 0x35, 0x8c, 0xf3, 0x9e, 0x41, 0x49, 0x4e, 0x94, 0x97, 0x07, 0x92,
0x7c, 0xee, 0x0a, 0xf2, 0x0a, 0x3f, 0xf5, 0x53, 0x90, 0x4c, 0x86, 0xb0,
0x8f, 0x21, 0xcc, 0x41, 0x4b, 0xcf, 0xd6, 0x91, 0x58, 0x9d, 0x27, 0xcf,
0x5e, 0x15, 0x36, 0x9c, 0xbb, 0xff, 0x8b, 0x9a, 0x4c, 0x2e, 0xb1, 0x78,
0x00, 0x85, 0x5d, 0x02, 0x35, 0xff, 0x63, 0x5d, 0xa8, 0x25, 0x33, 0xec,
0x6b, 0x75, 0x9b, 0x69,
]),
},
{
// Sample #6 - KMAC256, large data, with customization, 512-bit output
algorithm: 'KMAC256',
key: Buffer.from([
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b,
0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
]),
data: Buffer.from(Array.from({ length: 200 }, (_, i) => i)), // 0x00-0xC7
customization: Buffer.from('My Tagged Application'),
length: 512,
expected: Buffer.from([
0xb5, 0x86, 0x18, 0xf7, 0x1f, 0x92, 0xe1, 0xd5, 0x6c, 0x1b, 0x8c, 0x55,
0xdd, 0xd7, 0xcd, 0x18, 0x8b, 0x97, 0xb4, 0xca, 0x4d, 0x99, 0x83, 0x1e,
0xb2, 0x69, 0x9a, 0x83, 0x7d, 0xa2, 0xe4, 0xd9, 0x70, 0xfb, 0xac, 0xfd,
0xe5, 0x00, 0x33, 0xae, 0xa5, 0x85, 0xf1, 0xa2, 0x70, 0x85, 0x10, 0xc3,
0x2d, 0x07, 0x88, 0x08, 0x01, 0xbd, 0x18, 0x28, 0x98, 0xfe, 0x47, 0x68,
0x76, 0xfc, 0x89, 0x65,
]),
},
];
return vectors;
};

View File

@ -8,6 +8,7 @@ const shake128 = crypto.getHashes().includes('shake128');
const shake256 = crypto.getHashes().includes('shake256'); const shake256 = crypto.getHashes().includes('shake256');
const chacha = crypto.getCiphers().includes('chacha20-poly1305'); const chacha = crypto.getCiphers().includes('chacha20-poly1305');
const ocb = hasOpenSSL(3); const ocb = hasOpenSSL(3);
const kmac = hasOpenSSL(3);
const { subtle } = globalThis.crypto; const { subtle } = globalThis.crypto;
const X25519 = await subtle.generateKey('X25519', false, ['deriveBits', 'deriveKey']); const X25519 = await subtle.generateKey('X25519', false, ['deriveBits', 'deriveKey']);
@ -34,6 +35,10 @@ export const vectors = {
[false, 'Argon2d'], [false, 'Argon2d'],
[false, 'Argon2i'], [false, 'Argon2i'],
[false, 'Argon2id'], [false, 'Argon2id'],
[false, 'KMAC128'],
[false, 'KMAC256'],
[kmac, { name: 'KMAC128', length: 256 }],
[kmac, { name: 'KMAC256', length: 256 }],
], ],
'generateKey': [ 'generateKey': [
[pqc, 'ML-DSA-44'], [pqc, 'ML-DSA-44'],
@ -47,6 +52,14 @@ export const vectors = {
[false, 'Argon2d'], [false, 'Argon2d'],
[false, 'Argon2i'], [false, 'Argon2i'],
[false, 'Argon2id'], [false, 'Argon2id'],
[kmac, 'KMAC128'],
[kmac, 'KMAC256'],
[kmac, { name: 'KMAC128', length: 256 }],
[kmac, { name: 'KMAC256', length: 128 }],
[false, { name: 'KMAC128', length: 0 }],
[false, { name: 'KMAC256', length: 0 }],
[false, { name: 'KMAC128', length: 1 }],
[false, { name: 'KMAC256', length: 1 }],
], ],
'importKey': [ 'importKey': [
[pqc, 'ML-DSA-44'], [pqc, 'ML-DSA-44'],
@ -60,6 +73,14 @@ export const vectors = {
[argon2, 'Argon2d'], [argon2, 'Argon2d'],
[argon2, 'Argon2i'], [argon2, 'Argon2i'],
[argon2, 'Argon2id'], [argon2, 'Argon2id'],
[kmac, 'KMAC128'],
[kmac, 'KMAC256'],
[kmac, { name: 'KMAC128', length: 256 }],
[kmac, { name: 'KMAC256', length: 128 }],
[false, { name: 'KMAC128', length: 0 }],
[false, { name: 'KMAC256', length: 0 }],
[false, { name: 'KMAC128', length: 1 }],
[false, { name: 'KMAC256', length: 1 }],
], ],
'exportKey': [ 'exportKey': [
[pqc, 'ML-DSA-44'], [pqc, 'ML-DSA-44'],
@ -73,6 +94,8 @@ export const vectors = {
[false, 'Argon2d'], [false, 'Argon2d'],
[false, 'Argon2i'], [false, 'Argon2i'],
[false, 'Argon2id'], [false, 'Argon2id'],
[kmac, 'KMAC128'],
[kmac, 'KMAC256'],
], ],
'getPublicKey': [ 'getPublicKey': [
[true, 'RSA-OAEP'], [true, 'RSA-OAEP'],
@ -99,6 +122,8 @@ export const vectors = {
[false, 'Argon2d'], [false, 'Argon2d'],
[false, 'Argon2i'], [false, 'Argon2i'],
[false, 'Argon2id'], [false, 'Argon2id'],
[false, 'KMAC128'],
[false, 'KMAC256'],
], ],
'deriveKey': [ 'deriveKey': [
[argon2, [argon2,

View File

@ -86,7 +86,7 @@ function assertCryptoKey(cryptoKey, keyObject, algorithm, extractable, usages) {
hmac.toCryptoKey({ name: algorithm, hash: 'SHA-256', length: 0 }, true, usages); hmac.toCryptoKey({ name: algorithm, hash: 'SHA-256', length: 0 }, true, usages);
}, { }, {
name: 'DataError', name: 'DataError',
message: 'Zero-length key is not supported', message: 'HmacImportParams.length cannot be 0',
}); });
const cryptoKey = hmac.toCryptoKey({ name: algorithm, hash }, extractable, usages); const cryptoKey = hmac.toCryptoKey({ name: algorithm, hash }, extractable, usages);
assertCryptoKey(cryptoKey, hmac, algorithm, extractable, usages); assertCryptoKey(cryptoKey, hmac, algorithm, extractable, usages);
@ -205,3 +205,38 @@ if (hasOpenSSL(3, 5)) {
} }
} }
} }
if (hasOpenSSL(3)) {
for (const algorithm of ['KMAC128', 'KMAC256']) {
const hmac = createSecretKey(randomBytes(32));
const usages = ['sign', 'verify'];
assert.throws(() => {
createSecretKey(Buffer.alloc(0)).toCryptoKey({ name: algorithm }, true, usages);
}, {
name: 'DataError',
message: 'Zero-length key is not supported',
});
assert.throws(() => {
hmac.toCryptoKey({
name: algorithm,
}, true, []);
}, {
name: 'SyntaxError',
message: 'Usages cannot be empty when importing a secret key.'
});
for (const extractable of [true, false]) {
assert.throws(() => {
hmac.toCryptoKey({ name: algorithm, length: 0 }, true, usages);
}, {
name: 'DataError',
message: 'KmacImportParams.length cannot be 0',
});
const cryptoKey = hmac.toCryptoKey({ name: algorithm }, extractable, usages);
assertCryptoKey(cryptoKey, hmac, algorithm, extractable, usages);
assert.strictEqual(cryptoKey.algorithm.length, 256);
}
}
}

View File

@ -1,4 +1,3 @@
// Flags: --expose-internals --no-warnings
'use strict'; 'use strict';
const common = require('../common'); const common = require('../common');
@ -6,6 +5,8 @@ const common = require('../common');
if (!common.hasCrypto) if (!common.hasCrypto)
common.skip('missing crypto'); common.skip('missing crypto');
const { hasOpenSSL } = require('../common/crypto');
const assert = require('assert'); const assert = require('assert');
const { subtle } = globalThis.crypto; const { subtle } = globalThis.crypto;
const { KeyObject } = require('crypto'); const { KeyObject } = require('crypto');
@ -167,6 +168,15 @@ const { KeyObject } = require('crypto');
// [{ name: 'HMAC', hash: 'SHA3-512' }, 'sign', 512], // [{ name: 'HMAC', hash: 'SHA3-512' }, 'sign', 512],
]; ];
if (hasOpenSSL(3)) {
vectors.push(
['KMAC128', 'sign', 128],
[{ name: 'KMAC128', length: 384 }, 'sign', 384],
['KMAC256', 'sign', 256],
[{ name: 'KMAC256', length: 384 }, 'sign', 384],
);
}
(async () => { (async () => {
const keyPair = await subtle.generateKey({ name: 'ECDH', namedCurve: 'P-521' }, false, ['deriveKey']); const keyPair = await subtle.generateKey({ name: 'ECDH', namedCurve: 'P-521' }, false, ['deriveKey']);
for (const [derivedKeyAlgorithm, usage, expected] of vectors) { for (const [derivedKeyAlgorithm, usage, expected] of vectors) {
@ -183,7 +193,7 @@ const { KeyObject } = require('crypto');
} else { } else {
assert.strictEqual(result.status, 'fulfilled'); assert.strictEqual(result.status, 'fulfilled');
const derived = result.value; const derived = result.value;
if (derived.algorithm.name === 'HMAC') { if (derived.algorithm.name === 'HMAC' || derived.algorithm.name.startsWith('KMAC')) {
assert.strictEqual(derived.algorithm.length, expected); assert.strictEqual(derived.algorithm.length, expected);
} else { } else {
// KDFs cannot be exportable and do not indicate their length // KDFs cannot be exportable and do not indicate their length
@ -211,6 +221,15 @@ const { KeyObject } = require('crypto');
// [{ name: 'HMAC', hash: 'SHA3-512' }, 'sign', 512], // [{ name: 'HMAC', hash: 'SHA3-512' }, 'sign', 512],
]; ];
if (hasOpenSSL(3)) {
vectors.push(
['KMAC128', 'sign', 128],
[{ name: 'KMAC128', length: 384 }, 'sign', 384],
['KMAC256', 'sign', 256],
[{ name: 'KMAC256', length: 384 }, 'sign', 384],
);
}
(async () => { (async () => {
for (const [derivedKeyAlgorithm, usage, expected] of vectors) { for (const [derivedKeyAlgorithm, usage, expected] of vectors) {
const derived = await subtle.deriveKey( const derived = await subtle.deriveKey(

View File

@ -6,6 +6,8 @@ const fixtures = require('../common/fixtures');
if (!common.hasCrypto) if (!common.hasCrypto)
common.skip('missing crypto'); common.skip('missing crypto');
const { hasOpenSSL } = require('../common/crypto');
const assert = require('assert'); const assert = require('assert');
const { subtle } = globalThis.crypto; const { subtle } = globalThis.crypto;
const { createPrivateKey, createPublicKey, createSecretKey } = require('crypto'); const { createPrivateKey, createPublicKey, createSecretKey } = require('crypto');
@ -53,7 +55,7 @@ const { createPrivateKey, createPublicKey, createSecretKey } = require('crypto')
hash: 'SHA-256' hash: 'SHA-256'
}, false, ['deriveBits']), { }, false, ['deriveBits']), {
name: 'SyntaxError', name: 'SyntaxError',
message: 'Unsupported key usage for an HMAC key' message: 'Unsupported key usage for HMAC key'
}); });
await assert.rejects( await assert.rejects(
subtle.importKey('raw', keyData, { subtle.importKey('raw', keyData, {
@ -62,7 +64,7 @@ const { createPrivateKey, createPublicKey, createSecretKey } = require('crypto')
length: 0 length: 0
}, false, ['sign', 'verify']), { }, false, ['sign', 'verify']), {
name: 'DataError', name: 'DataError',
message: 'Zero-length key is not supported' message: 'HmacImportParams.length cannot be 0'
}); });
await assert.rejects( await assert.rejects(
subtle.importKey('raw', keyData, { subtle.importKey('raw', keyData, {
@ -71,7 +73,7 @@ const { createPrivateKey, createPublicKey, createSecretKey } = require('crypto')
length: 1 length: 1
}, false, ['sign', 'verify']), { }, false, ['sign', 'verify']), {
name: 'NotSupportedError', name: 'NotSupportedError',
message: 'Unsupported algorithm.length' message: 'Unsupported HmacImportParams.length'
}); });
await assert.rejects( await assert.rejects(
subtle.importKey('jwk', null, { subtle.importKey('jwk', null, {
@ -97,7 +99,6 @@ const { createPrivateKey, createPublicKey, createSecretKey } = require('crypto')
hash: 'SHA-256' hash: 'SHA-256'
}, true, ['sign', 'verify']); }, true, ['sign', 'verify']);
assert.strictEqual(key.algorithm, key.algorithm); assert.strictEqual(key.algorithm, key.algorithm);
assert.strictEqual(key.usages, key.usages); assert.strictEqual(key.usages, key.usages);
@ -111,11 +112,44 @@ const { createPrivateKey, createPublicKey, createSecretKey } = require('crypto')
assert.deepStrictEqual(jwk.key_ops, ['sign', 'verify']); assert.deepStrictEqual(jwk.key_ops, ['sign', 'verify']);
assert(jwk.ext); assert(jwk.ext);
assert.strictEqual(jwk.kty, 'oct'); assert.strictEqual(jwk.kty, 'oct');
assert.strictEqual(jwk.alg, 'HS256');
assert.deepStrictEqual( assert.deepStrictEqual(
Buffer.from(jwk.k, 'base64').toString('hex'), Buffer.from(jwk.k, 'base64').toString('hex'),
Buffer.from(raw).toString('hex')); Buffer.from(raw).toString('hex'));
await subtle.importKey(
'jwk',
jwk,
{
name: 'HMAC',
hash: 'SHA-256'
},
true,
['sign', 'verify']);
await subtle.importKey(
'jwk',
{ ...jwk, alg: undefined },
{
name: 'HMAC',
hash: 'SHA-256'
},
true,
['sign', 'verify']);
await assert.rejects(
subtle.importKey(
'jwk',
{ ...jwk, alg: 'HS384' },
{
name: 'HMAC',
hash: 'SHA-256'
},
true,
['sign', 'verify']),
{ name: 'DataError', message: 'JWK "alg" does not match the requested algorithm' });
await assert.rejects( await assert.rejects(
subtle.importKey( subtle.importKey(
'raw', 'raw',
@ -132,6 +166,76 @@ const { createPrivateKey, createPublicKey, createSecretKey } = require('crypto')
test().then(common.mustCall()); test().then(common.mustCall());
} }
// Import/Export KMAC Secret Key
if (hasOpenSSL(3)) {
async function test(name) {
const keyData = globalThis.crypto.getRandomValues(new Uint8Array(32));
const key = await subtle.importKey(
'raw-secret',
keyData, name, true, ['sign', 'verify']);
assert.strictEqual(key.algorithm, key.algorithm);
assert.strictEqual(key.usages, key.usages);
const raw = await subtle.exportKey('raw-secret', key);
assert.deepStrictEqual(
Buffer.from(keyData).toString('hex'),
Buffer.from(raw).toString('hex'));
const jwk = await subtle.exportKey('jwk', key);
assert.deepStrictEqual(jwk.key_ops, ['sign', 'verify']);
assert(jwk.ext);
assert.strictEqual(jwk.kty, 'oct');
assert.strictEqual(jwk.alg, `K${name.substring(4)}`);
assert.deepStrictEqual(
Buffer.from(jwk.k, 'base64').toString('hex'),
Buffer.from(raw).toString('hex'));
await subtle.importKey(
'jwk',
jwk,
name,
true,
['sign', 'verify']);
await subtle.importKey(
'jwk',
{ ...jwk, alg: undefined },
name,
true,
['sign', 'verify']);
await assert.rejects(
subtle.importKey(
'jwk',
{ ...jwk, alg: name === 'KMAC128' ? 'K256' : 'K128' },
name,
true,
['sign', 'verify']),
{ name: 'DataError', message: 'JWK "alg" does not match the requested algorithm' });
await assert.rejects(
subtle.importKey(
'raw',
keyData, name, true, ['sign', 'verify']),
{ name: 'NotSupportedError', message: `Unable to import ${name} using raw format` });
await assert.rejects(
subtle.importKey(
'raw-secret',
keyData,
name,
true,
[/* empty usages */]),
{ name: 'SyntaxError', message: 'Usages cannot be empty when importing a secret key.' });
}
test('KMAC128').then(common.mustCall());
test('KMAC256').then(common.mustCall());
}
// Import/Export AES Secret Key // Import/Export AES Secret Key
{ {
async function test() { async function test() {

View File

@ -0,0 +1,50 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const { hasOpenSSL } = require('../common/crypto');
if (!hasOpenSSL(3))
common.skip('requires OpenSSL >= 3');
const assert = require('assert');
const { types: { isCryptoKey } } = require('util');
const { subtle } = globalThis.crypto;
const usages = ['sign', 'verify'];
async function test(name, length) {
length = length ?? name === 'KMAC128' ? 128 : 256;
const key = await subtle.generateKey({
name,
length,
}, true, usages);
assert(key);
assert(isCryptoKey(key));
assert.strictEqual(key.type, 'secret');
assert.strictEqual(key.toString(), '[object CryptoKey]');
assert.strictEqual(key.extractable, true);
assert.deepStrictEqual(key.usages, usages);
assert.strictEqual(key.algorithm.name, name);
assert.strictEqual(key.algorithm.length, length);
assert.strictEqual(key.algorithm, key.algorithm);
assert.strictEqual(key.usages, key.usages);
}
const kTests = [
['KMAC128', 128],
['KMAC128', 256],
['KMAC128'],
['KMAC256', 128],
['KMAC256', 256],
['KMAC256'],
];
const tests = Promise.all(kTests.map((args) => test(...args)));
tests.then(common.mustCall());

View File

@ -184,6 +184,16 @@ if (hasOpenSSL(3)) {
'unwrapKey', 'unwrapKey',
], ],
}; };
for (const name of ['KMAC128', 'KMAC256']) {
vectors[name] = {
result: 'CryptoKey',
usages: [
'sign',
'verify',
],
};
}
} }
if (hasOpenSSL(3, 5)) { if (hasOpenSSL(3, 5)) {

View File

@ -0,0 +1,193 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const { hasOpenSSL } = require('../common/crypto');
if (!hasOpenSSL(3))
common.skip('requires OpenSSL >= 3');
const assert = require('assert');
const { subtle } = globalThis.crypto;
const vectors = require('../fixtures/crypto/kmac')();
async function testVerify({ algorithm,
key,
data,
customization,
length,
expected }) {
const [
verifyKey,
noVerifyKey,
keyPair,
] = await Promise.all([
subtle.importKey(
'raw-secret',
key,
{ name: algorithm },
false,
['verify']),
subtle.importKey(
'raw-secret',
key,
{ name: algorithm },
false,
['sign']),
subtle.generateKey(
'Ed25519',
false,
['sign']),
]);
const signParams = {
name: algorithm,
length,
customization,
};
assert(await subtle.verify(signParams, verifyKey, expected, data));
// Test verification with altered buffers
const copy = Buffer.from(data);
const sigcopy = Buffer.from(expected);
const p = subtle.verify(signParams, verifyKey, sigcopy, copy);
copy[0] = 255 - copy[0];
sigcopy[0] = 255 - sigcopy[0];
assert(await p);
// Test failure when using wrong key
await assert.rejects(
subtle.verify(signParams, noVerifyKey, expected, data), {
message: /Unable to use this key to verify/
});
// Test failure when using the wrong algorithms
await assert.rejects(
subtle.verify(signParams, keyPair.publicKey, expected, data), {
message: /Unable to use this key to verify/
});
// Test failure when signature is altered
{
const copy = Buffer.from(expected);
copy[0] = 255 - copy[0];
assert(!(await subtle.verify(
signParams,
verifyKey,
copy,
data)));
assert(!(await subtle.verify(
signParams,
verifyKey,
copy.slice(1),
data)));
}
// Test failure when data is altered
{
const copy = Buffer.from(data);
copy[0] = 255 - copy[0];
assert(!(await subtle.verify(signParams, verifyKey, expected, copy)));
}
// Test failure when wrong algorithm is used
{
const otherAlgorithm = algorithm === 'KMAC128' ? 'KMAC256' : 'KMAC128';
const keyWithOtherAlgorithm = await subtle.importKey(
'raw-secret',
key,
{ name: otherAlgorithm },
false,
['verify']);
const otherParams = { ...signParams, name: otherAlgorithm };
assert(!(await subtle.verify(otherParams, keyWithOtherAlgorithm, expected, data)));
}
// Test failure when output length is different
{
assert(!(await subtle.verify({
...signParams,
length: length === 256 ? 512 : 256,
}, verifyKey, expected, data)));
}
}
async function testSign({ algorithm,
key,
data,
customization,
length,
expected }) {
const [
signKey,
noSignKey,
keyPair,
] = await Promise.all([
subtle.importKey(
'raw-secret',
key,
{ name: algorithm },
false,
['verify', 'sign']),
subtle.importKey(
'raw-secret',
key,
{ name: algorithm },
false,
['verify']),
subtle.generateKey(
'Ed25519',
false,
['sign']),
]);
const signParams = {
name: algorithm,
length,
customization,
};
{
const sig = await subtle.sign(signParams, signKey, data);
assert.strictEqual(
Buffer.from(sig).toString('hex'),
Buffer.from(expected).toString('hex'));
assert(await subtle.verify(signParams, signKey, sig, data));
}
{
const copy = Buffer.from(data);
const p = subtle.sign(signParams, signKey, copy);
copy[0] = 255 - copy[0];
const sig = await p;
assert(await subtle.verify(signParams, signKey, sig, data));
}
// Test failure when no sign usage
await assert.rejects(
subtle.sign(signParams, noSignKey, data), {
message: /Unable to use this key to sign/
});
// Test failure when using the wrong algorithms
await assert.rejects(
subtle.sign(signParams, keyPair.privateKey, data), {
message: /Unable to use this key to sign/
});
}
(async function() {
const variations = [];
for (const vector of vectors) {
variations.push(testVerify(vector));
variations.push(testSign(vector));
}
await Promise.all(variations);
})().then(common.mustCall());

View File

@ -107,6 +107,30 @@ const { subtle } = globalThis.crypto;
test('hello world').then(common.mustCall()); test('hello world').then(common.mustCall());
} }
// Test Sign/Verify KMAC
if (hasOpenSSL(3)) {
async function test(name, data) {
const ec = new TextEncoder();
const key = await subtle.generateKey({
name,
}, true, ['sign', 'verify']);
const signature = await subtle.sign({
name,
length: 256,
}, key, ec.encode(data));
assert(await subtle.verify({
name,
length: 256,
}, key, signature, ec.encode(data)));
}
test('KMAC128', 'hello world').then(common.mustCall());
test('KMAC256', 'hello world').then(common.mustCall());
}
// Test Sign/Verify Ed25519 // Test Sign/Verify Ed25519
{ {
async function test(data) { async function test(data) {

View File

@ -125,6 +125,10 @@ const customTypesMap = {
'Ed448Params': 'webcrypto.html#class-ed448params', 'Ed448Params': 'webcrypto.html#class-ed448params',
'ContextParams': 'webcrypto.html#class-contextparams', 'ContextParams': 'webcrypto.html#class-contextparams',
'CShakeParams': 'webcrypto.html#class-cshakeparams', 'CShakeParams': 'webcrypto.html#class-cshakeparams',
'KmacImportParams': 'webcrypto.html#class-kmacimportparams',
'KmacKeyAlgorithm': 'webcrypto.html#class-kmackeyalgorithm',
'KmacKeyGenParams': 'webcrypto.html#class-kmackeygenparams',
'KmacParams': 'webcrypto.html#class-kmacparams',
'dgram.Socket': 'dgram.html#class-dgramsocket', 'dgram.Socket': 'dgram.html#class-dgramsocket',