Source: security/identity/private-key-storage.js

/**
 * Copyright (C) 2014-2016 Regents of the University of California.
 * @author: Jeff Thompson <jefft0@remap.ucla.edu>
 * From ndn-cxx security by Yingdi Yu <yingdi@cs.ucla.edu>.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * A copy of the GNU Lesser General Public License is in the file COPYING.
 */

/** @ignore */
var SyncPromise = require('../../util/sync-promise.js').SyncPromise; /** @ignore */
var DerNode = require('../../encoding/der/der-node.js').DerNode;

/**
 * PrivateKeyStorage is an abstract class which declares methods for working
 * with a private key storage. You should use a subclass.
 * @constructor
 */
var PrivateKeyStorage = function PrivateKeyStorage()
{
};

exports.PrivateKeyStorage = PrivateKeyStorage;

/**
 * Generate a pair of asymmetric keys.
 * @param {Name} keyName The name of the key pair.
 * @param {KeyParams} params The parameters of the key.
 * @param {boolean} (optional) useSync If true then return a SyncPromise which
 * is already fulfilled. If omitted or false, this may return a SyncPromise or
 * an async Promise.
 * @return {Promise|SyncPromise} A promise that fulfills when the pair is
 * generated.
 */
PrivateKeyStorage.prototype.generateKeyPairPromise = function
  (keyName, params, useSync)
{
  return SyncPromise.reject(new Error
    ("PrivateKeyStorage.generateKeyPairPromise is not implemented"));
};

/**
 * Generate a pair of asymmetric keys.
 * @param {Name} keyName The name of the key pair.
 * @param {KeyParams} params The parameters of the key.
 * @throws Error If generateKeyPairPromise doesn't return a SyncPromise which
 * is already fulfilled.
 */
PrivateKeyStorage.prototype.generateKeyPair = function(keyName, params)
{
  SyncPromise.getValue(this.generateKeyPairPromise(keyName, params, true));
};

/**
 * Delete a pair of asymmetric keys. If the key doesn't exist, do nothing.
 * @param {Name} keyName The name of the key pair.
 * @param {boolean} useSync (optional) If true then return a SyncPromise which
 * is already fulfilled. If omitted or false, this may return a SyncPromise or
 * an async Promise.
 * @return {Promise|SyncPromise} A promise that fulfills when the key pair is
 * deleted.
 */
PrivateKeyStorage.prototype.deleteKeyPairPromise = function(keyName, useSync)
{
  return SyncPromise.reject(new Error
    ("PrivateKeyStorage.deleteKeyPairPromise is not implemented"));
};

/**
 * Delete a pair of asymmetric keys. If the key doesn't exist, do nothing.
 * @param {Name} keyName The name of the key pair.
 * @throws Error If deleteKeyPairPromise doesn't return a SyncPromise which
 * is already fulfilled.
 */
PrivateKeyStorage.prototype.deleteKeyPair = function(keyName)
{
  SyncPromise.getValue(this.deleteKeyPairPromise(keyName, true));
};

/**
 * Get the public key
 * @param {Name} keyName The name of public key.
 * @param {boolean} useSync (optional) If true then return a SyncPromise which
 * is already fulfilled. If omitted or false, this may return a SyncPromise or
 * an async Promise.
 * @return {Promise|SyncPromise} A promise that returns the PublicKey.
 */
PrivateKeyStorage.prototype.getPublicKeyPromise = function(keyName, useSync)
{
  return SyncPromise.reject(new Error
    ("PrivateKeyStorage.getPublicKeyPromise is not implemented"));
};

/**
 * Get the public key
 * @param {Name} keyName The name of public key.
 * @return {PublicKey} The public key.
 * @throws Error If getPublicKeyPromise doesn't return a SyncPromise which
 * is already fulfilled.
 */
PrivateKeyStorage.prototype.getPublicKey = function(keyName)
{
  return SyncPromise.getValue(this.getPublicKeyPromise(keyName, true));
};

/**
 * Fetch the private key for keyName and sign the data to produce a signature Blob.
 * @param {Buffer} data Pointer to the input byte array.
 * @param {Name} keyName The name of the signing key.
 * @param {number} digestAlgorithm (optional) The digest algorithm from
 * DigestAlgorithm, such as DigestAlgorithm.SHA256. If omitted, use
 * DigestAlgorithm.SHA256.
 * @param {boolean} useSync (optional) If true then return a SyncPromise which
 * is already fulfilled. If omitted or false, this may return a SyncPromise or
 * an async Promise.
 * @return {Promise|SyncPromise} A promise that returns the signature Blob.
 */
