Source: security/certificate/public-key.js

/**
 * Copyright (C) 2014-2018 Regents of the University of California.
 * @author: Jeff Thompson <[email protected]>
 * From ndn-cxx security by Yingdi Yu <[email protected]>.
 *
 * 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 constants = require('constants'); /** @ignore */
var Crypto = require('../../crypto.js'); /** @ignore */
var Blob = require('../../util/blob.js').Blob; /** @ignore */
var DerNode = require('../../encoding/der/der-node.js').DerNode; /** @ignore */
var SecurityException = require('../security-exception.js').SecurityException; /** @ignore */
var UnrecognizedKeyFormatException = require('../security-exception.js').UnrecognizedKeyFormatException; /** @ignore */
var KeyType = require('../security-types.js').KeyType; /** @ignore */
var EncryptAlgorithmType = require('../../encrypt/algo/encrypt-params.js').EncryptAlgorithmType; /** @ignore */
var SyncPromise = require('../../util/sync-promise.js').SyncPromise; /** @ignore */
var UseSubtleCrypto = require('../../use-subtle-crypto-node.js').UseSubtleCrypto; /** @ignore */
var DigestAlgorithm = require('../security-types.js').DigestAlgorithm;

/**
 * Create a new PublicKey by decoding the keyDer. Set the key type from the
 * decoding.
 * @param {Blob} keyDer The blob of the SubjectPublicKeyInfo DER.
 * @throws UnrecognizedKeyFormatException if can't decode the key DER.
 * @constructor
 */
var PublicKey = function PublicKey(keyDer)
{
  if (!keyDer) {
    this.keyDer = new Blob();
    this.keyType = null;
    return;
  }

  this.keyDer = keyDer;

  // Get the public key OID.
  var oidString = null;
  try {
    var parsedNode = DerNode.parse(keyDer.buf(), 0);
    var rootChildren = parsedNode.getChildren();
    var algorithmIdChildren = DerNode.getSequence(rootChildren, 0).getChildren();
    oidString = algorithmIdChildren[0].toVal();
  }
  catch (ex) {
    throw new UnrecognizedKeyFormatException(new Error
      ("PublicKey.decodeKeyType: Error decoding the public key: " + ex.message));
  }

  // Verify that the we can decode.
  if (oidString == PublicKey.RSA_ENCRYPTION_OID) {
    this.keyType = KeyType.RSA;
    // TODO: Check RSA decoding.
  }
  else if (oidString == PublicKey.EC_ENCRYPTION_OID) {
    this.keyType = KeyType.EC;
    // TODO: Check EC decoding.
  }
};

exports.PublicKey = PublicKey;

/**
 * Encode the public key into DER.
 * @return {DerNode} The encoded DER syntax tree.
 */
PublicKey.prototype.toDer = function()
{
  return DerNode.parse(this.keyDer.buf());
};

/**
 * Get the key type.
 * @return {number} The key type as an int from KeyType.
 */
PublicKey.prototype.getKeyType = function()
{
  return this.keyType;
};

/**
 * Get the digest of the public key.
 * @param {number} digestAlgorithm (optional) The integer from DigestAlgorithm,
 * such as DigestAlgorithm.SHA256. If omitted, use DigestAlgorithm.SHA256 .
 * @return {Blob} The digest value.
 */
PublicKey.prototype.getDigest = function(digestAlgorithm)
{
  if (digestAlgorithm == undefined)
    digestAlgorithm = DigestAlgorithm.SHA256;

  if (digestAlgorithm == DigestAlgorithm.SHA256) {
    var hash = Crypto.createHash('sha256');
    hash.update(this.keyDer.buf());
    return new Blob(hash.digest(), false);
  }
  else
    throw new SecurityException(new Error("Wrong format!"));
};

/**
 * Get the raw bytes of the public key in DER format.
 * @return {Blob} The public key DER.
 */
PublicKey.prototype.getKeyDer = function()
{
  return this.keyDer;
};

/**
 * Encrypt the plainData using the keyBits according the encrypt algorithm type.
 * @param {Blob|Buffer} plainData The data to encrypt.
 * @param {number} algorithmType The algorithm type from the
 * EncryptAlgorithmType enum, e.g., RsaOaep.
 * @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 the encrypted Blob.
 */
PublicKey.prototype.encryptPromise = function(plainData, algorithmType, useSync)
{
  if (typeof plainData === 'object' && plainData instanceof Blob)
    plainData = plainData.buf();

  if (UseSubtleCrypto() && !useSync &&
      // Crypto.subtle doesn't implement PKCS1 padding.
      algorithmType != EncryptAlgorithmType.RsaPkcs) {
    if (algorithmType == EncryptAlgorithmType.RsaOaep) {
      if (this.keyType != KeyType.RSA)
        return Promise.reject(new Error("The key type must be RSA"));

      return crypto.subtle.importKey
        ("spki", this.keyDer.buf(), { name: "RSA-OAEP", hash: {name: "SHA-1"} },
         false, ["encrypt"])
      .then(function(publicKey) {
        return crypto.subtle.encrypt({ name: "RSA-OAEP" }, publicKey, plainData);
      })
      .then(function(result) {
        return Promise.resolve(new Blob(new Uint8Array(result), false));
      });
    }
    else
      return Promise.reject(new Error("unsupported padding scheme"));
  }
  else {
    // Encode the key DER as a PEM public key as needed by Crypto.
    var keyBase64 = this.keyDer.buf().toString('base64');
    var keyPem = "-----BEGIN PUBLIC KEY-----\n";
    for (var i = 0; i < keyBase64.length; i += 64)
      keyPem += (keyBase64.substr(i, 64) + "\n");
    keyPem += "-----END PUBLIC KEY-----";

    var padding;
    if (algorithmType == EncryptAlgorithmType.RsaPkcs) {
      if (this.keyType != KeyType.RSA)
        return SyncPromise.reject(new Error("The key type must be RSA"));

      padding = constants.RSA_PKCS1_PADDING;
    }
    else if (algorithmType == EncryptAlgorithmType.RsaOaep) {
      if (this.keyType != KeyType.RSA)
        return SyncPromise.reject(new Error("The key type must be RSA"));

      padding = constants.RSA_PKCS1_OAEP_PADDING;
    }
    else
      return SyncPromise.reject(new Error("unsupported padding scheme"));

    try {
      // In Node.js, publicEncrypt requires version v0.12.
      return SyncPromise.resolve(new Blob
        (Crypto.publicEncrypt({ key: keyPem, padding: padding }, plainData),
         false));
    } catch (err) {
      return SyncPromise.reject(err);
    }
  }
};

/**
 * Encrypt the plainData using the keyBits according the encrypt algorithm type.
 * @param {Blob|Buffer} plainData The data to encrypt.
 * @param {number} algorithmType The algorithm type from the
 * EncryptAlgorithmType enum, e.g., RsaOaep.
 * @return {Blob} The encrypted data.
 * @throws Error If encryptPromise doesn't return a SyncPromise which is
 * already fulfilled.
 */
PublicKey.prototype.encrypt = function(plainData, algorithmType)
{
  return SyncPromise.getValue(this.encryptPromise
    (plainData, algorithmType, true));
};

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