Source: security/identity/memory-private-key-storage.js

  1. /**
  2. * Copyright (C) 2014-2016 Regents of the University of California.
  3. * @author: Jeff Thompson <jefft0@remap.ucla.edu>
  4. * From ndn-cxx security by Yingdi Yu <yingdi@cs.ucla.edu>.
  5. *
  6. * This program is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU Lesser General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. * A copy of the GNU Lesser General Public License is in the file COPYING.
  19. */
  20. // Use capitalized Crypto to not clash with the browser's crypto.subtle.
  21. /** @ignore */
  22. var Crypto = require('../../crypto.js'); /** @ignore */
  23. var Blob = require('../../util/blob.js').Blob; /** @ignore */
  24. var SecurityException = require('../security-exception.js').SecurityException; /** @ignore */
  25. var PublicKey = require('../certificate/public-key.js').PublicKey; /** @ignore */
  26. var KeyClass = require('../security-types.js').KeyClass; /** @ignore */
  27. var KeyType = require('../security-types').KeyType; /** @ignore */
  28. var DigestAlgorithm = require('../security-types.js').DigestAlgorithm; /** @ignore */
  29. var DataUtils = require('../../encoding/data-utils.js').DataUtils; /** @ignore */
  30. var PrivateKeyStorage = require('./private-key-storage.js').PrivateKeyStorage; /** @ignore */
  31. var DerNode = require('../../encoding/der/der-node.js').DerNode; /** @ignore */
  32. var OID = require('../../encoding/oid.js').OID; /** @ignore */
  33. var SyncPromise = require('../../util/sync-promise.js').SyncPromise; /** @ignore */
  34. var UseSubtleCrypto = require('../../use-subtle-crypto-node.js').UseSubtleCrypto; /** @ignore */
  35. var rsaKeygen = null;
  36. try {
  37. // This should be installed with: sudo npm install rsa-keygen
  38. rsaKeygen = require('rsa-keygen');
  39. }
  40. catch (e) {}
  41. /**
  42. * MemoryPrivateKeyStorage class extends PrivateKeyStorage to implement private
  43. * key storage in memory.
  44. * @constructor
  45. */
  46. var MemoryPrivateKeyStorage = function MemoryPrivateKeyStorage()
  47. {
  48. // Call the base constructor.
  49. PrivateKeyStorage.call(this);
  50. // The key is the keyName.toUri(). The value is security.certificate.PublicKey.
  51. this.publicKeyStore = {};
  52. // The key is the keyName.toUri(). The value is the object
  53. // {keyType, // number from KeyType
  54. // privateKey // The PEM-encoded private key.
  55. // }.
  56. this.privateKeyStore = {};
  57. };
  58. MemoryPrivateKeyStorage.prototype = new PrivateKeyStorage();
  59. MemoryPrivateKeyStorage.prototype.name = "MemoryPrivateKeyStorage";
  60. exports.MemoryPrivateKeyStorage = MemoryPrivateKeyStorage;
  61. /**
  62. * Set the public key for the keyName.
  63. * @param {Name} keyName The key name.
  64. * @param {number} keyType The KeyType, such as KeyType.RSA.
  65. * @param {Buffer} publicKeyDer The public key DER byte array.
  66. */
  67. MemoryPrivateKeyStorage.prototype.setPublicKeyForKeyName = function
  68. (keyName, keyType, publicKeyDer)
  69. {
  70. this.publicKeyStore[keyName.toUri()] = new PublicKey
  71. (new Blob(publicKeyDer, true));
  72. };
  73. /**
  74. * Set the private key for the keyName.
  75. * @param {Name} keyName The key name.
  76. * @param {number} keyType The KeyType, such as KeyType.RSA.
  77. * @param {Buffer} privateKeyDer The private key DER byte array.
  78. */
  79. MemoryPrivateKeyStorage.prototype.setPrivateKeyForKeyName = function
  80. (keyName, keyType, privateKeyDer)
  81. {
  82. // Encode the DER as PEM.
  83. var keyBase64 = privateKeyDer.toString('base64');
  84. var keyPem = "-----BEGIN RSA PRIVATE KEY-----\n";
  85. for (var i = 0; i < keyBase64.length; i += 64)
  86. keyPem += (keyBase64.substr(i, 64) + "\n");
  87. keyPem += "-----END RSA PRIVATE KEY-----";
  88. this.privateKeyStore[keyName.toUri()] =
  89. { keyType: keyType, privateKey: keyPem };
  90. };
  91. /**
  92. * Set the public and private key for the keyName.
  93. * @param {Name} keyName The key name.
  94. * @param {number} keyType The KeyType, such as KeyType.RSA.
  95. * @param {Buffer} publicKeyDer The public key DER byte array.
  96. * @param {Buffer} privateKeyDer The private key DER byte array.
  97. */
  98. MemoryPrivateKeyStorage.prototype.setKeyPairForKeyName = function
  99. (keyName, keyType, publicKeyDer, privateKeyDer)
  100. {
  101. this.setPublicKeyForKeyName(keyName, keyType, publicKeyDer);
  102. this.setPrivateKeyForKeyName(keyName, keyType, privateKeyDer);
  103. };
  104. /**
  105. * Generate a pair of asymmetric keys.
  106. * @param {Name} keyName The name of the key pair.
  107. * @param {KeyParams} params The parameters of the key.
  108. * @param {boolean} useSync (optional) If true then use blocking crypto and
  109. * return a SyncPromise which is already fulfilled. If omitted or false, if
  110. * possible use crypto.subtle and return an async Promise, otherwise use
  111. * blocking crypto and return a SyncPromise.
  112. * @return {Promise|SyncPromise} A promise that fulfills when the pair is
  113. * generated.
  114. */
  115. MemoryPrivateKeyStorage.prototype.generateKeyPairPromise = function
  116. (keyName, params, useSync)
  117. {
  118. if (this.doesKeyExist(keyName, KeyClass.PUBLIC))
  119. return SyncPromise.reject(new SecurityException(new Error
  120. ("Public key already exists")));
  121. if (this.doesKeyExist(keyName, KeyClass.PRIVATE))
  122. return SyncPromise.reject(new SecurityException(new Error
  123. ("Private key already exists")));
  124. var thisStore = this;
  125. if (UseSubtleCrypto() && !useSync) {
  126. if (params.getKeyType() === KeyType.RSA) {
  127. var privateKey = null;
  128. var publicKeyDer = null;
  129. return crypto.subtle.generateKey
  130. ({ name: "RSASSA-PKCS1-v1_5", modulusLength: params.getKeySize(),
  131. publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
  132. hash: {name: "SHA-256"} },
  133. true, ["sign", "verify"])
  134. .then(function(key) {
  135. privateKey = key.privateKey;
  136. // Export the public key to DER.
  137. return crypto.subtle.exportKey("spki", key.publicKey);
  138. })
  139. .then(function(exportedPublicKey) {
  140. publicKeyDer = new Blob(new Uint8Array(exportedPublicKey), false).buf();
  141. // Export the private key to DER.
  142. return crypto.subtle.exportKey("pkcs8", privateKey);
  143. })
  144. .then(function(pkcs8Der) {
  145. // Crypto.subtle exports the private key as PKCS #8. Decode it to find
  146. // the inner private key DER.
  147. var parsedNode = DerNode.parse
  148. (new Blob(new Uint8Array(pkcs8Der), false).buf());
  149. // Get the value of the 3rd child which is the octet string.
  150. var privateKeyDer = parsedNode.getChildren()[2].toVal();
  151. // Save the key pair.
  152. thisStore.setKeyPairForKeyName
  153. (keyName, params.getKeyType(), publicKeyDer, privateKeyDer.buf());
  154. // sign will use subtleKey directly.
  155. thisStore.privateKeyStore[keyName.toUri()].subtleKey = privateKey;
  156. return Promise.resolve();
  157. });
  158. }
  159. else
  160. return SyncPromise.reject(new SecurityException(new Error
  161. ("Only RSA key generation currently supported")));
  162. }
  163. else {
  164. return SyncPromise.resolve()
  165. .then(function() {
  166. if (typeof RSAKey !== 'undefined') {
  167. // Assume we are in the browser.
  168. if (params.getKeyType() === KeyType.RSA) {
  169. var rsaKey = new RSAKey();
  170. rsaKey.generate(params.getKeySize(), '010001');
  171. thisStore.setKeyPairForKeyName
  172. (keyName, params.getKeyType(),
  173. PrivateKeyStorage.encodePublicKeyFromRSAKey(rsaKey).buf(),
  174. PrivateKeyStorage.encodePkcs1PrivateKeyFromRSAKey(rsaKey).buf());
  175. }
  176. else
  177. return SyncPromise.reject(new SecurityException(new Error
  178. ("Only RSA key generation currently supported")));
  179. }
  180. else {
  181. // Assume we are in Node.js.
  182. var publicKeyDer;
  183. var privateKeyPem;
  184. if (params.getKeyType() === KeyType.RSA) {
  185. if (!rsaKeygen)
  186. return SyncPromise.reject(new SecurityException(new Error
  187. ("Need to install rsa-keygen: sudo npm install rsa-keygen")));
  188. var keyPair = rsaKeygen.generate(params.getKeySize());
  189. // Get the public key DER from the PEM string.
  190. var publicKeyBase64 = keyPair.public_key.toString().replace
  191. ("-----BEGIN PUBLIC KEY-----", "").replace
  192. ("-----END PUBLIC KEY-----", "");
  193. publicKeyDer = new Buffer(publicKeyBase64, 'base64');
  194. privateKeyPem = keyPair.private_key.toString();
  195. }
  196. else
  197. return SyncPromise.reject(new SecurityException(new Error
  198. ("Only RSA key generation currently supported")));
  199. thisStore.setPublicKeyForKeyName(keyName, params.getKeyType(), publicKeyDer);
  200. thisStore.privateKeyStore[keyName.toUri()] =
  201. { keyType: params.getKeyType(), privateKey: privateKeyPem };
  202. }
  203. return SyncPromise.resolve();
  204. });
  205. }
  206. };
  207. /**
  208. * Delete a pair of asymmetric keys. If the key doesn't exist, do nothing.
  209. * @param {Name} keyName The name of the key pair.
  210. * @return {SyncPromise} A promise that fulfills when the key pair is deleted.
  211. */
  212. MemoryPrivateKeyStorage.prototype.deleteKeyPairPromise = function(keyName)
  213. {
  214. var keyUri = keyName.toUri();
  215. delete this.publicKeyStore[keyUri];
  216. delete this.privateKeyStore[keyUri];
  217. return SyncPromise.resolve();
  218. };
  219. /**
  220. * Get the public key
  221. * @param {Name} keyName The name of public key.
  222. * @return {SyncPromise} A promise that returns the PublicKey.
  223. */
  224. MemoryPrivateKeyStorage.prototype.getPublicKeyPromise = function(keyName)
  225. {
  226. var keyUri = keyName.toUri();
  227. var publicKey = this.publicKeyStore[keyUri];
  228. if (publicKey === undefined)
  229. return SyncPromise.reject(new SecurityException(new Error
  230. ("MemoryPrivateKeyStorage: Cannot find public key " + keyName.toUri())));
  231. return SyncPromise.resolve(publicKey);
  232. };
  233. /**
  234. * Fetch the private key for keyName and sign the data to produce a signature Blob.
  235. * @param {Buffer} data Pointer to the input byte array.
  236. * @param {Name} keyName The name of the signing key.
  237. * @param {number} digestAlgorithm (optional) The digest algorithm from
  238. * DigestAlgorithm, such as DigestAlgorithm.SHA256. If omitted, use
  239. * DigestAlgorithm.SHA256.
  240. * @param {boolean} useSync (optional) If true then use blocking crypto and
  241. * return a SyncPromise which is already fulfilled. If omitted or false, if
  242. * possible use crypto.subtle and return an async Promise, otherwise use
  243. * blocking crypto and return a SyncPromise.
  244. * @return {Promise|SyncPromise} A promise that returns the signature Blob.
  245. */
  246. MemoryPrivateKeyStorage.prototype.signPromise = function
  247. (data, keyName, digestAlgorithm, useSync)
  248. {
  249. useSync = (typeof digestAlgorithm === "boolean") ? digestAlgorithm : useSync;
  250. digestAlgorithm = (typeof digestAlgorithm === "boolean" || !digestAlgorithm) ? DigestAlgorithm.SHA256 : digestAlgorithm;
  251. if (digestAlgorithm != DigestAlgorithm.SHA256)
  252. return SyncPromise.reject(new SecurityException(new Error
  253. ("MemoryPrivateKeyStorage.sign: Unsupported digest algorithm")));
  254. // Find the private key.
  255. var keyUri = keyName.toUri();
  256. var privateKey = this.privateKeyStore[keyUri];
  257. if (privateKey === undefined)
  258. return SyncPromise.reject(new SecurityException(new Error
  259. ("MemoryPrivateKeyStorage: Cannot find private key " + keyUri)));
  260. if (UseSubtleCrypto() && !useSync){
  261. var algo = {name:"RSASSA-PKCS1-v1_5",hash:{name:"SHA-256"}};
  262. if (!privateKey.subtleKey){
  263. //this is the first time in the session that we're using crypto subtle with this key
  264. //so we have to convert to pkcs8 and import it.
  265. //assigning it to privateKey.subtleKey means we only have to do this once per session,
  266. //giving us a small, but not insignificant, performance boost.
  267. var privateDER = DataUtils.privateKeyPemToDer(privateKey.privateKey);
  268. var pkcs8 = PrivateKeyStorage.encodePkcs8PrivateKey
  269. (privateDER, new OID(PrivateKeyStorage.RSA_ENCRYPTION_OID),
  270. new DerNode.DerNull()).buf();
  271. var promise = crypto.subtle.importKey("pkcs8", pkcs8.buffer, algo, true, ["sign"]).then(function(subtleKey){
  272. //cache the crypto.subtle key object
  273. privateKey.subtleKey = subtleKey;
  274. return crypto.subtle.sign(algo, subtleKey, data);
  275. });
  276. } else {
  277. // The crypto.subtle key has been cached on a previous sign or from keygen.
  278. var promise = crypto.subtle.sign(algo, privateKey.subtleKey, data);
  279. }
  280. return promise.then(function(signature){
  281. var result = new Blob(new Uint8Array(signature), true);
  282. return Promise.resolve(result);
  283. });
  284. } else {
  285. var rsa = Crypto.createSign('RSA-SHA256');
  286. rsa.update(data);
  287. var signature = new Buffer
  288. (DataUtils.toNumbersIfString(rsa.sign(privateKey.privateKey)));
  289. var result = new Blob(signature, false);
  290. return SyncPromise.resolve(result);
  291. }
  292. };
  293. /**
  294. * Check if a particular key exists.
  295. * @param {Name} keyName The name of the key.
  296. * @param {number} keyClass The class of the key, e.g. KeyClass.PUBLIC,
  297. * KeyClass.PRIVATE, or KeyClass.SYMMETRIC.
  298. * @return {SyncPromise} A promise which returns true if the key exists.
  299. */
  300. MemoryPrivateKeyStorage.prototype.doesKeyExistPromise = function
  301. (keyName, keyClass)
  302. {
  303. var keyUri = keyName.toUri();
  304. var result = false;
  305. if (keyClass == KeyClass.PUBLIC)
  306. result = this.publicKeyStore[keyUri] !== undefined;
  307. else if (keyClass == KeyClass.PRIVATE)
  308. result = this.privateKeyStore[keyUri] !== undefined;
  309. return SyncPromise.resolve(result);
  310. };