UNPKG

ndn-js

Version:

A JavaScript client library for Named Data Networking

249 lines (221 loc) 9.15 kB
/** * Copyright (C) 2015-2019 Regents of the University of California. * @author: Jeff Thompson <jefft0@remap.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. var Crypto = require('../../crypto.js'); // Don't require other modules since this is meant for the browser, not Node.js. /** * IndexedDbPrivateKeyStorage extends PrivateKeyStorage to implement private key * storage using the browser's IndexedDB service. * @constructor */ var IndexedDbPrivateKeyStorage = function IndexedDbPrivateKeyStorage() { PrivateKeyStorage.call(this); this.database = new Dexie("ndnsec-tpm"); this.database.version(1).stores({ // "nameHash" is transformName(keyName) // string // "encoding" is the public key DER // Uint8Array publicKey: "nameHash", // "nameHash" is transformName(keyName) // string // "encoding" is the PKCS 8 private key DER // Uint8Array privateKey: "nameHash" }); this.database.open(); }; IndexedDbPrivateKeyStorage.prototype = new PrivateKeyStorage(); IndexedDbPrivateKeyStorage.prototype.name = "IndexedDbPrivateKeyStorage"; /** * 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 return a rejected promise * since this only supports async code. * @return {Promise} A promise that fulfills when the pair is generated. */ IndexedDbPrivateKeyStorage.prototype.generateKeyPairPromise = function (keyName, params, useSync) { if (useSync) return Promise.reject(new SecurityException(new Error ("IndexedDbPrivateKeyStorage.generateKeyPairPromise is only supported for async"))); var thisStorage = this; return thisStorage.doesKeyExistPromise(keyName, KeyClass.PUBLIC) .then(function(exists) { if (exists) throw new Error("Public key already exists"); return thisStorage.doesKeyExistPromise(keyName, KeyClass.PRIVATE); }) .then(function(exists) { if (exists) throw new Error("Private key already exists"); 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 Uint8Array(exportedPublicKey); // Export the private key to DER. return crypto.subtle.exportKey("pkcs8", privateKey); }) .then(function(pkcs8Der) { // Save the key pair return thisStorage.database.transaction ("rw", thisStorage.database.privateKey, thisStorage.database.publicKey, function () { thisStorage.database.publicKey.put ({nameHash: IndexedDbPrivateKeyStorage.transformName(keyName), encoding: publicKeyDer}); thisStorage.database.privateKey.put ({nameHash: IndexedDbPrivateKeyStorage.transformName(keyName), encoding: new Uint8Array(pkcs8Der)}); }); }); } else throw new Error("Only RSA key generation currently supported"); }); }; /** * 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 rejected promise * since this only supports async code. * @return {Promise} A promise that fulfills when the key pair is deleted. */ IndexedDbPrivateKeyStorage.prototype.deleteKeyPairPromise = function (keyName, useSync) { if (useSync) return Promise.reject(new SecurityException(new Error ("IndexedDbPrivateKeyStorage.deleteKeyPairPromise is only supported for async"))); var thisStorage = this; // delete does nothing if the key doesn't exist. return this.database.publicKey.delete (IndexedDbPrivateKeyStorage.transformName(keyName)) .then(function() { return thisStorage.database.privateKey.delete (IndexedDbPrivateKeyStorage.transformName(keyName)); }); }; /** * Get the public key * @param {Name} keyName The name of public key. * @param {boolean} useSync (optional) If true then return a rejected promise * since this only supports async code. * @return {Promise} A promise that returns the PublicKey. */ IndexedDbPrivateKeyStorage.prototype.getPublicKeyPromise = function (keyName, useSync) { if (useSync) return Promise.reject(new SecurityException(new Error ("IndexedDbPrivateKeyStorage.getPublicKeyPromise is only supported for async"))); return this.database.publicKey.get (IndexedDbPrivateKeyStorage.transformName(keyName)) .then(function(publicKeyEntry) { return Promise.resolve(new PublicKey(new Blob(publicKeyEntry.encoding))); }); }; /** * 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 rejected promise * since this only supports async code. * @return {Promise} A promise that returns the signature Blob. */ IndexedDbPrivateKeyStorage.prototype.signPromise = function (data, keyName, digestAlgorithm, useSync) { useSync = (typeof digestAlgorithm === "boolean") ? digestAlgorithm : useSync; digestAlgorithm = (typeof digestAlgorithm === "boolean" || !digestAlgorithm) ? DigestAlgorithm.SHA256 : digestAlgorithm; if (useSync) return Promise.reject(new SecurityException(new Error ("IndexedDbPrivateKeyStorage.signPromise is only supported for async"))); if (digestAlgorithm != DigestAlgorithm.SHA256) return Promise.reject(new SecurityException(new Error ("IndexedDbPrivateKeyStorage.sign: Unsupported digest algorithm"))); // TODO: Support non-RSA keys. var algo = { name: "RSASSA-PKCS1-v1_5", hash: {name: "SHA-256" }}; // Find the private key. return this.database.privateKey.get (IndexedDbPrivateKeyStorage.transformName(keyName)) .then(function(privateKeyEntry) { return crypto.subtle.importKey ("pkcs8", new Blob(privateKeyEntry.encoding).buf(), algo, true, ["sign"]); }) .then(function(privateKey) { return crypto.subtle.sign(algo, privateKey, data); }) .then(function(signature) { return Promise.resolve(new Blob(new Uint8Array(signature), true)); }); }; /** * 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 rejected promise * since this only supports async code. * @return {Promise} A promise which returns true if the key exists. */ IndexedDbPrivateKeyStorage.prototype.doesKeyExistPromise = function (keyName, keyClass, useSync) { if (useSync) return Promise.reject(new SecurityException(new Error ("IndexedDbPrivateKeyStorage.doesKeyExistPromise is only supported for async"))); var table = null; if (keyClass == KeyClass.PUBLIC) table = this.database.publicKey; else if (keyClass == KeyClass.PRIVATE) table = this.database.privateKey; else // Silently say that anything else doesn't exist. return Promise.resolve(false); return table.where("nameHash").equals (IndexedDbPrivateKeyStorage.transformName(keyName)) .count() .then(function(count) { return Promise.resolve(count > 0); }); }; /** * Transform the key name into the base64 encoding of the hash (the same as in * FilePrivateKeyStorage without the file name extension). */ IndexedDbPrivateKeyStorage.transformName = function(keyName) { var hash = Crypto.createHash('sha256'); hash.update(new Buffer(keyName.toUri())); var fileName = hash.digest('base64'); return fileName.replace(/\//g, '%'); };