From 551066ed9a255bd47c1c5789ec1998fda64bd5aa Mon Sep 17 00:00:00 2001 From: vnugent Date: Thu, 12 Jan 2023 17:47:40 -0500 Subject: Large project reorder and consolidation --- .../MFA/FidoAuthenticatorSelection.cs | 40 --- .../MFA/FidoRegClientData.cs | 40 --- .../MFA/FidoRegistrationMessage.cs | 52 --- VNLib.Plugins.Essentials.Accounts/MFA/MFAConfig.cs | 103 ------ VNLib.Plugins.Essentials.Accounts/MFA/MFAType.cs | 31 -- .../MFA/MFAUpgrade.cs | 65 ---- .../MFA/UserMFAExtensions.cs | 384 --------------------- 7 files changed, 715 deletions(-) delete mode 100644 VNLib.Plugins.Essentials.Accounts/MFA/FidoAuthenticatorSelection.cs delete mode 100644 VNLib.Plugins.Essentials.Accounts/MFA/FidoRegClientData.cs delete mode 100644 VNLib.Plugins.Essentials.Accounts/MFA/FidoRegistrationMessage.cs delete mode 100644 VNLib.Plugins.Essentials.Accounts/MFA/MFAConfig.cs delete mode 100644 VNLib.Plugins.Essentials.Accounts/MFA/MFAType.cs delete mode 100644 VNLib.Plugins.Essentials.Accounts/MFA/MFAUpgrade.cs delete mode 100644 VNLib.Plugins.Essentials.Accounts/MFA/UserMFAExtensions.cs (limited to 'VNLib.Plugins.Essentials.Accounts/MFA') diff --git a/VNLib.Plugins.Essentials.Accounts/MFA/FidoAuthenticatorSelection.cs b/VNLib.Plugins.Essentials.Accounts/MFA/FidoAuthenticatorSelection.cs deleted file mode 100644 index 0ea6dad..0000000 --- a/VNLib.Plugins.Essentials.Accounts/MFA/FidoAuthenticatorSelection.cs +++ /dev/null @@ -1,40 +0,0 @@ -/* -* Copyright (c) 2022 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Plugins.Essentials.Accounts -* File: FidoAuthenticatorSelection.cs -* -* FidoAuthenticatorSelection.cs is part of VNLib.Plugins.Essentials.Accounts which is part of the larger -* VNLib collection of libraries and utilities. -* -* VNLib.Plugins.Essentials.Accounts is free software: you can redistribute it and/or modify -* it under the terms of the GNU Affero General Public License as -* published by the Free Software Foundation, either version 3 of the -* License, or (at your option) any later version. -* -* VNLib.Plugins.Essentials.Accounts is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU Affero General Public License for more details. -* -* You should have received a copy of the GNU Affero General Public License -* along with this program. If not, see https://www.gnu.org/licenses/. -*/ - -using System.Text.Json.Serialization; - -#nullable enable - -namespace VNLib.Plugins.Essentials.Accounts.MFA -{ - class FidoAuthenticatorSelection - { - [JsonPropertyName("requireResidentKey")] - public bool RequireResidentKey { get; set; } = false; - [JsonPropertyName("authenticatorAttachment")] - public string? AuthenticatorAttachment { get; set; } = "cross-platform"; - [JsonPropertyName("userVerification")] - public string? UserVerification { get; set; } = "required"; - } -} diff --git a/VNLib.Plugins.Essentials.Accounts/MFA/FidoRegClientData.cs b/VNLib.Plugins.Essentials.Accounts/MFA/FidoRegClientData.cs deleted file mode 100644 index 1ef7d59..0000000 --- a/VNLib.Plugins.Essentials.Accounts/MFA/FidoRegClientData.cs +++ /dev/null @@ -1,40 +0,0 @@ -/* -* Copyright (c) 2022 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Plugins.Essentials.Accounts -* File: FidoRegClientData.cs -* -* FidoRegClientData.cs is part of VNLib.Plugins.Essentials.Accounts which is part of the larger -* VNLib collection of libraries and utilities. -* -* VNLib.Plugins.Essentials.Accounts is free software: you can redistribute it and/or modify -* it under the terms of the GNU Affero General Public License as -* published by the Free Software Foundation, either version 3 of the -* License, or (at your option) any later version. -* -* VNLib.Plugins.Essentials.Accounts is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU Affero General Public License for more details. -* -* You should have received a copy of the GNU Affero General Public License -* along with this program. If not, see https://www.gnu.org/licenses/. -*/ - -using System.Text.Json.Serialization; - -#nullable enable - -namespace VNLib.Plugins.Essentials.Accounts.MFA -{ - internal class FidoRegClientData - { - [JsonPropertyName("challenge")] - public string? Challenge { get; set; } - [JsonPropertyName("origin")] - public string? Origin { get; set; } - [JsonPropertyName("type")] - public string? Type { get; set; } - } -} diff --git a/VNLib.Plugins.Essentials.Accounts/MFA/FidoRegistrationMessage.cs b/VNLib.Plugins.Essentials.Accounts/MFA/FidoRegistrationMessage.cs deleted file mode 100644 index e8fbcc4..0000000 --- a/VNLib.Plugins.Essentials.Accounts/MFA/FidoRegistrationMessage.cs +++ /dev/null @@ -1,52 +0,0 @@ -/* -* Copyright (c) 2022 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Plugins.Essentials.Accounts -* File: FidoRegistrationMessage.cs -* -* FidoRegistrationMessage.cs is part of VNLib.Plugins.Essentials.Accounts which is part of the larger -* VNLib collection of libraries and utilities. -* -* VNLib.Plugins.Essentials.Accounts is free software: you can redistribute it and/or modify -* it under the terms of the GNU Affero General Public License as -* published by the Free Software Foundation, either version 3 of the -* License, or (at your option) any later version. -* -* VNLib.Plugins.Essentials.Accounts is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU Affero General Public License for more details. -* -* You should have received a copy of the GNU Affero General Public License -* along with this program. If not, see https://www.gnu.org/licenses/. -*/ - -using System.Text.Json.Serialization; - -#nullable enable - -namespace VNLib.Plugins.Essentials.Accounts.MFA -{ - /// - /// Represents a fido device registration message to be sent - /// to a currently signed in user - /// - class FidoRegistrationMessage - { - [JsonPropertyName("id")] - public string? GuidUserId { get; set; } - [JsonPropertyName("challenge")] - public string? Base64Challenge { get; set; } = null; - [JsonPropertyName("timeout")] - public int Timeout { get; set; } = 60000; - [JsonPropertyName("cose_alg")] - public int CoseAlgNumber { get; set; } - [JsonPropertyName("rp_name")] - public string? SiteName { get; set; } - [JsonPropertyName("attestation")] - public string? AttestationType { get; set; } = "none"; - [JsonPropertyName("authenticatorSelection")] - public FidoAuthenticatorSelection? AuthSelection { get; set; } = new(); - } -} diff --git a/VNLib.Plugins.Essentials.Accounts/MFA/MFAConfig.cs b/VNLib.Plugins.Essentials.Accounts/MFA/MFAConfig.cs deleted file mode 100644 index 03d5a20..0000000 --- a/VNLib.Plugins.Essentials.Accounts/MFA/MFAConfig.cs +++ /dev/null @@ -1,103 +0,0 @@ -/* -* Copyright (c) 2022 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Plugins.Essentials.Accounts -* File: MFAConfig.cs -* -* MFAConfig.cs is part of VNLib.Plugins.Essentials.Accounts which is part of the larger -* VNLib collection of libraries and utilities. -* -* VNLib.Plugins.Essentials.Accounts is free software: you can redistribute it and/or modify -* it under the terms of the GNU Affero General Public License as -* published by the Free Software Foundation, either version 3 of the -* License, or (at your option) any later version. -* -* VNLib.Plugins.Essentials.Accounts is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU Affero General Public License for more details. -* -* You should have received a copy of the GNU Affero General Public License -* along with this program. If not, see https://www.gnu.org/licenses/. -*/ - -using System; -using System.Linq; -using System.Text.Json; -using System.Collections.Generic; - -using VNLib.Hashing; -using VNLib.Utils.Extensions; -using VNLib.Hashing.IdentityUtility; - -namespace VNLib.Plugins.Essentials.Accounts.MFA -{ - internal class MFAConfig - { - public ReadOnlyJsonWebKey? MFASecret { get; set; } - - public bool TOTPEnabled { get; } - public string? IssuerName { get; } - public TimeSpan TOTPPeriod { get; } - public HashAlg TOTPAlg { get; } - public int TOTPDigits { get; } - public int TOTPSecretBytes { get; } - public int TOTPTimeWindowSteps { get; } - - - public bool FIDOEnabled { get; } - public int FIDOChallangeSize { get; } - public int FIDOTimeout { get; } - public string? FIDOSiteName { get; } - public string? FIDOAttestationType { get; } - public FidoAuthenticatorSelection? FIDOAuthSelection { get; } - - public TimeSpan UpgradeValidFor { get; } - public int NonceLenBytes { get; } - - public MFAConfig(IReadOnlyDictionary conf) - { - UpgradeValidFor = conf["upgrade_expires_secs"].GetTimeSpan(TimeParseType.Seconds); - NonceLenBytes = conf["nonce_size"].GetInt32(); - string siteName = conf["site_name"].GetString() ?? throw new KeyNotFoundException("Missing required key 'site_name' in 'mfa' config"); - - //Totp setup - if (conf.TryGetValue("totp", out JsonElement totpEl)) - { - IReadOnlyDictionary totp = totpEl.EnumerateObject().ToDictionary(k => k.Name, k => k.Value); - - //Get totp config - IssuerName = siteName; - //Get alg name - string TOTPAlgName = totp["algorithm"].GetString()?.ToUpper() ?? throw new KeyNotFoundException("Missing required key 'algorithm' in plugin 'mfa' config"); - //Parse from enum string - TOTPAlg = Enum.Parse(TOTPAlgName); - - - TOTPDigits = totp["digits"].GetInt32(); - TOTPPeriod = TimeSpan.FromSeconds(totp["period_secs"].GetInt32()); - TOTPSecretBytes = totp["secret_size"].GetInt32(); - TOTPTimeWindowSteps = totp["window_size"].GetInt32(); - //Set enabled flag - TOTPEnabled = true; - } - //Fido setup - if(conf.TryGetValue("fido", out JsonElement fidoEl)) - { - IReadOnlyDictionary fido = fidoEl.EnumerateObject().ToDictionary(k => k.Name, k => k.Value); - FIDOChallangeSize = fido["challenge_size"].GetInt32(); - FIDOAttestationType = fido["attestation"].GetString(); - FIDOTimeout = fido["timeout"].GetInt32(); - FIDOSiteName = siteName; - //Deserailze a - if(fido.TryGetValue("authenticatorSelection", out JsonElement authSel)) - { - FIDOAuthSelection = authSel.Deserialize(); - } - //Set enabled flag - FIDOEnabled = true; - } - } - } -} diff --git a/VNLib.Plugins.Essentials.Accounts/MFA/MFAType.cs b/VNLib.Plugins.Essentials.Accounts/MFA/MFAType.cs deleted file mode 100644 index 208eea3..0000000 --- a/VNLib.Plugins.Essentials.Accounts/MFA/MFAType.cs +++ /dev/null @@ -1,31 +0,0 @@ -/* -* Copyright (c) 2022 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Plugins.Essentials.Accounts -* File: MFAType.cs -* -* MFAType.cs is part of VNLib.Plugins.Essentials.Accounts which is part of the larger -* VNLib collection of libraries and utilities. -* -* VNLib.Plugins.Essentials.Accounts is free software: you can redistribute it and/or modify -* it under the terms of the GNU Affero General Public License as -* published by the Free Software Foundation, either version 3 of the -* License, or (at your option) any later version. -* -* VNLib.Plugins.Essentials.Accounts is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU Affero General Public License for more details. -* -* You should have received a copy of the GNU Affero General Public License -* along with this program. If not, see https://www.gnu.org/licenses/. -*/ - -namespace VNLib.Plugins.Essentials.Accounts.MFA -{ - public enum MFAType - { - TOTP, FIDO, PGP - } -} diff --git a/VNLib.Plugins.Essentials.Accounts/MFA/MFAUpgrade.cs b/VNLib.Plugins.Essentials.Accounts/MFA/MFAUpgrade.cs deleted file mode 100644 index 5577d51..0000000 --- a/VNLib.Plugins.Essentials.Accounts/MFA/MFAUpgrade.cs +++ /dev/null @@ -1,65 +0,0 @@ -/* -* Copyright (c) 2022 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Plugins.Essentials.Accounts -* File: MFAUpgrade.cs -* -* MFAUpgrade.cs is part of VNLib.Plugins.Essentials.Accounts which is part of the larger -* VNLib collection of libraries and utilities. -* -* VNLib.Plugins.Essentials.Accounts is free software: you can redistribute it and/or modify -* it under the terms of the GNU Affero General Public License as -* published by the Free Software Foundation, either version 3 of the -* License, or (at your option) any later version. -* -* VNLib.Plugins.Essentials.Accounts is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU Affero General Public License for more details. -* -* You should have received a copy of the GNU Affero General Public License -* along with this program. If not, see https://www.gnu.org/licenses/. -*/ - -using System.Text.Json.Serialization; - -#nullable enable - -namespace VNLib.Plugins.Essentials.Accounts.MFA -{ - internal class MFAUpgrade - { - /// - /// The login's client id specifier - /// - [JsonPropertyName("cid")] - public string? ClientID { get; set; } - /// - /// The id of the user that is requesting a login - /// - [JsonPropertyName("uname")] - public string? UserName{ get; set; } - /// - /// The of the upgrade request - /// - [JsonPropertyName("type")] - public MFAType Type { get; set; } - /// - /// The a base64 encoded string of the user's - /// public key - /// - [JsonPropertyName("pubkey")] - public string? Base64PubKey { get; set; } - /// - /// The user's specified language - /// - [JsonPropertyName("lang")] - public string? ClientLocalLanguage { get; set; } - /// - /// The encrypted password token for the client - /// - [JsonPropertyName("cd")] - public string? PwClientData { get; set; } - } -} diff --git a/VNLib.Plugins.Essentials.Accounts/MFA/UserMFAExtensions.cs b/VNLib.Plugins.Essentials.Accounts/MFA/UserMFAExtensions.cs deleted file mode 100644 index 1ec9953..0000000 --- a/VNLib.Plugins.Essentials.Accounts/MFA/UserMFAExtensions.cs +++ /dev/null @@ -1,384 +0,0 @@ -/* -* Copyright (c) 2022 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Plugins.Essentials.Accounts -* File: UserMFAExtensions.cs -* -* UserMFAExtensions.cs is part of VNLib.Plugins.Essentials.Accounts which is part of the larger -* VNLib collection of libraries and utilities. -* -* VNLib.Plugins.Essentials.Accounts is free software: you can redistribute it and/or modify -* it under the terms of the GNU Affero General Public License as -* published by the Free Software Foundation, either version 3 of the -* License, or (at your option) any later version. -* -* VNLib.Plugins.Essentials.Accounts is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU Affero General Public License for more details. -* -* You should have received a copy of the GNU Affero General Public License -* along with this program. If not, see https://www.gnu.org/licenses/. -*/ - -using System; -using System.Linq; -using System.Text.Json; -using System.Collections.Generic; -using System.Security.Cryptography; -using System.Text.Json.Serialization; -using System.Diagnostics.CodeAnalysis; - -using VNLib.Hashing; -using VNLib.Utils; -using VNLib.Utils.Memory; -using VNLib.Utils.Extensions; -using VNLib.Hashing.IdentityUtility; -using VNLib.Plugins.Essentials.Users; -using VNLib.Plugins.Essentials.Sessions; -using VNLib.Plugins.Extensions.Loading; - -namespace VNLib.Plugins.Essentials.Accounts.MFA -{ - - internal static class UserMFAExtensions - { - public const string WEBAUTHN_KEY_ENTRY = "mfa.fido"; - public const string TOTP_KEY_ENTRY = "mfa.totp"; - public const string PGP_PUB_KEY = "mfa.pgpp"; - public const string SESSION_SIG_KEY = "mfa.sig"; - - /// - /// Determines if the user account has an - /// - /// - /// True if any form of MFA is enabled for the user account - public static bool MFAEnabled(this IUser user) - { - return !(string.IsNullOrWhiteSpace(user[TOTP_KEY_ENTRY]) && string.IsNullOrWhiteSpace(user[WEBAUTHN_KEY_ENTRY])); - } - - #region totp - - /// - /// Recovers the base32 encoded TOTP secret for the current user - /// - /// - /// The base32 encoded TOTP secret, or an emtpy string (user spec) if not set - public static string MFAGetTOTPSecret(this IUser user) => user[TOTP_KEY_ENTRY]; - - /// - /// Stores or removes the current user's TOTP secret, stored in base32 format - /// - /// - /// The base32 encoded TOTP secret - public static void MFASetTOTPSecret(this IUser user, string? secret) => user[TOTP_KEY_ENTRY] = secret!; - - - /// - /// Generates/overwrites the current user's TOTP secret entry and returns a - /// byte array of the generated secret bytes - /// - /// The to modify the TOTP configuration of - /// The raw secret that was encrypted and stored in the , to send to the client - /// - public static byte[] MFAGenreateTOTPSecret(this IUser user, MFAConfig config) - { - //Generate a random key - byte[] newSecret = RandomHash.GetRandomBytes(config.TOTPSecretBytes); - //Store secret in user storage - user.MFASetTOTPSecret(VnEncoding.ToBase32String(newSecret, false)); - //return the raw secret bytes - return newSecret; - } - - /// - /// Verfies the supplied TOTP code against the current user's totp codes - /// This method should not be used for verifying TOTP codes for authentication - /// - /// The user account to verify the TOTP code against - /// The code to verify - /// A readonly referrence to the MFA configuration structure - /// True if the user has TOTP configured and code matches against its TOTP secret entry, false otherwise - /// - /// - public static bool VerifyTOTP(this MFAConfig config, IUser user, uint code) - { - //Get the base32 TOTP secret for the user and make sure its actually set - string base32Secret = user.MFAGetTOTPSecret(); - if (string.IsNullOrWhiteSpace(base32Secret)) - { - return false; - } - //Alloc buffer with zero o - using UnsafeMemoryHandle buffer = Memory.UnsafeAlloc(base32Secret.Length, true); - ERRNO count = VnEncoding.TryFromBase32Chars(base32Secret, buffer); - //Verify the TOTP using the decrypted secret - return count && VerifyTOTP(code, buffer.AsSpan(0, count), config); - } - - private static bool VerifyTOTP(uint totpCode, ReadOnlySpan userSecret, MFAConfig config) - { - //A basic attempt at a constant time TOTP verification, run the calculation a fixed number of times, regardless of the resutls - bool codeMatches = false; - - //cache current time - DateTimeOffset currentUtc = DateTimeOffset.UtcNow; - //Start the current window with the minimum window - int currenStep = -config.TOTPTimeWindowSteps; - Span stepBuffer = stackalloc byte[sizeof(long)]; - Span hashBuffer = stackalloc byte[(int)config.TOTPAlg]; - //Run the loop at least once to allow a 0 step tight window - do - { - //Calculate the window by multiplying the window by the current step, then add it to the current time offset to produce a new window - DateTimeOffset window = currentUtc.Add(config.TOTPPeriod.Multiply(currenStep)); - //calculate the time step - long timeStep = (long)Math.Floor(window.ToUnixTimeSeconds() / config.TOTPPeriod.TotalSeconds); - //try to compute the hash - _ = BitConverter.TryWriteBytes(stepBuffer, timeStep) ? 0 : throw new InternalBufferTooSmallException("Failed to format TOTP time step"); - //If platform is little endian, reverse the byte order - if (BitConverter.IsLittleEndian) - { - stepBuffer.Reverse(); - } - ERRNO result = ManagedHash.ComputeHmac(userSecret, stepBuffer, hashBuffer, config.TOTPAlg); - //try to compute the hash of the time step - if (result < 1) - { - throw new InternalBufferTooSmallException("Failed to compute TOTP time step hash because the buffer was too small"); - } - //Hash bytes - ReadOnlySpan hash = hashBuffer[..(int)result]; - //compute the TOTP code and compare it to the supplied, then store the result - codeMatches |= (totpCode == CalcTOTPCode(hash, config)); - //next step - currenStep++; - } while (currenStep <= config.TOTPTimeWindowSteps); - - return codeMatches; - } - - private static uint CalcTOTPCode(ReadOnlySpan hash, MFAConfig config) - { - //Calculate the offset, RFC defines, the lower 4 bits of the last byte in the hash output - byte offset = (byte)(hash[^1] & 0x0Fu); - - uint TOTPCode; - if (BitConverter.IsLittleEndian) - { - //Store the code components - TOTPCode = (hash[offset] & 0x7Fu) << 24 | (hash[offset + 1] & 0xFFu) << 16 | (hash[offset + 2] & 0xFFu) << 8 | hash[offset + 3] & 0xFFu; - } - else - { - //Store the code components (In reverse order for big-endian machines) - TOTPCode = (hash[offset + 3] & 0x7Fu) << 24 | (hash[offset + 2] & 0xFFu) << 16 | (hash[offset + 1] & 0xFFu) << 8 | hash[offset] & 0xFFu; - } - //calculate the modulus value - TOTPCode %= (uint)Math.Pow(10, config.TOTPDigits); - return TOTPCode; - } - - #endregion - - #region loading - - const string MFA_CONFIG_KEY = "mfa"; - - /// - /// Gets the plugins ambient if loaded, or loads it if required. This class will - /// be unloaded when the plugin us unloaded. - /// - /// - /// The ambient - /// - /// - /// - public static MFAConfig? GetMfaConfig(this PluginBase plugin) - { - static MFAConfig? LoadMfaConfig(PluginBase pbase) - { - //Try to get the configuration object - IReadOnlyDictionary? conf = pbase.TryGetConfig(MFA_CONFIG_KEY); - - if (conf == null) - { - return null; - } - //Init mfa config - MFAConfig mfa = new(conf); - - //Recover secret from config and dangerous 'lazy load' - _ = pbase.DeferTask(async () => - { - mfa.MFASecret = await pbase.TryGetSecretAsync("mfa_secret").ToJsonWebKey(); - - }, 50); - - return mfa; - } - - plugin.ThrowIfUnloaded(); - //Get/load the passwords one time only - return LoadingExtensions.GetOrCreateSingleton(plugin, LoadMfaConfig); - } - - #endregion - - #region pgp - - private class PgpMfaCred - { - [JsonPropertyName("p")] - public string? SpkiPublicKey { get; set; } - - [JsonPropertyName("c")] - public string? CurveFriendlyName { get; set; } - } - - - /// - /// Gets the stored PGP public key for the user - /// - /// - /// The stored PGP signature key - public static string MFAGetPGPPubKey(this IUser user) => user[PGP_PUB_KEY]; - - public static void MFASetPGPPubKey(this IUser user, string? pubKey) => user[PGP_PUB_KEY] = pubKey!; - - public static void VerifySignedData(string data) - { - - } - - #endregion - - #region webauthn - - #endregion - - /// - /// Recovers a signed MFA upgrade JWT and verifies its authenticity, and confrims its not expired, - /// then recovers the upgrade mssage - /// - /// - /// The signed JWT upgrade message - /// The recovered upgrade - /// The stored base64 encoded signature from the session that requested an upgrade - /// True if the upgrade was verified, not expired, and was recovered from the signed message, false otherwise - public static bool RecoverUpgrade(this MFAConfig config, ReadOnlySpan upgradeJwtString, ReadOnlySpan base64sessionSig, [NotNullWhen(true)] out MFAUpgrade? upgrade) - { - //Verifies a jwt stored signature against the actual signature - static bool VerifyStoredSig(ReadOnlySpan base64string, ReadOnlySpan signature) - { - using UnsafeMemoryHandle buffer = Memory.UnsafeAlloc(base64string.Length, true); - //Recover base64 - ERRNO count = VnEncoding.TryFromBase64Chars(base64string, buffer.Span); - //Compare - return CryptographicOperations.FixedTimeEquals(signature, buffer.Span[..(int)count]); - } - - //Verify config secret - _ = config.MFASecret ?? throw new InvalidOperationException("MFA config is missing required upgrade signing key"); - - upgrade = null; - - //Parse jwt - using JsonWebToken jwt = JsonWebToken.Parse(upgradeJwtString); - - if (!jwt.VerifyFromJwk(config.MFASecret)) - { - return false; - } - - if(!VerifyStoredSig(base64sessionSig, jwt.SignatureData)) - { - return false; - } - - //get request body - using JsonDocument doc = jwt.GetPayload(); - //Recover issued at time - DateTimeOffset iat = DateTimeOffset.FromUnixTimeMilliseconds(doc.RootElement.GetProperty("iat").GetInt64()); - //Verify its not timed out - if (iat.Add(config.UpgradeValidFor) < DateTimeOffset.UtcNow) - { - //expired - return false; - } - - //Recover the upgrade message - upgrade = doc.RootElement.GetProperty("upgrade").Deserialize(); - return upgrade != null; - } - - - /// - /// Generates an upgrade for the requested user, using the highest prirotiy method - /// - /// The message from the user requesting the login - /// A signed upgrade message the client will pass back to the server after the MFA verification - /// - public static Tuple? MFAGetUpgradeIfEnabled(this IUser user, MFAConfig? conf, LoginMessage login, string pwClientData) - { - //Webauthn config - - - //Search for totp secret entry - string base32Secret = user.MFAGetTOTPSecret(); - - //Check totp entry - if (!string.IsNullOrWhiteSpace(base32Secret)) - { - //Verify config secret - _ = conf?.MFASecret ?? throw new InvalidOperationException("MFA config is missing required upgrade signing key"); - - //setup the upgrade - MFAUpgrade upgrade = new() - { - //Set totp upgrade type - Type = MFAType.TOTP, - //Store login message details - UserName = login.UserName, - ClientID = login.ClientID, - Base64PubKey = login.ClientPublicKey, - ClientLocalLanguage = login.LocalLanguage, - PwClientData = pwClientData - }; - - //Init jwt for upgrade - return GetUpgradeMessage(upgrade, conf.MFASecret, conf.UpgradeValidFor); - } - return null; - } - - private static Tuple GetUpgradeMessage(MFAUpgrade upgrade, ReadOnlyJsonWebKey secret, TimeSpan expires) - { - //Add some random entropy to the upgrade message, to help prevent forgery - string entropy = RandomHash.GetRandomBase32(16); - //Init jwt - using JsonWebToken upgradeJwt = new(); - upgradeJwt.WriteHeader(secret.JwtHeader); - //Write claims - upgradeJwt.InitPayloadClaim() - .AddClaim("iat", DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()) - .AddClaim("upgrade", upgrade) - .AddClaim("type", upgrade.Type.ToString().ToLower()) - .AddClaim("expires", expires.TotalSeconds) - .AddClaim("a", entropy) - .CommitClaims(); - - //Sign with jwk - upgradeJwt.SignFromJwk(secret); - - //compile and return jwt upgrade - return new(upgradeJwt.Compile(), Convert.ToBase64String(upgradeJwt.SignatureData)); - } - - public static void MfaUpgradeSignature(this in SessionInfo session, string? base64Signature) => session[SESSION_SIG_KEY] = base64Signature!; - - public static string? MfaUpgradeSignature(this in SessionInfo session) => session[SESSION_SIG_KEY]; - } -} -- cgit