mirror of
https://github.com/zebrajr/node.git
synced 2025-12-06 00:20:08 +01:00
crypto: add AES-OCB Web Cryptography algorithm
PR-URL: https://github.com/nodejs/node/pull/59539 Reviewed-By: Tobias Nießen <tniessen@tnie.de> Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
parent
28e6ba8e4d
commit
8692e601cc
8
deps/ncrypto/ncrypto.cc
vendored
8
deps/ncrypto/ncrypto.cc
vendored
|
|
@ -3041,6 +3041,9 @@ const Cipher Cipher::AES_256_GCM = Cipher::FromNid(NID_aes_256_gcm);
|
|||
const Cipher Cipher::AES_128_KW = Cipher::FromNid(NID_id_aes128_wrap);
|
||||
const Cipher Cipher::AES_192_KW = Cipher::FromNid(NID_id_aes192_wrap);
|
||||
const Cipher Cipher::AES_256_KW = Cipher::FromNid(NID_id_aes256_wrap);
|
||||
const Cipher Cipher::AES_128_OCB = Cipher::FromNid(NID_aes_128_ocb);
|
||||
const Cipher Cipher::AES_192_OCB = Cipher::FromNid(NID_aes_192_ocb);
|
||||
const Cipher Cipher::AES_256_OCB = Cipher::FromNid(NID_aes_256_ocb);
|
||||
const Cipher Cipher::CHACHA20_POLY1305 = Cipher::FromNid(NID_chacha20_poly1305);
|
||||
|
||||
bool Cipher::isGcmMode() const {
|
||||
|
|
@ -3243,6 +3246,11 @@ bool CipherCtxPointer::isGcmMode() const {
|
|||
return getMode() == EVP_CIPH_GCM_MODE;
|
||||
}
|
||||
|
||||
bool CipherCtxPointer::isOcbMode() const {
|
||||
if (!ctx_) return false;
|
||||
return getMode() == EVP_CIPH_OCB_MODE;
|
||||
}
|
||||
|
||||
bool CipherCtxPointer::isCcmMode() const {
|
||||
if (!ctx_) return false;
|
||||
return getMode() == EVP_CIPH_CCM_MODE;
|
||||
|
|
|
|||
4
deps/ncrypto/ncrypto.h
vendored
4
deps/ncrypto/ncrypto.h
vendored
|
|
@ -373,6 +373,9 @@ class Cipher final {
|
|||
static const Cipher AES_128_KW;
|
||||
static const Cipher AES_192_KW;
|
||||
static const Cipher AES_256_KW;
|
||||
static const Cipher AES_128_OCB;
|
||||
static const Cipher AES_192_OCB;
|
||||
static const Cipher AES_256_OCB;
|
||||
static const Cipher CHACHA20_POLY1305;
|
||||
|
||||
struct CipherParams {
|
||||
|
|
@ -738,6 +741,7 @@ class CipherCtxPointer final {
|
|||
int getNid() const;
|
||||
|
||||
bool isGcmMode() const;
|
||||
bool isOcbMode() const;
|
||||
bool isCcmMode() const;
|
||||
bool isWrapMode() const;
|
||||
bool isChaCha20Poly1305() const;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
<!-- YAML
|
||||
changes:
|
||||
- version: REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/59539
|
||||
description: AES-OCB algorithm is now supported.
|
||||
- version: REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/59569
|
||||
description: ML-KEM algorithms are now supported.
|
||||
|
|
@ -104,6 +107,7 @@ WICG proposal:
|
|||
|
||||
Algorithms:
|
||||
|
||||
* `'AES-OCB'`[^openssl30]
|
||||
* `'ChaCha20-Poly1305'`
|
||||
* `'cSHAKE128'`
|
||||
* `'cSHAKE256'`
|
||||
|
|
@ -501,6 +505,7 @@ implementation and the APIs supported for each:
|
|||
| `'AES-CTR'` | ✔ | ✔ | ✔ | |
|
||||
| `'AES-GCM'` | ✔ | ✔ | ✔ | |
|
||||
| `'AES-KW'` | ✔ | ✔ | ✔ | |
|
||||
| `'AES-OCB'` | ✔ | ✔ | ✔ | |
|
||||
| `'ChaCha20-Poly1305'`[^modern-algos] | ✔ | ✔ | ✔ | |
|
||||
| `'ECDH'` | ✔ | ✔ | ✔ | ✔ |
|
||||
| `'ECDSA'` | ✔ | ✔ | ✔ | ✔ |
|
||||
|
|
@ -539,6 +544,7 @@ implementation and the APIs supported for each:
|
|||
| `'AES-CTR'` | ✔ | | | ✔ | | |
|
||||
| `'AES-GCM'` | ✔ | | | ✔ | | |
|
||||
| `'AES-KW'` | | | | ✔ | | |
|
||||
| `'AES-OCB'` | ✔ | | | ✔ | | |
|
||||
| `'ChaCha20-Poly1305'`[^modern-algos] | ✔ | | | ✔ | | |
|
||||
| `'cSHAKE128'`[^modern-algos] | | | | | | ✔ |
|
||||
| `'cSHAKE256'`[^modern-algos] | | | | | | ✔ |
|
||||
|
|
@ -707,6 +713,7 @@ Valid key usages depend on the key algorithm (identified by
|
|||
| `'AES-CTR'` | ✔ | | | ✔ | |
|
||||
| `'AES-GCM'` | ✔ | | | ✔ | |
|
||||
| `'AES-KW'` | | | | ✔ | |
|
||||
| `'AES-OCB'` | ✔ | | | ✔ | |
|
||||
| `'ChaCha20-Poly1305'`[^modern-algos] | ✔ | | | ✔ | |
|
||||
| `'ECDH'` | | | ✔ | | |
|
||||
| `'ECDSA'` | | ✔ | | | |
|
||||
|
|
@ -825,6 +832,9 @@ The algorithms currently supported include:
|
|||
<!-- YAML
|
||||
added: v15.0.0
|
||||
changes:
|
||||
- version: REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/59539
|
||||
description: AES-OCB algorithm is now supported.
|
||||
- version: REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/59365
|
||||
description: ChaCha20-Poly1305 algorithm is now supported.
|
||||
|
|
@ -845,6 +855,7 @@ The algorithms currently supported include:
|
|||
* `'AES-CBC'`
|
||||
* `'AES-CTR'`
|
||||
* `'AES-GCM'`
|
||||
* `'AES-OCB'`[^modern-algos]
|
||||
* `'ChaCha20-Poly1305'`[^modern-algos]
|
||||
* `'RSA-OAEP'`
|
||||
|
||||
|
|
@ -1015,6 +1026,9 @@ The algorithms currently supported include:
|
|||
<!-- YAML
|
||||
added: v15.0.0
|
||||
changes:
|
||||
- version: REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/59539
|
||||
description: AES-OCB algorithm is now supported.
|
||||
- version: REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/59365
|
||||
description: ChaCha20-Poly1305 algorithm is now supported.
|
||||
|
|
@ -1035,6 +1049,7 @@ The algorithms currently supported include:
|
|||
* `'AES-CBC'`
|
||||
* `'AES-CTR'`
|
||||
* `'AES-GCM'`
|
||||
* `'AES-OCB'`[^modern-algos]
|
||||
* `'ChaCha20-Poly1305'`[^modern-algos]
|
||||
* `'RSA-OAEP'`
|
||||
|
||||
|
|
@ -1086,6 +1101,7 @@ specification.
|
|||
| `'AES-CTR'` | | | ✔ | ✔ | ✔ | | |
|
||||
| `'AES-GCM'` | | | ✔ | ✔ | ✔ | | |
|
||||
| `'AES-KW'` | | | ✔ | ✔ | ✔ | | |
|
||||
| `'AES-OCB'`[^modern-algos] | | | ✔ | | ✔ | | |
|
||||
| `'ChaCha20-Poly1305'`[^modern-algos] | | | ✔ | | ✔ | | |
|
||||
| `'ECDH'` | ✔ | ✔ | ✔ | ✔ | | ✔ | |
|
||||
| `'ECDSA'` | ✔ | ✔ | ✔ | ✔ | | ✔ | |
|
||||
|
|
@ -1171,6 +1187,7 @@ The {CryptoKey} (secret key) generating algorithms supported include:
|
|||
* `'AES-CTR'`
|
||||
* `'AES-GCM'`
|
||||
* `'AES-KW'`
|
||||
* `'AES-OCB'`[^modern-algos]
|
||||
* `'ChaCha20-Poly1305'`[^modern-algos]
|
||||
* `'HMAC'`
|
||||
|
||||
|
|
@ -1228,6 +1245,7 @@ The algorithms currently supported include:
|
|||
| `'AES-CTR'` | | | ✔ | ✔ | ✔ | | |
|
||||
| `'AES-GCM'` | | | ✔ | ✔ | ✔ | | |
|
||||
| `'AES-KW'` | | | ✔ | ✔ | ✔ | | |
|
||||
| `'AES-OCB'`[^modern-algos] | | | ✔ | | ✔ | | |
|
||||
| `'ChaCha20-Poly1305'`[^modern-algos] | | | ✔ | | ✔ | | |
|
||||
| `'ECDH'` | ✔ | ✔ | ✔ | ✔ | | ✔ | |
|
||||
| `'ECDSA'` | ✔ | ✔ | ✔ | ✔ | | ✔ | |
|
||||
|
|
@ -1294,6 +1312,9 @@ The algorithms currently supported include:
|
|||
<!-- YAML
|
||||
added: v15.0.0
|
||||
changes:
|
||||
- version: REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/59539
|
||||
description: AES-OCB algorithm is now supported.
|
||||
- version: REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/59365
|
||||
description: ChaCha20-Poly1305 algorithm is now supported.
|
||||
|
|
@ -1330,6 +1351,7 @@ The wrapping algorithms currently supported include:
|
|||
* `'AES-CTR'`
|
||||
* `'AES-GCM'`
|
||||
* `'AES-KW'`
|
||||
* `'AES-OCB'`[^modern-algos]
|
||||
* `'ChaCha20-Poly1305'`[^modern-algos]
|
||||
* `'RSA-OAEP'`
|
||||
|
||||
|
|
@ -1339,6 +1361,7 @@ The unwrapped key algorithms supported include:
|
|||
* `'AES-CTR'`
|
||||
* `'AES-GCM'`
|
||||
* `'AES-KW'`
|
||||
* `'AES-OCB'`[^modern-algos]
|
||||
* `'ChaCha20-Poly1305'`[^modern-algos]
|
||||
* `'ECDH'`
|
||||
* `'ECDSA'`
|
||||
|
|
@ -1404,6 +1427,9 @@ The algorithms currently supported include:
|
|||
<!-- YAML
|
||||
added: v15.0.0
|
||||
changes:
|
||||
- version: REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/59539
|
||||
description: AES-OCB algorithm is now supported.
|
||||
- version: REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/59365
|
||||
description: ChaCha20-Poly1305 algorithm is now supported.
|
||||
|
|
@ -1436,6 +1462,7 @@ The wrapping algorithms currently supported include:
|
|||
* `'AES-CTR'`
|
||||
* `'AES-GCM'`
|
||||
* `'AES-KW'`
|
||||
* `'AES-OCB'`[^modern-algos]
|
||||
* `'ChaCha20-Poly1305'`[^modern-algos]
|
||||
* `'RSA-OAEP'`
|
||||
|
||||
|
|
@ -1493,7 +1520,7 @@ given key.
|
|||
added: v15.0.0
|
||||
-->
|
||||
|
||||
* Type: {string} Must be `'AES-GCM'` or `'ChaCha20-Poly1305'`.
|
||||
* Type: {string} Must be `'AES-GCM'`, `'AES-OCB'`, or `'ChaCha20-Poly1305'`.
|
||||
|
||||
#### `aeadParams.tagLength`
|
||||
|
||||
|
|
@ -1515,8 +1542,7 @@ added: v15.0.0
|
|||
added: v15.0.0
|
||||
-->
|
||||
|
||||
* Type: {string} Must be one of `'AES-CBC'`, `'AES-CTR'`, `'AES-GCM'`, or
|
||||
`'AES-KW'`
|
||||
* Type: {string} Must be one of `'AES-CBC'`, `'AES-CTR'`, `'AES-GCM'`, `'AES-OCB'`, or `'AES-KW'`
|
||||
|
||||
#### `aesDerivedKeyParams.length`
|
||||
|
||||
|
|
@ -2392,6 +2418,8 @@ The length (in bytes) of the random salt to use.
|
|||
|
||||
[^modern-algos]: See [Modern Algorithms in the Web Cryptography API][]
|
||||
|
||||
[^openssl30]: Requires OpenSSL >= 3.0
|
||||
|
||||
[^openssl35]: Requires OpenSSL >= 3.5
|
||||
|
||||
[JSON Web Key]: https://tools.ietf.org/html/rfc7517
|
||||
|
|
|
|||
|
|
@ -18,14 +18,17 @@ const {
|
|||
kKeyVariantAES_CBC_128,
|
||||
kKeyVariantAES_GCM_128,
|
||||
kKeyVariantAES_KW_128,
|
||||
kKeyVariantAES_OCB_128,
|
||||
kKeyVariantAES_CTR_192,
|
||||
kKeyVariantAES_CBC_192,
|
||||
kKeyVariantAES_GCM_192,
|
||||
kKeyVariantAES_KW_192,
|
||||
kKeyVariantAES_OCB_192,
|
||||
kKeyVariantAES_CTR_256,
|
||||
kKeyVariantAES_CBC_256,
|
||||
kKeyVariantAES_GCM_256,
|
||||
kKeyVariantAES_KW_256,
|
||||
kKeyVariantAES_OCB_256,
|
||||
kWebCryptoCipherDecrypt,
|
||||
kWebCryptoCipherEncrypt,
|
||||
} = internalBinding('crypto');
|
||||
|
|
@ -62,6 +65,7 @@ function getAlgorithmName(name, length) {
|
|||
case 'AES-CTR': return `A${length}CTR`;
|
||||
case 'AES-GCM': return `A${length}GCM`;
|
||||
case 'AES-KW': return `A${length}KW`;
|
||||
case 'AES-OCB': return `A${length}OCB`;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -100,6 +104,13 @@ function getVariant(name, length) {
|
|||
case 256: return kKeyVariantAES_KW_256;
|
||||
}
|
||||
break;
|
||||
case 'AES-OCB':
|
||||
switch (length) {
|
||||
case 128: return kKeyVariantAES_OCB_128;
|
||||
case 192: return kKeyVariantAES_OCB_192;
|
||||
case 256: return kKeyVariantAES_OCB_256;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -173,11 +184,49 @@ function asyncAesGcmCipher(mode, key, data, algorithm) {
|
|||
algorithm.additionalData));
|
||||
}
|
||||
|
||||
function asyncAesOcbCipher(mode, key, data, algorithm) {
|
||||
const { tagLength = 128 } = algorithm;
|
||||
|
||||
const tagByteLength = tagLength / 8;
|
||||
let tag;
|
||||
switch (mode) {
|
||||
case kWebCryptoCipherDecrypt: {
|
||||
const slice = ArrayBufferIsView(data) ?
|
||||
TypedArrayPrototypeSlice : ArrayBufferPrototypeSlice;
|
||||
tag = slice(data, -tagByteLength);
|
||||
|
||||
// Similar to GCM, OCB requires the tag to be present for decryption
|
||||
if (tagByteLength > tag.byteLength) {
|
||||
return PromiseReject(lazyDOMException(
|
||||
'The provided data is too small.',
|
||||
'OperationError'));
|
||||
}
|
||||
|
||||
data = slice(data, 0, -tagByteLength);
|
||||
break;
|
||||
}
|
||||
case kWebCryptoCipherEncrypt:
|
||||
tag = tagByteLength;
|
||||
break;
|
||||
}
|
||||
|
||||
return jobPromise(() => new AESCipherJob(
|
||||
kCryptoJobAsync,
|
||||
mode,
|
||||
key[kKeyObject][kHandle],
|
||||
data,
|
||||
getVariant('AES-OCB', key.algorithm.length),
|
||||
algorithm.iv,
|
||||
tag,
|
||||
algorithm.additionalData));
|
||||
}
|
||||
|
||||
function aesCipher(mode, key, data, algorithm) {
|
||||
switch (algorithm.name) {
|
||||
case 'AES-CTR': return asyncAesCtrCipher(mode, key, data, algorithm);
|
||||
case 'AES-CBC': return asyncAesCbcCipher(mode, key, data, algorithm);
|
||||
case 'AES-GCM': return asyncAesGcmCipher(mode, key, data, algorithm);
|
||||
case 'AES-OCB': return asyncAesOcbCipher(mode, key, data, algorithm);
|
||||
case 'AES-KW': return asyncAesKwCipher(mode, key, data);
|
||||
}
|
||||
}
|
||||
|
|
@ -236,7 +285,11 @@ function aesImportKey(
|
|||
keyObject = keyData;
|
||||
break;
|
||||
}
|
||||
case 'raw-secret':
|
||||
case 'raw': {
|
||||
if (format === 'raw' && name === 'AES-OCB') {
|
||||
return undefined;
|
||||
}
|
||||
validateKeyLength(keyData.byteLength * 8);
|
||||
keyObject = createSecretKey(keyData);
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -199,6 +199,8 @@ const {
|
|||
case 'AES-GCM':
|
||||
// Fall through
|
||||
case 'AES-KW':
|
||||
// Fall through
|
||||
case 'AES-OCB':
|
||||
result = require('internal/crypto/aes')
|
||||
.aesImportKey(algorithm, 'KeyObject', this, extractable, keyUsages);
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ const {
|
|||
EVP_PKEY_ML_KEM_512,
|
||||
EVP_PKEY_ML_KEM_768,
|
||||
EVP_PKEY_ML_KEM_1024,
|
||||
kKeyVariantAES_OCB_128: hasAesOcbMode,
|
||||
} = internalBinding('crypto');
|
||||
|
||||
const { getOptionValue } = require('internal/options');
|
||||
|
|
@ -208,6 +209,14 @@ const kAlgorithmDefinitions = {
|
|||
'wrapKey': null,
|
||||
'unwrapKey': null,
|
||||
},
|
||||
'AES-OCB': {
|
||||
'generateKey': 'AesKeyGenParams',
|
||||
'exportKey': null,
|
||||
'importKey': null,
|
||||
'encrypt': 'AeadParams',
|
||||
'decrypt': 'AeadParams',
|
||||
'get key length': 'AesDerivedKeyParams',
|
||||
},
|
||||
'ChaCha20-Poly1305': {
|
||||
'generateKey': null,
|
||||
'exportKey': null,
|
||||
|
|
@ -350,6 +359,7 @@ const kAlgorithmDefinitions = {
|
|||
// Conditionally supported algorithms
|
||||
const conditionalAlgorithms = {
|
||||
'AES-KW': !process.features.openssl_is_boringssl,
|
||||
'AES-OCB': !!hasAesOcbMode,
|
||||
'ChaCha20-Poly1305': !process.features.openssl_is_boringssl ||
|
||||
ArrayPrototypeIncludes(getCiphers(), 'chacha20-poly1305'),
|
||||
'cSHAKE128': !process.features.openssl_is_boringssl ||
|
||||
|
|
@ -374,6 +384,7 @@ const conditionalAlgorithms = {
|
|||
|
||||
// Experimental algorithms
|
||||
const experimentalAlgorithms = [
|
||||
'AES-OCB',
|
||||
'ChaCha20-Poly1305',
|
||||
'cSHAKE128',
|
||||
'cSHAKE256',
|
||||
|
|
|
|||
|
|
@ -155,6 +155,8 @@ async function generateKey(
|
|||
// Fall through
|
||||
case 'AES-GCM':
|
||||
// Fall through
|
||||
case 'AES-OCB':
|
||||
// Fall through
|
||||
case 'AES-KW':
|
||||
resultType = 'CryptoKey';
|
||||
result = await require('internal/crypto/aes')
|
||||
|
|
@ -253,6 +255,7 @@ function getKeyLength({ name, length, hash }) {
|
|||
case 'AES-CTR':
|
||||
case 'AES-CBC':
|
||||
case 'AES-GCM':
|
||||
case 'AES-OCB':
|
||||
case 'AES-KW':
|
||||
if (length !== 128 && length !== 192 && length !== 256)
|
||||
throw lazyDOMException('Invalid key length', 'OperationError');
|
||||
|
|
@ -510,6 +513,8 @@ async function exportKeyRawSecret(key, format) {
|
|||
// Fall through
|
||||
case 'HMAC':
|
||||
return key[kKeyObject][kHandle].export().buffer;
|
||||
case 'AES-OCB':
|
||||
// Fall through
|
||||
case 'ChaCha20-Poly1305':
|
||||
if (format === 'raw-secret') {
|
||||
return key[kKeyObject][kHandle].export().buffer;
|
||||
|
|
@ -572,6 +577,8 @@ async function exportKeyJWK(key) {
|
|||
// Fall through
|
||||
case 'AES-GCM':
|
||||
// Fall through
|
||||
case 'AES-OCB':
|
||||
// Fall through
|
||||
case 'AES-KW':
|
||||
parameters.alg = require('internal/crypto/aes')
|
||||
.getAlgorithmName(key[kAlgorithm].name, key[kAlgorithm].length);
|
||||
|
|
@ -758,7 +765,11 @@ async function importKey(
|
|||
case 'AES-GCM':
|
||||
// Fall through
|
||||
case 'AES-KW':
|
||||
format = aliasKeyFormat(format);
|
||||
// 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;
|
||||
|
|
@ -1060,6 +1071,8 @@ async function cipherOrWrap(mode, algorithm, key, data, op) {
|
|||
case 'AES-CBC':
|
||||
// Fall through
|
||||
case 'AES-GCM':
|
||||
// Fall through
|
||||
case 'AES-OCB':
|
||||
return require('internal/crypto/aes')
|
||||
.aesCipher(mode, key, data, algorithm);
|
||||
case 'ChaCha20-Poly1305':
|
||||
|
|
|
|||
|
|
@ -699,6 +699,13 @@ converters.AeadParams = createDictionaryConverter(
|
|||
case 'aes-gcm':
|
||||
validateMaxBufferLength(V, 'algorithm.iv');
|
||||
break;
|
||||
case 'aes-ocb':
|
||||
if (V.byteLength > 15) {
|
||||
throw lazyDOMException(
|
||||
'AES-OCB algorithm.iv must be no more than 15 bytes',
|
||||
'OperationError');
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
required: true,
|
||||
|
|
@ -723,6 +730,13 @@ converters.AeadParams = createDictionaryConverter(
|
|||
'OperationError');
|
||||
}
|
||||
break;
|
||||
case 'aes-ocb':
|
||||
if (!ArrayPrototypeIncludes([64, 96, 128], V)) {
|
||||
throw lazyDOMException(
|
||||
`${V} is not a valid AES-OCB tag length`,
|
||||
'OperationError');
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -61,7 +61,8 @@ WebCryptoCipherStatus AES_Cipher(Environment* env,
|
|||
return WebCryptoCipherStatus::FAILED;
|
||||
}
|
||||
|
||||
if (params.cipher.isGcmMode() && !ctx.setIvLength(params.iv.size())) {
|
||||
if ((params.cipher.isGcmMode() || params.cipher.isOcbMode()) &&
|
||||
!ctx.setIvLength(params.iv.size())) {
|
||||
return WebCryptoCipherStatus::FAILED;
|
||||
}
|
||||
|
||||
|
|
@ -76,11 +77,20 @@ WebCryptoCipherStatus AES_Cipher(Environment* env,
|
|||
|
||||
size_t tag_len = 0;
|
||||
|
||||
if (params.cipher.isGcmMode()) {
|
||||
if (params.cipher.isGcmMode() || params.cipher.isOcbMode()) {
|
||||
switch (cipher_mode) {
|
||||
case kWebCryptoCipherDecrypt: {
|
||||
// If in decrypt mode, the auth tag must be set in the params.tag.
|
||||
CHECK(params.tag);
|
||||
|
||||
// For OCB mode, we need to set the auth tag length before setting the
|
||||
// tag
|
||||
if (params.cipher.isOcbMode()) {
|
||||
if (!ctx.setAeadTagLength(params.tag.size())) {
|
||||
return WebCryptoCipherStatus::FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
ncrypto::Buffer<const char> buffer = {
|
||||
.data = params.tag.data<char>(),
|
||||
.len = params.tag.size(),
|
||||
|
|
@ -91,12 +101,19 @@ WebCryptoCipherStatus AES_Cipher(Environment* env,
|
|||
break;
|
||||
}
|
||||
case kWebCryptoCipherEncrypt: {
|
||||
// In decrypt mode, we grab the tag length here. We'll use it to
|
||||
// In encrypt mode, we grab the tag length here. We'll use it to
|
||||
// ensure that that allocated buffer has enough room for both the
|
||||
// final block and the auth tag. Unlike our other AES-GCM implementation
|
||||
// in CipherBase, in WebCrypto, the auth tag is concatenated to the end
|
||||
// of the generated ciphertext and returned in the same ArrayBuffer.
|
||||
tag_len = params.length;
|
||||
|
||||
// For OCB mode, we need to set the auth tag length
|
||||
if (params.cipher.isOcbMode()) {
|
||||
if (!ctx.setAeadTagLength(tag_len)) {
|
||||
return WebCryptoCipherStatus::FAILED;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
|
@ -112,8 +129,8 @@ WebCryptoCipherStatus AES_Cipher(Environment* env,
|
|||
.data = params.additional_data.data<unsigned char>(),
|
||||
.len = params.additional_data.size(),
|
||||
};
|
||||
if (params.cipher.isGcmMode() && params.additional_data.size() &&
|
||||
!ctx.update(buffer, nullptr, &out_len)) {
|
||||
if ((params.cipher.isGcmMode() || params.cipher.isOcbMode()) &&
|
||||
params.additional_data.size() && !ctx.update(buffer, nullptr, &out_len)) {
|
||||
return WebCryptoCipherStatus::FAILED;
|
||||
}
|
||||
|
||||
|
|
@ -147,9 +164,9 @@ WebCryptoCipherStatus AES_Cipher(Environment* env,
|
|||
}
|
||||
total += out_len;
|
||||
|
||||
// If using AES_GCM, grab the generated auth tag and append
|
||||
// If using AES_GCM or AES_OCB, grab the generated auth tag and append
|
||||
// it to the end of the ciphertext.
|
||||
if (encrypt && params.cipher.isGcmMode()) {
|
||||
if (encrypt && (params.cipher.isGcmMode() || params.cipher.isOcbMode())) {
|
||||
if (!ctx.getAeadTag(tag_len, ptr + total)) {
|
||||
return WebCryptoCipherStatus::FAILED;
|
||||
}
|
||||
|
|
@ -492,7 +509,7 @@ Maybe<void> AESCipherTraits::AdditionalConfig(
|
|||
if (!ValidateCounter(env, args[offset + 2], params)) {
|
||||
return Nothing<void>();
|
||||
}
|
||||
} else if (params->cipher.isGcmMode()) {
|
||||
} else if (params->cipher.isGcmMode() || params->cipher.isOcbMode()) {
|
||||
if (!ValidateAuthTag(env, mode, cipher_mode, args[offset + 2], params) ||
|
||||
!ValidateAdditionalData(env, mode, args[offset + 3], params)) {
|
||||
return Nothing<void>();
|
||||
|
|
@ -502,9 +519,18 @@ Maybe<void> AESCipherTraits::AdditionalConfig(
|
|||
UseDefaultIV(params);
|
||||
}
|
||||
|
||||
if (params->iv.size() < static_cast<size_t>(params->cipher.getIvLength())) {
|
||||
THROW_ERR_CRYPTO_INVALID_IV(env);
|
||||
return Nothing<void>();
|
||||
// For OCB mode, allow variable IV lengths (1-15 bytes)
|
||||
if (params->cipher.isOcbMode()) {
|
||||
if (params->iv.size() == 0 || params->iv.size() > 15) {
|
||||
THROW_ERR_CRYPTO_INVALID_IV(env);
|
||||
return Nothing<void>();
|
||||
}
|
||||
} else {
|
||||
// For other modes, check against the cipher's expected IV length
|
||||
if (params->iv.size() < static_cast<size_t>(params->cipher.getIvLength())) {
|
||||
THROW_ERR_CRYPTO_INVALID_IV(env);
|
||||
return Nothing<void>();
|
||||
}
|
||||
}
|
||||
|
||||
return JustVoid();
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
namespace node::crypto {
|
||||
constexpr unsigned kNoAuthTagLength = static_cast<unsigned>(-1);
|
||||
|
||||
#define VARIANTS(V) \
|
||||
#define VARIANTS_COMMON(V) \
|
||||
V(CTR_128, AES_CTR_Cipher, ncrypto::Cipher::AES_128_CTR) \
|
||||
V(CTR_192, AES_CTR_Cipher, ncrypto::Cipher::AES_192_CTR) \
|
||||
V(CTR_256, AES_CTR_Cipher, ncrypto::Cipher::AES_256_CTR) \
|
||||
|
|
@ -26,6 +26,19 @@ constexpr unsigned kNoAuthTagLength = static_cast<unsigned>(-1);
|
|||
V(KW_192, AES_Cipher, ncrypto::Cipher::AES_192_KW) \
|
||||
V(KW_256, AES_Cipher, ncrypto::Cipher::AES_256_KW)
|
||||
|
||||
#if OPENSSL_VERSION_MAJOR >= 3
|
||||
#define VARIANTS_OCB(V) \
|
||||
V(OCB_128, AES_Cipher, ncrypto::Cipher::AES_128_OCB) \
|
||||
V(OCB_192, AES_Cipher, ncrypto::Cipher::AES_192_OCB) \
|
||||
V(OCB_256, AES_Cipher, ncrypto::Cipher::AES_256_OCB)
|
||||
#else
|
||||
#define VARIANTS_OCB(V)
|
||||
#endif
|
||||
|
||||
#define VARIANTS(V) \
|
||||
VARIANTS_COMMON(V) \
|
||||
VARIANTS_OCB(V)
|
||||
|
||||
enum class AESKeyVariant {
|
||||
#define V(name, _, __) name,
|
||||
VARIANTS(V)
|
||||
|
|
|
|||
124
test/fixtures/crypto/aes_ocb.js
vendored
Normal file
124
test/fixtures/crypto/aes_ocb.js
vendored
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
'use strict';
|
||||
|
||||
module.exports = function() {
|
||||
const kPlaintext =
|
||||
Buffer.from('546869732073706563696669636174696f6e206465736372696265' +
|
||||
'732061204a6176615363726970742041504920666f722070657266' +
|
||||
'6f726d696e672062617369632063727970746f6772617068696320' +
|
||||
'6f7065726174696f6e7320696e20776562206170706c6963617469' +
|
||||
'6f6e732c20737563682061732068617368696e672c207369676e61' +
|
||||
'747572652067656e65726174696f6e20616e642076657269666963' +
|
||||
'6174696f6e2c20616e6420656e6372797074696f6e20616e642064' +
|
||||
'656372797074696f6e2e204164646974696f6e616c6c792c206974' +
|
||||
'2064657363726962657320616e2041504920666f72206170706c69' +
|
||||
'636174696f6e7320746f2067656e657261746520616e642f6f7220' +
|
||||
'6d616e61676520746865206b6579696e67206d6174657269616c20' +
|
||||
'6e656365737361727920746f20706572666f726d20746865736520' +
|
||||
'6f7065726174696f6e732e205573657320666f7220746869732041' +
|
||||
'50492072616e67652066726f6d2075736572206f72207365727669' +
|
||||
'63652061757468656e7469636174696f6e2c20646f63756d656e74' +
|
||||
'206f7220636f6465207369676e696e672c20616e64207468652063' +
|
||||
'6f6e666964656e7469616c69747920616e6420696e746567726974' +
|
||||
'79206f6620636f6d6d756e69636174696f6e732e', 'hex');
|
||||
|
||||
const kKeyBytes = {
|
||||
'128': Buffer.from('dec0d4fcbf3c4741c892dabd1cd4c04e', 'hex'),
|
||||
'256': Buffer.from('67693823fb1d58073f91ece9cc3af910e5532616a4d27b1' +
|
||||
'3eb7b74d8000bbf30', 'hex')
|
||||
}
|
||||
|
||||
const iv = Buffer.from('3a92732aa6ea39bf3986e0c73fa920', 'hex');
|
||||
|
||||
const additionalData = Buffer.from(
|
||||
'5468657265206172652037206675727468657220656469746f72696' +
|
||||
'16c206e6f74657320696e2074686520646f63756d656e742e', 'hex');
|
||||
|
||||
const vectorData = {
|
||||
'128': {
|
||||
'64': {
|
||||
ciphertext: Buffer.from('4680d176c2fa66ef4376bc013ca5435ebd27b260c1236ae0148eb84eb24869ec1f1ebba2ba5356a2ee36944e717f668ab180c94817058216930d0192f403652bd2b0f3adac6466a74a69a8676d8460e2d81811de0cf8c0ec0c1aea48d470d0b6818fffb30dcdba67ffcf4bcf62e241e853c04370014cbea9cd68de4b90f8e52b5d40e972df70104fb70a78ddff9e7eb6e0c528c52aca9738030a6ad253d042697de254a059d06606ce718e8c95afd35767d05640b11367c5de4be405dd0c0bbbff54c8adfdae259b6588a44af382b3c5a2dec4c91bc8c3c156ae4859bd95e1a12f13fd292e0e80de25267941b8c5974e53dcff3741211d9c9e312919283f625201b201bb208f341b792d50c26b3c5769107e28c694ee55396a92b8ef18f6aa5849e44f63da4ab7d6d27d0b7c0869be21c650049dba5c3691de3fdc0dc9cd9676857d35d924372487e87c5ce4d656f69ee0cd62edbd949db134f9850eb6f017d5ba1933e8a39e56822fbe6a35eb9590e28bd1bbd46217c2db14264518caad1929885c143d28f4274fb4a655de0e24b2f37f1351c4820cb5c4fe49e9433f28bc1a0ac63a52200ac0876471c4db9a7ef1852b679f8a1d9bd54e739ce642bdfca700ed162516a33798733b52b726376e10f840714109150c7afeb71c652970ea86', 'hex'),
|
||||
tagWithAD: Buffer.from('cd1d3fa016ffebf8', 'hex'),
|
||||
tagWithoutAD: Buffer.from('85917be096f12614', 'hex')
|
||||
},
|
||||
'96': {
|
||||
ciphertext: Buffer.from('165e7cb1789fc9cb1e9b81e48d2c30d22a019bce5ff79d45ea7adbcf585b7bfc015a5959c9c1478714f4621ee0675f785a1689f1a9254b76580d368cc4a02b18b3f2a8abb5173e2b9f27042af4c0daeae44d88679e7bb79cab48a8f100804c0dad11547c68f2ac0e9a74f1abdeaa7c95e12b97361c4217905f25a03d9a5f8982af979b7756768cd9b044cc928d25ccd56e4fc494e4f62d96aabf3a4bd4889478990e58dcc180c4a81aceaf93afbbcf866a47030d579a981e42d78fae1907df32fd6c8cb37e1bd12e9ac7e81636e411e1717dac7836cf35b2683dd055fd0032a37d048835ef977b381d282ebb4c743eb09126d37764bd177af48d40f0c50534484dfd23ca9d046be673f493a83f705bd3a7d6579814690ee936095f1d80175271f33832ce9d93fff24d4c4ac3fbfa5e12b57109a56fdd5fa302391fe561095dafa4e41ce8e6dc5aac6091aefd7ca3b694ff6301ffdbe02c0c2ce438101ee92a08f85f3b153aa3116a80bc7778040ed9ee8b408909fc6d86004f23798ae85d9b1957435c9f74becdc53b38a7f0b9ac3d515e17d0ca9f5874096db5fb234d0d45e8149f6d15e2d7d3138622fa6fa7eded639fb6929fcaacf03060ec9db0106e58a3fa45d9ab2f6a2b56eee39cfc8cb305901c8f612e24da4cd3b07d4cf966cdf1', 'hex'),
|
||||
tagWithAD: Buffer.from('80b55f5111770c4fd51ae2a1', 'hex'),
|
||||
tagWithoutAD: Buffer.from('c8391b119179c1a3c534f03b', 'hex')
|
||||
},
|
||||
'128': {
|
||||
ciphertext: Buffer.from('0e33334c6fd3cf8c371d06875f342d239832a94c43b2f721d8bd70b6d62e7ac34bfd2041b214cd77624e0330e0892abfd696577144205882a24a1f4f0234602503222558c8c0e7dd033c3888d7f747107d3b11ad3f4d2c6088a80413d12a83587503a7393022ec541b284b358fa1fe3236ae706cd49fb8e2d4216318e8659275d80616940d2f3762e672a19ece3f2ee918c4e99b173c544dfb3300a867564790a436967563fa2bd3240dbb4d370d9153110411d772ff7542651db8c38672cc0f0ceae4f24065dfc996dd8b8d915f1bce206878ac54fad4df8a8157af6f1a8dc0344f526cd6cc398e1f049af3af9334204c5025a653292c0db11985ab83acf1bce2879754c0684a40d7e64e1f062a5d586a7c8702f326119dec9b1d0d316f8ba93f63d07546ee796db70fa66738499126c3a4bdada811dfc698b96569fbfcb935059c9b80349ce2b5caf6def0f2f6ba0d8ebf3395bb1766cddbc93a946d9706342bc378cda55eaee8edb411314c73bb2c480dea05e2eab5f83d089624bc9884dd14ba714d62e15767f730782e37c519b608d8d4ccee98e6d4ba28171417753f72a3b476403ccd5f0bbc4d7021170c751f8d844ee58ce6d2558270333d26e14e184d07e46a22d9270258517c7d6fa55875642d07a74ae0056c41e2931ef08d3f', 'hex'),
|
||||
tagWithAD: Buffer.from('cdee14358fba08d170fe5906ab34a56f', 'hex'),
|
||||
tagWithoutAD: Buffer.from('856250750fb4c53d60d04b9cb18534ba', 'hex')
|
||||
}
|
||||
},
|
||||
'256': {
|
||||
'64': {
|
||||
ciphertext: Buffer.from('188ee89ebee501f6a1dd111aa09b00eb67e1b2c6e1f205737d7f47e2cca0b80c9408daccfe820ebeab75f290589dc2175039d60c891002dea5214237393a5672bf91403d69fe41122b666fdd4b796ca18eb84a219895a58ca91689758dfc578079f89f27016a3b3080d0d8177a5ed8fc4b6e0af40604eaf4d91125aeac6656277a429c120bd9a2fa73086eb93302e3cc4d31dd2433d2f07cfbf604e60e380b7f94fb9de182f9752664c57dcb5c9797a952cc8a27b88a582342747c84bdcaae7ddfce460ab681856432429c6cc6e3658929f5669d5088a123a9158b680a8601960b068ea5a7b8f9bac98c3b7399c8fb8067ecbf6313606afdc3d1528d048b6803e12bdb44b119a2107463d01db4bc2791df8f3d0761ce5b401f8e0383f279fe7b1af335b10b48bef05d0e91bdab9631fa79a67a03a4790b4b1d325be028f6beda26d68958811670c86050d05b745f399ac77ff97be3b91cff64fd15e5047b1698c2dffe6d3d7c6cb0c8b5956cee43e8ecf7bc22199bfd7d61178843f9554bc0db539e7a59001ba3c963f299a1d838b8629bddc7c5646145a86ce52077af7785d213c787640b2010424b73b7b786ac7ca946fff501fecd6793ac00e9fb10ce7fb3a6d7b277b7096c83c94a421747f63c43723dd098b144549edbaeda4536ded1', 'hex'),
|
||||
tagWithAD: Buffer.from('dd2a37097d430860', 'hex'),
|
||||
tagWithoutAD: Buffer.from('4aefbda08fd14436', 'hex')
|
||||
},
|
||||
'96': {
|
||||
ciphertext: Buffer.from('18da5c8e77a1fa3cbe6c510c594b905f027ba3b56e446f23bbc17d12f265f260799ec5531792b8ec5cfec2b660bef94760ff5e7a714947cb342a7e9a1e0f3085e7c1608ea9dd1c71507cf09a7683e855b664615702cb556553319b5bd0fcb600d5c3a3bb8c36d8014f30b85a2d26ac36e83cfe8cebb6f7118c3e5875597fa39efd269f5ea237edd60036a906ea592fbe2868455503e9b1928396a894fefe2321138e8f5df4fcc932f4f05f3c15cd16cd5da9d54399dd0a90448f12c54a288b1724b60dfb7a434e72e357ddb631c1038efe9331a5037eb98e61f2a69df51d17de14799df035b5e468782d95d58d0cfd68d69c1b1529b4ad191bbf6f4c10f8a85e2c4ac7c525b3ef258de9a2f9b6193d74ca6e0180333167de426039cf8490d15e7f8ea86bfd143f98f2bb8e5c32a17e19aa0370cc7cbad8cfaeb69be29b6a6a7c27354a9a0dae13258578c681d182855c917c300e96912d24a80db1824e719fd5bddafd839f67fe18f632a892e69e46e0bef56aa94ff26ab7943a4b7de052f5d24302feb3add1c481b019d0d97bd442bd7c0021721d13b9e471686409c7c69551b3977ed3505eff8213e5564d1afe6042dc1ea572aca30cd1b7540e954b2e6c0498ebd2526fb0fb5ffcf48c6ef23b385e99649fd49290e0ce4de6d0a57498b7', 'hex'),
|
||||
tagWithAD: Buffer.from('06a22a5a7776797981c5ee6d', 'hex'),
|
||||
tagWithoutAD: Buffer.from('9167a0f385e4352f4c97aa94', 'hex')
|
||||
},
|
||||
'128': {
|
||||
ciphertext: Buffer.from('4604fafe03ab3897dd54e41c5884cac385d575768a2eb1c4c21c8470636a62565309605c8a556c4f3837be13df6f02de9b5a1ebbedba07e34a1fe0cd037eefc5c324ddfd95a9d16eaebecafd5b93d3c8d9a2deadb079497437d120c1fb0ae4b4d60b9fe220486c54f49c23c00fc3ffb1d57022761315c51f609fb28d70e4de2479325851af9c71671b6507c81fdecc5e5b8335b9d78320b0a4dfdff2f4a0dc86401128fdbe5601491acc6b0876d72fa842e95b75626dde15e602593b82874ed9233ddc64b06c41ea25dcccd678eb720d10d1c85f17635aceef2f102706f6de89b6d0fe6dcd686677d0a682fa3bf781a1fdb13c506be5b1c46ead578c54161129dbe0763d897fde4bfaf87ba61c5cf6884bd1e75678c086aeb2fcf057faf14ec38492ecba850595fa5b84d66c07576486da9cff68dbd961872985b1094d23f9dc31dda35cbe68ee570323843374cb89e07d2f11adf3476e6bdc2b2525cffdffcb7ee58190b19d8601b73d175bd8ebda079c97ed36e77c09a7c1c5e48f57c1881b91e2b17b6a737c79a1528c90ffbc1504914677593b9f6eca64fad08cdd4d318cd7f163cdf325667e949d829bebcccd932481ef132b49ac156eef34947924c165ce64b9e1e1461bd8d3d1e4e928411b448faa5d7db7f8bddc4fdce1ea035d60', 'hex'),
|
||||
tagWithAD: Buffer.from('a36db50a9235d475609287268818792c', 'hex'),
|
||||
tagWithoutAD: Buffer.from('34a83fa360a79823adc0c3dfc8e64d20', 'hex')
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const kKeyLengths = [128, 256];
|
||||
const kTagLengths = [64, 96, 128];
|
||||
|
||||
const passing = [];
|
||||
kKeyLengths.forEach((keyLength) => {
|
||||
kTagLengths.forEach((tagLength) => {
|
||||
const byteCount = tagLength / 8;
|
||||
const data = vectorData[keyLength][tagLength];
|
||||
|
||||
// With additional data
|
||||
const result = new Uint8Array(data.ciphertext.byteLength + byteCount);
|
||||
result.set(data.ciphertext, 0);
|
||||
result.set(data.tagWithAD.slice(0, byteCount), data.ciphertext.byteLength);
|
||||
passing.push({
|
||||
keyBuffer: kKeyBytes[keyLength],
|
||||
algorithm: { name: 'AES-OCB', iv, additionalData, tagLength },
|
||||
plaintext: kPlaintext,
|
||||
result
|
||||
});
|
||||
|
||||
// Without additional data
|
||||
const noadresult = new Uint8Array(data.ciphertext.byteLength + byteCount);
|
||||
noadresult.set(data.ciphertext, 0);
|
||||
noadresult.set(data.tagWithoutAD.slice(0, byteCount), data.ciphertext.byteLength);
|
||||
passing.push({
|
||||
keyBuffer: kKeyBytes[keyLength],
|
||||
algorithm: { name: 'AES-OCB', iv, tagLength },
|
||||
plaintext: kPlaintext,
|
||||
result: noadresult
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const failing = [];
|
||||
kKeyLengths.forEach((keyLength) => {
|
||||
[24, 48, 72, 95, 129].forEach((badTagLength) => {
|
||||
failing.push({
|
||||
keyBuffer: kKeyBytes[keyLength],
|
||||
algorithm: {
|
||||
name: 'AES-OCB',
|
||||
iv,
|
||||
additionalData,
|
||||
tagLength: badTagLength
|
||||
},
|
||||
plaintext: kPlaintext,
|
||||
result: vectorData[keyLength]['128'].ciphertext,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return { passing, failing, decryptionFailing: [] };
|
||||
};
|
||||
|
|
@ -6,6 +6,7 @@ const pqc = hasOpenSSL(3, 5);
|
|||
const shake128 = crypto.getHashes().includes('shake128');
|
||||
const shake256 = crypto.getHashes().includes('shake256');
|
||||
const chacha = crypto.getCiphers().includes('chacha20-poly1305');
|
||||
const ocb = hasOpenSSL(3);
|
||||
|
||||
export const vectors = {
|
||||
'digest': [
|
||||
|
|
@ -35,6 +36,7 @@ export const vectors = {
|
|||
[pqc, 'ML-KEM-768'],
|
||||
[pqc, 'ML-KEM-1024'],
|
||||
[chacha, 'ChaCha20-Poly1305'],
|
||||
[ocb, { name: 'AES-OCB', length: 128 }],
|
||||
],
|
||||
'importKey': [
|
||||
[pqc, 'ML-DSA-44'],
|
||||
|
|
@ -44,6 +46,7 @@ export const vectors = {
|
|||
[pqc, 'ML-KEM-768'],
|
||||
[pqc, 'ML-KEM-1024'],
|
||||
[chacha, 'ChaCha20-Poly1305'],
|
||||
[ocb, { name: 'AES-OCB', length: 128 }],
|
||||
],
|
||||
'exportKey': [
|
||||
[pqc, 'ML-DSA-44'],
|
||||
|
|
@ -53,6 +56,7 @@ export const vectors = {
|
|||
[pqc, 'ML-KEM-768'],
|
||||
[pqc, 'ML-KEM-1024'],
|
||||
[chacha, 'ChaCha20-Poly1305'],
|
||||
[ocb, 'AES-OCB'],
|
||||
],
|
||||
'getPublicKey': [
|
||||
[true, 'RSA-OAEP'],
|
||||
|
|
@ -73,6 +77,7 @@ export const vectors = {
|
|||
[false, 'AES-CTR'],
|
||||
[false, 'AES-CBC'],
|
||||
[false, 'AES-GCM'],
|
||||
[false, 'AES-OCB'],
|
||||
[false, 'AES-KW'],
|
||||
[false, 'ChaCha20-Poly1305'],
|
||||
],
|
||||
|
|
@ -82,6 +87,13 @@ export const vectors = {
|
|||
[chacha, { name: 'ChaCha20-Poly1305', iv: Buffer.alloc(12), tagLength: 128 }],
|
||||
[false, { name: 'ChaCha20-Poly1305', iv: Buffer.alloc(12), tagLength: 64 }],
|
||||
[false, 'ChaCha20-Poly1305'],
|
||||
[ocb, { name: 'AES-OCB', iv: Buffer.alloc(15) }],
|
||||
[false, { name: 'AES-OCB', iv: Buffer.alloc(16) }],
|
||||
[ocb, { name: 'AES-OCB', iv: Buffer.alloc(12), tagLength: 128 }],
|
||||
[ocb, { name: 'AES-OCB', iv: Buffer.alloc(12), tagLength: 96 }],
|
||||
[ocb, { name: 'AES-OCB', iv: Buffer.alloc(12), tagLength: 64 }],
|
||||
[false, { name: 'AES-OCB', iv: Buffer.alloc(12), tagLength: 32 }],
|
||||
[false, 'AES-OCB'],
|
||||
],
|
||||
'encapsulateBits': [
|
||||
[pqc, 'ML-KEM-512'],
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ if (!common.hasCrypto)
|
|||
common.skip('missing crypto');
|
||||
|
||||
const assert = require('assert');
|
||||
const { hasOpenSSL } = require('../common/crypto');
|
||||
const { subtle } = globalThis.crypto;
|
||||
|
||||
function getDeriveKeyInfo(name, length, hash, ...usages) {
|
||||
|
|
@ -34,7 +35,16 @@ if (!process.features.openssl_is_boringssl) {
|
|||
['HMAC', 256, 'SHA3-512', 'sign', 'verify'],
|
||||
);
|
||||
} else {
|
||||
common.printSkipMessage('Skipping unsupported AES-KW test cases');
|
||||
common.printSkipMessage('Skipping unsupported test cases');
|
||||
}
|
||||
|
||||
if (hasOpenSSL(3)) {
|
||||
kDerivedKeyTypes.push(
|
||||
['AES-OCB', 128, undefined, 'encrypt', 'decrypt'],
|
||||
['AES-OCB', 256, undefined, 'encrypt', 'decrypt'],
|
||||
);
|
||||
} else {
|
||||
common.printSkipMessage('Skipping unsupported test cases');
|
||||
}
|
||||
|
||||
const kDerivedKeys = {
|
||||
|
|
@ -464,7 +474,7 @@ async function testDeriveKey(
|
|||
true,
|
||||
usages);
|
||||
|
||||
const bits = await subtle.exportKey('raw', key);
|
||||
const bits = await subtle.exportKey(key.algorithm.name === 'AES-OCB' ? 'raw-secret' : 'raw', key);
|
||||
|
||||
assert.strictEqual(
|
||||
Buffer.from(bits).toString('hex'),
|
||||
|
|
|
|||
|
|
@ -5,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;
|
||||
|
||||
|
|
@ -12,8 +14,9 @@ async function testEncrypt({ keyBuffer, algorithm, plaintext, result }) {
|
|||
// Using a copy of plaintext to prevent tampering of the original
|
||||
plaintext = Buffer.from(plaintext);
|
||||
|
||||
const keyFormat = algorithm.name === 'AES-OCB' ? 'raw-secret' : 'raw';
|
||||
const key = await subtle.importKey(
|
||||
'raw',
|
||||
keyFormat,
|
||||
keyBuffer,
|
||||
{ name: algorithm.name },
|
||||
false,
|
||||
|
|
@ -37,8 +40,9 @@ async function testEncrypt({ keyBuffer, algorithm, plaintext, result }) {
|
|||
}
|
||||
|
||||
async function testEncryptNoEncrypt({ keyBuffer, algorithm, plaintext }) {
|
||||
const keyFormat = algorithm.name === 'AES-OCB' ? 'raw-secret' : 'raw';
|
||||
const key = await subtle.importKey(
|
||||
'raw',
|
||||
keyFormat,
|
||||
keyBuffer,
|
||||
{ name: algorithm.name },
|
||||
false,
|
||||
|
|
@ -50,8 +54,9 @@ async function testEncryptNoEncrypt({ keyBuffer, algorithm, plaintext }) {
|
|||
}
|
||||
|
||||
async function testEncryptNoDecrypt({ keyBuffer, algorithm, plaintext }) {
|
||||
const keyFormat = algorithm.name === 'AES-OCB' ? 'raw-secret' : 'raw';
|
||||
const key = await subtle.importKey(
|
||||
'raw',
|
||||
keyFormat,
|
||||
keyBuffer,
|
||||
{ name: algorithm.name },
|
||||
false,
|
||||
|
|
@ -66,8 +71,9 @@ async function testEncryptNoDecrypt({ keyBuffer, algorithm, plaintext }) {
|
|||
|
||||
async function testEncryptWrongAlg({ keyBuffer, algorithm, plaintext }, alg) {
|
||||
assert.notStrictEqual(algorithm.name, alg);
|
||||
const keyFormat = alg === 'AES-OCB' ? 'raw-secret' : 'raw';
|
||||
const key = await subtle.importKey(
|
||||
'raw',
|
||||
keyFormat,
|
||||
keyBuffer,
|
||||
{ name: alg },
|
||||
false,
|
||||
|
|
@ -79,8 +85,9 @@ async function testEncryptWrongAlg({ keyBuffer, algorithm, plaintext }, alg) {
|
|||
}
|
||||
|
||||
async function testDecrypt({ keyBuffer, algorithm, result }) {
|
||||
const keyFormat = algorithm.name === 'AES-OCB' ? 'raw-secret' : 'raw';
|
||||
const key = await subtle.importKey(
|
||||
'raw',
|
||||
keyFormat,
|
||||
keyBuffer,
|
||||
{ name: algorithm.name },
|
||||
false,
|
||||
|
|
@ -202,6 +209,43 @@ async function testDecrypt({ keyBuffer, algorithm, result }) {
|
|||
})().then(common.mustCall());
|
||||
}
|
||||
|
||||
// Test aes-ocb vectors
|
||||
if (hasOpenSSL(3)) {
|
||||
const {
|
||||
passing,
|
||||
failing,
|
||||
decryptionFailing
|
||||
} = require('../fixtures/crypto/aes_ocb')();
|
||||
|
||||
(async function() {
|
||||
const variations = [];
|
||||
|
||||
passing.forEach((vector) => {
|
||||
variations.push(testEncrypt(vector));
|
||||
variations.push(testEncryptNoEncrypt(vector));
|
||||
variations.push(testEncryptNoDecrypt(vector));
|
||||
variations.push(testEncryptWrongAlg(vector, 'AES-GCM'));
|
||||
});
|
||||
|
||||
failing.forEach((vector) => {
|
||||
variations.push(assert.rejects(testEncrypt(vector), {
|
||||
message: /is not a valid AES-OCB tag length/
|
||||
}));
|
||||
variations.push(assert.rejects(testDecrypt(vector), {
|
||||
message: /is not a valid AES-OCB tag length/
|
||||
}));
|
||||
});
|
||||
|
||||
decryptionFailing.forEach((vector) => {
|
||||
variations.push(assert.rejects(testDecrypt(vector), {
|
||||
name: 'OperationError'
|
||||
}));
|
||||
});
|
||||
|
||||
await Promise.all(variations);
|
||||
})().then(common.mustCall());
|
||||
}
|
||||
|
||||
{
|
||||
(async function() {
|
||||
const secretKey = await subtle.generateKey(
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ if (!common.hasCrypto)
|
|||
common.skip('missing crypto');
|
||||
|
||||
const assert = require('assert');
|
||||
const { hasOpenSSL } = require('../common/crypto');
|
||||
const { subtle } = globalThis.crypto;
|
||||
|
||||
// This is only a partial test. The WebCrypto Web Platform Tests
|
||||
|
|
@ -181,3 +182,32 @@ if (!process.features.openssl_is_boringssl) {
|
|||
|
||||
test().then(common.mustCall());
|
||||
}
|
||||
|
||||
// Test Encrypt/Decrypt AES-OCB
|
||||
if (hasOpenSSL(3)) {
|
||||
const buf = globalThis.crypto.getRandomValues(new Uint8Array(50));
|
||||
const iv = globalThis.crypto.getRandomValues(new Uint8Array(12));
|
||||
|
||||
async function test() {
|
||||
const key = await subtle.generateKey({
|
||||
name: 'AES-OCB',
|
||||
length: 256
|
||||
}, true, ['encrypt', 'decrypt']);
|
||||
|
||||
const ciphertext = await subtle.encrypt(
|
||||
{ name: 'AES-OCB', iv }, key, buf,
|
||||
);
|
||||
|
||||
const plaintext = await subtle.decrypt(
|
||||
{ name: 'AES-OCB', iv }, key, ciphertext,
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
Buffer.from(plaintext).toString('hex'),
|
||||
Buffer.from(buf).toString('hex'));
|
||||
}
|
||||
|
||||
test().then(common.mustCall());
|
||||
} else {
|
||||
common.printSkipMessage('Skipping unsupported AES-OCB test cases');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -173,6 +173,19 @@ if (!process.features.openssl_is_boringssl) {
|
|||
common.printSkipMessage('Skipping unsupported test cases');
|
||||
}
|
||||
|
||||
if (hasOpenSSL(3)) {
|
||||
vectors['AES-OCB'] = {
|
||||
algorithm: { length: 256 },
|
||||
result: 'CryptoKey',
|
||||
usages: [
|
||||
'encrypt',
|
||||
'decrypt',
|
||||
'wrapKey',
|
||||
'unwrapKey',
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
if (hasOpenSSL(3, 5)) {
|
||||
for (const name of ['ML-DSA-44', 'ML-DSA-65', 'ML-DSA-87']) {
|
||||
vectors[name] = {
|
||||
|
|
|
|||
|
|
@ -59,6 +59,18 @@ if (!process.features.openssl_is_boringssl) {
|
|||
common.printSkipMessage('Skipping unsupported AES-KW test case');
|
||||
}
|
||||
|
||||
if (hasOpenSSL(3)) {
|
||||
kWrappingData['AES-OCB'] = {
|
||||
generate: { length: 128 },
|
||||
wrap: {
|
||||
iv: new Uint8Array(15),
|
||||
additionalData: new Uint8Array(16),
|
||||
tagLength: 128
|
||||
},
|
||||
pair: false
|
||||
};
|
||||
}
|
||||
|
||||
function generateWrappingKeys() {
|
||||
return Promise.all(Object.keys(kWrappingData).map(async (name) => {
|
||||
const keys = await subtle.generateKey(
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user