@ 261106.9
current
'use strict';

const {
  FunctionPrototypeCall,
  NumberIsInteger,
  ObjectSetPrototypeOf,
} = primordials;

const {
  CipherBase,
  privateDecrypt: _privateDecrypt,
  privateEncrypt: _privateEncrypt,
  publicDecrypt: _publicDecrypt,
  publicEncrypt: _publicEncrypt,
  getCipherInfo: _getCipherInfo,
} = internalBinding('crypto');

const {
  crypto: {
    RSA_PKCS1_OAEP_PADDING,
    RSA_PKCS1_PADDING,
  },
} = internalBinding('constants');

const {
  codes: {
    ERR_CRYPTO_INVALID_STATE,
    ERR_INVALID_ARG_TYPE,
    ERR_INVALID_ARG_VALUE,
    ERR_UNKNOWN_ENCODING,
  },
} = require('internal/errors');

const {
  validateEncoding,
  validateOneOf,
  validateUint32,
  validateObject,
  validateString,
} = require('internal/validators');

const {
  preparePrivateKey,
  preparePublicOrPrivateKey,
  prepareSecretKey,
} = require('internal/crypto/keys');

const {
  getArrayBufferOrView,
  getStringOption,
  kHandle,
} = require('internal/crypto/util');

const {
  isArrayBufferView,
} = require('internal/util/types');

const LazyTransform = require('internal/streams/lazy_transform');

const { normalizeEncoding, kEmptyObject } = require('internal/util');

const { StringDecoder } = require('string_decoder');

const kCtsModes = ['CS1', 'CS2', 'CS3'];
const kXtsStandards = ['GB', 'IEEE'];

function rsaFunctionFor(method, defaultPadding, keyType) {
  const keyName = keyType === 'private' ? 'privateKey' : undefined;
  return (key, buffer) => {
    const { format, type, data, passphrase, namedCurve } =
      keyType === 'private' ?
        preparePrivateKey(key, keyName) :
        preparePublicOrPrivateKey(key, keyName);
    const padding = key.padding || defaultPadding;
    const { oaepHash, mgf1Hash, encoding } = key;
    let { oaepLabel } = key;
    if (oaepHash !== undefined)
      validateString(oaepHash, 'key.oaepHash');
    if (mgf1Hash !== undefined)
      validateString(mgf1Hash, 'key.mgf1Hash');
    if (oaepLabel !== undefined)
      oaepLabel = getArrayBufferOrView(oaepLabel, 'key.oaepLabel', encoding);
    buffer = getArrayBufferOrView(buffer, 'buffer', encoding);
    return method(data, format, type, passphrase, namedCurve, buffer,
                  padding, oaepHash, oaepLabel, mgf1Hash);
  };
}

const publicEncrypt = rsaFunctionFor(_publicEncrypt, RSA_PKCS1_OAEP_PADDING,
                                     'public');
const publicDecrypt = rsaFunctionFor(_publicDecrypt, RSA_PKCS1_PADDING,
                                     'public');
const privateEncrypt = rsaFunctionFor(_privateEncrypt, RSA_PKCS1_PADDING,
                                      'private');
const privateDecrypt = rsaFunctionFor(_privateDecrypt, RSA_PKCS1_OAEP_PADDING,
                                      'private');

function getDecoder(decoder, encoding) {
  const normalizedEncoding = normalizeEncoding(encoding);
  decoder ||= new StringDecoder(encoding);
  if (decoder.encoding !== normalizedEncoding) {
    if (normalizedEncoding === undefined) {
      throw new ERR_UNKNOWN_ENCODING(encoding);
    }
    throw new ERR_INVALID_ARG_VALUE(
      'outputEncoding',
      encoding,
      `cannot be changed from '${decoder.encoding}'`,
    );
  }
  return decoder;
}

function getUIntOption(options, key) {
  let value;
  if (options && (value = options[key]) != null) {
    if (value >>> 0 !== value)
      throw new ERR_INVALID_ARG_VALUE(`options.${key}`, value);
    // Coerce -0 to +0.
    return value + 0;
  }
  return -1;
}

function createCipherBase(cipher, credential, options, isEncrypt, iv) {
  let authTagLength = -1;
  let ctsMode;
  let xtsStandard;
  if (options != null) {
    authTagLength = getUIntOption(options, 'authTagLength');
    ctsMode = getStringOption(options, 'ctsMode') ?? undefined;
    xtsStandard = getStringOption(options, 'xtsStandard') ?? undefined;
    if (ctsMode !== undefined)
      validateOneOf(ctsMode, 'options.ctsMode', kCtsModes);
    if (xtsStandard !== undefined)
      validateOneOf(xtsStandard, 'options.xtsStandard', kXtsStandards);
  }

  if (ctsMode === undefined && xtsStandard === undefined) {
    this[kHandle] = new CipherBase(
      isEncrypt, cipher, credential, iv, authTagLength);
  } else {
    this[kHandle] = new CipherBase(
      isEncrypt,
      cipher,
      credential,
      iv,
      authTagLength,
      ctsMode,
      xtsStandard);
  }
  this._decoder = null;

  FunctionPrototypeCall(LazyTransform, this, options);
}

function createCipherWithIV(cipher, key, options, isEncrypt, iv) {
  validateString(cipher, 'cipher');
  const encoding = getStringOption(options, 'encoding');
  key = prepareSecretKey(key, encoding);
  iv = iv === null ? null : getArrayBufferOrView(iv, 'iv');
  FunctionPrototypeCall(createCipherBase, this, cipher, key, options, isEncrypt, iv);
}

