• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2
3const {
4  ObjectSetPrototypeOf,
5  ReflectApply,
6  StringPrototypeToLowerCase,
7} = primordials;
8
9const {
10  CipherBase,
11  privateDecrypt: _privateDecrypt,
12  privateEncrypt: _privateEncrypt,
13  publicDecrypt: _publicDecrypt,
14  publicEncrypt: _publicEncrypt,
15  getCipherInfo: _getCipherInfo,
16} = internalBinding('crypto');
17
18const {
19  crypto: {
20    RSA_PKCS1_OAEP_PADDING,
21    RSA_PKCS1_PADDING,
22  },
23} = internalBinding('constants');
24
25const {
26  codes: {
27    ERR_CRYPTO_INVALID_STATE,
28    ERR_INVALID_ARG_TYPE,
29    ERR_INVALID_ARG_VALUE,
30    ERR_UNKNOWN_ENCODING,
31  },
32} = require('internal/errors');
33
34const {
35  validateEncoding,
36  validateInt32,
37  validateObject,
38  validateString,
39} = require('internal/validators');
40
41const {
42  preparePrivateKey,
43  preparePublicOrPrivateKey,
44  prepareSecretKey,
45} = require('internal/crypto/keys');
46
47const {
48  getDefaultEncoding,
49  getArrayBufferOrView,
50  getStringOption,
51  kHandle,
52} = require('internal/crypto/util');
53
54const {
55  isArrayBufferView,
56} = require('internal/util/types');
57
58const assert = require('internal/assert');
59
60const LazyTransform = require('internal/streams/lazy_transform');
61
62const { normalizeEncoding } = require('internal/util');
63
64const { StringDecoder } = require('string_decoder');
65
66function rsaFunctionFor(method, defaultPadding, keyType) {
67  return (options, buffer) => {
68    const { format, type, data, passphrase } =
69      keyType === 'private' ?
70        preparePrivateKey(options) :
71        preparePublicOrPrivateKey(options);
72    const padding = options.padding || defaultPadding;
73    const { oaepHash, encoding } = options;
74    let { oaepLabel } = options;
75    if (oaepHash !== undefined)
76      validateString(oaepHash, 'key.oaepHash');
77    if (oaepLabel !== undefined)
78      oaepLabel = getArrayBufferOrView(oaepLabel, 'key.oaepLabel', encoding);
79    buffer = getArrayBufferOrView(buffer, 'buffer', encoding);
80    return method(data, format, type, passphrase, buffer, padding, oaepHash,
81                  oaepLabel);
82  };
83}
84
85const publicEncrypt = rsaFunctionFor(_publicEncrypt, RSA_PKCS1_OAEP_PADDING,
86                                     'public');
87const publicDecrypt = rsaFunctionFor(_publicDecrypt, RSA_PKCS1_PADDING,
88                                     'public');
89const privateEncrypt = rsaFunctionFor(_privateEncrypt, RSA_PKCS1_PADDING,
90                                      'private');
91const privateDecrypt = rsaFunctionFor(_privateDecrypt, RSA_PKCS1_OAEP_PADDING,
92                                      'private');
93
94function getDecoder(decoder, encoding) {
95  const normalizedEncoding = normalizeEncoding(encoding);
96  decoder = decoder || new StringDecoder(encoding);
97  if (decoder.encoding !== normalizedEncoding) {
98    if (normalizedEncoding === undefined) {
99      throw new ERR_UNKNOWN_ENCODING(encoding);
100    }
101    assert(false, 'Cannot change encoding');
102  }
103  return decoder;
104}
105
106function getUIntOption(options, key) {
107  let value;
108  if (options && (value = options[key]) != null) {
109    if (value >>> 0 !== value)
110      throw new ERR_INVALID_ARG_VALUE(`options.${key}`, value);
111    return value;
112  }
113  return -1;
114}
115
116function createCipherBase(cipher, credential, options, decipher, iv) {
117  const authTagLength = getUIntOption(options, 'authTagLength');
118  this[kHandle] = new CipherBase(decipher);
119  if (iv === undefined) {
120    this[kHandle].init(cipher, credential, authTagLength);
121  } else {
122    this[kHandle].initiv(cipher, credential, iv, authTagLength);
123  }
124  this._decoder = null;
125
126  ReflectApply(LazyTransform, this, [options]);
127}
128
129function createCipher(cipher, password, options, decipher) {
130  validateString(cipher, 'cipher');
131  password = getArrayBufferOrView(password, 'password');
132
133  ReflectApply(createCipherBase, this, [cipher, password, options, decipher]);
134}
135
136function createCipherWithIV(cipher, key, options, decipher, iv) {
137  validateString(cipher, 'cipher');
138  const encoding = getStringOption(options, 'encoding');
139  key = prepareSecretKey(key, encoding);
140  iv = iv === null ? null : getArrayBufferOrView(iv, 'iv');
141  ReflectApply(createCipherBase, this, [cipher, key, options, decipher, iv]);
142}
143
144// The Cipher class is part of the legacy Node.js crypto API. It exposes
145// a stream-based encryption/decryption model. For backwards compatibility
146// the Cipher class is defined using the legacy function syntax rather than
147// ES6 classes.
148
149function Cipher(cipher, password, options) {
150  if (!(this instanceof Cipher))
151    return new Cipher(cipher, password, options);
152
153  ReflectApply(createCipher, this, [cipher, password, options, true]);
154}
155
156ObjectSetPrototypeOf(Cipher.prototype, LazyTransform.prototype);
157ObjectSetPrototypeOf(Cipher, LazyTransform);
158
159Cipher.prototype._transform = function _transform(chunk, encoding, callback) {
160  this.push(this[kHandle].update(chunk, encoding));
161  callback();
162};
163
164Cipher.prototype._flush = function _flush(callback) {
165  try {
166    this.push(this[kHandle].final());
167  } catch (e) {
168    callback(e);
169    return;
170  }
171  callback();
172};
173
174Cipher.prototype.update = function update(data, inputEncoding, outputEncoding) {
175  const encoding = getDefaultEncoding();
176  inputEncoding = inputEncoding || encoding;
177  outputEncoding = outputEncoding || encoding;
178
179  if (typeof data === 'string') {
180    validateEncoding(data, inputEncoding);
181  } else if (!isArrayBufferView(data)) {
182    throw new ERR_INVALID_ARG_TYPE(
183      'data', ['string', 'Buffer', 'TypedArray', 'DataView'], data);
184  }
185
186  const ret = this[kHandle].update(data, inputEncoding);
187
188  if (outputEncoding && outputEncoding !== 'buffer') {
189    this._decoder = getDecoder(this._decoder, outputEncoding);
190    return this._decoder.write(ret);
191  }
192
193  return ret;
194};
195
196
197Cipher.prototype.final = function final(outputEncoding) {
198  outputEncoding = outputEncoding || getDefaultEncoding();
199  const ret = this[kHandle].final();
200
201  if (outputEncoding && outputEncoding !== 'buffer') {
202    this._decoder = getDecoder(this._decoder, outputEncoding);
203    return this._decoder.end(ret);
204  }
205
206  return ret;
207};
208
209
210Cipher.prototype.setAutoPadding = function setAutoPadding(ap) {
211  if (!this[kHandle].setAutoPadding(!!ap))
212    throw new ERR_CRYPTO_INVALID_STATE('setAutoPadding');
213  return this;
214};
215
216Cipher.prototype.getAuthTag = function getAuthTag() {
217  const ret = this[kHandle].getAuthTag();
218  if (ret === undefined)
219    throw new ERR_CRYPTO_INVALID_STATE('getAuthTag');
220  return ret;
221};
222
223
224function setAuthTag(tagbuf, encoding) {
225  tagbuf = getArrayBufferOrView(tagbuf, 'buffer', encoding);
226  if (!this[kHandle].setAuthTag(tagbuf))
227    throw new ERR_CRYPTO_INVALID_STATE('setAuthTag');
228  return this;
229}
230
231Cipher.prototype.setAAD = function setAAD(aadbuf, options) {
232  const encoding = getStringOption(options, 'encoding');
233  const plaintextLength = getUIntOption(options, 'plaintextLength');
234  aadbuf = getArrayBufferOrView(aadbuf, 'aadbuf', encoding);
235  if (!this[kHandle].setAAD(aadbuf, plaintextLength))
236    throw new ERR_CRYPTO_INVALID_STATE('setAAD');
237  return this;
238};
239
240// The Cipheriv class is part of the legacy Node.js crypto API. It exposes
241// a stream-based encryption/decryption model. For backwards compatibility
242// the Cipheriv class is defined using the legacy function syntax rather than
243// ES6 classes.
244
245function Cipheriv(cipher, key, iv, options) {
246  if (!(this instanceof Cipheriv))
247    return new Cipheriv(cipher, key, iv, options);
248
249  ReflectApply(createCipherWithIV, this, [cipher, key, options, true, iv]);
250}
251
252function addCipherPrototypeFunctions(constructor) {
253  constructor.prototype._transform = Cipher.prototype._transform;
254  constructor.prototype._flush = Cipher.prototype._flush;
255  constructor.prototype.update = Cipher.prototype.update;
256  constructor.prototype.final = Cipher.prototype.final;
257  constructor.prototype.setAutoPadding = Cipher.prototype.setAutoPadding;
258  if (constructor === Cipheriv) {
259    constructor.prototype.getAuthTag = Cipher.prototype.getAuthTag;
260  } else {
261    constructor.prototype.setAuthTag = setAuthTag;
262  }
263  constructor.prototype.setAAD = Cipher.prototype.setAAD;
264}
265
266ObjectSetPrototypeOf(Cipheriv.prototype, LazyTransform.prototype);
267ObjectSetPrototypeOf(Cipheriv, LazyTransform);
268addCipherPrototypeFunctions(Cipheriv);
269
270// The Decipher class is part of the legacy Node.js crypto API. It exposes
271// a stream-based encryption/decryption model. For backwards compatibility
272// the Decipher class is defined using the legacy function syntax rather than
273// ES6 classes.
274
275function Decipher(cipher, password, options) {
276  if (!(this instanceof Decipher))
277    return new Decipher(cipher, password, options);
278
279  ReflectApply(createCipher, this, [cipher, password, options, false]);
280}
281
282ObjectSetPrototypeOf(Decipher.prototype, LazyTransform.prototype);
283ObjectSetPrototypeOf(Decipher, LazyTransform);
284addCipherPrototypeFunctions(Decipher);
285
286// The Decipheriv class is part of the legacy Node.js crypto API. It exposes
287// a stream-based encryption/decryption model. For backwards compatibility
288// the Decipheriv class is defined using the legacy function syntax rather than
289// ES6 classes.
290
291function Decipheriv(cipher, key, iv, options) {
292  if (!(this instanceof Decipheriv))
293    return new Decipheriv(cipher, key, iv, options);
294
295  ReflectApply(createCipherWithIV, this, [cipher, key, options, false, iv]);
296}
297
298ObjectSetPrototypeOf(Decipheriv.prototype, LazyTransform.prototype);
299ObjectSetPrototypeOf(Decipheriv, LazyTransform);
300addCipherPrototypeFunctions(Decipheriv);
301
302function getCipherInfo(nameOrNid, options) {
303  if (typeof nameOrNid !== 'string' && typeof nameOrNid !== 'number') {
304    throw new ERR_INVALID_ARG_TYPE(
305      'nameOrNid',
306      ['string', 'number'],
307      nameOrNid);
308  }
309  if (typeof nameOrNid === 'number')
310    validateInt32(nameOrNid, 'nameOrNid');
311  let keyLength, ivLength;
312  if (options !== undefined) {
313    validateObject(options, 'options');
314    ({ keyLength, ivLength } = options);
315    if (keyLength !== undefined)
316      validateInt32(keyLength, 'options.keyLength');
317    if (ivLength !== undefined)
318      validateInt32(ivLength, 'options.ivLength');
319  }
320
321  const ret = _getCipherInfo({}, nameOrNid, keyLength, ivLength);
322  if (ret !== undefined) {
323    if (ret.name) ret.name = StringPrototypeToLowerCase(ret.name);
324    if (ret.type) ret.type = StringPrototypeToLowerCase(ret.type);
325  }
326  return ret;
327}
328
329module.exports = {
330  Cipher,
331  Cipheriv,
332  Decipher,
333  Decipheriv,
334  privateDecrypt,
335  privateEncrypt,
336  publicDecrypt,
337  publicEncrypt,
338  getCipherInfo,
339};
340