diff options
author | vnugent <public@vaughnnugent.com> | 2024-03-24 21:01:06 -0400 |
---|---|---|
committer | vnugent <public@vaughnnugent.com> | 2024-03-24 21:01:06 -0400 |
commit | 55859158fbd0bf54473a0baeb486045a025c7c5d (patch) | |
tree | 380fe100af2b29246398237bfe95f392dc513ffe /lib/Hashing.Portable/src | |
parent | dd0f384ec3b2fd86ec03aa0fb42387091b5430a7 (diff) |
Squashed commit of the following:
commit 6c1667be23597513537f8190e2f55d65eb9b7c7a
Author: vnugent <public@vaughnnugent.com>
Date: Fri Mar 22 12:01:53 2024 -0400
refactor: Overhauled native library loading and lazy init
commit ebf688f2f974295beabf7b5def7e6f6f150551d0
Author: vnugent <public@vaughnnugent.com>
Date: Wed Mar 20 22:16:17 2024 -0400
refactor: Update compression header files and macros + Ci build
commit 9c7b564911080ccd5cbbb9851a0757b05e1e9047
Author: vnugent <public@vaughnnugent.com>
Date: Tue Mar 19 21:54:49 2024 -0400
refactor: JWK overhaul & add length getter to FileUpload
commit 6d8c3444e09561e5957491b3cc1ae858e0abdd14
Author: vnugent <public@vaughnnugent.com>
Date: Mon Mar 18 16:13:20 2024 -0400
feat: Add FNV1a software checksum and basic correction tests
commit 00d182088cecefc08ca80b1faee9bed3f215f40b
Author: vnugent <public@vaughnnugent.com>
Date: Fri Mar 15 01:05:27 2024 -0400
chore: #6 Use utils filewatcher instead of built-in
commit d513c10d9895c6693519ef1d459c6a5a76929436
Author: vnugent <public@vaughnnugent.com>
Date: Sun Mar 10 21:58:14 2024 -0400
source tree project location updated
Diffstat (limited to 'lib/Hashing.Portable/src')
5 files changed, 230 insertions, 63 deletions
diff --git a/lib/Hashing.Portable/src/Argon2/VnArgon2.cs b/lib/Hashing.Portable/src/Argon2/VnArgon2.cs index 9d98050..b88c232 100644 --- a/lib/Hashing.Portable/src/Argon2/VnArgon2.cs +++ b/lib/Hashing.Portable/src/Argon2/VnArgon2.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Hashing.Portable @@ -24,7 +24,6 @@ using System; using System.Text; -using System.Threading; using System.Diagnostics; using System.Buffers.Text; using System.Security.Cryptography; @@ -33,6 +32,7 @@ using System.Runtime.InteropServices; using VNLib.Utils.Memory; using VNLib.Utils.Native; using VNLib.Utils.Extensions; +using VNLib.Utils.Resources; using VNLib.Hashing.Native.MonoCypher; namespace VNLib.Hashing @@ -52,12 +52,12 @@ namespace VNLib.Hashing public const string ARGON2_LIB_ENVIRONMENT_VAR_NAME = "VNLIB_ARGON2_DLL_PATH"; private static readonly Encoding LocEncoding = Encoding.Unicode; - private static readonly Lazy<IUnmangedHeap> _heap = new (static () => MemoryUtil.InitializeNewHeapForProcess(true), LazyThreadSafetyMode.PublicationOnly); - private static readonly Lazy<IArgon2Library> _nativeLibrary = new(LoadSharedLibInternal, LazyThreadSafetyMode.PublicationOnly); + private static readonly LazyInitializer<IUnmangedHeap> _heap = new (static () => MemoryUtil.InitializeNewHeapForProcess(true)); + private static readonly LazyInitializer<IArgon2Library> _nativeLibrary = new(LoadSharedLibInternal); //Private heap initialized to 10k size, and all allocated buffers will be zeroed when allocated - private static IUnmangedHeap PwHeap => _heap.Value; + private static IUnmangedHeap PwHeap => _heap.Instance; private static IArgon2Library LoadSharedLibInternal() { @@ -70,7 +70,7 @@ namespace VNLib.Hashing Trace.WriteLine("Using the native MonoCypher library for Argon2 password hashing", "VnArgon2"); //Load shared monocyphter argon2 library - return MonoCypherLibrary.Shared.Argon2CreateLibrary(_heap.Value); + return MonoCypherLibrary.Shared.Argon2CreateLibrary(_heap.Instance); } else { @@ -90,7 +90,7 @@ namespace VNLib.Hashing /// </summary> /// <returns>The shared library instance</returns> /// <exception cref="DllNotFoundException"></exception> - public static IArgon2Library GetOrLoadSharedLib() => _nativeLibrary.Value; + public static IArgon2Library GetOrLoadSharedLib() => _nativeLibrary.Instance; /// <summary> /// Loads a native Argon2 shared library from the specified path diff --git a/lib/Hashing.Portable/src/Checksums/FNV1a.cs b/lib/Hashing.Portable/src/Checksums/FNV1a.cs new file mode 100644 index 0000000..f150638 --- /dev/null +++ b/lib/Hashing.Portable/src/Checksums/FNV1a.cs @@ -0,0 +1,113 @@ +/* +* Copyright (c) 2024 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Hashing.Portable +* File: FNV1a.cs +* +* FNV1a.cs is part of VNLib.Hashing.Portable which is part of the larger +* VNLib collection of libraries and utilities. +* +* VNLib.Hashing.Portable is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published +* by the Free Software Foundation, either version 2 of the License, +* or (at your option) any later version. +* +* VNLib.Hashing.Portable 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 +* General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with VNLib.Hashing.Portable. If not, see http://www.gnu.org/licenses/. +*/ + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace VNLib.Hashing.Checksums +{ + /// <summary> + /// A managed software implementation of the FNV-1a 64-bit non cryptographic hash algorithm + /// </summary> + public static class FNV1a + { + /* + * Constants taken from the spec + * https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function + */ + const ulong FNV_PRIME = 0x100000001b3UL; + const ulong FNV_OFFSET_BASIS = 0xcbf29ce484222325UL; + + /// <summary> + /// Computes the next 64-bit FNV-1a hash value using the current hash + /// value and the next byte of data. + /// </summary> + /// <param name="initalizer">The inital hash to begin the computation with</param> + /// <param name="data"></param> + /// <param name="length"></param> + /// <returns>The next value of the checksum representing current and previously computed segments</returns> + /// <exception cref="ArgumentNullException"></exception> + public static ulong Update64(ulong initalizer, ref byte data, nuint length) + { + if (Unsafe.IsNullRef(ref data)) + { + throw new ArgumentNullException(nameof(data)); + } + + ulong digest = initalizer; + + for (nuint i = 0; i < length; i++) + { + digest ^= Unsafe.AddByteOffset(ref data, i); + digest *= FNV_PRIME; + } + + return digest; + } + + /// <summary> + /// Computes the next 64-bit FNV-1a hash value using the current hash + /// value and the next byte of data. + /// </summary> + /// <param name="initalizer">The initial hash to begin the computation with</param> + /// <param name="data">A span structure pointing to the memory block to compute the digest of</param> + /// <returns>The next value of the checksum representing current and previously computed segments</returns> + /// <exception cref="ArgumentNullException"></exception> + public static ulong Update64(ulong initalizer, ReadOnlySpan<byte> data) + { + ref byte r0 = ref MemoryMarshal.GetReference(data); + return Update64(initalizer, ref r0, (nuint)data.Length); + } + + /// <summary> + /// Begins computing the FNV-1a 64-bit hash of the input data and returns the + /// initial hash value which may be updated if more data is available + /// </summary> + /// <param name="data">A managed pointer to the first byte of the sequence to compute</param> + /// <param name="length">A platform specific integer representing the length of the input data</param> + /// <returns>The 64bit unsigned integer representing the message sum or digest</returns> + /// <remarks> + /// WARNING: This function produces a non-cryptographic hash and should not be used for + /// security or cryptographic purposes. It is intended for fast data integrity checks + /// </remarks> + public static ulong Compute64(ref byte data, nuint length) => Update64(FNV_OFFSET_BASIS, ref data, length); + + /// <summary> + /// Computes the next 64-bit FNV-1a hash value using the current hash + /// value and the next byte of data. + /// </summary> + /// <param name="data">A span structure pointing to the memory block to compute the digest of</param> + /// <returns>The 64bit unsigned integer representng the message sum or digest</returns> + /// <remarks> + /// WARNING: This function produces a non-cryptographic hash and should not be used for + /// security or cryptographic purposes. It is intended for fast data integrity checks + /// </remarks> + public static ulong Compute64(ReadOnlySpan<byte> data) + { + ref byte r0 = ref MemoryMarshal.GetReference(data); + return Compute64(ref r0, (nuint)data.Length); + } + } +} diff --git a/lib/Hashing.Portable/src/IdentityUtility/JsonWebKey.cs b/lib/Hashing.Portable/src/IdentityUtility/JsonWebKey.cs index 65f0837..a237db0 100644 --- a/lib/Hashing.Portable/src/IdentityUtility/JsonWebKey.cs +++ b/lib/Hashing.Portable/src/IdentityUtility/JsonWebKey.cs @@ -292,7 +292,7 @@ namespace VNLib.Hashing.IdentityUtility } /// <summary> - /// Gets the RSA private key algorithm from the supplied Json Web Key <see cref="JsonElement"/> + /// Gets the RSA private key algorithm from the supplied Json Web Key /// </summary> /// <param name="jwk"></param> /// <returns>The <see cref="RSA"/> algorithm if found, or null if the element does not contain private key</returns> @@ -303,7 +303,23 @@ namespace VNLib.Hashing.IdentityUtility return rSAParameters.HasValue ? RSA.Create(rSAParameters.Value) : null; } - private static RSAParameters? GetRsaParameters<TKey>(in TKey jwk, bool includePrivateKey) where TKey : IJsonWebKey + /// <summary> + /// Gets the RSA key parameters from the current Json Web Key + /// </summary>> + /// <param name="jwk"></param> + /// <param name="includePrivateKey">A value that indicates that a private key should be parsed and included in the parameters</param> + /// <returns>A nullable structure that contains the parsed keys, or null if required properties were empty</returns> + public static RSAParameters? GetRsaParameters(this ReadOnlyJsonWebKey jwk, bool includePrivateKey) + => GetRsaParameters(in jwk, includePrivateKey); + + /// <summary> + /// Gets the RSA key parameters from the current Json Web Key + /// </summary> + /// <typeparam name="TKey"></typeparam> + /// <param name="jwk"></param> + /// <param name="includePrivateKey">A value that indicates that a private key should be parsed and included in the parameters</param> + /// <returns>A nullable structure that contains the parsed keys, or null if required properties were empty</returns> + public static RSAParameters? GetRsaParameters<TKey>(in TKey jwk, bool includePrivateKey) where TKey : IJsonWebKey { //Get the RSA public key credentials ReadOnlySpan<char> e = jwk.GetKeyProperty("e"); @@ -372,9 +388,23 @@ namespace VNLib.Hashing.IdentityUtility //Return new alg return ecParams.HasValue ? ECDsa.Create(ecParams.Value) : null; } - - private static ECParameters? GetECParameters<TKey>(in TKey jwk, bool includePrivate) where TKey : IJsonWebKey + /// <summary> + /// Gets the EC key parameters from the current Json Web Key + /// </summary> + /// <param name="jwk"></param> + /// <param name="includePrivate">A value that inidcates if private key parameters should be parsed and included </param> + /// <returns>The parsed key parameter structure, or null if the key parameters were empty or could not be parsed</returns> + public static ECParameters? GetECParameters(this ReadOnlyJsonWebKey jwk, bool includePrivate) => GetECParameters(in jwk, includePrivate); + + /// <summary> + /// Gets the EC key parameters from the current Json Web Key + /// </summary> + /// <typeparam name="TKey"></typeparam> + /// <param name="jwk"></param> + /// <param name="includePrivate">A value that inidcates if private key parameters should be parsed and included </param> + /// <returns>The parsed key parameter structure, or null if the key parameters were empty or could not be parsed</returns> + public static ECParameters? GetECParameters<TKey>(in TKey jwk, bool includePrivate) where TKey : IJsonWebKey { //Get the RSA public key credentials ReadOnlySpan<char> x = jwk.GetKeyProperty("x"); diff --git a/lib/Hashing.Portable/src/IdentityUtility/ReadOnlyJsonWebKey.cs b/lib/Hashing.Portable/src/IdentityUtility/ReadOnlyJsonWebKey.cs index 009d6bf..c97a023 100644 --- a/lib/Hashing.Portable/src/IdentityUtility/ReadOnlyJsonWebKey.cs +++ b/lib/Hashing.Portable/src/IdentityUtility/ReadOnlyJsonWebKey.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Hashing.Portable @@ -23,11 +23,12 @@ */ using System; +using System.Linq; using System.Text.Json; using System.Collections.Generic; +using System.Collections.Frozen; -using VNLib.Utils; -using VNLib.Utils.Extensions; +using VNLib.Utils.Memory; namespace VNLib.Hashing.IdentityUtility { @@ -35,24 +36,19 @@ namespace VNLib.Hashing.IdentityUtility /// A readonly Json Web Key (JWK) data structure that may be used for signing /// or verifying messages. /// </summary> - public sealed class ReadOnlyJsonWebKey : VnDisposeable, IJsonWebKey + public sealed class ReadOnlyJsonWebKey : IJsonWebKey { - private readonly JsonElement _jwk; - private readonly JsonDocument? _doc; + private readonly FrozenDictionary<string, string?> _properties; /// <summary> - /// Creates a new instance of <see cref="ReadOnlyJsonWebKey"/> from a <see cref="JsonElement"/>. - /// This will call <see cref="JsonElement.Clone"/> on the element and store an internal copy + /// Creates a new instance of <see cref="ReadOnlyJsonWebKey"/> from a dictionary of + /// JWK string properties /// </summary> - /// <param name="keyElement">The <see cref="JsonElement"/> to create the <see cref="ReadOnlyJsonWebKey"/> from</param> - public ReadOnlyJsonWebKey(ref readonly JsonElement keyElement) + /// <param name="properties">The frozen dictionary instance of parsed JWK properties</param> + public ReadOnlyJsonWebKey(FrozenDictionary<string, string?> properties) { - _jwk = keyElement.Clone(); - //Set initial values - KeyId = _jwk.GetPropString("kid"); - KeyType = _jwk.GetPropString("kty"); - Algorithm = _jwk.GetPropString("alg"); - Use = _jwk.GetPropString("use"); + ArgumentNullException.ThrowIfNull(properties); + _properties = properties; //Create a JWT header from the values JwtHeader = new Dictionary<string, string?>() @@ -71,58 +67,81 @@ namespace VNLib.Hashing.IdentityUtility } /// <summary> + /// Creates a new instance of <see cref="ReadOnlyJsonWebKey"/> from a <see cref="JsonElement"/>. + /// This will call <see cref="JsonElement.Clone"/> on the element and store an internal copy + /// </summary> + /// <param name="keyElement">The <see cref="JsonElement"/> to create the <see cref="ReadOnlyJsonWebKey"/> from</param> + public ReadOnlyJsonWebKey(ref readonly JsonElement keyElement) + :this( + //Get only top-level string properties and store them in a dictionary + keyElement.EnumerateObject() + .Where(static k => k.Value.ValueKind == JsonValueKind.String) + .ToDictionary(static k => k.Name, v => v.Value.GetString(), StringComparer.OrdinalIgnoreCase) + .ToFrozenDictionary() + ) + { } + + /// <summary> /// Creates a new instance of <see cref="ReadOnlyJsonWebKey"/> from a raw utf8 encoded json /// binary sequence /// </summary> /// <param name="rawValue">The utf8 encoded json binary sequence</param> /// <exception cref="ArgumentException"></exception> /// <exception cref="JsonException"></exception> - public ReadOnlyJsonWebKey(ReadOnlySpan<byte> rawValue) + public static ReadOnlyJsonWebKey FromUtf8Bytes(ReadOnlySpan<byte> rawValue) { - //Pare the raw value Utf8JsonReader reader = new (rawValue); - _doc = JsonDocument.ParseValue(ref reader); - //store element - _jwk = _doc.RootElement; - - //Set initial values - KeyId = _jwk.GetPropString("kid"); - KeyType = _jwk.GetPropString("kty"); - Algorithm = _jwk.GetPropString("alg"); - Use = _jwk.GetPropString("use"); + using JsonDocument doc = JsonDocument.ParseValue(ref reader); + JsonElement root = doc.RootElement; + return new ReadOnlyJsonWebKey(ref root); + } - //Create a JWT header from the values - JwtHeader = new Dictionary<string, string?>() - { - { "alg" , Algorithm }, - { "typ" , "JWT" }, - }; + /// <summary> + /// Creates a new instance of <see cref="ReadOnlyJsonWebKey"/> from a raw utf8 encoded json + /// memory segment + /// </summary> + /// <param name="rawValue">The utf8 encoded json binary sequence</param> + /// <returns>The readonly JWK object</returns> + /// <exception cref="ArgumentException"></exception> + /// <exception cref="JsonException"></exception> + public static ReadOnlyJsonWebKey FromUtf8Bytes(ReadOnlyMemory<byte> rawValue) + { + using JsonDocument doc = JsonDocument.Parse(rawValue); + JsonElement root = doc.RootElement; + return new ReadOnlyJsonWebKey(ref root); + } - //Configure key usage - KeyUse = (Use?.ToLower(null)) switch - { - "sig" => JwkKeyUsage.Signature, - "enc" => JwkKeyUsage.Encryption, - _ => JwkKeyUsage.None, - }; + /// <summary> + /// Creates a new instance of <see cref="ReadOnlyJsonWebKey"/> from a json string + /// </summary> + /// <param name="jsonString">The json encoded string to recover the JWK from</param> + /// <returns></returns> + public static ReadOnlyJsonWebKey FromJsonString(string jsonString) + { + using JsonDocument doc = JsonDocument.Parse(jsonString); + JsonElement root = doc.RootElement; + return new ReadOnlyJsonWebKey(ref root); } /// <summary> /// The key identifier /// </summary> - public string? KeyId { get; } + public string? KeyId => _properties.GetValueOrDefault("kid"); + /// <summary> /// The key type /// </summary> - public string? KeyType { get; } + public string? KeyType => _properties.GetValueOrDefault("kty"); + /// <summary> /// The key algorithm /// </summary> - public string? Algorithm { get; } + public string? Algorithm => _properties.GetValueOrDefault("alg"); + /// <summary> /// The key "use" value /// </summary> - public string? Use { get; } + public string? Use => _properties.GetValueOrDefault("use"); /// <summary> /// Returns the JWT header that matches this key @@ -133,12 +152,17 @@ namespace VNLib.Hashing.IdentityUtility public JwkKeyUsage KeyUse { get; } ///<inheritdoc/> - public string? GetKeyProperty(string propertyName) => _jwk.GetPropString(propertyName); + public string? GetKeyProperty(string propertyName) => _properties.GetValueOrDefault(propertyName); - ///<inheritdoc/> - protected override void Free() + /// <summary> + /// Attemts to erase all property values from memory by securely writing over them with zeros + /// </summary> + public void EraseValues() { - _doc?.Dispose(); + foreach(string? value in _properties.Values) + { + PrivateStringManager.EraseString(value); + } } } diff --git a/lib/Hashing.Portable/src/Native/MonoCypher/MonoCypherLibrary.cs b/lib/Hashing.Portable/src/Native/MonoCypher/MonoCypherLibrary.cs index 11e524b..4c6f405 100644 --- a/lib/Hashing.Portable/src/Native/MonoCypher/MonoCypherLibrary.cs +++ b/lib/Hashing.Portable/src/Native/MonoCypher/MonoCypherLibrary.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Hashing.Portable @@ -25,10 +25,10 @@ using System; using System.Diagnostics; using System.Runtime.InteropServices; -using System.Threading; using VNLib.Utils; using VNLib.Utils.Native; +using VNLib.Utils.Resources; using VNLib.Utils.Extensions; namespace VNLib.Hashing.Native.MonoCypher @@ -50,7 +50,7 @@ namespace VNLib.Hashing.Native.MonoCypher /// <returns>true if the user enabled the default library, false otherwise</returns> public static bool CanLoadDefaultLibrary() => string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable(MONOCYPHER_LIB_ENVIRONMENT_VAR_NAME)) == false; - private static readonly Lazy<MonoCypherLibrary> _defaultLib = new (LoadDefaultLibraryInternal, LazyThreadSafetyMode.PublicationOnly); + private static readonly LazyInitializer<MonoCypherLibrary> _defaultLib = new (LoadDefaultLibraryInternal); /// <summary> /// Gets the default MonoCypher library for the current process @@ -59,7 +59,7 @@ namespace VNLib.Hashing.Native.MonoCypher /// this property to ensure that the default library can be loaded /// </para> /// </summary> - public static MonoCypherLibrary Shared => _defaultLib.Value; + public static MonoCypherLibrary Shared => _defaultLib.Instance; /// <summary> /// Loads a new instance of the MonoCypher library with environment defaults |