Source: security/tpm/tpm.js

/**
 * Copyright (C) 2017-2018 Regents of the University of California.
 * @author: Jeff Thompson <jefft0@remap.ucla.edu>
 * @author: From ndn-cxx security https://github.com/named-data/ndn-cxx/blob/master/src/security/tpm/tpm.cpp
 *
 * 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 KeyType = require('../security-types').KeyType; /** @ignore */
var SyncPromise = require('../../util/sync-promise.js').SyncPromise;

/**
 * The TPM (Trusted Platform Module) stores the private portion of a user's
 * cryptography keys. The format and location of stored information is indicated
 * by the TPM locator. The TPM is designed to work with a PIB (Public
 * Information Base) which stores public keys and related information such as
 * certificates.
 *
 * The TPM also provides functionalities of cryptographic transformation, such
 * as signing and decryption.
 *
 * A TPM consists of a unified front-end interface and a backend implementation.
 * The front-end caches the handles of private keys which are provided by the
 * backend implementation.
 *
 * Note: A Tpm instance is created and managed only by the KeyChain. It is
 * returned by the KeyChain getTpm() method, through which it is possible to
 * check for the existence of private keys, get public keys for the private
 * keys, sign, and decrypt the supplied buffers using managed private keys.
 *
 * Create a new TPM instance with the specified location. This constructor
 * should only be called by KeyChain.
 *
 * @param {string} scheme The scheme for the TPM.
 * @param {string} location The location for the TPM.
 * @param {TpmBackEnd} backEnd The TPM back-end implementation.
 * @constructor
 */
var Tpm = function Tpm(scheme, location, backEnd)
{
  // Name URI string => TpmKeyHandle
  // (Use a string because we can't use the Name object as the key in JavaScript.)
  this.keys_ = {};

  this.scheme_ = scheme;
  this.location_ = location;
  this.backEnd_ = backEnd;
  this.initializePib_ = null;
  this.isInitialized_ = false;
};

exports.Tpm = Tpm;

/**
 * Create a Tpm.Error which which represents a semantic error in TPM processing.
 * Call with: throw new Tpm.Error(new Error("message")).
 * @constructor
 * @param {Error} error The exception created with new Error.
 */
Tpm.Error = function TpmError(error)
{
  if (error) {
    error.__proto__ = Tpm.Error.prototype;
    return error;
  }
};

Tpm.Error.prototype = new Error();
Tpm.Error.prototype.name = "TpmError";

Tpm.prototype.getTpmLocator = function()
{
  if (!this.isInitialized_)
    throw new Tpm.Error(new Error("getTpmLocator: The Tpm is not initialized"));

  return this.scheme_ + ":" + this.location_;
};

/**
 * Check if the key with name keyName exists in the TPM.
 * @param {Name} keyName The name of the 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 which returns true if the key exists.
 */
Tpm.prototype.hasKeyPromise = function(keyName, useSync)
{
  var thisTpm = this;
  return this.initializePromise_(useSync)
  .then(function() {
    return thisTpm.backEnd_.hasKeyPromise(keyName, useSync);
  });
};

/**
 * Get the public portion of an asymmetric key pair with name keyName.
 * @param {Name} keyName The name of the 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 which returns the encoded public key
 * Blob (or an isNull Blob if the key does not exist).
 */
Tpm.prototype.getPublicKeyPromise = function(keyName, useSync)
{
  var thisTpm = this;
  return this.initializePromise_(useSync)
  .then(function() {
    return thisTpm.findKeyPromise_(keyName, useSync);
  })
  .then(function(key) {
    if (key == null)
      return SyncPromise.resolve(new Blob());
    else
      return SyncPromise.resolve(key.derivePublicKey());
  });
};

/**
 * Compute a digital signature from the byte buffer using the key with name
 * keyName.
 * @param {Buffer} data The input byte buffer.
 * @param {Name} keyName The name of the key.
 * @param {number} digestAlgorithm The digest algorithm as an int from the
 * DigestAlgorithm enum.
 * @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 signature Blob (or
 * an isNull Blob if the key does not exist), or a promise rejected
 * with TpmBackEnd.Error for an error in signing.
 */
Tpm.prototype.signPromise = function(data, keyName, digestAlgorithm, useSync)
{
  var thisTpm = this;
  return this.initializePromise_(useSync)
  .then(function() {
    return thisTpm.findKeyPromise_(keyName, useSync);
  })
  .then(function(key) {
    if (key == null)
      return SyncPromise.resolve(new Blob());
    else
      return key.signPromise(digestAlgorithm, data, useSync);
  });
};

/**
 * Return the plain text which is decrypted from cipherText using the key with
 * name keyName.
 * @param {Buffer} cipherText The cipher text byte buffer.
 * @param {Name} keyName The name of the 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 which returns the decrypted data Blob
 * (or an isNull Blob if the key does not exist).
 */
Tpm.prototype.decryptPromise = function(cipherText, keyName, useSync)
{
  var thisTpm = this;
  return this.initializePromise_(useSync)
  .then(function() {
    return thisTpm.findKeyPromise_(keyName, useSync);
  })
  .then(function(key) {
    if (key == null)
      return SyncPromise.resolve(new Blob());
    else
      return key.decryptPromise(cipherText, useSync);
  });
};

