• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2
3const {
4  SafeSet,
5  Uint8Array,
6} = primordials;
7
8const {
9  KeyObjectHandle,
10  RSACipherJob,
11  RSAKeyExportJob,
12  SignJob,
13  kCryptoJobAsync,
14  kSignJobModeSign,
15  kSignJobModeVerify,
16  kKeyVariantRSA_SSA_PKCS1_v1_5,
17  kKeyVariantRSA_PSS,
18  kKeyVariantRSA_OAEP,
19  kKeyTypePrivate,
20  kWebCryptoCipherEncrypt,
21  RSA_PKCS1_PSS_PADDING,
22} = internalBinding('crypto');
23
24const {
25  validateInt32,
26} = require('internal/validators');
27
28const {
29  bigIntArrayToUnsignedInt,
30  getUsagesUnion,
31  hasAnyNotIn,
32  jobPromise,
33  normalizeHashName,
34  validateKeyOps,
35  validateMaxBufferLength,
36  kHandle,
37  kKeyObject,
38} = require('internal/crypto/util');
39
40const {
41  lazyDOMException,
42  promisify,
43} = require('internal/util');
44
45const {
46  InternalCryptoKey,
47  PrivateKeyObject,
48  PublicKeyObject,
49  createPublicKey,
50  createPrivateKey,
51} = require('internal/crypto/keys');
52
53const {
54  generateKeyPair: _generateKeyPair,
55} = require('internal/crypto/keygen');
56
57const kRsaVariants = {
58  'RSASSA-PKCS1-v1_5': kKeyVariantRSA_SSA_PKCS1_v1_5,
59  'RSA-PSS': kKeyVariantRSA_PSS,
60  'RSA-OAEP': kKeyVariantRSA_OAEP,
61};
62const generateKeyPair = promisify(_generateKeyPair);
63
64function verifyAcceptableRsaKeyUse(name, isPublic, usages) {
65  let checkSet;
66  switch (name) {
67    case 'RSA-OAEP':
68      checkSet = isPublic ? ['encrypt', 'wrapKey'] : ['decrypt', 'unwrapKey'];
69      break;
70    case 'RSA-PSS':
71      // Fall through
72    case 'RSASSA-PKCS1-v1_5':
73      checkSet = isPublic ? ['verify'] : ['sign'];
74      break;
75    default:
76      throw lazyDOMException(
77        'The algorithm is not supported', 'NotSupportedError');
78  }
79  if (hasAnyNotIn(usages, checkSet)) {
80    throw lazyDOMException(
81      `Unsupported key usage for an ${name} key`,
82      'SyntaxError');
83  }
84}
85
86function rsaOaepCipher(mode, key, data, { label }) {
87  const type = mode === kWebCryptoCipherEncrypt ? 'public' : 'private';
88  if (key.type !== type) {
89    throw lazyDOMException(
90      'The requested operation is not valid for the provided key',
91      'InvalidAccessError');
92  }
93  if (label !== undefined) {
94    validateMaxBufferLength(label, 'algorithm.label');
95  }
96
97  return jobPromise(() => new RSACipherJob(
98    kCryptoJobAsync,
99    mode,
100    key[kKeyObject][kHandle],
101    data,
102    kKeyVariantRSA_OAEP,
103    normalizeHashName(key.algorithm.hash.name),
104    label));
105}
106
107async function rsaKeyGenerate(
108  algorithm,
109  extractable,
110  keyUsages) {
111
112  const {
113    name,
114    modulusLength,
115    publicExponent,
116    hash,
117  } = algorithm;
118
119  const usageSet = new SafeSet(keyUsages);
120
121  const publicExponentConverted = bigIntArrayToUnsignedInt(publicExponent);
122  if (publicExponentConverted === undefined) {
123    throw lazyDOMException(
124      'The publicExponent must be equivalent to an unsigned 32-bit value',
125      'OperationError');
126  }
127
128  switch (name) {
129    case 'RSA-OAEP':
130      if (hasAnyNotIn(usageSet,
131                      ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey'])) {
132        throw lazyDOMException(
133          'Unsupported key usage for a RSA key',
134          'SyntaxError');
135      }
136      break;
137    default:
138      if (hasAnyNotIn(usageSet, ['sign', 'verify'])) {
139        throw lazyDOMException(
140          'Unsupported key usage for a RSA key',
141          'SyntaxError');
142      }
143  }
144
145  const keypair = await generateKeyPair('rsa', {
146    modulusLength,
147    publicExponent: publicExponentConverted,
148  }).catch((err) => {
149    throw lazyDOMException(
150      'The operation failed for an operation-specific reason',
151      { name: 'OperationError', cause: err });
152  });
153
154  const keyAlgorithm = {
155    name,
156    modulusLength,
157    publicExponent,
158    hash: { name: hash.name },
159  };
160
161  let publicUsages;
162  let privateUsages;
163  switch (name) {
164    case 'RSA-OAEP': {
165      publicUsages = getUsagesUnion(usageSet, 'encrypt', 'wrapKey');
166      privateUsages = getUsagesUnion(usageSet, 'decrypt', 'unwrapKey');
167      break;
168    }
169    default: {
170      publicUsages = getUsagesUnion(usageSet, 'verify');
171      privateUsages = getUsagesUnion(usageSet, 'sign');
172      break;
173    }
174  }
175
176  const publicKey =
177    new InternalCryptoKey(
178      keypair.publicKey,
179      keyAlgorithm,
180      publicUsages,
181      true);
182
183  const privateKey =
184    new InternalCryptoKey(
185      keypair.privateKey,
186      keyAlgorithm,
187      privateUsages,
188      extractable);
189
190  return { publicKey, privateKey };
191}
192
193function rsaExportKey(key, format) {
194  return jobPromise(() => new RSAKeyExportJob(
195    kCryptoJobAsync,
196    format,
197    key[kKeyObject][kHandle],
198    kRsaVariants[key.algorithm.name]));
199}
200
201async function rsaImportKey(
202  format,
203  keyData,
204  algorithm,
205  extractable,
206  keyUsages) {
207  const usagesSet = new SafeSet(keyUsages);
208  let keyObject;
209  switch (format) {
210    case 'spki': {
211      verifyAcceptableRsaKeyUse(algorithm.name, true, usagesSet);
212      try {
213        keyObject = createPublicKey({
214          key: keyData,
215          format: 'der',
216          type: 'spki',
217        });
218      } catch (err) {
219        throw lazyDOMException(
220          'Invalid keyData', { name: 'DataError', cause: err });
221      }
222      break;
223    }
224    case 'pkcs8': {
225      verifyAcceptableRsaKeyUse(algorithm.name, false, usagesSet);
226      try {
227        keyObject = createPrivateKey({
228          key: keyData,
229          format: 'der',
230          type: 'pkcs8',
231        });
232      } catch (err) {
233        throw lazyDOMException(
234          'Invalid keyData', { name: 'DataError', cause: err });
235      }
236      break;
237    }
238    case 'jwk': {
239      if (!keyData.kty)
240        throw lazyDOMException('Invalid keyData', 'DataError');
241
242      if (keyData.kty !== 'RSA')
243        throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError');
244
245      verifyAcceptableRsaKeyUse(
246        algorithm.name,
247        keyData.d === undefined,
248        usagesSet);
249
250      if (usagesSet.size > 0 && keyData.use !== undefined) {
251        const checkUse = algorithm.name === 'RSA-OAEP' ? 'enc' : 'sig';
252        if (keyData.use !== checkUse)
253          throw lazyDOMException('Invalid JWK "use" Parameter', 'DataError');
254      }
255
256      validateKeyOps(keyData.key_ops, usagesSet);
257
258      if (keyData.ext !== undefined &&
259          keyData.ext === false &&
260          extractable === true) {
261        throw lazyDOMException(
262          'JWK "ext" Parameter and extractable mismatch',
263          'DataError');
264      }
265
266      if (keyData.alg !== undefined) {
267        const hash =
268          normalizeHashName(keyData.alg, normalizeHashName.kContextWebCrypto);
269        if (hash !== algorithm.hash.name)
270          throw lazyDOMException(
271            'JWK "alg" does not match the requested algorithm',
272            'DataError');
273      }
274
275      const handle = new KeyObjectHandle();
276      const type = handle.initJwk(keyData);
277      if (type === undefined)
278        throw lazyDOMException('Invalid JWK', 'DataError');
279
280      keyObject = type === kKeyTypePrivate ?
281        new PrivateKeyObject(handle) :
282        new PublicKeyObject(handle);
283
284      break;
285    }
286    default:
287      throw lazyDOMException(
288        `Unable to import RSA key with format ${format}`,
289        'NotSupportedError');
290  }
291
292  if (keyObject.asymmetricKeyType !== 'rsa') {
293    throw lazyDOMException('Invalid key type', 'DataError');
294  }
295
296  const {
297    modulusLength,
298    publicExponent,
299  } = keyObject[kHandle].keyDetail({});
300
301  return new InternalCryptoKey(keyObject, {
302    name: algorithm.name,
303    modulusLength,
304    publicExponent: new Uint8Array(publicExponent),
305    hash: algorithm.hash,
306  }, keyUsages, extractable);
307}
308
309function rsaSignVerify(key, data, { saltLength }, signature) {
310  let padding;
311  if (key.algorithm.name === 'RSA-PSS') {
312    padding = RSA_PKCS1_PSS_PADDING;
313    // TODO(@jasnell): Validate maximum size of saltLength
314    // based on the key size:
315    //   Math.ceil((keySizeInBits - 1)/8) - digestSizeInBytes - 2
316    validateInt32(saltLength, 'algorithm.saltLength', -2);
317  }
318
319  const mode = signature === undefined ? kSignJobModeSign : kSignJobModeVerify;
320  const type = mode === kSignJobModeSign ? 'private' : 'public';
321
322  if (key.type !== type)
323    throw lazyDOMException(`Key must be a ${type} key`, 'InvalidAccessError');
324
325  return jobPromise(() => new SignJob(
326    kCryptoJobAsync,
327    signature === undefined ? kSignJobModeSign : kSignJobModeVerify,
328    key[kKeyObject][kHandle],
329    undefined,
330    undefined,
331    undefined,
332    data,
333    normalizeHashName(key.algorithm.hash.name),
334    saltLength,
335    padding,
336    undefined,
337    signature));
338}
339
340
341module.exports = {
342  rsaCipher: rsaOaepCipher,
343  rsaExportKey,
344  rsaImportKey,
345  rsaKeyGenerate,
346  rsaSignVerify,
347};
348