• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2
3const {
4  ObjectSetPrototypeOf,
5} = primordials;
6
7const {
8  RSA_PKCS1_OAEP_PADDING,
9  RSA_PKCS1_PADDING
10} = internalBinding('constants').crypto;
11
12const {
13  ERR_CRYPTO_INVALID_STATE,
14  ERR_INVALID_ARG_TYPE,
15  ERR_INVALID_OPT_VALUE
16} = require('internal/errors').codes;
17const { validateEncoding, validateString } = require('internal/validators');
18
19const {
20  preparePrivateKey,
21  preparePublicOrPrivateKey,
22  prepareSecretKey
23} = require('internal/crypto/keys');
24const {
25  getDefaultEncoding,
26  kHandle,
27  getArrayBufferView
28} = require('internal/crypto/util');
29
30const { isArrayBufferView } = require('internal/util/types');
31
32const {
33  CipherBase,
34  privateDecrypt: _privateDecrypt,
35  privateEncrypt: _privateEncrypt,
36  publicDecrypt: _publicDecrypt,
37  publicEncrypt: _publicEncrypt
38} = internalBinding('crypto');
39
40const assert = require('internal/assert');
41const LazyTransform = require('internal/streams/lazy_transform');
42
43const { normalizeEncoding } = require('internal/util');
44
45// Lazy loaded for startup performance.
46let StringDecoder;
47
48function rsaFunctionFor(method, defaultPadding, keyType) {
49  return (options, buffer) => {
50    const { format, type, data, passphrase } =
51      keyType === 'private' ?
52        preparePrivateKey(options) :
53        preparePublicOrPrivateKey(options);
54    const padding = options.padding || defaultPadding;
55    const { oaepHash, oaepLabel } = options;
56    if (oaepHash !== undefined && typeof oaepHash !== 'string')
57      throw new ERR_INVALID_ARG_TYPE('options.oaepHash', 'string', oaepHash);
58    if (oaepLabel !== undefined && !isArrayBufferView(oaepLabel)) {
59      throw new ERR_INVALID_ARG_TYPE('options.oaepLabel',
60                                     ['Buffer', 'TypedArray', 'DataView'],
61                                     oaepLabel);
62    }
63    return method(data, format, type, passphrase, buffer, padding, oaepHash,
64                  oaepLabel);
65  };
66}
67
68const publicEncrypt = rsaFunctionFor(_publicEncrypt, RSA_PKCS1_OAEP_PADDING,
69                                     'public');
70const publicDecrypt = rsaFunctionFor(_publicDecrypt, RSA_PKCS1_PADDING,
71                                     'public');
72const privateEncrypt = rsaFunctionFor(_privateEncrypt, RSA_PKCS1_PADDING,
73                                      'private');
74const privateDecrypt = rsaFunctionFor(_privateDecrypt, RSA_PKCS1_OAEP_PADDING,
75                                      'private');
76
77function getDecoder(decoder, encoding) {
78  encoding = normalizeEncoding(encoding);
79  if (StringDecoder === undefined)
80    StringDecoder = require('string_decoder').StringDecoder;
81  decoder = decoder || new StringDecoder(encoding);
82  assert(decoder.encoding === encoding, 'Cannot change encoding');
83  return decoder;
84}
85
86function getUIntOption(options, key) {
87  let value;
88  if (options && (value = options[key]) != null) {
89    if (value >>> 0 !== value)
90      throw new ERR_INVALID_OPT_VALUE(key, value);
91    return value;
92  }
93  return -1;
94}
95
96function createCipherBase(cipher, credential, options, decipher, iv) {
97  const authTagLength = getUIntOption(options, 'authTagLength');
98
99  this[kHandle] = new CipherBase(decipher);
100  if (iv === undefined) {
101    this[kHandle].init(cipher, credential, authTagLength);
102  } else {
103    this[kHandle].initiv(cipher, credential, iv, authTagLength);
104  }
105  this._decoder = null;
106
107  LazyTransform.call(this, options);
108}
109
110function createCipher(cipher, password, options, decipher) {
111  validateString(cipher, 'cipher');
112  password = getArrayBufferView(password, 'password');
113
114  createCipherBase.call(this, cipher, password, options, decipher);
115}
116
117function createCipherWithIV(cipher, key, options, decipher, iv) {
118  validateString(cipher, 'cipher');
119  key = prepareSecretKey(key);
120  iv = iv === null ? null : getArrayBufferView(iv, 'iv');
121  createCipherBase.call(this, cipher, key, options, decipher, iv);
122}
123
124function Cipher(cipher, password, options) {
125  if (!(this instanceof Cipher))
126    return new Cipher(cipher, password, options);
127
128  createCipher.call(this, cipher, password, options, true);
129}
130
131ObjectSetPrototypeOf(Cipher.prototype, LazyTransform.prototype);
132ObjectSetPrototypeOf(Cipher, LazyTransform);
133
134Cipher.prototype._transform = function _transform(chunk, encoding, callback) {
135  this.push(this[kHandle].update(chunk, encoding));
136  callback();
137};
138
139Cipher.prototype._flush = function _flush(callback) {
140  try {
141    this.push(this[kHandle].final());
142  } catch (e) {
143    callback(e);
144    return;
145  }
146  callback();
147};
148
149Cipher.prototype.update = function update(data, inputEncoding, outputEncoding) {
150  const encoding = getDefaultEncoding();
151  inputEncoding = inputEncoding || encoding;
152  outputEncoding = outputEncoding || encoding;
153
154  if (typeof data === 'string') {
155    validateEncoding(data, inputEncoding);
156  } else if (!isArrayBufferView(data)) {
157    throw new ERR_INVALID_ARG_TYPE(
158      'data', ['string', 'Buffer', 'TypedArray', 'DataView'], data);
159  }
160
161  const ret = this[kHandle].update(data, inputEncoding);
162
163  if (outputEncoding && outputEncoding !== 'buffer') {
164    this._decoder = getDecoder(this._decoder, outputEncoding);
165    return this._decoder.write(ret);
166  }
167
168  return ret;
169};
170
171
172Cipher.prototype.final = function final(outputEncoding) {
173  outputEncoding = outputEncoding || getDefaultEncoding();
174  const ret = this[kHandle].final();
175
176  if (outputEncoding && outputEncoding !== 'buffer') {
177    this._decoder = getDecoder(this._decoder, outputEncoding);
178    return this._decoder.end(ret);
179  }
180
181  return ret;
182};
183
184
185Cipher.prototype.setAutoPadding = function setAutoPadding(ap) {
186  if (!this[kHandle].setAutoPadding(!!ap))
187    throw new ERR_CRYPTO_INVALID_STATE('setAutoPadding');
188  return this;
189};
190
191Cipher.prototype.getAuthTag = function getAuthTag() {
192  const ret = this[kHandle].getAuthTag();
193  if (ret === undefined)
194    throw new ERR_CRYPTO_INVALID_STATE('getAuthTag');
195  return ret;
196};
197
198
199function setAuthTag(tagbuf) {
200  if (!isArrayBufferView(tagbuf)) {
201    throw new ERR_INVALID_ARG_TYPE('buffer',
202                                   ['Buffer', 'TypedArray', 'DataView'],
203                                   tagbuf);
204  }
205  if (!this[kHandle].setAuthTag(tagbuf))
206    throw new ERR_CRYPTO_INVALID_STATE('setAuthTag');
207  return this;
208}
209
210Cipher.prototype.setAAD = function setAAD(aadbuf, options) {
211  if (!isArrayBufferView(aadbuf)) {
212    throw new ERR_INVALID_ARG_TYPE('buffer',
213                                   ['Buffer', 'TypedArray', 'DataView'],
214                                   aadbuf);
215  }
216
217  const plaintextLength = getUIntOption(options, 'plaintextLength');
218  if (!this[kHandle].setAAD(aadbuf, plaintextLength))
219    throw new ERR_CRYPTO_INVALID_STATE('setAAD');
220  return this;
221};
222
223function Cipheriv(cipher, key, iv, options) {
224  if (!(this instanceof Cipheriv))
225    return new Cipheriv(cipher, key, iv, options);
226
227  createCipherWithIV.call(this, cipher, key, options, true, iv);
228}
229
230function addCipherPrototypeFunctions(constructor) {
231  constructor.prototype._transform = Cipher.prototype._transform;
232  constructor.prototype._flush = Cipher.prototype._flush;
233  constructor.prototype.update = Cipher.prototype.update;
234  constructor.prototype.final = Cipher.prototype.final;
235  constructor.prototype.setAutoPadding = Cipher.prototype.setAutoPadding;
236  if (constructor === Cipheriv) {
237    constructor.prototype.getAuthTag = Cipher.prototype.getAuthTag;
238  } else {
239    constructor.prototype.setAuthTag = setAuthTag;
240  }
241  constructor.prototype.setAAD = Cipher.prototype.setAAD;
242}
243
244ObjectSetPrototypeOf(Cipheriv.prototype, LazyTransform.prototype);
245ObjectSetPrototypeOf(Cipheriv, LazyTransform);
246addCipherPrototypeFunctions(Cipheriv);
247
248function Decipher(cipher, password, options) {
249  if (!(this instanceof Decipher))
250    return new Decipher(cipher, password, options);
251
252  createCipher.call(this, cipher, password, options, false);
253}
254
255ObjectSetPrototypeOf(Decipher.prototype, LazyTransform.prototype);
256ObjectSetPrototypeOf(Decipher, LazyTransform);
257addCipherPrototypeFunctions(Decipher);
258
259
260function Decipheriv(cipher, key, iv, options) {
261  if (!(this instanceof Decipheriv))
262    return new Decipheriv(cipher, key, iv, options);
263
264  createCipherWithIV.call(this, cipher, key, options, false, iv);
265}
266
267ObjectSetPrototypeOf(Decipheriv.prototype, LazyTransform.prototype);
268ObjectSetPrototypeOf(Decipheriv, LazyTransform);
269addCipherPrototypeFunctions(Decipheriv);
270
271module.exports = {
272  Cipher,
273  Cipheriv,
274  Decipher,
275  Decipheriv,
276  privateDecrypt,
277  privateEncrypt,
278  publicDecrypt,
279  publicEncrypt,
280};
281