crypto: add ChaCha20-Poly1305 Web Cryptography algorithm · nodejs/node@3f47a2f
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+};