mirror of
https://github.com/zebrajr/node.git
synced 2025-12-06 00:20:08 +01:00
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:
parent
a7fde8a86f
commit
14c68e3b53
90
deps/ncrypto/ncrypto.cc
vendored
90
deps/ncrypto/ncrypto.cc
vendored
|
|
@ -4413,6 +4413,96 @@ HMACCtxPointer HMACCtxPointer::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,
|
||||
const EVP_MD* md) {
|
||||
if (md == nullptr) return {};
|
||||
|
|
|
|||
52
deps/ncrypto/ncrypto.h
vendored
52
deps/ncrypto/ncrypto.h
vendored
|
|
@ -229,6 +229,8 @@ class DataPointer;
|
|||
class DHPointer;
|
||||
class ECKeyPointer;
|
||||
class EVPKeyPointer;
|
||||
class EVPMacCtxPointer;
|
||||
class EVPMacPointer;
|
||||
class EVPMDCtxPointer;
|
||||
class SSLCtxPointer;
|
||||
class SSLPointer;
|
||||
|
|
@ -1451,6 +1453,56 @@ class HMACCtxPointer final {
|
|||
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
|
||||
class EnginePointer final {
|
||||
public:
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
<!-- YAML
|
||||
changes:
|
||||
- version: REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/59647
|
||||
description: KMAC algorithms are now supported.
|
||||
- version: REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/59544
|
||||
description: Argon2 algorithms are now supported.
|
||||
|
|
@ -117,6 +120,8 @@ Algorithms:
|
|||
* `'ChaCha20-Poly1305'`
|
||||
* `'cSHAKE128'`
|
||||
* `'cSHAKE256'`
|
||||
* `'KMAC128'`[^openssl30]
|
||||
* `'KMAC256'`[^openssl30]
|
||||
* `'ML-DSA-44'`[^openssl35]
|
||||
* `'ML-DSA-65'`[^openssl35]
|
||||
* `'ML-DSA-87'`[^openssl35]
|
||||
|
|
@ -522,6 +527,8 @@ implementation and the APIs supported for each:
|
|||
| `'Ed448'`[^secure-curves] | ✔ | ✔ | ✔ | ✔ |
|
||||
| `'HKDF'` | | | ✔ | |
|
||||
| `'HMAC'` | ✔ | ✔ | ✔ | |
|
||||
| `'KMAC128'`[^modern-algos] | ✔ | ✔ | ✔ | |
|
||||
| `'KMAC256'`[^modern-algos] | ✔ | ✔ | ✔ | |
|
||||
| `'ML-DSA-44'`[^modern-algos] | ✔ | ✔ | ✔ | ✔ |
|
||||
| `'ML-DSA-65'`[^modern-algos] | ✔ | ✔ | ✔ | ✔ |
|
||||
| `'ML-DSA-87'`[^modern-algos] | ✔ | ✔ | ✔ | ✔ |
|
||||
|
|
@ -566,6 +573,8 @@ implementation and the APIs supported for each:
|
|||
| `'Ed448'`[^secure-curves] | | ✔ | | | | |
|
||||
| `'HKDF'` | | | ✔ | | | |
|
||||
| `'HMAC'` | | ✔ | | | | |
|
||||
| `'KMAC128'`[^modern-algos] | | ✔ | | | | |
|
||||
| `'KMAC256'`[^modern-algos] | | ✔ | | | | |
|
||||
| `'ML-DSA-44'`[^modern-algos] | | ✔ | | | | |
|
||||
| `'ML-DSA-65'`[^modern-algos] | | ✔ | | | | |
|
||||
| `'ML-DSA-87'`[^modern-algos] | | ✔ | | | | |
|
||||
|
|
@ -648,7 +657,7 @@ added: v15.0.0
|
|||
|
||||
<!--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-->
|
||||
|
||||
|
|
@ -736,6 +745,8 @@ Valid key usages depend on the key algorithm (identified by
|
|||
| `'Ed448'`[^secure-curves] | | ✔ | | | |
|
||||
| `'HDKF'` | | | ✔ | | |
|
||||
| `'HMAC'` | | ✔ | | | |
|
||||
| `'KMAC128'`[^modern-algos] | | ✔ | | | |
|
||||
| `'KMAC256'`[^modern-algos] | | ✔ | | | |
|
||||
| `'ML-DSA-44'`[^modern-algos] | | ✔ | | | |
|
||||
| `'ML-DSA-65'`[^modern-algos] | | ✔ | | | |
|
||||
| `'ML-DSA-87'`[^modern-algos] | | ✔ | | | |
|
||||
|
|
@ -831,7 +842,7 @@ added: v24.7.0
|
|||
* `decapsulationAlgorithm` {string|Algorithm}
|
||||
* `decapsulationKey` {CryptoKey}
|
||||
* `ciphertext` {ArrayBuffer|TypedArray|DataView|Buffer}
|
||||
* `sharedKeyAlgorithm` {string|Algorithm|HmacImportParams|AesDerivedKeyParams}
|
||||
* `sharedKeyAlgorithm` {string|Algorithm|HmacImportParams|AesDerivedKeyParams|KmacImportParams}
|
||||
* `extractable` {boolean}
|
||||
* `usages` {string\[]} See [Key usages][].
|
||||
* Returns: {Promise} Fulfills with {CryptoKey} upon success.
|
||||
|
|
@ -946,7 +957,7 @@ changes:
|
|||
|
||||
* `algorithm` {EcdhKeyDeriveParams|HkdfParams|Pbkdf2Params|Argon2Params}
|
||||
* `baseKey` {CryptoKey}
|
||||
* `derivedKeyAlgorithm` {string|Algorithm|HmacImportParams|AesDerivedKeyParams}
|
||||
* `derivedKeyAlgorithm` {string|Algorithm|HmacImportParams|AesDerivedKeyParams|KmacImportParams}
|
||||
* `extractable` {boolean}
|
||||
* `keyUsages` {string\[]} See [Key usages][].
|
||||
* Returns: {Promise} Fulfills with a {CryptoKey} upon success.
|
||||
|
|
@ -1037,7 +1048,7 @@ added: v24.7.0
|
|||
|
||||
* `encapsulationAlgorithm` {string|Algorithm}
|
||||
* `encapsulationKey` {CryptoKey}
|
||||
* `sharedKeyAlgorithm` {string|Algorithm|HmacImportParams|AesDerivedKeyParams}
|
||||
* `sharedKeyAlgorithm` {string|Algorithm|HmacImportParams|AesDerivedKeyParams|KmacImportParams}
|
||||
* `extractable` {boolean}
|
||||
* `usages` {string\[]} See [Key usages][].
|
||||
* Returns: {Promise} Fulfills with {EncapsulatedKey} upon success.
|
||||
|
|
@ -1085,6 +1096,9 @@ The algorithms currently supported include:
|
|||
<!-- YAML
|
||||
added: v15.0.0
|
||||
changes:
|
||||
- version: REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/59647
|
||||
description: KMAC algorithms are now supported.
|
||||
- version: v24.7.0
|
||||
pr-url: https://github.com/nodejs/node/pull/59569
|
||||
description: ML-KEM algorithms are now supported.
|
||||
|
|
@ -1135,6 +1149,8 @@ specification.
|
|||
| `'Ed25519'` | ✔ | ✔ | ✔ | ✔ | | ✔ | |
|
||||
| `'Ed448'`[^secure-curves] | ✔ | ✔ | ✔ | ✔ | | ✔ | |
|
||||
| `'HMAC'` | | | ✔ | ✔ | ✔ | | |
|
||||
| `'KMAC128'`[^modern-algos] | | | ✔ | | ✔ | | |
|
||||
| `'KMAC256'`[^modern-algos] | | | ✔ | | ✔ | | |
|
||||
| `'ML-DSA-44'`[^modern-algos] | ✔ | ✔ | ✔ | | | ✔ | ✔ |
|
||||
| `'ML-DSA-65'`[^modern-algos] | ✔ | ✔ | ✔ | | | ✔ | ✔ |
|
||||
| `'ML-DSA-87'`[^modern-algos] | ✔ | ✔ | ✔ | | | ✔ | ✔ |
|
||||
|
|
@ -1164,6 +1180,9 @@ Derives the public key from a given private key.
|
|||
<!-- YAML
|
||||
added: v15.0.0
|
||||
changes:
|
||||
- version: REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/59647
|
||||
description: KMAC algorithms are now supported.
|
||||
- version: v24.7.0
|
||||
pr-url: https://github.com/nodejs/node/pull/59569
|
||||
description: ML-KEM algorithms are now supported.
|
||||
|
|
@ -1177,7 +1196,7 @@ changes:
|
|||
|
||||
<!--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-->
|
||||
|
||||
|
|
@ -1217,12 +1236,17 @@ The {CryptoKey} (secret key) generating algorithms supported include:
|
|||
* `'AES-OCB'`[^modern-algos]
|
||||
* `'ChaCha20-Poly1305'`[^modern-algos]
|
||||
* `'HMAC'`
|
||||
* `'KMAC128'`[^modern-algos]
|
||||
* `'KMAC256'`[^modern-algos]
|
||||
|
||||
### `subtle.importKey(format, keyData, algorithm, extractable, keyUsages)`
|
||||
|
||||
<!-- YAML
|
||||
added: v15.0.0
|
||||
changes:
|
||||
- version: REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/59647
|
||||
description: KMAC algorithms are now supported.
|
||||
- version: v24.7.0
|
||||
pr-url: https://github.com/nodejs/node/pull/59569
|
||||
description: ML-KEM algorithms are now supported.
|
||||
|
|
@ -1249,7 +1273,7 @@ changes:
|
|||
|
||||
<!--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-->
|
||||
|
||||
|
|
@ -1283,6 +1307,8 @@ The algorithms currently supported include:
|
|||
| `'Ed448'`[^secure-curves] | ✔ | ✔ | ✔ | ✔ | | ✔ | |
|
||||
| `'HDKF'` | | | | ✔ | ✔ | | |
|
||||
| `'HMAC'` | | | ✔ | ✔ | ✔ | | |
|
||||
| `'KMAC128'`[^modern-algos] | | | ✔ | | ✔ | | |
|
||||
| `'KMAC256'`[^modern-algos] | | | ✔ | | ✔ | | |
|
||||
| `'ML-DSA-44'`[^modern-algos] | ✔ | ✔ | ✔ | | | ✔ | ✔ |
|
||||
| `'ML-DSA-65'`[^modern-algos] | ✔ | ✔ | ✔ | | | ✔ | ✔ |
|
||||
| `'ML-DSA-87'`[^modern-algos] | ✔ | ✔ | ✔ | | | ✔ | ✔ |
|
||||
|
|
@ -1301,6 +1327,9 @@ The algorithms currently supported include:
|
|||
<!-- YAML
|
||||
added: v15.0.0
|
||||
changes:
|
||||
- version: REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/59647
|
||||
description: KMAC algorithms are now supported.
|
||||
- version: v24.7.0
|
||||
pr-url: https://github.com/nodejs/node/pull/59365
|
||||
description: ML-DSA algorithms are now supported.
|
||||
|
|
@ -1313,7 +1342,7 @@ changes:
|
|||
|
||||
<!--lint disable maximum-line-length remark-lint-->
|
||||
|
||||
* `algorithm` {string|Algorithm|RsaPssParams|EcdsaParams|Ed448Params|ContextParams}
|
||||
* `algorithm` {string|Algorithm|RsaPssParams|EcdsaParams|Ed448Params|ContextParams|KmacParams}
|
||||
* `key` {CryptoKey}
|
||||
* `data` {ArrayBuffer|TypedArray|DataView|Buffer}
|
||||
* Returns: {Promise} Fulfills with an {ArrayBuffer} upon success.
|
||||
|
|
@ -1331,6 +1360,8 @@ The algorithms currently supported include:
|
|||
* `'Ed25519'`
|
||||
* `'Ed448'`[^secure-curves]
|
||||
* `'HMAC'`
|
||||
* `'KMAC128'`[^modern-algos]
|
||||
* `'KMAC256'`[^modern-algos]
|
||||
* `'ML-DSA-44'`[^modern-algos]
|
||||
* `'ML-DSA-65'`[^modern-algos]
|
||||
* `'ML-DSA-87'`[^modern-algos]
|
||||
|
|
@ -1358,7 +1389,7 @@ changes:
|
|||
<!--lint disable maximum-line-length remark-lint-->
|
||||
|
||||
* `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-->
|
||||
|
||||
|
|
@ -1398,6 +1429,8 @@ The unwrapped key algorithms supported include:
|
|||
* `'Ed25519'`
|
||||
* `'Ed448'`[^secure-curves]
|
||||
* `'HMAC'`
|
||||
* `'KMAC128'`[^secure-curves]
|
||||
* `'KMAC256'`[^secure-curves]
|
||||
* `'ML-DSA-44'`[^modern-algos]
|
||||
* `'ML-DSA-65'`[^modern-algos]
|
||||
* `'ML-DSA-87'`[^modern-algos]
|
||||
|
|
@ -1415,6 +1448,9 @@ The unwrapped key algorithms supported include:
|
|||
<!-- YAML
|
||||
added: v15.0.0
|
||||
changes:
|
||||
- version: REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/59647
|
||||
description: KMAC algorithms are now supported.
|
||||
- version: v24.7.0
|
||||
pr-url: https://github.com/nodejs/node/pull/59365
|
||||
description: ML-DSA algorithms are now supported.
|
||||
|
|
@ -1446,6 +1482,8 @@ The algorithms currently supported include:
|
|||
* `'Ed25519'`
|
||||
* `'Ed448'`[^secure-curves]
|
||||
* `'HMAC'`
|
||||
* `'KMAC128'`[^secure-curves]
|
||||
* `'KMAC256'`[^secure-curves]
|
||||
* `'ML-DSA-44'`[^modern-algos]
|
||||
* `'ML-DSA-65'`[^modern-algos]
|
||||
* `'ML-DSA-87'`[^modern-algos]
|
||||
|
|
@ -2271,6 +2309,115 @@ added: v15.0.0
|
|||
|
||||
* 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`
|
||||
|
||||
<!-- YAML
|
||||
|
|
|
|||
|
|
@ -189,8 +189,12 @@ const {
|
|||
let result;
|
||||
switch (algorithm.name) {
|
||||
case 'HMAC':
|
||||
// Fall through
|
||||
case 'KMAC128':
|
||||
// Fall through
|
||||
case 'KMAC256':
|
||||
result = require('internal/crypto/mac')
|
||||
.hmacImportKey('KeyObject', this, algorithm, extractable, keyUsages);
|
||||
.macImportKey('KeyObject', this, algorithm, extractable, keyUsages);
|
||||
break;
|
||||
case 'AES-CTR':
|
||||
// Fall through
|
||||
|
|
|
|||
|
|
@ -3,11 +3,13 @@
|
|||
const {
|
||||
ArrayFrom,
|
||||
SafeSet,
|
||||
StringPrototypeSubstring,
|
||||
} = primordials;
|
||||
|
||||
const {
|
||||
HmacJob,
|
||||
KeyObjectHandle,
|
||||
KmacJob,
|
||||
kCryptoJobAsync,
|
||||
kSignJobModeSign,
|
||||
kSignJobModeVerify,
|
||||
|
|
@ -32,6 +34,13 @@ const {
|
|||
generateKey: _generateKey,
|
||||
} = require('internal/crypto/keygen');
|
||||
|
||||
|
||||
const {
|
||||
randomBytes: _randomBytes,
|
||||
} = require('internal/crypto/random');
|
||||
|
||||
const randomBytes = promisify(_randomBytes);
|
||||
|
||||
const {
|
||||
InternalCryptoKey,
|
||||
SecretKeyObject,
|
||||
|
|
@ -42,11 +51,11 @@ const {
|
|||
const generateKey = promisify(_generateKey);
|
||||
|
||||
async function hmacGenerateKey(algorithm, extractable, keyUsages) {
|
||||
const { hash, name } = algorithm;
|
||||
let { length } = algorithm;
|
||||
|
||||
if (length === undefined)
|
||||
length = getBlockSize(hash.name);
|
||||
const {
|
||||
hash,
|
||||
name,
|
||||
length = getBlockSize(hash.name),
|
||||
} = algorithm;
|
||||
|
||||
const usageSet = new SafeSet(keyUsages);
|
||||
if (hasAnyNotIn(usageSet, ['sign', 'verify'])) {
|
||||
|
|
@ -68,17 +77,49 @@ async function hmacGenerateKey(algorithm, extractable, keyUsages) {
|
|||
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,
|
||||
keyData,
|
||||
algorithm,
|
||||
extractable,
|
||||
keyUsages,
|
||||
) {
|
||||
const isHmac = algorithm.name === 'HMAC';
|
||||
const usagesSet = new SafeSet(keyUsages);
|
||||
if (hasAnyNotIn(usagesSet, ['sign', 'verify'])) {
|
||||
throw lazyDOMException(
|
||||
'Unsupported key usage for an HMAC key',
|
||||
`Unsupported key usage for ${algorithm.name} key`,
|
||||
'SyntaxError');
|
||||
}
|
||||
let keyObject;
|
||||
|
|
@ -87,7 +128,11 @@ function hmacImportKey(
|
|||
keyObject = keyData;
|
||||
break;
|
||||
}
|
||||
case 'raw-secret':
|
||||
case 'raw': {
|
||||
if (format === 'raw' && !isHmac) {
|
||||
return undefined;
|
||||
}
|
||||
keyObject = createSecretKey(keyData);
|
||||
break;
|
||||
}
|
||||
|
|
@ -115,8 +160,9 @@ function hmacImportKey(
|
|||
}
|
||||
|
||||
if (keyData.alg !== undefined) {
|
||||
const expected =
|
||||
normalizeHashName(algorithm.hash.name, normalizeHashName.kContextJwkHmac);
|
||||
const expected = isHmac ?
|
||||
normalizeHashName(algorithm.hash.name, normalizeHashName.kContextJwkHmac) :
|
||||
`K${StringPrototypeSubstring(algorithm.name, 4)}`;
|
||||
if (expected && keyData.alg !== expected)
|
||||
throw lazyDOMException(
|
||||
'JWK "alg" does not match the requested algorithm',
|
||||
|
|
@ -147,12 +193,18 @@ function hmacImportKey(
|
|||
throw lazyDOMException('Invalid key length', 'DataError');
|
||||
}
|
||||
|
||||
return new InternalCryptoKey(
|
||||
keyObject, {
|
||||
name: 'HMAC',
|
||||
hash: algorithm.hash,
|
||||
const algorithmObject = {
|
||||
name: algorithm.name,
|
||||
length,
|
||||
},
|
||||
};
|
||||
|
||||
if (isHmac) {
|
||||
algorithmObject.hash = algorithm.hash;
|
||||
}
|
||||
|
||||
return new InternalCryptoKey(
|
||||
keyObject,
|
||||
algorithmObject,
|
||||
keyUsages,
|
||||
extractable);
|
||||
}
|
||||
|
|
@ -168,8 +220,23 @@ function hmacSignVerify(key, data, algorithm, 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 = {
|
||||
hmacImportKey,
|
||||
macImportKey,
|
||||
hmacGenerateKey,
|
||||
hmacSignVerify,
|
||||
kmacGenerateKey,
|
||||
kmacSignVerify,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ const {
|
|||
EVP_PKEY_ML_KEM_1024,
|
||||
kKeyVariantAES_OCB_128: hasAesOcbMode,
|
||||
Argon2Job,
|
||||
KmacJob,
|
||||
} = internalBinding('crypto');
|
||||
|
||||
const { getOptionValue } = require('internal/options');
|
||||
|
|
@ -283,6 +284,22 @@ const kAlgorithmDefinitions = {
|
|||
'verify': null,
|
||||
'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': {
|
||||
'generateKey': null,
|
||||
'exportKey': null,
|
||||
|
|
@ -386,6 +403,8 @@ const conditionalAlgorithms = {
|
|||
'cSHAKE256': !process.features.openssl_is_boringssl ||
|
||||
ArrayPrototypeIncludes(getHashes(), 'shake256'),
|
||||
'Ed448': !process.features.openssl_is_boringssl,
|
||||
'KMAC128': !!KmacJob,
|
||||
'KMAC256': !!KmacJob,
|
||||
'ML-DSA-44': !!EVP_PKEY_ML_DSA_44,
|
||||
'ML-DSA-65': !!EVP_PKEY_ML_DSA_65,
|
||||
'ML-DSA-87': !!EVP_PKEY_ML_DSA_87,
|
||||
|
|
@ -411,6 +430,8 @@ const experimentalAlgorithms = [
|
|||
'cSHAKE128',
|
||||
'cSHAKE256',
|
||||
'Ed448',
|
||||
'KMAC128',
|
||||
'KMAC256',
|
||||
'ML-DSA-44',
|
||||
'ML-DSA-65',
|
||||
'ML-DSA-87',
|
||||
|
|
@ -488,6 +509,9 @@ const simpleAlgorithmDictionaries = {
|
|||
nonce: 'BufferSource',
|
||||
secretValue: 'BufferSource',
|
||||
},
|
||||
KmacParams: {
|
||||
customization: 'BufferSource',
|
||||
},
|
||||
};
|
||||
|
||||
function validateMaxBufferLength(data, name) {
|
||||
|
|
|
|||
|
|
@ -185,6 +185,13 @@ async function generateKey(
|
|||
result = await require('internal/crypto/ml_kem')
|
||||
.mlKemGenerateKey(algorithm, extractable, keyUsages);
|
||||
break;
|
||||
case 'KMAC128':
|
||||
// Fall through
|
||||
case 'KMAC256':
|
||||
resultType = 'CryptoKey';
|
||||
result = await require('internal/crypto/mac')
|
||||
.kmacGenerateKey(algorithm, extractable, keyUsages);
|
||||
break;
|
||||
default:
|
||||
throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError');
|
||||
}
|
||||
|
|
@ -278,6 +285,13 @@ function getKeyLength({ name, length, hash }) {
|
|||
}
|
||||
|
||||
throw lazyDOMException('Invalid key length', 'OperationError');
|
||||
case 'KMAC128':
|
||||
case 'KMAC256':
|
||||
if (typeof length === 'number') {
|
||||
return length;
|
||||
}
|
||||
|
||||
return name === 'KMAC128' ? 128 : 256;
|
||||
case 'HKDF':
|
||||
case 'PBKDF2':
|
||||
case 'Argon2d':
|
||||
|
|
@ -533,6 +547,10 @@ async function exportKeyRawSecret(key, format) {
|
|||
return key[kKeyObject][kHandle].export().buffer;
|
||||
case 'AES-OCB':
|
||||
// Fall through
|
||||
case 'KMAC128':
|
||||
// Fall through
|
||||
case 'KMAC256':
|
||||
// Fall through
|
||||
case 'ChaCha20-Poly1305':
|
||||
if (format === 'raw-secret') {
|
||||
return key[kKeyObject][kHandle].export().buffer;
|
||||
|
|
@ -611,6 +629,13 @@ async function exportKeyJWK(key) {
|
|||
if (alg) parameters.alg = alg;
|
||||
break;
|
||||
}
|
||||
case 'KMAC128':
|
||||
parameters.alg = 'K128';
|
||||
break;
|
||||
case 'KMAC256': {
|
||||
parameters.alg = 'K256';
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
|
|
@ -772,9 +797,12 @@ async function importKey(
|
|||
.cfrgImportKey(format, keyData, algorithm, extractable, keyUsages);
|
||||
break;
|
||||
case 'HMAC':
|
||||
format = aliasKeyFormat(format);
|
||||
// Fall through
|
||||
case 'KMAC128':
|
||||
// Fall through
|
||||
case 'KMAC256':
|
||||
result = require('internal/crypto/mac')
|
||||
.hmacImportKey(format, keyData, algorithm, extractable, keyUsages);
|
||||
.macImportKey(format, keyData, algorithm, extractable, keyUsages);
|
||||
break;
|
||||
case 'AES-CTR':
|
||||
// Fall through
|
||||
|
|
@ -785,9 +813,6 @@ async function importKey(
|
|||
case 'AES-KW':
|
||||
// Fall through
|
||||
case 'AES-OCB':
|
||||
if (algorithm.name !== 'AES-OCB') {
|
||||
format = aliasKeyFormat(format);
|
||||
}
|
||||
result = require('internal/crypto/aes')
|
||||
.aesImportKey(algorithm, format, keyData, extractable, keyUsages);
|
||||
break;
|
||||
|
|
@ -1024,6 +1049,11 @@ function signVerify(algorithm, key, data, signature) {
|
|||
case 'ML-DSA-87':
|
||||
return require('internal/crypto/ml_dsa')
|
||||
.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');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
return (V, dict) => {
|
||||
if (V.byteLength) {
|
||||
|
|
@ -527,22 +501,24 @@ converters.EcdsaParams = createDictionaryConverter(
|
|||
},
|
||||
]);
|
||||
|
||||
converters.HmacImportParams = createDictionaryConverter(
|
||||
'HmacImportParams', [
|
||||
for (const { 0: name, 1: zeroError } of [['HmacKeyGenParams', 'OperationError'], ['HmacImportParams', 'DataError']]) {
|
||||
converters[name] = createDictionaryConverter(
|
||||
name, [
|
||||
...new SafeArrayIterator(dictAlgorithm),
|
||||
{
|
||||
key: 'hash',
|
||||
converter: converters.HashAlgorithmIdentifier,
|
||||
validator: (V, dict) => ensureSHA(V, 'HmacImportParams'),
|
||||
validator: (V, dict) => ensureSHA(V, name),
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'length',
|
||||
converter: (V, opts) =>
|
||||
converters['unsigned long'](V, { ...opts, enforceRange: true }),
|
||||
validator: (V, dict) => validateHmacKeyAlgorithm(V),
|
||||
validator: validateMacKeyLength(`${name}.length`, zeroError),
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
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 = {
|
||||
converters,
|
||||
requiredArguments,
|
||||
|
|
|
|||
2
node.gyp
2
node.gyp
|
|
@ -345,6 +345,7 @@
|
|||
'src/crypto/crypto_ml_dsa.cc',
|
||||
'src/crypto/crypto_kem.cc',
|
||||
'src/crypto/crypto_hmac.cc',
|
||||
'src/crypto/crypto_kmac.cc',
|
||||
'src/crypto/crypto_random.cc',
|
||||
'src/crypto/crypto_rsa.cc',
|
||||
'src/crypto/crypto_spkac.cc',
|
||||
|
|
@ -362,6 +363,7 @@
|
|||
'src/crypto/crypto_clienthello-inl.h',
|
||||
'src/crypto/crypto_dh.h',
|
||||
'src/crypto/crypto_hmac.h',
|
||||
'src/crypto/crypto_kmac.h',
|
||||
'src/crypto/crypto_rsa.h',
|
||||
'src/crypto/crypto_spkac.h',
|
||||
'src/crypto/crypto_util.h',
|
||||
|
|
|
|||
220
src/crypto/crypto_kmac.cc
Normal file
220
src/crypto/crypto_kmac.cc
Normal 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
79
src/crypto/crypto_kmac.h
Normal 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_
|
||||
|
|
@ -66,11 +66,13 @@ namespace crypto {
|
|||
#define ARGON2_NAMESPACE_LIST(V)
|
||||
#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
|
||||
#define KEM_NAMESPACE_LIST(V) V(KEM)
|
||||
#define KMAC_NAMESPACE_LIST(V) V(Kmac)
|
||||
#else
|
||||
#define KEM_NAMESPACE_LIST(V)
|
||||
#define KMAC_NAMESPACE_LIST(V)
|
||||
#endif
|
||||
|
||||
#ifdef OPENSSL_NO_SCRYPT
|
||||
|
|
@ -83,6 +85,7 @@ namespace crypto {
|
|||
CRYPTO_NAMESPACE_LIST_BASE(V) \
|
||||
ARGON2_NAMESPACE_LIST(V) \
|
||||
KEM_NAMESPACE_LIST(V) \
|
||||
KMAC_NAMESPACE_LIST(V) \
|
||||
SCRYPT_NAMESPACE_LIST(V)
|
||||
|
||||
void Initialize(Local<Object> target,
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@
|
|||
#include "crypto/crypto_hmac.h"
|
||||
#if OPENSSL_VERSION_MAJOR >= 3
|
||||
#include "crypto/crypto_kem.h"
|
||||
#include "crypto/crypto_kmac.h"
|
||||
#endif
|
||||
#include "crypto/crypto_keygen.h"
|
||||
#include "crypto/crypto_keys.h"
|
||||
|
|
|
|||
120
test/fixtures/crypto/kmac.js
vendored
Normal file
120
test/fixtures/crypto/kmac.js
vendored
Normal 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;
|
||||
};
|
||||
|
|
@ -8,6 +8,7 @@ const shake128 = crypto.getHashes().includes('shake128');
|
|||
const shake256 = crypto.getHashes().includes('shake256');
|
||||
const chacha = crypto.getCiphers().includes('chacha20-poly1305');
|
||||
const ocb = hasOpenSSL(3);
|
||||
const kmac = hasOpenSSL(3);
|
||||
|
||||
const { subtle } = globalThis.crypto;
|
||||
const X25519 = await subtle.generateKey('X25519', false, ['deriveBits', 'deriveKey']);
|
||||
|
|
@ -34,6 +35,10 @@ export const vectors = {
|
|||
[false, 'Argon2d'],
|
||||
[false, 'Argon2i'],
|
||||
[false, 'Argon2id'],
|
||||
[false, 'KMAC128'],
|
||||
[false, 'KMAC256'],
|
||||
[kmac, { name: 'KMAC128', length: 256 }],
|
||||
[kmac, { name: 'KMAC256', length: 256 }],
|
||||
],
|
||||
'generateKey': [
|
||||
[pqc, 'ML-DSA-44'],
|
||||
|
|
@ -47,6 +52,14 @@ export const vectors = {
|
|||
[false, 'Argon2d'],
|
||||
[false, 'Argon2i'],
|
||||
[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': [
|
||||
[pqc, 'ML-DSA-44'],
|
||||
|
|
@ -60,6 +73,14 @@ export const vectors = {
|
|||
[argon2, 'Argon2d'],
|
||||
[argon2, 'Argon2i'],
|
||||
[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': [
|
||||
[pqc, 'ML-DSA-44'],
|
||||
|
|
@ -73,6 +94,8 @@ export const vectors = {
|
|||
[false, 'Argon2d'],
|
||||
[false, 'Argon2i'],
|
||||
[false, 'Argon2id'],
|
||||
[kmac, 'KMAC128'],
|
||||
[kmac, 'KMAC256'],
|
||||
],
|
||||
'getPublicKey': [
|
||||
[true, 'RSA-OAEP'],
|
||||
|
|
@ -99,6 +122,8 @@ export const vectors = {
|
|||
[false, 'Argon2d'],
|
||||
[false, 'Argon2i'],
|
||||
[false, 'Argon2id'],
|
||||
[false, 'KMAC128'],
|
||||
[false, 'KMAC256'],
|
||||
],
|
||||
'deriveKey': [
|
||||
[argon2,
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ function assertCryptoKey(cryptoKey, keyObject, algorithm, extractable, usages) {
|
|||
hmac.toCryptoKey({ name: algorithm, hash: 'SHA-256', length: 0 }, true, usages);
|
||||
}, {
|
||||
name: 'DataError',
|
||||
message: 'Zero-length key is not supported',
|
||||
message: 'HmacImportParams.length cannot be 0',
|
||||
});
|
||||
const cryptoKey = hmac.toCryptoKey({ name: algorithm, hash }, 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
// Flags: --expose-internals --no-warnings
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
|
|
@ -6,6 +5,8 @@ const common = require('../common');
|
|||
if (!common.hasCrypto)
|
||||
common.skip('missing crypto');
|
||||
|
||||
const { hasOpenSSL } = require('../common/crypto');
|
||||
|
||||
const assert = require('assert');
|
||||
const { subtle } = globalThis.crypto;
|
||||
const { KeyObject } = require('crypto');
|
||||
|
|
@ -167,6 +168,15 @@ const { KeyObject } = require('crypto');
|
|||
// [{ 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 () => {
|
||||
const keyPair = await subtle.generateKey({ name: 'ECDH', namedCurve: 'P-521' }, false, ['deriveKey']);
|
||||
for (const [derivedKeyAlgorithm, usage, expected] of vectors) {
|
||||
|
|
@ -183,7 +193,7 @@ const { KeyObject } = require('crypto');
|
|||
} else {
|
||||
assert.strictEqual(result.status, 'fulfilled');
|
||||
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);
|
||||
} else {
|
||||
// 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],
|
||||
];
|
||||
|
||||
if (hasOpenSSL(3)) {
|
||||
vectors.push(
|
||||
['KMAC128', 'sign', 128],
|
||||
[{ name: 'KMAC128', length: 384 }, 'sign', 384],
|
||||
['KMAC256', 'sign', 256],
|
||||
[{ name: 'KMAC256', length: 384 }, 'sign', 384],
|
||||
);
|
||||
}
|
||||
|
||||
(async () => {
|
||||
for (const [derivedKeyAlgorithm, usage, expected] of vectors) {
|
||||
const derived = await subtle.deriveKey(
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ const fixtures = require('../common/fixtures');
|
|||
if (!common.hasCrypto)
|
||||
common.skip('missing crypto');
|
||||
|
||||
const { hasOpenSSL } = require('../common/crypto');
|
||||
|
||||
const assert = require('assert');
|
||||
const { subtle } = globalThis.crypto;
|
||||
const { createPrivateKey, createPublicKey, createSecretKey } = require('crypto');
|
||||
|
|
@ -53,7 +55,7 @@ const { createPrivateKey, createPublicKey, createSecretKey } = require('crypto')
|
|||
hash: 'SHA-256'
|
||||
}, false, ['deriveBits']), {
|
||||
name: 'SyntaxError',
|
||||
message: 'Unsupported key usage for an HMAC key'
|
||||
message: 'Unsupported key usage for HMAC key'
|
||||
});
|
||||
await assert.rejects(
|
||||
subtle.importKey('raw', keyData, {
|
||||
|
|
@ -62,7 +64,7 @@ const { createPrivateKey, createPublicKey, createSecretKey } = require('crypto')
|
|||
length: 0
|
||||
}, false, ['sign', 'verify']), {
|
||||
name: 'DataError',
|
||||
message: 'Zero-length key is not supported'
|
||||
message: 'HmacImportParams.length cannot be 0'
|
||||
});
|
||||
await assert.rejects(
|
||||
subtle.importKey('raw', keyData, {
|
||||
|
|
@ -71,7 +73,7 @@ const { createPrivateKey, createPublicKey, createSecretKey } = require('crypto')
|
|||
length: 1
|
||||
}, false, ['sign', 'verify']), {
|
||||
name: 'NotSupportedError',
|
||||
message: 'Unsupported algorithm.length'
|
||||
message: 'Unsupported HmacImportParams.length'
|
||||
});
|
||||
await assert.rejects(
|
||||
subtle.importKey('jwk', null, {
|
||||
|
|
@ -97,7 +99,6 @@ const { createPrivateKey, createPublicKey, createSecretKey } = require('crypto')
|
|||
hash: 'SHA-256'
|
||||
}, true, ['sign', 'verify']);
|
||||
|
||||
|
||||
assert.strictEqual(key.algorithm, key.algorithm);
|
||||
assert.strictEqual(key.usages, key.usages);
|
||||
|
||||
|
|
@ -111,11 +112,44 @@ const { createPrivateKey, createPublicKey, createSecretKey } = require('crypto')
|
|||
assert.deepStrictEqual(jwk.key_ops, ['sign', 'verify']);
|
||||
assert(jwk.ext);
|
||||
assert.strictEqual(jwk.kty, 'oct');
|
||||
assert.strictEqual(jwk.alg, 'HS256');
|
||||
|
||||
assert.deepStrictEqual(
|
||||
Buffer.from(jwk.k, 'base64').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(
|
||||
subtle.importKey(
|
||||
'raw',
|
||||
|
|
@ -132,6 +166,76 @@ const { createPrivateKey, createPublicKey, createSecretKey } = require('crypto')
|
|||
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
|
||||
{
|
||||
async function test() {
|
||||
|
|
|
|||
50
test/parallel/test-webcrypto-keygen-kmac.js
Normal file
50
test/parallel/test-webcrypto-keygen-kmac.js
Normal 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());
|
||||
|
|
@ -184,6 +184,16 @@ if (hasOpenSSL(3)) {
|
|||
'unwrapKey',
|
||||
],
|
||||
};
|
||||
|
||||
for (const name of ['KMAC128', 'KMAC256']) {
|
||||
vectors[name] = {
|
||||
result: 'CryptoKey',
|
||||
usages: [
|
||||
'sign',
|
||||
'verify',
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (hasOpenSSL(3, 5)) {
|
||||
|
|
|
|||
193
test/parallel/test-webcrypto-sign-verify-kmac.js
Normal file
193
test/parallel/test-webcrypto-sign-verify-kmac.js
Normal 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());
|
||||
|
|
@ -107,6 +107,30 @@ const { subtle } = globalThis.crypto;
|
|||
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
|
||||
{
|
||||
async function test(data) {
|
||||
|
|
|
|||
|
|
@ -125,6 +125,10 @@ const customTypesMap = {
|
|||
'Ed448Params': 'webcrypto.html#class-ed448params',
|
||||
'ContextParams': 'webcrypto.html#class-contextparams',
|
||||
'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',
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user