/// <summary> /// This event handler gets called when the QuickPass timer has elapsed, /// indicating that the QuickPass belonging to this specific timer shall be cleared. /// </summary> private void QuickPassTimerElapsed(object sender, ElapsedEventArgs e) { QuickPassItem qpi = null; Timer timer = (Timer)sender; // Get the QuickPassInfo that this Timer belongs to lock (_dataSyncObj) { foreach (var item in _quickPassItems) { if (timer == item.Value.Timer) { qpi = item.Value; break; } } } Log.Information("QuickPass timer elapsed for identity id {IdentityUniqueId}", qpi?.IdentityUniqueId); // If such a QuickPassInfo item exists, clear it if (qpi != null) { ClearQuickPass(qpi.IdentityUniqueId, QuickPassClearReason.QuickPassTimeout); } }
/// <summary> /// Clears the QuickPass entries of all identities from memory. After calling /// this method, SQRL will ask for the full master password again for all /// available identities. /// </summary> /// <param name="identityUniqueId">The unique id (block 0) of the identity for /// which to clear the QuickPass entry.</param> /// <param name="reason">The reason for clearing the QuickPass.</param> /// <param name="fireClearedEvent">If set to <c>true</c>, a /// <c>QuickPassCleared</c> event will be fired if the QuickPass for the given /// <paramref name="identityUniqueId"/> could be successfully cleared.</param> public bool ClearQuickPass(string identityUniqueId, QuickPassClearReason reason, bool fireClearedEvent = true) { Log.Verbose("{MethodName} called", nameof(ClearQuickPass)); lock (_dataSyncObj) { if (!_quickPassItems.ContainsKey(identityUniqueId)) { Log.Warning("No QuickPass entry found for identity {IdentityUniqueId}", identityUniqueId); return(false); } QuickPassItem qpi = _quickPassItems[identityUniqueId]; if (reason == QuickPassClearReason.IdentityChange && !ClearQuickPassOnIdentityChange) { return(false); } if (reason == QuickPassClearReason.IdleTimeout && !qpi.ClearQuickPassOnIdle) { return(false); } if (reason == QuickPassClearReason.EnterBlankingState && !qpi.ClearQuickPassOnSleep) { return(false); } if (reason == QuickPassClearReason.UserSwitching && !qpi.ClearQuickPassOnSwitchingUser) { return(false); } // First, stop the QuickPass timer qpi.Timer.Stop(); // Then, overwrite the encrypted imk and ilk so that we don't // leave any traces of key material in RAM. qpi.EncryptedImk.ZeroFill(); qpi.EncryptedIlk.ZeroFill(); // Delete the QuickPass entry from our dictionary _quickPassItems.Remove(identityUniqueId); } Log.Information("QuickPass entry cleared. ID: {IdentityUniqueId} Reason: {Reason}", identityUniqueId, reason.ToString()); // Finally, fire the QuickPassCleared event if (fireClearedEvent) { QuickPassCleared?.Invoke(this, new QuickPassClearedEventArgs(new List <string>() { identityUniqueId })); Log.Information("Firing QuickPassCleared event"); } return(true); }
/// <summary> /// Creates a QuickPass from the given <paramref name="password"/> and /// <paramref name="imk"/> using the QuickPass settings stored in /// <paramref name="identity"/>, stores it in memory and establishes a /// timer that will clear the QuickPass after the timeout set forth in /// <paramref name="identity"/>'s QuickPass settings. /// </summary> /// <param name="password">The full identity master password.</param> /// <param name="imk">The identity's unencrypted Identity Master Key (IMK).</param> /// <param name="ilk">The identity's unencrypted Identity Lock Key (ILK).</param> /// <param name="identity">The identity that the QuickPass should be set for.</param> /// <param name="progress">An object implementing the IProgress interface for tracking the operation's progress (optional).</param> /// <param name="progressText">A string representing a text descrition for the progress indicator (optional).</param> public async void SetQuickPass(string password, byte[] imk, byte[] ilk, SQRLIdentity identity, IProgress <KeyValuePair <int, string> > progress = null, string progressText = null) { if (string.IsNullOrEmpty(password)) { Log.Warning("Can't use QuickPass on an empty password, aborting SetQuickPass()!"); return; } QuickPassItem qpi = new QuickPassItem() { EstablishedDate = DateTime.Now, QuickPassLength = identity.Block1.HintLength, IdentityUniqueId = identity.Block0.UniqueIdentifier.ToHex(), ScryptRandomSalt = SodiumCore.GetRandomBytes(16), Nonce = SodiumCore.GetRandomBytes(24), QuickPassTimeoutSecs = identity.Block1.PwdTimeoutMins * 60, ClearQuickPassOnIdle = identity.Block1.OptionFlags.ClearQuickPassOnIdle, ClearQuickPassOnSleep = identity.Block1.OptionFlags.ClearQuickPassOnSleep, ClearQuickPassOnSwitchingUser = identity.Block1.OptionFlags.ClearQuickPassOnSwitchingUser, Timer = new Timer() }; qpi.Timer.Enabled = false; qpi.Timer.AutoReset = false; // Dont restart timer after calling elapsed qpi.Timer.Interval = QP_GENERAL_TIMEOUT_SEC; qpi.Timer.Elapsed += QuickPassTimerElapsed; string quickPass = password.Substring(0, qpi.QuickPassLength); var enScryptResult = await SQRL.EnScryptTime( quickPass, qpi.ScryptRandomSalt, (int)Math.Pow(2, 9), QP_KEYDERIV_SEC, progress, progressText); qpi.ScryptIterationCount = enScryptResult.IterationCount; qpi.EncryptedImk = StreamEncryption.Encrypt(imk, qpi.Nonce, enScryptResult.Key); qpi.EncryptedIlk = StreamEncryption.Encrypt(ilk, qpi.Nonce, enScryptResult.Key); // If we already have a QuickPass entry for this identity, remove it first if (HasQuickPass(qpi.IdentityUniqueId)) { ClearQuickPass(qpi.IdentityUniqueId, QuickPassClearReason.Unspecified); } // Now, add the QuickPass item to our list and start the timer lock (_dataSyncObj) { _quickPassItems.Add(qpi.IdentityUniqueId, qpi); qpi.Timer.Start(); } Log.Information("QuickPass set for identity {IdentityUniqueId}", qpi.IdentityUniqueId); }
/// <summary> /// Decrypts the Identity Master Key (IMK) for for the given <paramref name="identityUniqueId"/> /// using the provided <paramref name="quickPass"/> and returns it. If <c>null</c> is passed for /// <paramref name="identityUniqueId"/>, the decrypted IML for the current identity will be returned. /// </summary> /// <param name="quickPass">The QuickPass (first x characters from the identity's master password /// which was used to encrypt the IMK when setting the QuickPass entry for the identity.</param> /// <param name="identityUniqueId">The hex representation of the identity's unique id (block 0), /// or <c>null</c> if you want to decrypt the current identity's IMK.</param> /// <param name="progress">An object implementing the IProgress interface for tracking the operation's progress (optional).</param> /// <param name="progressText">A string representing a text descrition for the progress indicator (optional).</param> /// <returns>The decrypted block 1 keys (IMK, ILK) for the given <paramref name="identityUniqueId"/>, or /// <c>null</c> if no such QuickPass entry exists.</returns> public async Task <QuickPassDecryptedKeys> GetQuickPassDecryptedImk(string quickPass, string identityUniqueId = null, IProgress <KeyValuePair <int, string> > progress = null, string progressText = null) { QuickPassItem qpi = null; if (identityUniqueId == null) { if (_identityManager.CurrentIdentity == null) { return(null); } identityUniqueId = _identityManager.CurrentIdentity?.Block0?.UniqueIdentifier?.ToHex(); } if (identityUniqueId == null) { Log.Error("Could not resolve current identity in {MethodName}, throwing Exception!", nameof(GetQuickPassDecryptedImk)); throw new InvalidOperationException("Cannot return QuickPass-decrypted IMK without an identity!"); } lock (_dataSyncObj) { if (!_quickPassItems.ContainsKey(identityUniqueId)) { Log.Warning("No identity found for id {IdentityUniqueId} in {MethodName}", identityUniqueId, nameof(GetQuickPassDecryptedImk)); return(null); } qpi = _quickPassItems[identityUniqueId]; } byte[] key = await SQRL.EnScryptCT(quickPass, qpi.ScryptRandomSalt, (int)Math.Pow(2, 9), qpi.ScryptIterationCount, progress, progressText); byte[] decryptedImk = StreamEncryption.Decrypt(qpi.EncryptedImk, qpi.Nonce, key); byte[] decryptedIlk = StreamEncryption.Decrypt(qpi.EncryptedIlk, qpi.Nonce, key); Log.Information("QuickPass retrieved for identity {IdentityUniqueId}", identityUniqueId); return(new QuickPassDecryptedKeys(decryptedImk, decryptedIlk)); }