crypto: add ChaCha20-Poly1305 Web Cryptography algorithm · nodejs/node@3f47a2f · GitHub
Skip to content

Commit 3f47a2f

Browse files
panvatargos
authored andcommitted
crypto: add ChaCha20-Poly1305 Web Cryptography algorithm
PR-URL: #59365 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Ethan Arrowood <ethan@arrowood.dev> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
1 parent 16afd10 commit 3f47a2f

25 files changed

Lines changed: 1239 additions & 176 deletions

deps/ncrypto/ncrypto.cc

Lines changed: 1 addition & 0 deletions

deps/ncrypto/ncrypto.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,7 @@ class Cipher final {
373373
static const Cipher AES_128_KW;
374374
static const Cipher AES_192_KW;
375375
static const Cipher AES_256_KW;
376+
static const Cipher CHACHA20_POLY1305;
376377

377378
struct CipherParams {
378379
int padding;

doc/api/webcrypto.md

Lines changed: 175 additions & 146 deletions
Large diffs are not rendered by default.
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
'use strict';
2+
3+
const {
4+
ArrayBufferIsView,
5+
ArrayBufferPrototypeSlice,
6+
ArrayFrom,
7+
PromiseReject,
8+
SafeSet,
9+
TypedArrayPrototypeSlice,
10+
} = primordials;
11+
12+
const {
13+
ChaCha20Poly1305CipherJob,
14+
KeyObjectHandle,
15+
kCryptoJobAsync,
16+
kWebCryptoCipherDecrypt,
17+
kWebCryptoCipherEncrypt,
18+
} = internalBinding('crypto');
19+
20+
const {
21+
hasAnyNotIn,
22+
jobPromise,
23+
validateKeyOps,
24+
kHandle,
25+
kKeyObject,
26+
} = require('internal/crypto/util');
27+
28+
const {
29+
lazyDOMException,
30+
promisify,
31+
} = require('internal/util');
32+
33+
const {
34+
InternalCryptoKey,
35+
SecretKeyObject,
36+
createSecretKey,
37+
} = require('internal/crypto/keys');
38+
39+
const {
40+
randomBytes: _randomBytes,
41+
} = require('internal/crypto/random');
42+
43+
const randomBytes = promisify(_randomBytes);
44+
45+
function validateKeyLength(length) {
46+
if (length !== 256)
47+
throw lazyDOMException('Invalid key length', 'DataError');
48+
}
49+
50+
function c20pCipher(mode, key, data, algorithm) {
51+
let tag;
52+
switch (mode) {
53+
case kWebCryptoCipherDecrypt: {
54+
const slice = ArrayBufferIsView(data) ?
55+
TypedArrayPrototypeSlice : ArrayBufferPrototypeSlice;
56+
57+
if (data.byteLength < 16) {
58+
return PromiseReject(lazyDOMException(
59+
'The provided data is too small.',
60+
'OperationError'));
61+
}
62+
63+
tag = slice(data, -16);
64+
data = slice(data, 0, -16);
65+
break;
66+
}
67+
case kWebCryptoCipherEncrypt:
68+
tag = 16;
69+
break;
70+
}
71+
72+
return jobPromise(() => new ChaCha20Poly1305CipherJob(
73+
kCryptoJobAsync,
74+
mode,
75+
key[kKeyObject][kHandle],
76+
data,
77+
algorithm.iv,
78+
tag,
79+
algorithm.additionalData));
80+
}
81+
82+
async function c20pGenerateKey(algorithm, extractable, keyUsages) {
83+
const { name } = algorithm;
84+
85+
const checkUsages = ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey'];
86+
87+
const usagesSet = new SafeSet(keyUsages);
88+
if (hasAnyNotIn(usagesSet, checkUsages)) {
89+
throw lazyDOMException(
90+
`Unsupported key usage for a ${algorithm.name} key`,
91+
'SyntaxError');
92+
}
93+
94+
const keyData = await randomBytes(32).catch((err) => {
95+
throw lazyDOMException(
96+
'The operation failed for an operation-specific reason' +
97+
`[${err.message}]`,
98+
{ name: 'OperationError', cause: err });
99+
});
100+
101+
return new InternalCryptoKey(
102+
createSecretKey(keyData),
103+
{ name },
104+
ArrayFrom(usagesSet),
105+
extractable);
106+
}
107+
108+
function c20pImportKey(
109+
algorithm,
110+
format,
111+
keyData,
112+
extractable,
113+
keyUsages) {
114+
const { name } = algorithm;
115+
const checkUsages = ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey'];
116+
117+
const usagesSet = new SafeSet(keyUsages);
118+
if (hasAnyNotIn(usagesSet, checkUsages)) {
119+
throw lazyDOMException(
120+
`Unsupported key usage for a ${algorithm.name} key`,
121+
'SyntaxError');
122+
}
123+
124+
let keyObject;
125+
switch (format) {
126+
case 'KeyObject': {
127+
keyObject = keyData;
128+
break;
129+
}
130+
case 'raw-secret': {
131+
keyObject = createSecretKey(keyData);
132+
break;
133+
}
134+
case 'jwk': {
135+
if (!keyData.kty)
136+
throw lazyDOMException('Invalid keyData', 'DataError');
137+
138+
if (keyData.kty !== 'oct')
139+
throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError');
140+
141+
if (usagesSet.size > 0 &&
142+
keyData.use !== undefined &&
143+
keyData.use !== 'enc') {
144+
throw lazyDOMException('Invalid JWK "use" Parameter', 'DataError');
145+
}
146+
147+
validateKeyOps(keyData.key_ops, usagesSet);
148+
149+
if (keyData.ext !== undefined &&
150+
keyData.ext === false &&
151+
extractable === true) {
152+
throw lazyDOMException(
153+
'JWK "ext" Parameter and extractable mismatch',
154+
'DataError');
155+
}
156+
157+
const handle = new KeyObjectHandle();
158+
try {
159+
handle.initJwk(keyData);
160+
} catch (err) {
161+
throw lazyDOMException(
162+
'Invalid keyData', { name: 'DataError', cause: err });
163+
}
164+
165+
if (keyData.alg !== undefined && keyData.alg !== 'C20P') {
166+
throw lazyDOMException(
167+
'JWK "alg" does not match the requested algorithm',
168+
'DataError');
169+
}
170+
171+
keyObject = new SecretKeyObject(handle);
172+
break;
173+
}
174+
default:
175+
return undefined;
176+
}
177+
178+
validateKeyLength(keyObject.symmetricKeySize * 8);
179+
180+
return new InternalCryptoKey(
181+
keyObject,
182+
{ name },
183+
keyUsages,
184+
extractable);
185+
}
186+
187+
module.exports = {
188+
c20pCipher,
189+
c20pGenerateKey,
190+
c20pImportKey,
191+
};

lib/internal/crypto/keys.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,10 @@ const {
199199
result = require('internal/crypto/aes')
200200
.aesImportKey(algorithm, 'KeyObject', this, extractable, keyUsages);
201201
break;
202+
case 'ChaCha20-Poly1305':
203+
result = require('internal/crypto/chacha20_poly1305')
204+
.c20pImportKey(algorithm, 'KeyObject', this, extractable, keyUsages);
205+
break;
202206
case 'HKDF':
203207
// Fall through
204208
case 'PBKDF2':

lib/internal/crypto/util.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -245,13 +245,13 @@ const kSupportedAlgorithms = {
245245
'encrypt': {
246246
'RSA-OAEP': 'RsaOaepParams',
247247
'AES-CBC': 'AesCbcParams',
248-
'AES-GCM': 'AesGcmParams',
248+
'AES-GCM': 'AeadParams',
249249
'AES-CTR': 'AesCtrParams',
250250
},
251251
'decrypt': {
252252
'RSA-OAEP': 'RsaOaepParams',
253253
'AES-CBC': 'AesCbcParams',
254-
'AES-GCM': 'AesGcmParams',
254+
'AES-GCM': 'AeadParams',
255255
'AES-CTR': 'AesCtrParams',
256256
},
257257
'get key length': {
@@ -290,6 +290,14 @@ const experimentalAlgorithms = ObjectEntries({
290290
'SHA3-256': { digest: null },
291291
'SHA3-384': { digest: null },
292292
'SHA3-512': { digest: null },
293+
'ChaCha20-Poly1305': {
294+
'encrypt': 'AeadParams',
295+
'decrypt': 'AeadParams',
296+
'generateKey': null,
297+
'importKey': null,
298+
'exportKey': null,
299+
'get key length': null,
300+
},
293301
});
294302

295303
for (const { 0: algorithm, 1: nid } of [
@@ -325,7 +333,7 @@ for (let i = 0; i < experimentalAlgorithms.length; i++) {
325333
}
326334

327335
const simpleAlgorithmDictionaries = {
328-
AesGcmParams: { iv: 'BufferSource', additionalData: 'BufferSource' },
336+
AeadParams: { iv: 'BufferSource', additionalData: 'BufferSource' },
329337
RsaHashedKeyGenParams: { hash: 'HashAlgorithmIdentifier' },
330338
EcKeyGenParams: {},
331339
HmacKeyGenParams: { hash: 'HashAlgorithmIdentifier' },

lib/internal/crypto/webcrypto.js

Lines changed: 25 additions & 3 deletions

0 commit comments

Comments
 (0)