Source: security/tpm/tpm-back-end-file.js

  1. /**
  2. * Copyright (C) 2017-2018 Regents of the University of California.
  3. * @author: Jeff Thompson <jefft0@remap.ucla.edu>
  4. * @author: From ndn-cxx security https://github.com/named-data/ndn-cxx/blob/master/src/security/tpm/back-end-file.hpp
  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. /** @ignore */
  21. var path = require('path'); /** @ignore */
  22. var fs = require('fs'); /** @ignore */
  23. var Crypto = require('../../crypto.js'); /** @ignore */
  24. var Blob = require('../../util/blob.js').Blob; /** @ignore */
  25. var TpmPrivateKey = require('./tpm-private-key.js').TpmPrivateKey; /** @ignore */
  26. var TpmKeyHandleMemory = require('./tpm-key-handle-memory.js').TpmKeyHandleMemory; /** @ignore */
  27. var TpmBackEnd = require('./tpm-back-end.js').TpmBackEnd; /** @ignore */
  28. var SyncPromise = require('../../util/sync-promise.js').SyncPromise;
  29. /**
  30. * TpmBackEndFile extends TpmBackEnd to implement a TPM back-end using on-disk
  31. * file storage. In this TPM, each private key is stored in a separate file with
  32. * permission 0400, i.e., owner read-only. The key is stored in PKCS #1 format
  33. * in base64 encoding.
  34. *
  35. * Create a TpmBackEndFile to use the given path to store files (of provided) or
  36. * to the default location.
  37. * @param {string} locationPath (optional) The full path of the directory to
  38. * store private keys. If omitted or null or "", use the default location
  39. * ~/.ndn/ndnsec-key-file.
  40. * @constructor
  41. */
  42. var TpmBackEndFile = function TpmBackEndFile(locationPath)
  43. {
  44. // Call the base constructor.
  45. TpmBackEnd.call(this);
  46. if (locationPath == undefined || locationPath == "") {
  47. var home = process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE;
  48. locationPath = path.join(home, ".ndn", "ndnsec-key-file");
  49. }
  50. this.keyStorePath_ = locationPath;
  51. };
  52. TpmBackEndFile.prototype = new TpmBackEnd();
  53. TpmBackEndFile.prototype.name = "TpmBackEndFile";
  54. exports.TpmBackEndFile = TpmBackEndFile;
  55. /**
  56. * Create a TpmBackEndFile.Error which extends TpmBackEnd.Error and represents a
  57. * non-semantic error in backend TPM file processing.
  58. * Call with: throw new TpmBackEndFile.Error(new Error("message")).
  59. * @constructor
  60. * @param {Error} error The exception created with new Error.
  61. */
  62. TpmBackEndFile.Error = function TpmBackEndFileError(error)
  63. {
  64. // Call the base constructor.
  65. TpmBackEnd.Error.call(this, error);
  66. }
  67. TpmBackEndFile.Error.prototype = new TpmBackEnd.Error();
  68. TpmBackEndFile.Error.prototype.name = "TpmBackEndFileError";
  69. TpmBackEndFile.getScheme = function() { return "tpm-file"; };
  70. /**
  71. * A protected method to check if the key with name keyName exists in the TPM.
  72. * @param {Name} keyName The name of the key.
  73. * @param {boolean} useSync (optional) If true then return a SyncPromise which
  74. * is already fulfilled. If omitted or false, this may return a SyncPromise or
  75. * an async Promise.
  76. * @return {Promise|SyncPromise} A promise which returns true if the key exists.
  77. */
  78. TpmBackEndFile.prototype.doHasKeyPromise_ = function(keyName, useSync)
  79. {
  80. return Promise.resolve(this.hasKey_(keyName));
  81. };
  82. /**
  83. * Do the work of doHasKeyPromise_
  84. * @param {Name} keyName The name of the key.
  85. * @return {boolean} True if the key exists.
  86. */
  87. TpmBackEndFile.prototype.hasKey_ = function(keyName)
  88. {
  89. if (!fs.existsSync(this.toFilePath_(keyName)))
  90. return false;
  91. try {
  92. this.loadKey_(keyName);
  93. return true;
  94. } catch (ex) {
  95. return false;
  96. }
  97. };
  98. /**
  99. * A protected method to get the handle of the key with name keyName.
  100. * @param {Name} keyName The name of the key.
  101. * @param {boolean} useSync (optional) If true then return a SyncPromise which
  102. * is already fulfilled. If omitted or false, this may return a SyncPromise or
  103. * an async Promise.
  104. * @return {Promise|SyncPromise} A promise which returns a TpmKeyHandle of the
  105. * key, or returns null if the key does not exist.
  106. */
  107. TpmBackEndFile.prototype.doGetKeyHandlePromise_ = function(keyName, useSync)
  108. {
  109. if (!this.hasKey_(keyName))
  110. return null;
  111. return new TpmKeyHandleMemory(this.loadKey_(keyName));
  112. };
  113. /**
  114. * A protected method to create a key for identityName according to params. The
  115. * created key is named as: /<identityName>/[keyId]/KEY . The key name is set in
  116. * the returned TpmKeyHandle.
  117. * @param {Name} identityName The name if the identity.
  118. * @param {KeyParams} params The KeyParams for creating the key.
  119. * @param {boolean} useSync (optional) If true then return a SyncPromise which
  120. * is already fulfilled. If omitted or false, this may return a SyncPromise or
  121. * an async Promise.
  122. * @return {Promise|SyncPromise} A promise which returns the TpmKeyHandle of
  123. * the created key, or a promise rejected with TpmBackEnd.Error if the key
  124. * cannot be created.
  125. */
  126. TpmBackEndFile.prototype.doCreateKeyPromise_ = function
  127. (identityName, params, useSync)
  128. {
  129. var key;
  130. try {
  131. // We know that TpmPrivateKey.generatePrivateKeyPromise is sync.
  132. key = SyncPromise.getValue(TpmPrivateKey.generatePrivateKeyPromise(params));
  133. } catch (ex) {
  134. return Promise.reject(new TpmBackEndFile.Error(new Error
  135. ("Error in TpmPrivateKey.generatePrivateKey: " + ex)));
  136. }
  137. var keyHandle = new TpmKeyHandleMemory(key);
  138. TpmBackEnd.setKeyName(keyHandle, identityName, params);
  139. try {
  140. this.saveKey_(keyHandle.getKeyName(), key);
  141. } catch (ex) {
  142. return Promise.reject(ex);
  143. }
  144. return Promise.resolve(keyHandle);
  145. };
  146. /**
  147. * A protected method to delete the key with name keyName. If the key doesn't
  148. * exist, do nothing.
  149. * @param {Name} keyName The name of the key to delete.
  150. * @param {boolean} useSync (optional) If true then return a SyncPromise which
  151. * is already fulfilled. If omitted or false, this may return a SyncPromise or
  152. * an async Promise.
  153. * @return {Promise|SyncPromise} A promise which fulfills when finished, or a
  154. * promise rejected with TpmBackEnd.Error if the deletion fails.
  155. */
  156. TpmBackEndFile.prototype.doDeleteKeyPromise_ = function(keyName, useSync)
  157. {
  158. var filePath = this.toFilePath_(keyName);
  159. if (fs.existsSync(filePath)) {
  160. try {
  161. fs.unlinkSync(filePath);
  162. } catch (ex) {
  163. return Promise.reject(new TpmBackEndFile.Error(new Error
  164. ("Error deleting private key file: " + ex)));
  165. }
  166. }
  167. return Promise.resolve();
  168. };
  169. // TODO: doExportKeyPromise_
  170. // TODO: doImportKeyPromise_
  171. /**
  172. * Load the private key with name keyName from the key file directory.
  173. * @param {Name} keyName The name of the key.
  174. * @return {TpmPrivateKey} The key loaded into a TpmPrivateKey.
  175. */
  176. TpmBackEndFile.prototype.loadKey_ = function(keyName)
  177. {
  178. var key = new TpmPrivateKey();
  179. var pkcs;
  180. try {
  181. var base64Content = fs.readFileSync(this.toFilePath_(keyName)).toString();
  182. pkcs = new Buffer(base64Content, 'base64');
  183. } catch (ex) {
  184. throw new TpmBackEndFile.Error(new Error
  185. ("Error reading private key file: " + ex));
  186. }
  187. try {
  188. key.loadPkcs1(pkcs, null);
  189. } catch (ex) {
  190. throw new TpmBackEndFile.Error(new Error
  191. ("Error decoding private key file: " + ex));
  192. }
  193. return key;
  194. };
  195. /**
  196. * Save the private key using keyName into the key file directory.
  197. * @param {Name} keyName The name of the key.
  198. * @param {TpmPrivateKey} key The private key to save.
  199. */
  200. TpmBackEndFile.prototype.saveKey_ = function(keyName, key)
  201. {
  202. var filePath = this.toFilePath_(keyName);
  203. var base64;
  204. try {
  205. base64 = key.toPkcs1().buf().toString('base64');
  206. } catch (ex) {
  207. throw new TpmBackEndFile.Error(new Error
  208. ("Error encoding private key file: " + ex));
  209. }
  210. try {
  211. var options = { mode: parseInt('0400', 8) };
  212. fs.writeFileSync(filePath, base64, options);
  213. } catch (ex) {
  214. throw new TpmBackEndFile.Error(new Error
  215. ("Error writing private key file: " + ex));
  216. }
  217. };
  218. /**
  219. * Get the file path for the keyName, which is keyStorePath_ + "/" +
  220. * hex(sha256(keyName-wire-encoding)) + ".privkey" .
  221. * @param {Name} keyName The name of the key.
  222. * @return {string} The file path for the key.
  223. */
  224. TpmBackEndFile.prototype.toFilePath_ = function(keyName)
  225. {
  226. var keyEncoding = keyName.wireEncode();
  227. var hash = Crypto.createHash('sha256');
  228. hash.update(keyEncoding.buf());
  229. var digest = hash.digest();
  230. return path.join
  231. (this.keyStorePath_, new Blob(digest, false).toHex() + ".privkey");
  232. };