1/* 2 * This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 */ 6 7"use strict"; 8 9const EXPORTED_SYMBOLS = ["EnigmailKeyRefreshService"]; 10 11const { XPCOMUtils } = ChromeUtils.import( 12 "resource://gre/modules/XPCOMUtils.jsm" 13); 14 15Cu.importGlobalProperties(["crypto"]); 16 17XPCOMUtils.defineLazyModuleGetters(this, { 18 EnigmailKeyRing: "chrome://openpgp/content/modules/keyRing.jsm", 19 EnigmailKeyServer: "chrome://openpgp/content/modules/keyserver.jsm", 20 EnigmailKeyserverURIs: "chrome://openpgp/content/modules/keyserverUris.jsm", 21 EnigmailLog: "chrome://openpgp/content/modules/log.jsm", 22 Services: "resource://gre/modules/Services.jsm", 23}); 24 25const ONE_HOUR_IN_MILLISEC = 60 * 60 * 1000; 26 27let gTimer = null; 28 29function getTimer() { 30 if (gTimer === null) { 31 gTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); 32 } 33 return gTimer; 34} 35 36function calculateMaxTimeForRefreshInMilliseconds(totalPublicKeys) { 37 const millisecondsAvailableForRefresh = 38 Services.prefs.getIntPref("temp.openpgp.hoursPerWeekEnigmailIsOn") * 39 ONE_HOUR_IN_MILLISEC; 40 return Math.floor(millisecondsAvailableForRefresh / totalPublicKeys); 41} 42 43function calculateWaitTimeInMilliseconds(totalPublicKeys) { 44 const randomNumber = crypto.getRandomValues(new Uint32Array(1)); 45 const maxTimeForRefresh = calculateMaxTimeForRefreshInMilliseconds( 46 totalPublicKeys 47 ); 48 const minDelay = 49 Services.prefs.getIntPref("temp.openpgp.refreshMinDelaySeconds") * 1000; 50 51 EnigmailLog.DEBUG( 52 "keyRefreshService.jsm: Wait time = random number: " + 53 randomNumber + 54 " % max time for refresh: " + 55 maxTimeForRefresh + 56 "\n" 57 ); 58 59 let millisec = randomNumber % maxTimeForRefresh; 60 if (millisec < minDelay) { 61 millisec += minDelay; 62 } 63 64 EnigmailLog.DEBUG( 65 "keyRefreshService.jsm: Time until next refresh in milliseconds: " + 66 millisec + 67 "\n" 68 ); 69 70 return millisec; 71} 72 73function refreshKey() { 74 const timer = getTimer(); 75 refreshWith(EnigmailKeyServer, timer, true); 76} 77 78function restartTimerInOneHour(timer) { 79 timer.initWithCallback( 80 refreshKey, 81 ONE_HOUR_IN_MILLISEC, 82 Ci.nsITimer.TYPE_ONE_SHOT 83 ); 84} 85 86function setupNextRefresh(timer, waitTime) { 87 timer.initWithCallback(refreshKey, waitTime, Ci.nsITimer.TYPE_ONE_SHOT); 88} 89 90function logMissingInformation(keyIdsExist, validKeyserversExist) { 91 if (!keyIdsExist) { 92 EnigmailLog.DEBUG( 93 "keyRefreshService.jsm: No keys available to refresh yet. Will recheck in an hour.\n" 94 ); 95 } 96 if (!validKeyserversExist) { 97 EnigmailLog.DEBUG( 98 "keyRefreshService.jsm: Either no keyservers exist or the protocols specified are invalid. Will recheck in an hour.\n" 99 ); 100 } 101} 102 103function getRandomKeyId(randomNumber) { 104 const keyRingLength = EnigmailKeyRing.getAllKeys().keyList.length; 105 106 if (keyRingLength === 0) { 107 return null; 108 } 109 110 return EnigmailKeyRing.getAllKeys().keyList[randomNumber % keyRingLength] 111 .keyId; 112} 113 114function refreshKeyIfReady(keyserver, readyToRefresh, keyId) { 115 if (readyToRefresh) { 116 EnigmailLog.DEBUG( 117 "keyRefreshService.jsm: refreshing key ID " + keyId + "\n" 118 ); 119 return keyserver.download(keyId); 120 } 121 122 return Promise.resolve(0); 123} 124 125async function refreshWith(keyserver, timer, readyToRefresh) { 126 const keyId = getRandomKeyId(crypto.getRandomValues(new Uint32Array(1))); 127 const keyIdsExist = keyId !== null; 128 const validKeyserversExist = EnigmailKeyserverURIs.validKeyserversExist(); 129 const ioService = Services.io; 130 131 if (keyIdsExist && validKeyserversExist) { 132 if (ioService && !ioService.offline) { 133 // don't try to refresh if we are offline 134 await refreshKeyIfReady(keyserver, readyToRefresh, keyId); 135 } else { 136 EnigmailLog.DEBUG( 137 "keyRefreshService.jsm: offline - not refreshing any key\n" 138 ); 139 } 140 const waitTime = calculateWaitTimeInMilliseconds( 141 EnigmailKeyRing.getAllKeys().keyList.length 142 ); 143 setupNextRefresh(timer, waitTime); 144 } else { 145 logMissingInformation(keyIdsExist, validKeyserversExist); 146 restartTimerInOneHour(timer); 147 } 148} 149 150/** 151 * Starts a process to continuously refresh keys on a random time interval and in random order. 152 * 153 * The default time period for all keys to be refreshed is one week, although the user can specifically set this in their preferences 154 * The wait time to refresh the next key is selected at random, from a range of zero milliseconds to the maximum time to refresh a key 155 * 156 * The maximum time to refresh a single key is calculated by averaging the total refresh time by the total number of public keys to refresh 157 * For example, if a user has 12 public keys to refresh, the maximum time to refresh a single key (by default) will be: milliseconds per week divided by 12 158 * 159 * This service does not keep state, it will restart each time Enigmail is initialized. 160 * 161 * @param keyserver | dependency injected for testability 162 */ 163function start(keyserver) { 164 if (Services.prefs.getBoolPref("temp.openpgp.keyRefreshOn")) { 165 EnigmailLog.DEBUG("keyRefreshService.jsm: Started\n"); 166 const timer = getTimer(); 167 refreshWith(keyserver, timer, false); 168 } 169} 170 171/* 172 This module intializes the continuous key refresh functionality. This includes randomly selecting th key to refresh and the timing to wait between each refresh 173*/ 174 175var EnigmailKeyRefreshService = { 176 start, 177}; 178