UNPKG

ndn-js

Version:

A JavaScript client library for Named Data Networking

358 lines (332 loc) 13.8 kB
/** * Copyright (C) 2017-2019 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/ndn-cxx/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 Blob = require('../../util/blob.js').Blob; /** @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); }); }; /** * Get the encoded private key with name keyName in PKCS #8 format, possibly * encrypted. This should only be called by KeyChain. * @param {Name} keyName The name of the key in the TPM. * @param {Buffer} password The password for encrypting the private key, which * should have characters in the range of 1 to 127. If the password is supplied, * use it to return a PKCS #8 EncryptedPrivateKeyInfo. If the password is null, * return 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 returns the private key encoded * in PKCS #8 format, or a promise rejected with TpmBackEnd.Error if the key * does not exist or if the key cannot be exported, e.g., insufficient privileges. */ Tpm.prototype.exportPrivateKeyPromise_ = function(keyName, password, useSync) { var thisTpm = this; return this.initializePromise_(useSync) .then(function() { return thisTpm.backEnd_.exportKeyPromise(keyName, password, useSync); }); }; /** * 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, which * should have characters in the range of 1 to 127. 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); };