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 <[email protected]> Reviewed-By: Ethan Arrowood <[email protected]> Reviewed-By: Yagiz Nizipli <[email protected]> Reviewed-By: Joyee Cheung <[email protected]>
1 parent 16afd10 commit 3f47a2f

25 files changed

+1239
-176
lines changed

‎deps/ncrypto/ncrypto.cc‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2927,6 +2927,7 @@ const Cipher Cipher::AES_256_GCM = Cipher::FromNid(NID_aes_256_gcm);
29272927
const Cipher Cipher::AES_128_KW = Cipher::FromNid(NID_id_aes128_wrap);
29282928
const Cipher Cipher::AES_192_KW = Cipher::FromNid(NID_id_aes192_wrap);
29292929
const Cipher Cipher::AES_256_KW = Cipher::FromNid(NID_id_aes256_wrap);
2930+
const Cipher Cipher::CHACHA20_POLY1305 = Cipher::FromNid(NID_chacha20_poly1305);
29302931

29312932
boolCipher::isGcmMode() const{
29322933
if (!cipher_) returnfalse;

‎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
staticconst Cipher AES_128_KW;
374374
staticconst Cipher AES_192_KW;
375375
staticconst Cipher AES_256_KW;
376+
staticconst Cipher CHACHA20_POLY1305;
376377

377378
structCipherParams{
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+
constrandomBytes=promisify(_randomBytes);
44+
45+
functionvalidateKeyLength(length){
46+
if(length!==256)
47+
throwlazyDOMException('Invalid key length','DataError');
48+
}
49+
50+
functionc20pCipher(mode,key,data,algorithm){
51+
lettag;
52+
switch(mode){
53+
casekWebCryptoCipherDecrypt: {
54+
constslice=ArrayBufferIsView(data) ?
55+
TypedArrayPrototypeSlice : ArrayBufferPrototypeSlice;
56+
57+
if(data.byteLength<16){
58+
returnPromiseReject(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+
casekWebCryptoCipherEncrypt:
68+
tag=16;
69+
break;
70+
}
71+
72+
returnjobPromise(()=>newChaCha20Poly1305CipherJob(
73+
kCryptoJobAsync,
74+
mode,
75+
key[kKeyObject][kHandle],
76+
data,
77+
algorithm.iv,
78+
tag,
79+
algorithm.additionalData));
80+
}
81+
82+
asyncfunctionc20pGenerateKey(algorithm,extractable,keyUsages){
83+
const{ name }=algorithm;
84+
85+
constcheckUsages=['encrypt','decrypt','wrapKey','unwrapKey'];
86+
87+
constusagesSet=newSafeSet(keyUsages);
88+
if(hasAnyNotIn(usagesSet,checkUsages)){
89+
throwlazyDOMException(
90+
`Unsupported key usage for a ${algorithm.name} key`,
91+
'SyntaxError');
92+
}
93+
94+
constkeyData=awaitrandomBytes(32).catch((err)=>{
95+
throwlazyDOMException(
96+
'The operation failed for an operation-specific reason'+
97+
`[${err.message}]`,
98+
{name: 'OperationError',cause: err});
99+
});
100+
101+
returnnewInternalCryptoKey(
102+
createSecretKey(keyData),
103+
{ name },
104+
ArrayFrom(usagesSet),
105+
extractable);
106+
}
107+
108+
functionc20pImportKey(
109+
algorithm,
110+
format,
111+
keyData,
112+
extractable,
113+
keyUsages){
114+
const{ name }=algorithm;
115+
constcheckUsages=['encrypt','decrypt','wrapKey','unwrapKey'];
116+
117+
constusagesSet=newSafeSet(keyUsages);
118+
if(hasAnyNotIn(usagesSet,checkUsages)){
119+
throwlazyDOMException(
120+
`Unsupported key usage for a ${algorithm.name} key`,
121+
'SyntaxError');
122+
}
123+
124+
letkeyObject;
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+
throwlazyDOMException('Invalid keyData','DataError');
137+
138+
if(keyData.kty!=='oct')
139+
throwlazyDOMException('Invalid JWK "kty" Parameter','DataError');
140+
141+
if(usagesSet.size>0&&
142+
keyData.use!==undefined&&
143+
keyData.use!=='enc'){
144+
throwlazyDOMException('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+
throwlazyDOMException(
153+
'JWK "ext" Parameter and extractable mismatch',
154+
'DataError');
155+
}
156+
157+
consthandle=newKeyObjectHandle();
158+
try{
159+
handle.initJwk(keyData);
160+
}catch(err){
161+
throwlazyDOMException(
162+
'Invalid keyData',{name: 'DataError',cause: err});
163+
}
164+
165+
if(keyData.alg!==undefined&&keyData.alg!=='C20P'){
166+
throwlazyDOMException(
167+
'JWK "alg" does not match the requested algorithm',
168+
'DataError');
169+
}
170+
171+
keyObject=newSecretKeyObject(handle);
172+
break;
173+
}
174+
default:
175+
returnundefined;
176+
}
177+
178+
validateKeyLength(keyObject.symmetricKeySize*8);
179+
180+
returnnewInternalCryptoKey(
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
constsimpleAlgorithmDictionaries={
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
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,11 @@ async function generateKey(
155155
result=awaitrequire('internal/crypto/aes')
156156
.aesGenerateKey(algorithm,extractable,keyUsages);
157157
break;
158+
case'ChaCha20-Poly1305':
159+
resultType='CryptoKey';
160+
result=awaitrequire('internal/crypto/chacha20_poly1305')
161+
.c20pGenerateKey(algorithm,extractable,keyUsages);
162+
break;
158163
case'ML-DSA-44':
159164
// Fall through
160165
case'ML-DSA-65':
@@ -252,6 +257,8 @@ function getKeyLength({name, length, hash }){
252257
case'HKDF':
253258
case'PBKDF2':
254259
returnnull;
260+
case'ChaCha20-Poly1305':
261+
return256;
255262
}
256263
}
257264

@@ -431,7 +438,7 @@ async function exportKeyRawSeed(key){
431438
}
432439
}
433440

434-
asyncfunctionexportKeyRawSecret(key){
441+
asyncfunctionexportKeyRawSecret(key,format){
435442
switch(key.algorithm.name){
436443
case'AES-CTR':
437444
// Fall through
@@ -443,6 +450,11 @@ async function exportKeyRawSecret(key){
443450
// Fall through
444451
case'HMAC':
445452
returnkey[kKeyObject][kHandle].export().buffer;
453+
case'ChaCha20-Poly1305':
454+
if(format==='raw-secret'){
455+
returnkey[kKeyObject][kHandle].export().buffer;
456+
}
457+
returnundefined;
446458
default:
447459
returnundefined;
448460
}
@@ -504,6 +516,9 @@ async function exportKeyJWK(key){
504516
parameters.alg=require('internal/crypto/aes')
505517
.getAlgorithmName(key.algorithm.name,key.algorithm.length);
506518
break;
519+
case'ChaCha20-Poly1305':
520+
parameters.alg='C20P';
521+
break;
507522
case'HMAC': {
508523
constalg=normalizeHashName(
509524
key.algorithm.hash.name,
@@ -563,7 +578,7 @@ async function exportKey(format, key){
563578
}
564579
case'raw-secret': {
565580
if(key.type==='secret'){
566-
result=awaitexportKeyRawSecret(key);
581+
result=awaitexportKeyRawSecret(key,format);
567582
}
568583
break;
569584
}
@@ -581,7 +596,7 @@ async function exportKey(format, key){
581596
}
582597
case'raw': {
583598
if(key.type==='secret'){
584-
result=awaitexportKeyRawSecret(key);
599+
result=awaitexportKeyRawSecret(key,format);
585600
}elseif(key.type==='public'){
586601
result=awaitexportKeyRawPublic(key,format);
587602
}
@@ -687,6 +702,10 @@ async function importKey(
687702
result=require('internal/crypto/aes')
688703
.aesImportKey(algorithm,format,keyData,extractable,keyUsages);
689704
break;
705+
case'ChaCha20-Poly1305':
706+
result=require('internal/crypto/chacha20_poly1305')
707+
.c20pImportKey(algorithm,format,keyData,extractable,keyUsages);
708+
break;
690709
case'HKDF':
691710
// Fall through
692711
case'PBKDF2':
@@ -975,6 +994,9 @@ async function cipherOrWrap(mode, algorithm, key, data, op){
975994
case'AES-GCM':
976995
returnrequire('internal/crypto/aes')
977996
.aesCipher(mode,key,data,algorithm);
997+
case'ChaCha20-Poly1305':
998+
returnrequire('internal/crypto/chacha20_poly1305')
999+
.c20pCipher(mode,key,data,algorithm);
9781000
case'AES-KW':
9791001
if(op==='wrapKey'||op==='unwrapKey'){
9801002
returnrequire('internal/crypto/aes')

0 commit comments

Comments
(0)