Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions deps/ncrypto/ncrypto.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2927,6 +2927,7 @@ 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::CHACHA20_POLY1305 = Cipher::FromNid(NID_chacha20_poly1305);

bool Cipher::isGcmMode() const {
if (!cipher_) return false;
Expand Down
1 change: 1 addition & 0 deletions deps/ncrypto/ncrypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,7 @@ class Cipher final {
static const Cipher AES_128_KW;
static const Cipher AES_192_KW;
static const Cipher AES_256_KW;
static const Cipher CHACHA20_POLY1305;

struct CipherParams {
int padding;
Expand Down
716 changes: 546 additions & 170 deletions doc/api/webcrypto.md

Large diffs are not rendered by default.

191 changes: 191 additions & 0 deletions lib/internal/crypto/chacha20_poly1305.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
'use strict';

const {
ArrayBufferIsView,
ArrayBufferPrototypeSlice,
ArrayFrom,
PromiseReject,
SafeSet,
TypedArrayPrototypeSlice,
} = primordials;

const {
ChaCha20Poly1305CipherJob,
KeyObjectHandle,
kCryptoJobAsync,
kWebCryptoCipherDecrypt,
kWebCryptoCipherEncrypt,
} = internalBinding('crypto');

const {
hasAnyNotIn,
jobPromise,
validateKeyOps,
kHandle,
kKeyObject,
} = require('internal/crypto/util');

const {
lazyDOMException,
promisify,
} = require('internal/util');

const {
InternalCryptoKey,
SecretKeyObject,
createSecretKey,
} = require('internal/crypto/keys');

const {
randomBytes: _randomBytes,
} = require('internal/crypto/random');

const randomBytes = promisify(_randomBytes);

function validateKeyLength(length) {
if (length !== 256)
throw lazyDOMException('Invalid key length', 'DataError');
}

function c20pCipher(mode, key, data, algorithm) {
let tag;
switch (mode) {
case kWebCryptoCipherDecrypt: {
const slice = ArrayBufferIsView(data) ?
TypedArrayPrototypeSlice : ArrayBufferPrototypeSlice;

if (data.byteLength < 16) {
return PromiseReject(lazyDOMException(
'The provided data is too small.',
'OperationError'));
}

tag = slice(data, -16);
data = slice(data, 0, -16);
break;
}
case kWebCryptoCipherEncrypt:
tag = 16;
break;
}

return jobPromise(() => new ChaCha20Poly1305CipherJob(
kCryptoJobAsync,
mode,
key[kKeyObject][kHandle],
data,
algorithm.iv,
tag,
algorithm.additionalData));
}

async function c20pGenerateKey(algorithm, extractable, keyUsages) {
const { name } = algorithm;

const checkUsages = ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey'];

const usagesSet = new SafeSet(keyUsages);
if (hasAnyNotIn(usagesSet, checkUsages)) {
throw lazyDOMException(
`Unsupported key usage for a ${algorithm.name} key`,
'SyntaxError');
}

const keyData = await randomBytes(32).catch((err) => {
throw lazyDOMException(
'The operation failed for an operation-specific reason' +
`[${err.message}]`,
{ name: 'OperationError', cause: err });
});

return new InternalCryptoKey(
createSecretKey(keyData),
{ name },
ArrayFrom(usagesSet),
extractable);
}

function c20pImportKey(
algorithm,
format,
keyData,
extractable,
keyUsages) {
const { name } = algorithm;
const checkUsages = ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey'];

const usagesSet = new SafeSet(keyUsages);
if (hasAnyNotIn(usagesSet, checkUsages)) {
throw lazyDOMException(
`Unsupported key usage for a ${algorithm.name} key`,
'SyntaxError');
}

let keyObject;
switch (format) {
case 'KeyObject': {
keyObject = keyData;
break;
}
case 'raw-secret': {
keyObject = createSecretKey(keyData);
break;
}
case 'jwk': {
if (!keyData.kty)
throw lazyDOMException('Invalid keyData', 'DataError');

if (keyData.kty !== 'oct')
throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError');

if (usagesSet.size > 0 &&
keyData.use !== undefined &&
keyData.use !== 'enc') {
throw lazyDOMException('Invalid JWK "use" Parameter', 'DataError');
}

validateKeyOps(keyData.key_ops, usagesSet);

if (keyData.ext !== undefined &&
keyData.ext === false &&
extractable === true) {
throw lazyDOMException(
'JWK "ext" Parameter and extractable mismatch',
'DataError');
}

const handle = new KeyObjectHandle();
try {
handle.initJwk(keyData);
} catch (err) {
throw lazyDOMException(
'Invalid keyData', { name: 'DataError', cause: err });
}

if (keyData.alg !== undefined && keyData.alg !== 'C20P') {
throw lazyDOMException(
'JWK "alg" does not match the requested algorithm',
'DataError');
}

keyObject = new SecretKeyObject(handle);
break;
}
default:
return undefined;
}

validateKeyLength(keyObject.symmetricKeySize * 8);

return new InternalCryptoKey(
keyObject,
{ name },
keyUsages,
extractable);
}

module.exports = {
c20pCipher,
c20pGenerateKey,
c20pImportKey,
};
13 changes: 12 additions & 1 deletion lib/internal/crypto/hash.js
Original file line number Diff line number Diff line change
Expand Up @@ -209,10 +209,21 @@ async function asyncDigest(algorithm, data) {
case 'SHA-384':
// Fall through
case 'SHA-512':
// Fall through
case 'SHA3-256':
// Fall through
case 'SHA3-384':
// Fall through
case 'SHA3-512':
// Fall through
case 'cSHAKE128':
// Fall through
case 'cSHAKE256':
return jobPromise(() => new HashJob(
kCryptoJobAsync,
normalizeHashName(algorithm.name),
data));
data,
algorithm.length));
}

throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError');
Expand Down
28 changes: 24 additions & 4 deletions lib/internal/crypto/hashnames.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,38 +17,58 @@ const kHashContextJwkHmac = 6;
// make it easier in the code.

const kHashNames = {
sha1: {
'sha1': {
[kHashContextNode]: 'sha1',
[kHashContextWebCrypto]: 'SHA-1',
[kHashContextJwkRsa]: 'RS1',
[kHashContextJwkRsaPss]: 'PS1',
[kHashContextJwkRsaOaep]: 'RSA-OAEP',
[kHashContextJwkHmac]: 'HS1',
},
sha256: {
'sha256': {
[kHashContextNode]: 'sha256',
[kHashContextWebCrypto]: 'SHA-256',
[kHashContextJwkRsa]: 'RS256',
[kHashContextJwkRsaPss]: 'PS256',
[kHashContextJwkRsaOaep]: 'RSA-OAEP-256',
[kHashContextJwkHmac]: 'HS256',
},
sha384: {
'sha384': {
[kHashContextNode]: 'sha384',
[kHashContextWebCrypto]: 'SHA-384',
[kHashContextJwkRsa]: 'RS384',
[kHashContextJwkRsaPss]: 'PS384',
[kHashContextJwkRsaOaep]: 'RSA-OAEP-384',
[kHashContextJwkHmac]: 'HS384',
},
sha512: {
'sha512': {
[kHashContextNode]: 'sha512',
[kHashContextWebCrypto]: 'SHA-512',
[kHashContextJwkRsa]: 'RS512',
[kHashContextJwkRsaPss]: 'PS512',
[kHashContextJwkRsaOaep]: 'RSA-OAEP-512',
[kHashContextJwkHmac]: 'HS512',
},
'shake128': {
[kHashContextNode]: 'shake128',
[kHashContextWebCrypto]: 'cSHAKE128',
},
'shake256': {
[kHashContextNode]: 'shake256',
[kHashContextWebCrypto]: 'cSHAKE256',
},
'sha3-256': {
[kHashContextNode]: 'sha3-256',
[kHashContextWebCrypto]: 'SHA3-256',
},
'sha3-384': {
[kHashContextNode]: 'sha3-384',
[kHashContextWebCrypto]: 'SHA3-384',
},
'sha3-512': {
[kHashContextNode]: 'sha3-512',
[kHashContextWebCrypto]: 'SHA3-512',
},
};

{
Expand Down
1 change: 1 addition & 0 deletions lib/internal/crypto/hkdf.js
Original file line number Diff line number Diff line change
Expand Up @@ -170,4 +170,5 @@ module.exports = {
hkdf,
hkdfSync,
hkdfDeriveBits,
validateHkdfDeriveBitsLength,
};
12 changes: 12 additions & 0 deletions lib/internal/crypto/keys.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,10 @@ const {
result = require('internal/crypto/aes')
.aesImportKey(algorithm, 'KeyObject', this, extractable, keyUsages);
break;
case 'ChaCha20-Poly1305':
result = require('internal/crypto/chacha20_poly1305')
.c20pImportKey(algorithm, 'KeyObject', this, extractable, keyUsages);
break;
case 'HKDF':
// Fall through
case 'PBKDF2':
Expand Down Expand Up @@ -293,6 +297,14 @@ const {
result = require('internal/crypto/cfrg')
.cfrgImportKey('KeyObject', this, algorithm, extractable, keyUsages);
break;
case 'ML-DSA-44':
// Fall through
case 'ML-DSA-65':
// Fall through
case 'ML-DSA-87':
result = require('internal/crypto/ml_dsa')
.mlDsaImportKey('KeyObject', this, algorithm, extractable, keyUsages);
break;
default:
throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError');
}
Expand Down
18 changes: 4 additions & 14 deletions lib/internal/crypto/mac.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,23 +62,11 @@ async function hmacGenerateKey(algorithm, extractable, keyUsages) {

return new InternalCryptoKey(
key,
{ name, length, hash: { name: hash.name } },
{ name, length, hash },
ArrayFrom(usageSet),
extractable);
}

function getAlgorithmName(hash) {
switch (hash) {
case 'SHA-1': // Fall through
case 'SHA-256': // Fall through
case 'SHA-384': // Fall through
case 'SHA-512': // Fall through
return `HS${hash.slice(4)}`;
default:
throw lazyDOMException('Unsupported digest algorithm', 'DataError');
}
}

function hmacImportKey(
format,
keyData,
Expand Down Expand Up @@ -126,7 +114,9 @@ function hmacImportKey(
}

if (keyData.alg !== undefined) {
if (keyData.alg !== getAlgorithmName(algorithm.hash.name))
const expected =
normalizeHashName(algorithm.hash.name, normalizeHashName.kContextJwkHmac);
if (expected && keyData.alg !== expected)
throw lazyDOMException(
'JWK "alg" does not match the requested algorithm',
'DataError');
Expand Down
Loading
Loading