// TODO: isTerminalModePromise
// TODO: setTerminalModePromise
// TODO: isTpmLockedPromise
// TODO: unlockTpmPromise

/**
 * Create a key for the identityName according to params. The created key is
 * named /<identityName>/[keyId]/KEY . This should only be called by KeyChain.
 * @param {Name} identityName The name if the identity.
 * @param {KeyParams} params The KeyParams for creating the 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 which returns the Name of the created
 * key, or a promise rejected with Tpm.Error if params is invalid or if the key
 * type is unsupported, or a promise rejected with TpmBackEnd.Error if the key
 * already exists or cannot be created.
 */
Tpm.prototype.createKeyPromise_ = function(identityName, params, useSync)
{
  var thisTpm = this;
  return this.initializePromise_(useSync)
  .then(function() {
    if (params.getKeyType() == KeyType.RSA ||
        params.getKeyType() == KeyType.EC) {
      return thisTpm.backEnd_.createKeyPromise(identityName, params, useSync)
      .then(function(keyHandle) {
        var keyName = keyHandle.getKeyName()
        thisTpm.keys_[keyName.toUri()] = keyHandle;
        return SyncPromise.resolve(keyName);
      });
    }
    else
      return SyncPromise.resolve(new Tpm.Error(new Error
        ("createKey: Unsupported key type")));
  });
};

/**
 * Delete the key with name keyName. If the key doesn't exist, do nothing.
 * Note: Continuing to use existing Key handles on a deleted key results in
 * undefined behavior. This should only be called by KeyChain.
 * @param {Name} keyName The name of the 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 which fulfills when finished, or a
 * promise rejected with TpmBackEnd.Error if the deletion fails.
 */
Tpm.prototype.deleteKeyPromise_ = function(keyName, useSync)
{
  var thisTpm = this;
  return this.initializePromise_(useSync)
  .then(function() {
    delete thisTpm.keys_[keyName.toUri()];

    return thisTpm.backEnd_.deleteKeyPromise(keyName, useSync);
  });
};

// TODO: exportPrivateKeyPromise_

/**
 * Import an encoded private key with name keyName in PKCS #8 format, possibly
 * password-encrypted. This should only be called by KeyChain.
 * @param {Name} keyName The name of the key to use in the TPM.
 * @param {Buffer} pkcs8 The input byte buffer. If the password is supplied,
 * this is a PKCS #8 EncryptedPrivateKeyInfo. If the password is none, this is
 * an unencrypted PKCS #8 PrivateKeyInfo.
 * @param {Buffer} password The password for decrypting the private key. If the
 * password is supplied, use it to decrypt the PKCS #8 EncryptedPrivateKeyInfo.
 * If the password is null, import an unencrypted PKCS #8 PrivateKeyInfo.
 * @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 fulfills when finished, or a
 * promise rejected with TpmBackEnd.Error for an error importing the key.
 */
Tpm.prototype.importPrivateKeyPromise_ = function(keyName, pkcs8, password, useSync)
{
  var thisTpm = this;
  return this.initializePromise_(useSync)
  .then(function() {
    return thisTpm.backEnd_.importKeyPromise(keyName, pkcs8, password, useSync);
  });
};

/**
 * Get the TpmKeyHandle with name keyName, using backEnd_.getKeyHandlePromise if
 * it is not already cached in keys_.
 * @param {Name} keyName The name of the key, which is copied.
 * @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 TpmKeyHandle in the
 * keys_ cache, or null if no key exists with name keyName.
 */
Tpm.prototype.findKeyPromise_ = function(keyName, useSync)
{
  var thisTpm = this;
  return this.initializePromise_(useSync)
  .then(function() {
    var keyNameUri = keyName.toUri();
    var handle = thisTpm.keys_[keyNameUri];

    if (handle != undefined)
      return SyncPromise.resolve(handle);

    return thisTpm.backEnd_.getKeyHandlePromise(keyName, useSync)
    .then(function(handle) {
      if (handle != null) {
        thisTpm.keys_[keyNameUri] = handle;
        return SyncPromise.resolve(handle);
      }

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

/**
 * If isInitialized_ is false and initializePib_ is not null (because it was set
 * by the KeyChain constructor), call initializePib_.initializePromise_ which
 * joinly initializes the Pib and Tpm and sets isInitialized_ true. However, if
 * isInitialized_ is already true or initializePib_ is null, do nothing. This
 * must be called by each method before using this object. This is necessary
 * because the constructor (and the KeyChain constructor) cannot perform async
 * operations.
 * @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 fulfills when finished.
 */
Tpm.prototype.initializePromise_ = function(useSync)
{
  if (this.isInitialized_)
    return SyncPromise.resolve();

  if (this.initializePib_ == null) {
    // We don't need to jointly initialize with the Pib.
    this.isInitialized_ = true;
    return SyncPromise.resolve();
  }

   return this.initializePib_.initializePromise_(useSync);
};