Source: encrypt/group-manager.js

  1. /**
  2. * Copyright (C) 2015-2016 Regents of the University of California.
  3. * @author: Jeff Thompson <jefft0@remap.ucla.edu>
  4. * @author: From ndn-group-encrypt src/group-manager https://github.com/named-data/ndn-group-encrypt
  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 Name = require('../name.js').Name; /** @ignore */
  22. var Data = require('../data.js').Data; /** @ignore */
  23. var SyncPromise = require('../util/sync-promise.js').SyncPromise; /** @ignore */
  24. var IdentityCertificate = require('../security/certificate/identity-certificate.js').IdentityCertificate; /** @ignore */
  25. var SecurityException = require('../security/security-exception.js').SecurityException; /** @ignore */
  26. var RsaKeyParams = require('../security/key-params.js').RsaKeyParams; /** @ignore */
  27. var EncryptParams = require('./algo/encrypt-params.js').EncryptParams; /** @ignore */
  28. var EncryptAlgorithmType = require('./algo/encrypt-params.js').EncryptAlgorithmType; /** @ignore */
  29. var Encryptor = require('./algo/encryptor.js').Encryptor; /** @ignore */
  30. var RsaAlgorithm = require('./algo/rsa-algorithm.js').RsaAlgorithm; /** @ignore */
  31. var Interval = require('./interval.js').Interval; /** @ignore */
  32. var Schedule = require('./schedule.js').Schedule;
  33. /**
  34. * A GroupManager manages keys and schedules for group members in a particular
  35. * namespace.
  36. * Create a group manager with the given values. The group manager namespace
  37. * is <prefix>/read/<dataType> .
  38. * @param {Name} prefix The prefix for the group manager namespace.
  39. * @param {Name} dataType The data type for the group manager namespace.
  40. * @param {GroupManagerDb} database The GroupManagerDb for storing the group
  41. * management information (including user public keys and schedules).
  42. * @param {number} keySize The group key will be an RSA key with keySize bits.
  43. * @param {number} freshnessHours The number of hours of the freshness period of
  44. * data packets carrying the keys.
  45. * @param {KeyChain} keyChain The KeyChain to use for signing data packets. This
  46. * signs with the default identity.
  47. * @note This class is an experimental feature. The API may change.
  48. * @constructor
  49. */
  50. var GroupManager = function GroupManager
  51. (prefix, dataType, database, keySize, freshnessHours, keyChain)
  52. {
  53. this.namespace_ = new Name(prefix).append(Encryptor.NAME_COMPONENT_READ)
  54. .append(dataType);
  55. this.database_ = database;
  56. this.keySize_ = keySize;
  57. this.freshnessHours_ = freshnessHours;
  58. this.keyChain_ = keyChain;
  59. };
  60. exports.GroupManager = GroupManager;
  61. /**
  62. * Create a group key for the interval into which timeSlot falls. This creates
  63. * a group key if it doesn't exist, and encrypts the key using the public key of
  64. * each eligible member.
  65. * @param {number} timeSlot The time slot to cover as milliseconds since
  66. * Jan 1, 1970 UTC.
  67. * @param {boolean} useSync (optional) If true then return a SyncPromise which
  68. * is already fulfilled. If omitted or false, this may return a SyncPromise or
  69. * an async Promise.
  70. * @return {Promise|SyncPromise} A promise that returns a List of Data packets
  71. * (where the first is the E-KEY data packet with the group's public key and the
  72. * rest are the D-KEY data packets with the group's private key encrypted with
  73. * the public key of each eligible member), or that is rejected with
  74. * GroupManagerDb.Error for a database error or SecurityException for an error
  75. * using the security KeyChain.
  76. */
  77. GroupManager.prototype.getGroupKeyPromise = function(timeSlot, useSync)
  78. {
  79. var memberKeys = [];
  80. var result = [];
  81. var thisManager = this;
  82. var privateKeyBlob;
  83. var publicKeyBlob;
  84. var startTimeStamp;
  85. var endTimeStamp;
  86. // Get the time interval.
  87. return this.calculateIntervalPromise_(timeSlot, memberKeys, useSync)
  88. .then(function(finalInterval) {
  89. if (finalInterval.isValid() == false)
  90. return SyncPromise.resolve(result);
  91. startTimeStamp = Schedule.toIsoString(finalInterval.getStartTime());
  92. endTimeStamp = Schedule.toIsoString(finalInterval.getEndTime());
  93. // Generate the private and public keys.
  94. return thisManager.generateKeyPairPromise_(useSync)
  95. .then(function(keyPair) {
  96. privateKeyBlob = keyPair.privateKeyBlob;
  97. publicKeyBlob = keyPair.publicKeyBlob;
  98. // Add the first element to the result.
  99. // The E-KEY (public key) data packet name convention is:
  100. // /<data_type>/E-KEY/[start-ts]/[end-ts]
  101. return thisManager.createEKeyDataPromise_
  102. (startTimeStamp, endTimeStamp, publicKeyBlob, useSync);
  103. })
  104. .then(function(data) {
  105. result.push(data);
  106. // Encrypt the private key with the public key from each member's certificate.
  107. // Process the memberKeys entry at i, and recursively call to process the
  108. // next entry. Return a promise which is resolved when all are processed.
  109. // (We have to make a recursive function to use Promises.)
  110. function processMemberKey(i) {
  111. if (i >= memberKeys.length)
  112. // Finished.
  113. return SyncPromise.resolve();
  114. var keyName = memberKeys[i].keyName;
  115. var certificateKey = memberKeys[i].publicKey;
  116. return thisManager.createDKeyDataPromise_
  117. (startTimeStamp, endTimeStamp, keyName, privateKeyBlob, certificateKey,
  118. useSync)
  119. .then(function(data) {
  120. result.push(data);
  121. return processMemberKey(i + 1);
  122. });
  123. }
  124. return processMemberKey(0);
  125. })
  126. .then(function() {
  127. return SyncPromise.resolve(result);
  128. });
  129. });
  130. };
  131. /**
  132. * Add a schedule with the given scheduleName.
  133. * @param {string} scheduleName The name of the schedule. The name cannot be
  134. * empty.
  135. * @param {Schedule} schedule The Schedule to add.
  136. * @param {boolean} useSync (optional) If true then return a SyncPromise which
  137. * is already fulfilled. If omitted or false, this may return a SyncPromise or
  138. * an async Promise.
  139. * @return {Promise|SyncPromise} A promise that fulfills when the schedule is
  140. * added, or that is rejected with GroupManagerDb.Error if a schedule with the
  141. * same name already exists, if the name is empty, or other database error.
  142. */
  143. GroupManager.prototype.addSchedulePromise = function
  144. (scheduleName, schedule, useSync)
  145. {
  146. return this.database_.addSchedulePromise(scheduleName, schedule, useSync);
  147. };
  148. /**
  149. * Delete the schedule with the given scheduleName. Also delete members which
  150. * use this schedule. If there is no schedule with the name, then do nothing.
  151. * @param {string} scheduleName The name of the schedule.
  152. * @param {boolean} useSync (optional) If true then return a SyncPromise which
  153. * is already fulfilled. If omitted or false, this may return a SyncPromise or
  154. * an async Promise.
  155. * @return {Promise|SyncPromise} A promise that fulfills when the schedule is
  156. * deleted (or there is no such schedule), or that is rejected with
  157. * GroupManagerDb.Error for a database error.
  158. */
  159. GroupManager.prototype.deleteSchedulePromise = function(scheduleName, useSync)
  160. {
  161. return this.database_.deleteSchedulePromise(scheduleName, useSync);
  162. };
  163. /**
  164. * Update the schedule with scheduleName and replace the old object with the
  165. * given schedule. Otherwise, if no schedule with name exists, a new schedule
  166. * with name and the given schedule will be added to database.
  167. * @param {string} scheduleName The name of the schedule. The name cannot be
  168. * empty.
  169. * @param {Schedule} schedule The Schedule to update or add.
  170. * @param {boolean} useSync (optional) If true then return a SyncPromise which
  171. * is already fulfilled. If omitted or false, this may return a SyncPromise or
  172. * an async Promise.
  173. * @return {Promise|SyncPromise} A promise that fulfills when the schedule is
  174. * updated, or that is rejected with GroupManagerDb.Error if the name is empty,
  175. * or other database error.
  176. */
  177. GroupManager.prototype.updateSchedulePromise = function
  178. (name, scheduleName, useSync)
  179. {
  180. return this.database_.updateSchedulePromise(scheduleName, schedule, useSync);
  181. };
  182. /**
  183. * Add a new member with the given memberCertificate into a schedule named
  184. * scheduleName. If cert is an IdentityCertificate made from memberCertificate,
  185. * then the member's identity name is cert.getPublicKeyName().getPrefix(-1).
  186. * @param {string} scheduleName The schedule name.
  187. * @param {Data} memberCertificate The member's certificate.
  188. * @param {boolean} useSync (optional) If true then return a SyncPromise which
  189. * is already fulfilled. If omitted or false, this may return a SyncPromise or
  190. * an async Promise.
  191. * @return {Promise|SyncPromise} A promise that fulfills when the member is
  192. * added, or that is rejected with GroupManagerDb.Error if there's no schedule
  193. * named scheduleName, if the member's identity name already exists, or other
  194. * database error. Or a promise that is rejected with DerDecodingException for
  195. * an error decoding memberCertificate as a certificate.
  196. */
  197. GroupManager.prototype.addMemberPromise = function
  198. (scheduleName, memberCertificate, useSync)
  199. {
  200. var cert = new IdentityCertificate(memberCertificate);
  201. return this.database_.addMemberPromise
  202. (scheduleName, cert.getPublicKeyName(), cert.getPublicKeyInfo().getKeyDer(),
  203. useSync);
  204. };
  205. /**
  206. * Remove a member with the given identity name. If there is no member with
  207. * the identity name, then do nothing.
  208. * @param {Name} identity The member's identity name.
  209. * @param {boolean} useSync (optional) If true then return a SyncPromise which
  210. * is already fulfilled. If omitted or false, this may return a SyncPromise or
  211. * an async Promise.
  212. * @return {Promise|SyncPromise} A promise that fulfills when the member is
  213. * removed (or there is no such member), or that is rejected with
  214. * GroupManagerDb.Error for a database error.
  215. */
  216. GroupManager.prototype.removeMemberPromise = function(identity, useSync)
  217. {
  218. return this.database_.deleteMemberPromise(identity, useSync);
  219. };
  220. /**
  221. * Change the name of the schedule for the given member's identity name.
  222. * @param {Name} identity The member's identity name.
  223. * @param {string} scheduleName The new schedule name.
  224. * @param {boolean} useSync (optional) If true then return a SyncPromise which
  225. * is already fulfilled. If omitted or false, this may return a SyncPromise or
  226. * an async Promise.
  227. * @return {Promise|SyncPromise} A promise that fulfills when the member is
  228. * updated, or that is rejected with GroupManagerDb.Error if there's no member
  229. * with the given identity name in the database, or there's no schedule named
  230. * scheduleName.
  231. */
  232. GroupManager.prototype.updateMemberSchedulePromise = function
  233. (identity, scheduleName, useSync)
  234. {
  235. return this.database_.updateMemberSchedulePromise
  236. (identity, scheduleName, useSync);
  237. };
  238. /**
  239. * Calculate an Interval that covers the timeSlot.
  240. * @param {number} timeSlot The time slot to cover as milliseconds since
  241. * Jan 1, 1970 UTC.
  242. * @param {Array<object>} memberKeys First clear memberKeys then fill it with
  243. * the info of members who are allowed to access the interval. memberKeys is an
  244. * array of object where "keyName" is the Name of the public key and "publicKey"
  245. * is the Blob of the public key DER. The memberKeys entries are sorted by
  246. * the entry keyName.
  247. * @param {boolean} useSync (optional) If true then return a SyncPromise which
  248. * is already fulfilled. If omitted or false, this may return a SyncPromise or
  249. * an async Promise.
  250. * @return {Promise|SyncPromise} A promise that returns a new nterval covering
  251. * the time slot, or that is rejected with GroupManagerDb.Error for a database
  252. * error.
  253. */
  254. GroupManager.prototype.calculateIntervalPromise_ = function
  255. (timeSlot, memberKeys, useSync)
  256. {
  257. // Prepare.
  258. var positiveResult = new Interval();
  259. var negativeResult = new Interval();
  260. // Clear memberKeys.
  261. memberKeys.splice(0, memberKeys.length);
  262. var thisManager = this;
  263. // Get the all intervals from the schedules.
  264. return this.database_.listAllScheduleNamesPromise(useSync)
  265. .then(function(scheduleNames) {
  266. // Process the scheduleNames entry at i, and recursively call to process the
  267. // next entry. Return a promise which is resolved when all are processed.
  268. // (We have to make a recursive function to use Promises.)
  269. function processSchedule(i) {
  270. if (i >= scheduleNames.length)
  271. // Finished.
  272. return SyncPromise.resolve();
  273. var scheduleName = scheduleNames[i];
  274. return thisManager.database_.getSchedulePromise(scheduleName, useSync)
  275. .then(function(schedule) {
  276. var result = schedule.getCoveringInterval(timeSlot);
  277. var tempInterval = result.interval;
  278. if (result.isPositive) {
  279. if (!positiveResult.isValid())
  280. positiveResult = tempInterval;
  281. positiveResult.intersectWith(tempInterval);
  282. return thisManager.database_.getScheduleMembersPromise
  283. (scheduleName, useSync)
  284. .then(function(map) {
  285. // Add each entry in map to memberKeys.
  286. for (var iMap = 0; iMap < map.length; ++iMap)
  287. GroupManager.memberKeysAdd_(memberKeys, map[iMap]);
  288. return processSchedule(i + 1);
  289. });
  290. }
  291. else {
  292. if (!negativeResult.isValid())
  293. negativeResult = tempInterval;
  294. negativeResult.intersectWith(tempInterval);
  295. return processSchedule(i + 1);
  296. }
  297. });
  298. }
  299. return processSchedule(0);
  300. })
  301. .then(function() {
  302. if (!positiveResult.isValid())
  303. // Return an invalid interval when there is no member which has an
  304. // interval covering the time slot.
  305. return SyncPromise.resolve(new Interval(false));
  306. // Get the final interval result.
  307. var finalInterval;
  308. if (negativeResult.isValid())
  309. finalInterval = positiveResult.intersectWith(negativeResult);
  310. else
  311. finalInterval = positiveResult;
  312. return SyncPromise.resolve(finalInterval);
  313. });
  314. };
  315. /**
  316. * Add entry to memberKeys, sorted by entry.keyName. If there is already an
  317. * entry with keyName, then don't add.
  318. */
  319. GroupManager.memberKeysAdd_ = function(memberKeys, entry)
  320. {
  321. // Find the index of the first node where the keyName is not less than
  322. // entry.keyName.
  323. var i = 0;
  324. while (i < memberKeys.length) {
  325. var comparison = memberKeys[i].keyName.compare(entry.keyName);
  326. if (comparison == 0)
  327. // A duplicate, so don't add.
  328. return;
  329. if (comparison > 0)
  330. break;
  331. i += 1;
  332. }
  333. memberKeys.splice(i, 0, entry);
  334. };
  335. /**
  336. * Generate an RSA key pair according to keySize_.
  337. * @param {boolean} useSync (optional) If true then return a SyncPromise which
  338. * is already fulfilled. If omitted or false, this may return a SyncPromise or
  339. * an async Promise.
  340. * @return {Promise|SyncPromise} A promise that returns an object where
  341. * "privateKeyBlob" is the encoding Blob of the private key and "publicKeyBlob"
  342. * is the encoding Blob of the public key.
  343. */
  344. GroupManager.prototype.generateKeyPairPromise_ = function(useSync)
  345. {
  346. var params = new RsaKeyParams(this.keySize_);
  347. return RsaAlgorithm.generateKeyPromise(params)
  348. .then(function(privateKey) {
  349. var privateKeyBlob = privateKey.getKeyBits();
  350. var publicKey = RsaAlgorithm.deriveEncryptKey(privateKeyBlob);
  351. var publicKeyBlob = publicKey.getKeyBits();
  352. return SyncPromise.resolve
  353. ({ privateKeyBlob: privateKeyBlob, publicKeyBlob: publicKeyBlob });
  354. });
  355. };
  356. /**
  357. * Create an E-KEY Data packet for the given public key.
  358. * @param {string} startTimeStamp The start time stamp string to put in the name.
  359. * @param {string} endTimeStamp The end time stamp string to put in the name.
  360. * @param {Blob} publicKeyBlob A Blob of the public key DER.
  361. * @return The Data packet.
  362. * @throws SecurityException for an error using the security KeyChain.
  363. * @param {boolean} useSync (optional) If true then return a SyncPromise which
  364. * is already fulfilled. If omitted or false, this may return a SyncPromise or
  365. * an async Promise.
  366. * @return {Promise|SyncPromise} A promise that returns the Data packet, or that
  367. * is rejected with SecurityException for an error using the security KeyChain.
  368. */
  369. GroupManager.prototype.createEKeyDataPromise_ = function
  370. (startTimeStamp, endTimeStamp, publicKeyBlob, useSync)
  371. {
  372. var name = new Name(this.namespace_);
  373. name.append(Encryptor.NAME_COMPONENT_E_KEY).append(startTimeStamp)
  374. .append(endTimeStamp);
  375. var data = new Data(name);
  376. data.getMetaInfo().setFreshnessPeriod
  377. (this.freshnessHours_ * GroupManager.MILLISECONDS_IN_HOUR);
  378. data.setContent(publicKeyBlob);
  379. return this.keyChain_.signPromise(data);
  380. };
  381. /**
  382. * Create a D-KEY Data packet with an EncryptedContent for the given private
  383. * key, encrypted with the certificate key.
  384. * @param {string} startTimeStamp The start time stamp string to put in the name.
  385. * @param {string} endTimeStamp The end time stamp string to put in the name.
  386. * @param {Name} keyName The key name to put in the data packet name and the
  387. * EncryptedContent key locator.
  388. * @param {Blob} privateKeyBlob A Blob of the encoded private key.
  389. * @param {Blob} certificateKey The certificate key encoding, used to encrypt
  390. * the private key.
  391. * @param {boolean} useSync (optional) If true then return a SyncPromise which
  392. * is already fulfilled. If omitted or false, this may return a SyncPromise or
  393. * an async Promise.
  394. * @return {Promise|SyncPromise} A promise that returns the Data packet, or that
  395. * is rejected with SecurityException for an error using the security KeyChain.
  396. */
  397. GroupManager.prototype.createDKeyDataPromise_ = function
  398. (startTimeStamp, endTimeStamp, keyName, privateKeyBlob, certificateKey,
  399. useSync)
  400. {
  401. var name = new Name(this.namespace_);
  402. name.append(Encryptor.NAME_COMPONENT_D_KEY);
  403. name.append(startTimeStamp).append(endTimeStamp);
  404. var data = new Data(name);
  405. data.getMetaInfo().setFreshnessPeriod
  406. (this.freshnessHours_ * GroupManager.MILLISECONDS_IN_HOUR);
  407. var encryptParams = new EncryptParams(EncryptAlgorithmType.RsaOaep);
  408. var thisManager = this;
  409. return Encryptor.encryptDataPromise
  410. (data, privateKeyBlob, keyName, certificateKey, encryptParams, useSync)
  411. .catch(function(ex) {
  412. // Consolidate errors such as InvalidKeyException.
  413. return SyncPromise.reject(SecurityException(new Error
  414. ("createDKeyData: Error in encryptData: " + ex)));
  415. })
  416. .then(function() {
  417. return thisManager.keyChain_.signPromise(data);
  418. });
  419. };
  420. GroupManager.MILLISECONDS_IN_HOUR = 3600 * 1000;