Source: security/identity/memory-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.
 */

// Use capitalized Crypto to not clash with the browser's crypto.subtle.
/** @ignore */
var Crypto = require('../../crypto.js'); /** @ignore */
var Blob = require('../../util/blob.js').Blob; /** @ignore */
var SecurityException = require('../security-exception.js').SecurityException; /** @ignore */
var PublicKey = require('../certificate/public-key.js').PublicKey; /** @ignore */
var KeyClass = require('../security-types.js').KeyClass; /** @ignore */
var KeyType = require('../security-types').KeyType; /** @ignore */
var DigestAlgorithm = require('../security-types.js').DigestAlgorithm; /** @ignore */
var DataUtils = require('../../encoding/data-utils.js').DataUtils; /** @ignore */
var PrivateKeyStorage = require('./private-key-storage.js').PrivateKeyStorage; /** @ignore */
var DerNode = require('../../encoding/der/der-node.js').DerNode; /** @ignore */
var OID = require('../../encoding/oid.js').OID; /** @ignore */
var SyncPromise = require('../../util/sync-promise.js').SyncPromise; /** @ignore */
var UseSubtleCrypto = require('../../use-subtle-crypto-node.js').UseSubtleCrypto; /** @ignore */
var rsaKeygen = null;
try {
  // This should be installed with: sudo npm install rsa-keygen
  rsaKeygen = require('rsa-keygen');
}
catch (e) {}

/**
 * MemoryPrivateKeyStorage class extends PrivateKeyStorage to implement private
 * key storage in memory.
 * @constructor
 */
var MemoryPrivateKeyStorage = function MemoryPrivateKeyStorage()
{
  // Call the base constructor.
  PrivateKeyStorage.call(this);

  // The key is the keyName.toUri(). The value is security.certificate.PublicKey.
  this.publicKeyStore = {};
  // The key is the keyName.toUri(). The value is the object
  //  {keyType,     // number from KeyType
  //   privateKey   // The PEM-encoded private key.
  //  }.
  this.privateKeyStore = {};
};

MemoryPrivateKeyStorage.prototype = new PrivateKeyStorage();
MemoryPrivateKeyStorage.prototype.name = "MemoryPrivateKeyStorage";

exports.MemoryPrivateKeyStorage = MemoryPrivateKeyStorage;

/**
 * Set the public key for the keyName.
 * @param {Name} keyName The key name.
 * @param {number} keyType The KeyType, such as KeyType.RSA.
 * @param {Buffer} publicKeyDer The public key DER byte array.
 */
MemoryPrivateKeyStorage.prototype.setPublicKeyForKeyName = function
  (keyName, keyType, publicKeyDer)
{
  this.publicKeyStore[keyName.toUri()] = new PublicKey
    (new Blob(publicKeyDer, true));
};

/**
 * Set the private key for the keyName.
 * @param {Name} keyName The key name.
 * @param {number} keyType The KeyType, such as KeyType.RSA.
 * @param {Buffer} privateKeyDer The private key DER byte array.
 */
MemoryPrivateKeyStorage.prototype.setPrivateKeyForKeyName = function
  (keyName, keyType, privateKeyDer)
{
  // Encode the DER as PEM.
  var keyBase64 = privateKeyDer.toString('base64');
  var keyPem = "-----BEGIN RSA PRIVATE KEY-----\n";
  for (var i = 0; i < keyBase64.length; i += 64)
    keyPem += (keyBase64.substr(i, 64) + "\n");
  keyPem += "-----END RSA PRIVATE KEY-----";

  this.privateKeyStore[keyName.toUri()] =
    { keyType: keyType, privateKey: keyPem };
};

/**
 * Set the public and private key for the keyName.
 * @param {Name} keyName The key name.
 * @param {number} keyType The KeyType, such as KeyType.RSA.
 * @param {Buffer} publicKeyDer The public key DER byte array.
 * @param {Buffer} privateKeyDer The private key DER byte array.
 */
MemoryPrivateKeyStorage.prototype.setKeyPairForKeyName = function
  (keyName, keyType, publicKeyDer, privateKeyDer)
{
  this.setPublicKeyForKeyName(keyName, keyType, publicKeyDer);
  this.setPrivateKeyForKeyName(keyName, keyType, privateKeyDer);
};

