Skip to content

Commit 28c034d

Browse files
panvadanielleadams
authored andcommitted
crypto: add CFRG curves to Web Crypto API
PR-URL: #42507 Reviewed-By: Tobias Nießen <tniessen@tnie.de>
1 parent 7f8f61a commit 28c034d

24 files changed

+1898
-1334
lines changed

doc/api/webcrypto.md

+129-212
Large diffs are not rendered by default.

lib/internal/crypto/cfrg.js

+369
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,369 @@
1+
'use strict';
2+
3+
const {
4+
Promise,
5+
SafeSet,
6+
} = primordials;
7+
8+
const { Buffer } = require('buffer');
9+
10+
const {
11+
ECKeyExportJob,
12+
KeyObjectHandle,
13+
SignJob,
14+
kCryptoJobAsync,
15+
kKeyTypePrivate,
16+
kKeyTypePublic,
17+
kSignJobModeSign,
18+
kSignJobModeVerify,
19+
} = internalBinding('crypto');
20+
21+
const {
22+
codes: {
23+
ERR_INVALID_ARG_TYPE,
24+
},
25+
} = require('internal/errors');
26+
27+
const {
28+
getArrayBufferOrView,
29+
getUsagesUnion,
30+
hasAnyNotIn,
31+
jobPromise,
32+
validateKeyOps,
33+
kHandle,
34+
kKeyObject,
35+
} = require('internal/crypto/util');
36+
37+
const {
38+
emitExperimentalWarning,
39+
lazyDOMException,
40+
} = require('internal/util');
41+
42+
const {
43+
generateKeyPair,
44+
} = require('internal/crypto/keygen');
45+
46+
const {
47+
InternalCryptoKey,
48+
PrivateKeyObject,
49+
PublicKeyObject,
50+
createPrivateKey,
51+
createPublicKey,
52+
isKeyObject,
53+
} = require('internal/crypto/keys');
54+
55+
function verifyAcceptableCfrgKeyUse(name, type, usages) {
56+
let checkSet;
57+
switch (name) {
58+
case 'X25519':
59+
// Fall through
60+
case 'X448':
61+
checkSet = ['deriveKey', 'deriveBits'];
62+
break;
63+
case 'Ed25519':
64+
// Fall through
65+
case 'Ed448':
66+
switch (type) {
67+
case 'private':
68+
checkSet = ['sign'];
69+
break;
70+
case 'public':
71+
checkSet = ['verify'];
72+
break;
73+
}
74+
}
75+
if (hasAnyNotIn(usages, checkSet)) {
76+
throw lazyDOMException(
77+
`Unsupported key usage for a ${name} key`,
78+
'SyntaxError');
79+
}
80+
}
81+
82+
function createECPublicKeyRaw(name, keyData) {
83+
const handle = new KeyObjectHandle();
84+
keyData = getArrayBufferOrView(keyData, 'keyData');
85+
if (handle.initECRaw(name.toLowerCase(), keyData))
86+
return new PublicKeyObject(handle);
87+
}
88+
89+
function createCFRGRawKey(name, keyData, isPublic) {
90+
const handle = new KeyObjectHandle();
91+
keyData = getArrayBufferOrView(keyData, 'keyData');
92+
93+
switch (name) {
94+
case 'Ed25519':
95+
case 'X25519':
96+
if (keyData.byteLength !== 32) {
97+
throw lazyDOMException(
98+
`${name} raw keys must be exactly 32-bytes`);
99+
}
100+
break;
101+
case 'Ed448':
102+
if (keyData.byteLength !== 57) {
103+
throw lazyDOMException(
104+
`${name} raw keys must be exactly 57-bytes`);
105+
}
106+
break;
107+
case 'X448':
108+
if (keyData.byteLength !== 56) {
109+
throw lazyDOMException(
110+
`${name} raw keys must be exactly 56-bytes`);
111+
}
112+
break;
113+
}
114+
115+
const keyType = isPublic ? kKeyTypePublic : kKeyTypePrivate;
116+
if (!handle.initEDRaw(name, keyData, keyType)) {
117+
throw lazyDOMException('Failure to generate key object');
118+
}
119+
120+
return isPublic ? new PublicKeyObject(handle) : new PrivateKeyObject(handle);
121+
}
122+
123+
async function cfrgGenerateKey(algorithm, extractable, keyUsages) {
124+
const { name } = algorithm;
125+
emitExperimentalWarning(`The ${name} Web Crypto API algorithm`);
126+
127+
const usageSet = new SafeSet(keyUsages);
128+
switch (name) {
129+
case 'Ed25519':
130+
// Fall through
131+
case 'Ed448':
132+
if (hasAnyNotIn(usageSet, ['sign', 'verify'])) {
133+
throw lazyDOMException(
134+
`Unsupported key usage for an ${name} key`,
135+
'SyntaxError');
136+
}
137+
break;
138+
case 'X25519':
139+
// Fall through
140+
case 'X448':
141+
if (hasAnyNotIn(usageSet, ['deriveKey', 'deriveBits'])) {
142+
throw lazyDOMException(
143+
`Unsupported key usage for an ${name} key`,
144+
'SyntaxError');
145+
}
146+
break;
147+
}
148+
return new Promise((resolve, reject) => {
149+
let genKeyType;
150+
switch (name) {
151+
case 'Ed25519':
152+
genKeyType = 'ed25519';
153+
break;
154+
case 'Ed448':
155+
genKeyType = 'ed448';
156+
break;
157+
case 'X25519':
158+
genKeyType = 'x25519';
159+
break;
160+
case 'X448':
161+
genKeyType = 'x448';
162+
break;
163+
}
164+
generateKeyPair(genKeyType, undefined, (err, pubKey, privKey) => {
165+
if (err) {
166+
return reject(lazyDOMException(
167+
'The operation failed for an operation-specific reason',
168+
'OperationError'));
169+
}
170+
171+
const algorithm = { name };
172+
173+
let publicUsages;
174+
let privateUsages;
175+
switch (name) {
176+
case 'Ed25519':
177+
// Fall through
178+
case 'Ed448':
179+
publicUsages = getUsagesUnion(usageSet, 'verify');
180+
privateUsages = getUsagesUnion(usageSet, 'sign');
181+
break;
182+
case 'X25519':
183+
// Fall through
184+
case 'X448':
185+
publicUsages = [];
186+
privateUsages = getUsagesUnion(usageSet, 'deriveKey', 'deriveBits');
187+
break;
188+
}
189+
190+
const publicKey =
191+
new InternalCryptoKey(
192+
pubKey,
193+
algorithm,
194+
publicUsages,
195+
true);
196+
197+
const privateKey =
198+
new InternalCryptoKey(
199+
privKey,
200+
algorithm,
201+
privateUsages,
202+
extractable);
203+
204+
resolve({ publicKey, privateKey });
205+
});
206+
});
207+
}
208+
209+
function cfrgExportKey(key, format) {
210+
emitExperimentalWarning(`The ${key.algorithm.name} Web Crypto API algorithm`);
211+
return jobPromise(new ECKeyExportJob(
212+
kCryptoJobAsync,
213+
format,
214+
key[kKeyObject][kHandle]));
215+
}
216+
217+
async function cfrgImportKey(
218+
format,
219+
keyData,
220+
algorithm,
221+
extractable,
222+
keyUsages) {
223+
224+
const { name } = algorithm;
225+
emitExperimentalWarning(`The ${name} Web Crypto API algorithm`);
226+
let keyObject;
227+
const usagesSet = new SafeSet(keyUsages);
228+
switch (format) {
229+
case 'node.keyObject': {
230+
if (!isKeyObject(keyData))
231+
throw new ERR_INVALID_ARG_TYPE('keyData', 'KeyObject', keyData);
232+
if (keyData.type === 'secret')
233+
throw lazyDOMException('Invalid key type', 'InvalidAccessException');
234+
verifyAcceptableCfrgKeyUse(name, keyData.type, usagesSet);
235+
keyObject = keyData;
236+
break;
237+
}
238+
case 'spki': {
239+
verifyAcceptableCfrgKeyUse(name, 'public', usagesSet);
240+
keyObject = createPublicKey({
241+
key: keyData,
242+
format: 'der',
243+
type: 'spki'
244+
});
245+
break;
246+
}
247+
case 'pkcs8': {
248+
verifyAcceptableCfrgKeyUse(name, 'private', usagesSet);
249+
keyObject = createPrivateKey({
250+
key: keyData,
251+
format: 'der',
252+
type: 'pkcs8'
253+
});
254+
break;
255+
}
256+
case 'jwk': {
257+
if (keyData == null || typeof keyData !== 'object')
258+
throw lazyDOMException('Invalid JWK keyData', 'DataError');
259+
if (keyData.kty !== 'OKP')
260+
throw lazyDOMException('Invalid key type', 'DataError');
261+
const isPublic = keyData.d === undefined;
262+
263+
if (usagesSet.size > 0 && keyData.use !== undefined) {
264+
let checkUse;
265+
switch (name) {
266+
case 'Ed25519':
267+
// Fall through
268+
case 'Ed448':
269+
checkUse = 'sig';
270+
break;
271+
case 'X25519':
272+
// Fall through
273+
case 'X448':
274+
checkUse = 'enc';
275+
break;
276+
}
277+
if (keyData.use !== checkUse)
278+
throw lazyDOMException('Invalid use type', 'DataError');
279+
}
280+
281+
validateKeyOps(keyData.key_ops, usagesSet);
282+
283+
if (keyData.ext !== undefined &&
284+
keyData.ext === false &&
285+
extractable === true) {
286+
throw lazyDOMException('JWK is not extractable', 'DataError');
287+
}
288+
289+
if (keyData.alg !== undefined) {
290+
if (typeof keyData.alg !== 'string')
291+
throw lazyDOMException('Invalid alg', 'DataError');
292+
if (
293+
(name === 'Ed25519' || name === 'Ed448') &&
294+
keyData.alg !== 'EdDSA'
295+
) {
296+
throw lazyDOMException('Invalid alg', 'DataError');
297+
}
298+
}
299+
300+
verifyAcceptableCfrgKeyUse(
301+
name,
302+
isPublic ? 'public' : 'private',
303+
usagesSet);
304+
keyObject = createCFRGRawKey(
305+
name,
306+
Buffer.from(
307+
isPublic ? keyData.x : keyData.d,
308+
'base64'),
309+
isPublic);
310+
break;
311+
}
312+
case 'raw': {
313+
verifyAcceptableCfrgKeyUse(name, 'public', usagesSet);
314+
keyObject = createECPublicKeyRaw(name, keyData);
315+
if (keyObject === undefined)
316+
throw lazyDOMException('Unable to import CFRG key', 'OperationError');
317+
break;
318+
}
319+
}
320+
321+
if (keyObject.asymmetricKeyType !== name.toLowerCase()) {
322+
throw lazyDOMException('Invalid key type', 'DataError');
323+
}
324+
325+
return new InternalCryptoKey(
326+
keyObject,
327+
{ name },
328+
keyUsages,
329+
extractable);
330+
}
331+
332+
function eddsaSignVerify(key, data, { name, context }, signature) {
333+
emitExperimentalWarning(`The ${name} Web Crypto API algorithm`);
334+
const mode = signature === undefined ? kSignJobModeSign : kSignJobModeVerify;
335+
const type = mode === kSignJobModeSign ? 'private' : 'public';
336+
337+
if (key.type !== type)
338+
throw lazyDOMException(`Key must be a ${type} key`, 'InvalidAccessError');
339+
340+
if (name === 'Ed448' && context !== undefined) {
341+
context =
342+
getArrayBufferOrView(context, 'algorithm.context');
343+
if (context.byteLength !== 0) {
344+
throw lazyDOMException(
345+
'Non zero-length context is not yet supported.', 'NotSupportedError');
346+
}
347+
}
348+
349+
return jobPromise(new SignJob(
350+
kCryptoJobAsync,
351+
mode,
352+
key[kKeyObject][kHandle],
353+
undefined,
354+
undefined,
355+
undefined,
356+
data,
357+
undefined,
358+
undefined,
359+
undefined,
360+
undefined,
361+
signature));
362+
}
363+
364+
module.exports = {
365+
cfrgExportKey,
366+
cfrgImportKey,
367+
cfrgGenerateKey,
368+
eddsaSignVerify,
369+
};

0 commit comments

Comments
 (0)