PrivateKeyStorage.prototype.signPromise = function
  (data, keyName, digestAlgorithm, useSync)
{
  return SyncPromise.reject(new Error("PrivateKeyStorage.sign is not implemented"));
};

/**
 * Fetch the private key for keyName and sign the data to produce a signature Blob.
 * @param {Buffer} data Pointer to the input byte array.
 * @param {Name} keyName The name of the signing key.
 * @param {number} digestAlgorithm (optional) The digest algorithm from
 * DigestAlgorithm, such as DigestAlgorithm.SHA256. If omitted, use
 * DigestAlgorithm.SHA256.
 * @return {Blob} The signature Blob.
 * @throws Error If signPromise doesn't return a SyncPromise which is already
 * fulfilled.
 */
PrivateKeyStorage.prototype.sign = function(data, keyName, digestAlgorithm)
{
  return SyncPromise.getValue
    (this.signPromise(data, keyName, digestAlgorithm, true));
};

/**
 * Decrypt data.
 * @param {Name} keyName The name of the decrypting key.
 * @param {Buffer} data The byte to be decrypted.
 * @param {boolean} isSymmetric (optional) If true symmetric encryption is used,
 * otherwise asymmetric encryption is used. If omitted, use asymmetric
 * encryption.
 * @return {Blob} The decrypted data.
 */
PrivateKeyStorage.prototype.decrypt = function(keyName, data, isSymmetric)
{
  throw new Error("PrivateKeyStorage.decrypt is not implemented");
};

/**
 * Encrypt data.
 * @param {Name} keyName The name of the encrypting key.
 * @param {Buffer} data The byte to be encrypted.
 * @param {boolean} isSymmetric (optional) If true symmetric encryption is used,
 * otherwise asymmetric encryption is used. If omitted, use asymmetric
 * encryption.
 * @return {Blob} The encrypted data.
 */
PrivateKeyStorage.prototype.encrypt = function(keyName, data, isSymmetric)
{
  throw new Error("PrivateKeyStorage.encrypt is not implemented");
};

/**
 * @brief Generate a symmetric key.
 * @param {Name} keyName The name of the key.
 * @param {KeyParams} params The parameters of the key.
 */
PrivateKeyStorage.prototype.generateKey = function(keyName, params)
{
  throw new Error("PrivateKeyStorage.generateKey is not implemented");
};

/**
 * Check if a particular key exists.
 * @param {Name} keyName The name of the key.
 * @param {number} keyClass The class of the key, e.g. KeyClass.PUBLIC,
 * KeyClass.PRIVATE, or KeyClass.SYMMETRIC.
 * @param {boolean} useSync (optional) If true then return a SyncPromise which
 * is already fulfilled. If omitted or false, this may return a SyncPromise or
 * an async Promise.
 * @return {Promise|SyncPromise} A promise which returns true if the key exists.
 */
PrivateKeyStorage.prototype.doesKeyExistPromise = function
  (keyName, keyClass, useSync)
{
  return SyncPromise.reject(new Error
    ("PrivateKeyStorage.doesKeyExist is not implemented"));
};

/**
 * Check if a particular key exists.
 * @param {Name} keyName The name of the key.
 * @param {number} keyClass The class of the key, e.g. KeyClass.PUBLIC,
 * KeyClass.PRIVATE, or KeyClass.SYMMETRIC.
 * @return {boolean} True if the key exists.
 * @throws Error If doesKeyExistPromise doesn't return a SyncPromise which
 * is already fulfilled.
 */
PrivateKeyStorage.prototype.doesKeyExist = function(keyName, keyClass)
{
  return SyncPromise.getValue(this.doesKeyExistPromise(keyName, keyClass, true));
};

/**
 * Encode the private key to a PKCS #8 private key. We do this explicitly here
 * to avoid linking to extra OpenSSL libraries.
 * @param {Buffer} privateKeyDer The input private key DER.
 * @param {OID} oid The OID of the privateKey.
 * @param {DerNode} parameters The DerNode of the parameters for the OID.
 * @return {Blob} The PKCS #8 private key DER.
 */