// The Cipher class is part of the legacy Node.js crypto API. It exposes
// a stream-based encryption/decryption model. For backwards compatibility
// the Cipher class is defined using the legacy function syntax rather than
// ES6 classes.

function _transform(chunk, encoding, callback) {
  this.push(this[kHandle].update(chunk, encoding));
  callback();
};

function _flush(callback) {
  try {
    this.push(this[kHandle].final());
  } catch (e) {
    callback(e);
    return;
  }
  callback();
};

function update(data, inputEncoding, outputEncoding) {
  if (typeof data === 'string') {
    validateEncoding(data, inputEncoding);
  } else if (!isArrayBufferView(data)) {
    throw new ERR_INVALID_ARG_TYPE(
      'data', ['string', 'Buffer', 'TypedArray', 'DataView'], data);
  }

  const ret = this[kHandle].update(data, inputEncoding);

  if (outputEncoding && outputEncoding !== 'buffer') {
    this._decoder = getDecoder(this._decoder, outputEncoding);
    return this._decoder.write(ret);
  }

  return ret;
};

function final(outputEncoding) {
  const ret = this[kHandle].final();

  if (outputEncoding && outputEncoding !== 'buffer') {
    this._decoder = getDecoder(this._decoder, outputEncoding);
    return this._decoder.end(ret);
  }

  return ret;
};

function setAutoPadding(ap) {
  if (!this[kHandle].setAutoPadding(!!ap))
    throw new ERR_CRYPTO_INVALID_STATE('setAutoPadding');
  return this;
};

function getAuthTag() {
  const ret = this[kHandle].getAuthTag();
  if (ret === undefined)
    throw new ERR_CRYPTO_INVALID_STATE('getAuthTag');
  return ret;
};

function setAuthTag(tagbuf, encoding) {
  tagbuf = getArrayBufferOrView(tagbuf, 'buffer', encoding);
  if (!this[kHandle].setAuthTag(tagbuf))
    throw new ERR_CRYPTO_INVALID_STATE('setAuthTag');
  return this;
}

function setAAD(aadbuf, options) {
  const encoding = getStringOption(options, 'encoding');
  const plaintextLength = getUIntOption(options, 'plaintextLength');
  aadbuf = getArrayBufferOrView(aadbuf, 'aadbuf', encoding);
  if (!this[kHandle].setAAD(aadbuf, plaintextLength))
    throw new ERR_CRYPTO_INVALID_STATE('setAAD');
  return this;
};

// The Cipheriv class is part of the legacy Node.js crypto API. It exposes
// a stream-based encryption/decryption model. For backwards compatibility
// the Cipheriv class is defined using the legacy function syntax rather than
// ES6 classes.

function Cipheriv(cipher, key, iv, options) {
  if (!(this instanceof Cipheriv))
    return new Cipheriv(cipher, key, iv, options);

  FunctionPrototypeCall(createCipherWithIV, this, cipher, key, options, true, iv);
}

function addCipherPrototypeFunctions(constructor) {
  constructor.prototype._transform = _transform;
  constructor.prototype._flush = _flush;
  constructor.prototype.update = update;
  constructor.prototype.final = final;
  constructor.prototype.setAutoPadding = setAutoPadding;
  if (constructor === Cipheriv) {
    constructor.prototype.getAuthTag = getAuthTag;
  } else {
    constructor.prototype.setAuthTag = setAuthTag;
  }
  constructor.prototype.setAAD = setAAD;
}

ObjectSetPrototypeOf(Cipheriv.prototype, LazyTransform.prototype);
ObjectSetPrototypeOf(Cipheriv, LazyTransform);
addCipherPrototypeFunctions(Cipheriv);

// The Decipheriv class is part of the legacy Node.js crypto API. It exposes
// a stream-based encryption/decryption model. For backwards compatibility
// the Decipheriv class is defined using the legacy function syntax rather than
// ES6 classes.
function Decipheriv(cipher, key, iv, options) {
  if (!(this instanceof Decipheriv))
    return new Decipheriv(cipher, key, iv, options);

  FunctionPrototypeCall(createCipherWithIV, this, cipher, key, options, false, iv);
}

ObjectSetPrototypeOf(Decipheriv.prototype, LazyTransform.prototype);
ObjectSetPrototypeOf(Decipheriv, LazyTransform);
addCipherPrototypeFunctions(Decipheriv);

const kMinNid = 1;
const kMaxNid = 2_147_483_647;
function getCipherInfo(nameOrNid, options = kEmptyObject) {
  validateObject(options, 'options');
  let { keyLength, ivLength } = options;
  if (keyLength !== undefined) {
    validateUint32(keyLength, 'options.keyLength');
    // Coerce -0 to +0.
    keyLength += 0;
  }
  if (ivLength !== undefined) {
    validateUint32(ivLength, 'options.ivLength');
    // Coerce -0 to +0.
    ivLength += 0;
  }

  const type = typeof nameOrNid;

  if (type === 'string') {
    if (nameOrNid.length === 0) return undefined;
    return _getCipherInfo(nameOrNid, keyLength, ivLength);
  } else if (type === 'number') {
    if (!NumberIsInteger(nameOrNid) || nameOrNid < kMinNid || nameOrNid > kMaxNid) {
      return undefined;
    }
    return _getCipherInfo(nameOrNid, keyLength, ivLength);
  }

  throw new ERR_INVALID_ARG_TYPE(
    'nameOrNid',
    ['string', 'number'],
    nameOrNid);
}

module.exports = {
  Cipheriv,
  Decipheriv,
  privateDecrypt,
  privateEncrypt,
  publicDecrypt,
  publicEncrypt,
  getCipherInfo,
};