/**
 * 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} useSync (optional) If true then use blocking crypto and
 * return a SyncPromise which is already fulfilled. If omitted or false, if
 * possible use crypto.subtle and return an async Promise, otherwise use
 * blocking crypto and return a SyncPromise.
 * @return {Promise|SyncPromise} A promise that fulfills when the pair is
 * generated.
 */
MemoryPrivateKeyStorage.prototype.generateKeyPairPromise = function
  (keyName, params, useSync)
{
  if (this.doesKeyExist(keyName, KeyClass.PUBLIC))
    return SyncPromise.reject(new SecurityException(new Error
      ("Public key already exists")));
  if (this.doesKeyExist(keyName, KeyClass.PRIVATE))
    return SyncPromise.reject(new SecurityException(new Error
      ("Private key already exists")));

  var thisStore = this;

  if (UseSubtleCrypto() && !useSync) {
    if (params.getKeyType() === KeyType.RSA) {
      var privateKey = null;
      var publicKeyDer = null;

      return crypto.subtle.generateKey
        ({ name: "RSASSA-PKCS1-v1_5", modulusLength: params.getKeySize(),
           publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
           hash: {name: "SHA-256"} },
         true, ["sign", "verify"])
      .then(function(key) {
        privateKey = key.privateKey;

        // Export the public key to DER.
        return crypto.subtle.exportKey("spki", key.publicKey);
      })
      .then(function(exportedPublicKey) {
        publicKeyDer = new Blob(new Uint8Array(exportedPublicKey), false).buf();

        // Export the private key to DER.
        return crypto.subtle.exportKey("pkcs8", privateKey);
      })
      .then(function(pkcs8Der) {
        // Crypto.subtle exports the private key as PKCS #8. Decode it to find
        // the inner private key DER.
        var parsedNode = DerNode.parse
          (new Blob(new Uint8Array(pkcs8Der), false).buf());
        // Get the value of the 3rd child which is the octet string.
        var privateKeyDer = parsedNode.getChildren()[2].toVal();

        // Save the key pair.
        thisStore.setKeyPairForKeyName
          (keyName, params.getKeyType(), publicKeyDer, privateKeyDer.buf());

        // sign will use subtleKey directly.
        thisStore.privateKeyStore[keyName.toUri()].subtleKey = privateKey;

        return Promise.resolve();
      });
    }
    else
      return SyncPromise.reject(new SecurityException(new Error
        ("Only RSA key generation currently supported")));
  }
  else {
    return SyncPromise.resolve()
    .then(function() {
      if (typeof RSAKey !== 'undefined') {
        // Assume we are in the browser.
        if (params.getKeyType() === KeyType.RSA) {
          var rsaKey = new RSAKey();
          rsaKey.generate(params.getKeySize(), '010001');
          thisStore.setKeyPairForKeyName
            (keyName, params.getKeyType(),
             PrivateKeyStorage.encodePublicKeyFromRSAKey(rsaKey).buf(),
             PrivateKeyStorage.encodePkcs1PrivateKeyFromRSAKey(rsaKey).buf());
        }
        else
          return SyncPromise.reject(new SecurityException(new Error
            ("Only RSA key generation currently supported")));
      }
      else {
        // Assume we are in Node.js.
        var publicKeyDer;
        var privateKeyPem;

        if (params.getKeyType() === KeyType.RSA) {
          if (!rsaKeygen)
            return SyncPromise.reject(new SecurityException(new Error
              ("Need to install rsa-keygen: sudo npm install rsa-keygen")));

          var keyPair = rsaKeygen.generate(params.getKeySize());

          // Get the public key DER from the PEM string.
          var publicKeyBase64 = keyPair.public_key.toString().replace
            ("-----BEGIN PUBLIC KEY-----", "").replace
            ("-----END PUBLIC KEY-----", "");
          publicKeyDer = new Buffer(publicKeyBase64, 'base64');

          privateKeyPem = keyPair.private_key.toString();
        }
        else
          return SyncPromise.reject(new SecurityException(new Error
            ("Only RSA key generation currently supported")));

        thisStore.setPublicKeyForKeyName(keyName, params.getKeyType(), publicKeyDer);
        thisStore.privateKeyStore[keyName.toUri()] =
          { keyType: params.getKeyType(), privateKey: privateKeyPem };
      }

      return SyncPromise.resolve();
    });
  }
};

/**
 * Delete a pair of asymmetric keys. If the key doesn't exist, do nothing.
 * @param {Name} keyName The name of the key pair.
 * @return {SyncPromise} A promise that fulfills when the key pair is deleted.
 */
