Source: encrypt/group-manager.js

  1. /**
  2. * Copyright (C) 2015-2018 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} needRegenerate (optional) needRegenerate should be true if
  68. * this is the first time this method is called, or a member was removed.
  69. * needRegenerate can be false if this is not the first time this method is
  70. * called, or a member was added. If omitted, use true. If useSync is specified,
  71. * then needRegenerate must also be specified (since this can't disambiguate
  72. * two optional boolean parameters).
  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. If useSync is specified, then needRegenerate must also be
  76. * specified (since this can't disambiguate two optional boolean parameters).
  77. * @return {Promise|SyncPromise} A promise that returns a List of Data packets
  78. * (where the first is the E-KEY data packet with the group's public key and the
  79. * rest are the D-KEY data packets with the group's private key encrypted with
  80. * the public key of each eligible member), or that is rejected with
  81. * GroupManagerDb.Error for a database error or SecurityException for an error
  82. * using the security KeyChain.
  83. */
  84. GroupManager.prototype.getGroupKeyPromise = function
  85. (timeSlot, needRegenerate, useSync)
  86. {
  87. if (needRegenerate == undefined)
  88. needRegenerate = true;
  89. var memberKeys = [];
  90. var result = [];
  91. var thisManager = this;
  92. var privateKeyBlob;
  93. var publicKeyBlob;
  94. var startTimeStamp;
  95. var endTimeStamp;
  96. // Get the time interval.
  97. return this.calculateIntervalPromise_(timeSlot, memberKeys, useSync)
  98. .then(function(finalInterval) {
  99. if (finalInterval.isValid() == false)
  100. return SyncPromise.resolve(result);
  101. startTimeStamp = Schedule.toIsoString(finalInterval.getStartTime());
  102. endTimeStamp = Schedule.toIsoString(finalInterval.getEndTime());
  103. // Generate the private and public keys.
  104. var eKeyName = new Name(thisManager.namespace_);
  105. eKeyName.append(Encryptor.NAME_COMPONENT_E_KEY).append(startTimeStamp)
  106. .append(endTimeStamp);
  107. return SyncPromise.resolve()
  108. .then(function() {
  109. // Only call hasEKeyPromise if needRegenerate is false.
  110. if (!needRegenerate)
  111. return thisManager.database_.hasEKeyPromise(eKeyName, useSync);
  112. else
  113. return SyncPromise.resolve(false);
  114. })
  115. .then(function(hasEKey) {
  116. if (!needRegenerate && hasEKey) {
  117. return thisManager.getEKeyPromise_(eKeyName, useSync)
  118. .then(function(keyPair) {
  119. privateKeyBlob = keyPair.privateKey;
  120. publicKeyBlob = keyPair.publicKey;
  121. return SyncPromise.resolve();
  122. });
  123. }
  124. else {
  125. return thisManager.generateKeyPairPromise_(useSync)
  126. .then(function(keyPair) {
  127. privateKeyBlob = keyPair.privateKeyBlob;
  128. publicKeyBlob = keyPair.publicKeyBlob;
  129. // deleteEKeyPromise_ does nothing if eKeyName does not exist.
  130. return thisManager.deleteEKeyPromise_(eKeyName, useSync);
  131. })
  132. .then(function() {
  133. return thisManager.addEKeyPromise_
  134. (eKeyName, publicKeyBlob, privateKeyBlob, useSync);
  135. });
  136. }
  137. })
  138. .then(function() {
  139. // Add the first element to the result.
  140. // The E-KEY (public key) data packet name convention is:
  141. // /<data_type>/E-KEY/[start-ts]/[end-ts]
  142. return thisManager.createEKeyDataPromise_
  143. (startTimeStamp, endTimeStamp, publicKeyBlob, useSync);
  144. })
  145. .then(function(data) {
  146. result.push(data);
  147. // Encrypt the private key with the public key from each member's certificate.
  148. // Process the memberKeys entry at i, and recursively call to process the
  149. // next entry. Return a promise which is resolved when all are processed.
  150. // (We have to make a recursive function to use Promises.)
  151. function processMemberKey(i) {
  152. if (i >= memberKeys.length)
  153. // Finished.
  154. return SyncPromise.resolve();
  155. var keyName = memberKeys[i].keyName;
  156. var certificateKey = memberKeys[i].publicKey;
  157. return thisManager.createDKeyDataPromise_
  158. (startTimeStamp, endTimeStamp, keyName, privateKeyBlob, certificateKey,
  159. useSync)
  160. .then(function(data) {
  161. result.push(data);
  162. return processMemberKey(i + 1);
  163. });
  164. }
  165. return processMemberKey(0);
  166. })
  167. .then(function() {
  168. return SyncPromise.resolve(result);
  169. });
  170. });
  171. };
  172. /**
  173. * Add a schedule with the given scheduleName.
  174. * @param {string} scheduleName The name of the schedule. The name cannot be
  175. * empty.
  176. * @param {Schedule} schedule The Schedule to add.
  177. * @param {boolean} useSync (optional) If true then return a SyncPromise which
  178. * is already fulfilled. If omitted or false, this may return a SyncPromise or
  179. * an async Promise.
  180. * @return {Promise|SyncPromise} A promise that fulfills when the schedule is
  181. * added, or that is rejected with GroupManagerDb.Error if a schedule with the
  182. * same name already exists, if the name is empty, or other database error.
  183. */
  184. GroupManager.prototype.addSchedulePromise = function
  185. (scheduleName, schedule, useSync)
  186. {
  187. return this.database_.addSchedulePromise(scheduleName, schedule, useSync);
  188. };
  189. /**
  190. * Delete the schedule with the given scheduleName. Also delete members which
  191. * use this schedule. If there is no schedule with the name, then do nothing.
  192. * @param {string} scheduleName The name of the schedule.
  193. * @param {boolean} useSync (optional) If true then return a SyncPromise which
  194. * is already fulfilled. If omitted or false, this may return a SyncPromise or
  195. * an async Promise.
  196. * @return {Promise|SyncPromise} A promise that fulfills when the schedule is
  197. * deleted (or there is no such schedule), or that is rejected with
  198. * GroupManagerDb.Error for a database error.
  199. */
  200. GroupManager.prototype.deleteSchedulePromise = function(scheduleName, useSync)
  201. {
  202. return this.database_.deleteSchedulePromise(scheduleName, useSync);
  203. };
  204. /**
  205. * Update the schedule with scheduleName and replace the old object with the
  206. * given schedule. Otherwise, if no schedule with name exists, a new schedule
  207. * with name and the given schedule will be added to database.
  208. * @param {string} scheduleName The name of the schedule. The name cannot be
  209. * empty.
  210. * @param {Schedule} schedule The Schedule to update or add.
  211. * @param {boolean} useSync (optional) If true then return a SyncPromise which
  212. * is already fulfilled. If omitted or false, this may return a SyncPromise or
  213. * an async Promise.
  214. * @return {Promise|SyncPromise} A promise that fulfills when the schedule is
  215. * updated, or that is rejected with GroupManagerDb.Error if the name is empty,
  216. * or other database error.
  217. */
  218. GroupManager.prototype.updateSchedulePromise = function
  219. (scheduleName, schedule, useSync)
  220. {
  221. return this.database_.updateSchedulePromise(scheduleName, schedule, useSync);
  222. };
  223. /**
  224. * Add a new member with the given memberCertificate into a schedule named
  225. * scheduleName. If cert is an IdentityCertificate made from memberCertificate,
  226. * then the member's identity name is cert.getPublicKeyName().getPrefix(-1).
  227. * @param {string} scheduleName The schedule name.
  228. * @param {Data} memberCertificate The member's certificate.
  229. * @param {boolean} useSync (optional) If true then return a SyncPromise which
  230. * is already fulfilled. If omitted or false, this may return a SyncPromise or
  231. * an async Promise.
  232. * @return {Promise|SyncPromise} A promise that fulfills when the member is
  233. * added, or that is rejected with GroupManagerDb.Error if there's no schedule
  234. * named scheduleName, if the member's identity name already exists, or other
  235. * database error. Or a promise that is rejected with DerDecodingException for
  236. * an error decoding memberCertificate as a certificate.
  237. */
  238. GroupManager.prototype.addMemberPromise = function
  239. (scheduleName, memberCertificate, useSync)
  240. {
  241. var cert = new IdentityCertificate(memberCertificate);
  242. return this.database_.addMemberPromise
  243. (scheduleName, cert.getPublicKeyName(), cert.getPublicKeyInfo().getKeyDer(),
  244. useSync);
  245. };
  246. /**
  247. * Remove a member with the given identity name. If there is no member with
  248. * the identity name, then do nothing.
  249. * @param {Name} identity The member's identity name.
  250. * @param {boolean} useSync (optional) If true then return a SyncPromise which
  251. * is already fulfilled. If omitted or false, this may return a SyncPromise or
  252. * an async Promise.
  253. * @return {Promise|SyncPromise} A promise that fulfills when the member is
  254. * removed (or there is no such member), or that is rejected with
  255. * GroupManagerDb.Error for a database error.
  256. */
  257. GroupManager.prototype.removeMemberPromise = function(identity, useSync)
  258. {
  259. return this.database_.deleteMemberPromise(identity, useSync);
  260. };
  261. /**
  262. * Change the name of the schedule for the given member's identity name.
  263. * @param {Name} identity The member's identity name.
  264. * @param {string} scheduleName The new schedule name.
  265. * @param {boolean} useSync (optional) If true then return a SyncPromise which
  266. * is already fulfilled. If omitted or false, this may return a SyncPromise or
  267. * an async Promise.
  268. * @return {Promise|SyncPromise} A promise that fulfills when the member is
  269. * updated, or that is rejected with GroupManagerDb.Error if there's no member
  270. * with the given identity name in the database, or there's no schedule named
  271. * scheduleName.
  272. */
  273. GroupManager.prototype.updateMemberSchedulePromise = function
  274. (identity, scheduleName, useSync)
  275. {
  276. return this.database_.updateMemberSchedulePromise
  277. (identity, scheduleName, useSync);
  278. };
  279. /**
  280. * Delete all the EKeys in the database. The database will keep growing because
  281. * EKeys will keep being added, so this method should be called periodically.
  282. * @param {boolean} useSync (optional) If true then return a SyncPromise which
  283. * is already fulfilled. If omitted or false, this may return a SyncPromise or
  284. * an async Promise.
  285. * @return {Promise|SyncPromise} A promise that fulfills when the EKeys are
  286. * deleted, or that is rejected with GroupManagerDb.Error for a database error.
  287. */
  288. GroupManager.prototype.cleanEKeysPromise = function(useSync)
  289. {
  290. return this.database_.cleanEKeysPromise(useSync);
  291. };
  292. /**
  293. * Calculate an Interval that covers the timeSlot.
  294. * @param {number} timeSlot The time slot to cover as milliseconds since
  295. * Jan 1, 1970 UTC.
  296. * @param {Array<object>} memberKeys First clear memberKeys then fill it with
  297. * the info of members who are allowed to access the interval. memberKeys is an
  298. * array of object where "keyName" is the Name of the public key and "publicKey"
  299. * is the Blob of the public key DER. The memberKeys entries are sorted by
  300. * the entry keyName.
  301. * @param {boolean} useSync (optional) If true then return a SyncPromise which
  302. * is already fulfilled. If omitted or false, this may return a SyncPromise or
  303. * an async Promise.
  304. * @return {Promise|SyncPromise} A promise that returns a new nterval covering
  305. * the time slot, or that is rejected with GroupManagerDb.Error for a database
  306. * error.
  307. */
  308. GroupManager.prototype.calculateIntervalPromise_ = function
  309. (timeSlot, memberKeys, useSync)
  310. {
  311. // Prepare.
  312. var positiveResult = new Interval();
  313. var negativeResult = new Interval();
  314. // Clear memberKeys.
  315. memberKeys.splice(0, memberKeys.length);
  316. var thisManager = this;
  317. // Get the all intervals from the schedules.
  318. return this.database_.listAllScheduleNamesPromise(useSync)
  319. .then(function(scheduleNames) {
  320. // Process the scheduleNames entry at i, and recursively call to process the
  321. // next entry. Return a promise which is resolved when all are processed.
  322. // (We have to make a recursive function to use Promises.)
  323. function processSchedule(i) {
  324. if (i >= scheduleNames.length)
  325. // Finished.
  326. return SyncPromise.resolve();
  327. var scheduleName = scheduleNames[i];
  328. return thisManager.database_.getSchedulePromise(scheduleName, useSync)
  329. .then(function(schedule) {
  330. var result = schedule.getCoveringInterval(timeSlot);
  331. var tempInterval = result.interval;
  332. if (result.isPositive) {
  333. if (!positiveResult.isValid())
  334. positiveResult = tempInterval;
  335. positiveResult.intersectWith(tempInterval);
  336. return thisManager.database_.getScheduleMembersPromise
  337. (scheduleName, useSync)
  338. .then(function(map) {
  339. // Add each entry in map to memberKeys.
  340. for (var iMap = 0; iMap < map.length; ++iMap)
  341. GroupManager.memberKeysAdd_(memberKeys, map[iMap]);
  342. return processSchedule(i + 1);
  343. });
  344. }
  345. else {
  346. if (!negativeResult.isValid())
  347. negativeResult = tempInterval;
  348. negativeResult.intersectWith(tempInterval);
  349. return processSchedule(i + 1);
  350. }
  351. });
  352. }
  353. return processSchedule(0);
  354. })
  355. .then(function() {
  356. if (!positiveResult.isValid())
  357. // Return an invalid interval when there is no member which has an
  358. // interval covering the time slot.
  359. return SyncPromise.resolve(new Interval(false));
  360. // Get the final interval result.
  361. var finalInterval;
  362. if (negativeResult.isValid())
  363. finalInterval = positiveResult.intersectWith(negativeResult);
  364. else
  365. finalInterval = positiveResult;
  366. return SyncPromise.resolve(finalInterval);
  367. });
  368. };
  369. /**
  370. * Add entry to memberKeys, sorted by entry.keyName. If there is already an
  371. * entry with keyName, then don't add.
  372. */
  373. GroupManager.memberKeysAdd_ = function(memberKeys, entry)
  374. {
  375. // Find the index of the first node where the keyName is not less than
  376. // entry.keyName.
  377. var i = 0;
  378. while (i < memberKeys.length) {
  379. var comparison = memberKeys[i].keyName.compare(entry.keyName);
  380. if (comparison == 0)
  381. // A duplicate, so don't add.
  382. return;
  383. if (comparison > 0)
  384. break;
  385. i += 1;
  386. }
  387. memberKeys.splice(i, 0, entry);
  388. };
  389. /**
  390. * Generate an RSA key pair according to keySize_.
  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 an object where
  395. * "privateKeyBlob" is the encoding Blob of the private key and "publicKeyBlob"
  396. * is the encoding Blob of the public key.
  397. */
  398. GroupManager.prototype.generateKeyPairPromise_ = function(useSync)
  399. {
  400. var params = new RsaKeyParams(this.keySize_);
  401. return RsaAlgorithm.generateKeyPromise(params)
  402. .then(function(privateKey) {
  403. var privateKeyBlob = privateKey.getKeyBits();
  404. var publicKey = RsaAlgorithm.deriveEncryptKey(privateKeyBlob);
  405. var publicKeyBlob = publicKey.getKeyBits();
  406. return SyncPromise.resolve
  407. ({ privateKeyBlob: privateKeyBlob, publicKeyBlob: publicKeyBlob });
  408. });
  409. };
  410. /**
  411. * Create an E-KEY Data packet for the given public key.
  412. * @param {string} startTimeStamp The start time stamp string to put in the name.
  413. * @param {string} endTimeStamp The end time stamp string to put in the name.
  414. * @param {Blob} publicKeyBlob A Blob of the public key DER.
  415. * @return The Data packet.
  416. * @throws SecurityException for an error using the security KeyChain.
  417. * @param {boolean} useSync (optional) If true then return a SyncPromise which
  418. * is already fulfilled. If omitted or false, this may return a SyncPromise or
  419. * an async Promise.
  420. * @return {Promise|SyncPromise} A promise that returns the Data packet, or that
  421. * is rejected with SecurityException for an error using the security KeyChain.
  422. */
  423. GroupManager.prototype.createEKeyDataPromise_ = function
  424. (startTimeStamp, endTimeStamp, publicKeyBlob, useSync)
  425. {
  426. var name = new Name(this.namespace_);
  427. name.append(Encryptor.NAME_COMPONENT_E_KEY).append(startTimeStamp)
  428. .append(endTimeStamp);
  429. var data = new Data(name);
  430. data.getMetaInfo().setFreshnessPeriod
  431. (this.freshnessHours_ * GroupManager.MILLISECONDS_IN_HOUR);
  432. data.setContent(publicKeyBlob);
  433. return this.keyChain_.signPromise(data);
  434. };
  435. /**
  436. * Create a D-KEY Data packet with an EncryptedContent for the given private
  437. * key, encrypted with the certificate key.
  438. * @param {string} startTimeStamp The start time stamp string to put in the name.
  439. * @param {string} endTimeStamp The end time stamp string to put in the name.
  440. * @param {Name} keyName The key name to put in the data packet name and the
  441. * EncryptedContent key locator.
  442. * @param {Blob} privateKeyBlob A Blob of the encoded private key.
  443. * @param {Blob} certificateKey The certificate key encoding, used to encrypt
  444. * the private key.
  445. * @param {boolean} useSync (optional) If true then return a SyncPromise which
  446. * is already fulfilled. If omitted or false, this may return a SyncPromise or
  447. * an async Promise.
  448. * @return {Promise|SyncPromise} A promise that returns the Data packet, or that
  449. * is rejected with SecurityException for an error using the security KeyChain.
  450. */
  451. GroupManager.prototype.createDKeyDataPromise_ = function
  452. (startTimeStamp, endTimeStamp, keyName, privateKeyBlob, certificateKey,
  453. useSync)
  454. {
  455. var name = new Name(this.namespace_);
  456. name.append(Encryptor.NAME_COMPONENT_D_KEY);
  457. name.append(startTimeStamp).append(endTimeStamp);
  458. var data = new Data(name);
  459. data.getMetaInfo().setFreshnessPeriod
  460. (this.freshnessHours_ * GroupManager.MILLISECONDS_IN_HOUR);
  461. var encryptParams = new EncryptParams(EncryptAlgorithmType.RsaOaep);
  462. var thisManager = this;
  463. return Encryptor.encryptDataPromise
  464. (data, privateKeyBlob, keyName, certificateKey, encryptParams, useSync)
  465. .catch(function(ex) {
  466. // Consolidate errors such as InvalidKeyException.
  467. return SyncPromise.reject(SecurityException(new Error
  468. ("createDKeyData: Error in encryptData: " + ex)));
  469. })
  470. .then(function() {
  471. return thisManager.keyChain_.signPromise(data);
  472. });
  473. };
  474. /**
  475. * Add the EKey with name eKeyName to the database.
  476. * @param {Name} eKeyName The name of the EKey. This copies the Name.
  477. * @param {Blob} publicKey The encoded public Key of the group key pair.
  478. * @param {Blob} privateKey The encoded private Key of the group key pair.
  479. * @param {boolean} useSync (optional) If true then return a SyncPromise which
  480. * is already fulfilled. If omitted or false, this may return a SyncPromise or
  481. * an async Promise.
  482. * @return {Promise|SyncPromise} A promise that fulfills when the EKey is added,
  483. * or that is rejected with GroupManagerDb.Error if a key with name eKeyName
  484. * already exists in the database, or other database error.
  485. */
  486. GroupManager.prototype.addEKeyPromise_ = function
  487. (eKeyName, publicKey, privateKey, useSync)
  488. {
  489. return this.database_.addEKeyPromise(eKeyName, publicKey, privateKey, useSync);
  490. };
  491. /**
  492. * Get the group key pair with the name eKeyName from the database.
  493. * @param {Name} eKeyName The name of the EKey.
  494. * @param {boolean} useSync (optional) If true then return a SyncPromise which
  495. * is already fulfilled. If omitted or false, this may return a SyncPromise or
  496. * an async Promise.
  497. * @return {Promise|SyncPromise} A promise that returns an object (where
  498. * "publicKey" is the public key Blob and "privateKey" is the private key Blob),
  499. * or that is rejected with GroupManagerDb.Error for a database error.
  500. */
  501. GroupManager.prototype.getEKeyPromise_ = function(eKeyName, useSync)
  502. {
  503. return this.database_.getEKeyPromise(eKeyName, useSync);
  504. };
  505. /**
  506. * Delete the EKey with name eKeyName from the database. If no key with the
  507. * name exists in the database, do nothing.
  508. * @param {Name} eKeyName The name of the EKey.
  509. * @param {boolean} useSync (optional) If true then return a SyncPromise which
  510. * is already fulfilled. If omitted or false, this may return a SyncPromise or
  511. * an async Promise.
  512. * @return {Promise|SyncPromise} A promise that fulfills when the EKey is
  513. * deleted (or there is no such key), or that is rejected with
  514. * GroupManagerDb.Error for a database error.
  515. */
  516. GroupManager.prototype.deleteEKeyPromise_ = function(eKeyName, useSync)
  517. {
  518. return this.database_.deleteEKeyPromise(eKeyName, useSync);
  519. };
  520. GroupManager.MILLISECONDS_IN_HOUR = 3600 * 1000;