PrivateKeyStorage.encodePkcs8PrivateKey = function
  (privateKeyDer, oid, parameters)
{
  var algorithmIdentifier = new DerNode.DerSequence();
  algorithmIdentifier.addChild(new DerNode.DerOid(oid));
  algorithmIdentifier.addChild(parameters);

  var result = new DerNode.DerSequence();
  result.addChild(new DerNode.DerInteger(0));
  result.addChild(algorithmIdentifier);
  result.addChild(new DerNode.DerOctetString(privateKeyDer));

  return result.encode();
};

/**
 * Encode the RSAKey private key as a PKCS #1 private key.
 * @param {RSAKey} rsaKey The RSAKey private key.
 * @return {Blob} The PKCS #1 private key DER.
 */
PrivateKeyStorage.encodePkcs1PrivateKeyFromRSAKey = function(rsaKey)
{
  // Imitate KJUR getEncryptedPKCS5PEMFromRSAKey.
  var result = new DerNode.DerSequence();

  result.addChild(new DerNode.DerInteger(0));
  result.addChild(new DerNode.DerInteger(PrivateKeyStorage.bigIntegerToBuffer(rsaKey.n)));
  result.addChild(new DerNode.DerInteger(rsaKey.e));
  result.addChild(new DerNode.DerInteger(PrivateKeyStorage.bigIntegerToBuffer(rsaKey.d)));
  result.addChild(new DerNode.DerInteger(PrivateKeyStorage.bigIntegerToBuffer(rsaKey.p)));
  result.addChild(new DerNode.DerInteger(PrivateKeyStorage.bigIntegerToBuffer(rsaKey.q)));
  result.addChild(new DerNode.DerInteger(PrivateKeyStorage.bigIntegerToBuffer(rsaKey.dmp1)));
  result.addChild(new DerNode.DerInteger(PrivateKeyStorage.bigIntegerToBuffer(rsaKey.dmq1)));
  result.addChild(new DerNode.DerInteger(PrivateKeyStorage.bigIntegerToBuffer(rsaKey.coeff)));

  return result.encode();
};

/**
 * Encode the public key values in the RSAKey private key as a
 * SubjectPublicKeyInfo.
 * @param {RSAKey} rsaKey The RSAKey private key with the public key values.
 * @return {Blob} The SubjectPublicKeyInfo DER.
 */
PrivateKeyStorage.encodePublicKeyFromRSAKey = function(rsaKey)
{
  var rsaPublicKey = new DerNode.DerSequence();

  rsaPublicKey.addChild(new DerNode.DerInteger(PrivateKeyStorage.bigIntegerToBuffer(rsaKey.n)));
  rsaPublicKey.addChild(new DerNode.DerInteger(rsaKey.e));

  var algorithmIdentifier = new DerNode.DerSequence();
  algorithmIdentifier.addChild
    (new DerNode.DerOid(new OID(PrivateKeyStorage.RSA_ENCRYPTION_OID)));
  algorithmIdentifier.addChild(new DerNode.DerNull());

  var result = new DerNode.DerSequence();

  result.addChild(algorithmIdentifier);
  result.addChild(new DerNode.DerBitString(rsaPublicKey.encode().buf(), 0));

  return result.encode();
};

/**
 * Convert a BigInteger to a Buffer.
 * @param {BigInteger} bigInteger The BigInteger.
 * @return {Buffer} The Buffer.
 */
PrivateKeyStorage.bigIntegerToBuffer = function(bigInteger)
{
  // Imitate KJUR.asn1.ASN1Util.bigIntToMinTwosComplementsHex.
  var hex = bigInteger.toString(16);
  if (hex.substr(0, 1) == "-")
    throw new Error
      ("PrivateKeyStorage.bigIntegerToBuffer: Negative integers are not currently supported");

  if (hex.length % 2 == 1)
    // Odd number of characters.
    hex = "0" + hex;
  else {
    if (! hex.match(/^[0-7]/))
      // The first byte is >= 0x80, so prepend a zero to keep it positive.
      hex = "00" + hex;
  }

  return new Buffer(hex, 'hex');
};

PrivateKeyStorage.RSA_ENCRYPTION_OID = "1.2.840.113549.1.1.1";
PrivateKeyStorage.EC_ENCRYPTION_OID = "1.2.840.10045.2.1";