MemoryPrivateKeyStorage.prototype.deleteKeyPairPromise = function(keyName)
{
  var keyUri = keyName.toUri();

  delete this.publicKeyStore[keyUri];
  delete this.privateKeyStore[keyUri];

  return SyncPromise.resolve();
};

/**
 * Get the public key
 * @param {Name} keyName The name of public key.
 * @return {SyncPromise} A promise that returns the PublicKey.
 */
MemoryPrivateKeyStorage.prototype.getPublicKeyPromise = function(keyName)
{
  var keyUri = keyName.toUri();
  var publicKey = this.publicKeyStore[keyUri];
  if (publicKey === undefined)
    return SyncPromise.reject(new SecurityException(new Error
      ("MemoryPrivateKeyStorage: Cannot find public key " + keyName.toUri())));

  return SyncPromise.resolve(publicKey);
};

/**
 * 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 use blocking crypto and
 * return a SyncPromise which is already fulfilled. If omitted or false, if
 * possible use crypto.subtle and return an async Promise, otherwise use
 * blocking crypto and return a SyncPromise.
 * @return {Promise|SyncPromise} A promise that returns the signature Blob.
 */
MemoryPrivateKeyStorage.prototype.signPromise = function
  (data, keyName, digestAlgorithm, useSync)
{
  useSync = (typeof digestAlgorithm === "boolean") ? digestAlgorithm : useSync;
  digestAlgorithm = (typeof digestAlgorithm === "boolean" || !digestAlgorithm) ? DigestAlgorithm.SHA256 : digestAlgorithm;

  if (digestAlgorithm != DigestAlgorithm.SHA256)
    return SyncPromise.reject(new SecurityException(new Error
      ("MemoryPrivateKeyStorage.sign: Unsupported digest algorithm")));

  // Find the private key.
  var keyUri = keyName.toUri();
  var privateKey = this.privateKeyStore[keyUri];
  if (privateKey === undefined)
    return SyncPromise.reject(new SecurityException(new Error
      ("MemoryPrivateKeyStorage: Cannot find private key " + keyUri)));

  if (UseSubtleCrypto() && !useSync){
    var algo = {name:"RSASSA-PKCS1-v1_5",hash:{name:"SHA-256"}};

    if (!privateKey.subtleKey){
      //this is the first time in the session that we're using crypto subtle with this key
      //so we have to convert to pkcs8 and import it.
      //assigning it to privateKey.subtleKey means we only have to do this once per session,
      //giving us a small, but not insignificant, performance boost.
      var privateDER = DataUtils.privateKeyPemToDer(privateKey.privateKey);
      var pkcs8 = PrivateKeyStorage.encodePkcs8PrivateKey
        (privateDER, new OID(PrivateKeyStorage.RSA_ENCRYPTION_OID),
         new DerNode.DerNull()).buf();

      var promise = crypto.subtle.importKey("pkcs8", pkcs8.buffer, algo, true, ["sign"]).then(function(subtleKey){
        //cache the crypto.subtle key object
        privateKey.subtleKey = subtleKey;
        return crypto.subtle.sign(algo, subtleKey, data);
      });
    } else {
      // The crypto.subtle key has been cached on a previous sign or from keygen.
      var promise = crypto.subtle.sign(algo, privateKey.subtleKey, data);
    }

    return promise.then(function(signature){
      var result = new Blob(new Uint8Array(signature), true);
      return Promise.resolve(result);
    });
  } else {
    var rsa = Crypto.createSign('RSA-SHA256');
    rsa.update(data);

    var signature = new Buffer
      (DataUtils.toNumbersIfString(rsa.sign(privateKey.privateKey)));
    var result = new Blob(signature, false);

    return SyncPromise.resolve(result);
  }
};

/**
 * 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 {SyncPromise} A promise which returns true if the key exists.
 */
MemoryPrivateKeyStorage.prototype.doesKeyExistPromise = function
  (keyName, keyClass)
{
  var keyUri = keyName.toUri();
  var result = false;
  if (keyClass == KeyClass.PUBLIC)
    result = this.publicKeyStore[keyUri] !== undefined;
  else if (keyClass == KeyClass.PRIVATE)
    result = this.privateKeyStore[keyUri] !== undefined;

  return SyncPromise.resolve(result);
};