From 4c17e67c51f59f4c829d6e450503682039b63486 Mon Sep 17 00:00:00 2001 From: Frans van Dorsselaer <17404029+dorssel@users.noreply.github.com> Date: Thu, 2 Jan 2025 02:08:01 +0100 Subject: [PATCH] Small improvements - remove unnecessary version API - add some XML documentation - fix Home link in WebAssembly example - refactor secure erase private blobs --- .github/linters/vs-spell-exclusion.txt | 3 + Examples/ConsoleApp/Program.cs | 4 - Examples/WebAssembly/Layout/NavMenu.razor | 2 +- GitVersion.yml | 1 - UnitTests/UnitTests/VersionTests.cs | 23 ---- .../InteropServices/CriticalXmssBlobHandle.cs | 10 +- .../CriticalXmssPrivateBlobHandle.cs | 17 +++ ...riticalXmssPrivateKeyStatefulBlobHandle.cs | 2 +- ...iticalXmssPrivateKeyStatelessBlobHandle.cs | 2 +- ...CriticalXmssPublicKeyInternalBlobHandle.cs | 2 +- .../CriticalXmssSignatureBlobHandle.cs | 2 +- .../XmssCertificateExtensions.cs | 12 +- Xmss/Xmss.cs | 120 ++++++++++-------- 13 files changed, 102 insertions(+), 98 deletions(-) delete mode 100644 UnitTests/UnitTests/VersionTests.cs create mode 100644 Xmss/InteropServices/CriticalXmssPrivateBlobHandle.cs diff --git a/.github/linters/vs-spell-exclusion.txt b/.github/linters/vs-spell-exclusion.txt index e643a45..16e39ad 100644 --- a/.github/linters/vs-spell-exclusion.txt +++ b/.github/linters/vs-spell-exclusion.txt @@ -2,8 +2,11 @@ Codecov Dorssel hashsig marshaller +navmenu param realloc +sitenav stateful xmss zeroize +Xtended diff --git a/Examples/ConsoleApp/Program.cs b/Examples/ConsoleApp/Program.cs index 0add36b..ab6e099 100644 --- a/Examples/ConsoleApp/Program.cs +++ b/Examples/ConsoleApp/Program.cs @@ -16,10 +16,6 @@ static Program() static async Task Main() { - { - Console.WriteLine($"Native headers version: {Xmss.NativeHeadersVersion}"); - Console.WriteLine($"Native library version: {Xmss.NativeLibraryVersion}"); - } { var oid = CryptoConfig.MapNameToOID("XMSS"); Console.WriteLine($"Found OID for 'XMSS': {oid}"); diff --git a/Examples/WebAssembly/Layout/NavMenu.razor b/Examples/WebAssembly/Layout/NavMenu.razor index 59fc09f..0595eef 100644 --- a/Examples/WebAssembly/Layout/NavMenu.razor +++ b/Examples/WebAssembly/Layout/NavMenu.razor @@ -9,7 +9,7 @@ diff --git a/GitVersion.yml b/GitVersion.yml index dbfac79..d606ba1 100644 --- a/GitVersion.yml +++ b/GitVersion.yml @@ -6,4 +6,3 @@ # yaml-language-server: $schema=https://raw.githubusercontent.com/GitTools/GitVersion/main/schemas/6.0/GitVersion.configuration.json assembly-file-versioning-format: '{Major}.{Minor}.{Patch}.{env:GITHUB_RUN_NUMBER ?? 0}' -next-version: 1.0.0 diff --git a/UnitTests/UnitTests/VersionTests.cs b/UnitTests/UnitTests/VersionTests.cs deleted file mode 100644 index 5f6bb23..0000000 --- a/UnitTests/UnitTests/VersionTests.cs +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-FileCopyrightText: 2024 Frans van Dorsselaer -// -// SPDX-License-Identifier: MIT - -using Dorssel.Security.Cryptography; - -namespace UnitTests; - -[TestClass] -sealed class VersionTests -{ - [TestMethod] - public void NativeHeadersVersion() - { - Assert.AreEqual(Version.Parse("2.0.0"), Xmss.NativeHeadersVersion); - } - - [TestMethod] - public void NativeLibraryVersion() - { - Assert.AreEqual(Version.Parse("2.0.0"), Xmss.NativeLibraryVersion); - } -} diff --git a/Xmss/InteropServices/CriticalXmssBlobHandle.cs b/Xmss/InteropServices/CriticalXmssBlobHandle.cs index e77ab89..ac6e3d7 100644 --- a/Xmss/InteropServices/CriticalXmssBlobHandle.cs +++ b/Xmss/InteropServices/CriticalXmssBlobHandle.cs @@ -3,11 +3,10 @@ // SPDX-License-Identifier: MIT using System.Runtime.InteropServices; -using System.Security.Cryptography; namespace Dorssel.Security.Cryptography.InteropServices; -abstract class CriticalXmssBlobHandle(bool SecureZeroize) : CriticalXmssHandle where T : unmanaged +abstract class CriticalXmssBlobHandle : CriticalXmssHandle where T : unmanaged { protected static H Alloc(int size) where H : CriticalXmssBlobHandle, new() { @@ -42,13 +41,8 @@ public Span Data } } - protected sealed override unsafe void Free(T* pointer) + protected override unsafe void Free(T* pointer) { - if (SecureZeroize) - { - // cannot use Data and DataLength, as object is marked as closed already - CryptographicOperations.ZeroMemory(new((nuint*)handle + 1, (int)*(nuint*)handle)); - } NativeMemory.Free(pointer); } } diff --git a/Xmss/InteropServices/CriticalXmssPrivateBlobHandle.cs b/Xmss/InteropServices/CriticalXmssPrivateBlobHandle.cs new file mode 100644 index 0000000..86f457d --- /dev/null +++ b/Xmss/InteropServices/CriticalXmssPrivateBlobHandle.cs @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2025 Frans van Dorsselaer +// +// SPDX-License-Identifier: MIT + +using System.Security.Cryptography; + +namespace Dorssel.Security.Cryptography.InteropServices; + +abstract class CriticalXmssPrivateBlobHandle : CriticalXmssBlobHandle where T : unmanaged +{ + protected sealed override unsafe void Free(T* pointer) + { + // cannot use Data and DataLength, as object is marked as closed already + CryptographicOperations.ZeroMemory(new((nuint*)handle + 1, (int)*(nuint*)handle)); + base.Free(pointer); + } +} diff --git a/Xmss/InteropServices/CriticalXmssPrivateKeyStatefulBlobHandle.cs b/Xmss/InteropServices/CriticalXmssPrivateKeyStatefulBlobHandle.cs index bd25c56..2ebbc92 100644 --- a/Xmss/InteropServices/CriticalXmssPrivateKeyStatefulBlobHandle.cs +++ b/Xmss/InteropServices/CriticalXmssPrivateKeyStatefulBlobHandle.cs @@ -6,7 +6,7 @@ namespace Dorssel.Security.Cryptography.InteropServices; -sealed class CriticalXmssPrivateKeyStatefulBlobHandle() : CriticalXmssBlobHandle(true) +sealed class CriticalXmssPrivateKeyStatefulBlobHandle : CriticalXmssPrivateBlobHandle { public static CriticalXmssPrivateKeyStatefulBlobHandle Alloc() { diff --git a/Xmss/InteropServices/CriticalXmssPrivateKeyStatelessBlobHandle.cs b/Xmss/InteropServices/CriticalXmssPrivateKeyStatelessBlobHandle.cs index 1ab7d30..7ce26cc 100644 --- a/Xmss/InteropServices/CriticalXmssPrivateKeyStatelessBlobHandle.cs +++ b/Xmss/InteropServices/CriticalXmssPrivateKeyStatelessBlobHandle.cs @@ -6,7 +6,7 @@ namespace Dorssel.Security.Cryptography.InteropServices; -sealed class CriticalXmssPrivateKeyStatelessBlobHandle() : CriticalXmssBlobHandle(true) +sealed class CriticalXmssPrivateKeyStatelessBlobHandle : CriticalXmssPrivateBlobHandle { public static CriticalXmssPrivateKeyStatelessBlobHandle Alloc() { diff --git a/Xmss/InteropServices/CriticalXmssPublicKeyInternalBlobHandle.cs b/Xmss/InteropServices/CriticalXmssPublicKeyInternalBlobHandle.cs index f5d9e05..000e2a7 100644 --- a/Xmss/InteropServices/CriticalXmssPublicKeyInternalBlobHandle.cs +++ b/Xmss/InteropServices/CriticalXmssPublicKeyInternalBlobHandle.cs @@ -6,7 +6,7 @@ namespace Dorssel.Security.Cryptography.InteropServices; -sealed class CriticalXmssPublicKeyInternalBlobHandle() : CriticalXmssBlobHandle(false) +sealed class CriticalXmssPublicKeyInternalBlobHandle : CriticalXmssBlobHandle { public static CriticalXmssPublicKeyInternalBlobHandle Alloc(XmssCacheType cacheType, byte cacheLevel, XmssParameterSet parameterSet) { diff --git a/Xmss/InteropServices/CriticalXmssSignatureBlobHandle.cs b/Xmss/InteropServices/CriticalXmssSignatureBlobHandle.cs index c00e547..210699a 100644 --- a/Xmss/InteropServices/CriticalXmssSignatureBlobHandle.cs +++ b/Xmss/InteropServices/CriticalXmssSignatureBlobHandle.cs @@ -6,6 +6,6 @@ namespace Dorssel.Security.Cryptography.InteropServices; -sealed class CriticalXmssSignatureBlobHandle() : CriticalXmssBlobHandle(false) +sealed class CriticalXmssSignatureBlobHandle : CriticalXmssBlobHandle { } diff --git a/Xmss/X509Certificates/XmssCertificateExtensions.cs b/Xmss/X509Certificates/XmssCertificateExtensions.cs index 2c24d1f..70d7933 100644 --- a/Xmss/X509Certificates/XmssCertificateExtensions.cs +++ b/Xmss/X509Certificates/XmssCertificateExtensions.cs @@ -2,20 +2,24 @@ // // SPDX-License-Identifier: MIT +using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; namespace Dorssel.Security.Cryptography.X509Certificates; /// -/// TODO +/// Provides an extension method for retrieving the XMSS implementation for the public key of an . /// public static class XmssCertificateExtensions { /// - /// TODO + /// Gets the XMSS public key from the . /// - /// TODO - /// TODO + /// The certificate. + /// The public key, or if the certificate does not have an XMSS public key. + /// is . + /// The XMSS library reports an error. + /// See the property for more information. public static Xmss? GetXmssPublicKey(this X509Certificate2 certificate) { ArgumentNullException.ThrowIfNull(certificate); diff --git a/Xmss/Xmss.cs b/Xmss/Xmss.cs index 20a64bb..8aabe38 100644 --- a/Xmss/Xmss.cs +++ b/Xmss/Xmss.cs @@ -16,49 +16,47 @@ namespace Dorssel.Security.Cryptography; /// -/// TODO +/// Provides an implementation of the XMSS algorithm. /// public sealed class Xmss : AsymmetricAlgorithm { #region Construction - /// - /// TODO - /// - public Xmss() + static Xmss() { - LegalKeySizesValue = [new(256, 256, 0)]; - KeySizeValue = 256; + VerifyLibraryVersion(); + TryRegisterWithCryptoConfig(); } /// - /// TODO + /// Throws if either the library cannot be loaded, or it does not expose the expected version (i.e., it is the wrong one). /// - /// TODO - public static new Xmss Create() + [ExcludeFromCodeCoverage(Justification = "Not testable.")] + static void VerifyLibraryVersion() { - return new Xmss(); + var runtimeVersion = SafeNativeMethods.xmss_library_get_version(); + if (runtimeVersion != Defines.XMSS_LIBRARY_VERSION) + { + throw new DllNotFoundException($"XMSS library version mismatch ({runtimeVersion})"); + } } - #endregion - #region Version /// - /// TODO + /// Initializes a new instance of the class. /// - public static Version NativeHeadersVersion => new(Defines.XMSS_LIBRARY_VERSION_MAJOR, Defines.XMSS_LIBRARY_VERSION_MINOR, - Defines.XMSS_LIBRARY_VERSION_PATCH); + public Xmss() + { + LegalKeySizesValue = [new(256, 256, 0)]; + KeySizeValue = 256; + } /// - /// TODO + /// Creates a new instance of the default implementation of the eXtended Merkle Signature Scheme (XMSS). /// - public static Version NativeLibraryVersion + /// A new instance of the default implementation () of this class. + public static new Xmss Create() { - get - { - var nativeVersion = SafeNativeMethods.xmss_library_get_version(); - return new(Defines.XMSS_LIBRARY_GET_VERSION_MAJOR(nativeVersion), - Defines.XMSS_LIBRARY_GET_VERSION_MINOR(nativeVersion), Defines.XMSS_LIBRARY_GET_VERSION_PATCH(nativeVersion)); - } + return new Xmss(); } #endregion @@ -81,7 +79,7 @@ static void RegisterWithCryptoConfig() } [ExcludeFromCodeCoverage(Justification = "Not testable; WASM only.")] - static Xmss() + static void TryRegisterWithCryptoConfig() { try { @@ -112,10 +110,7 @@ void ResetState() ParameterSet = XmssParameterSet.None; } - /// - /// TODO - /// - /// TODO + /// protected override void Dispose(bool disposing) { if (!IsDisposed) @@ -142,6 +137,7 @@ protected override void Dispose(bool disposing) /// /// /// + /// When the current is . public override string? SignatureAlgorithm => ParameterSet switch { XmssParameterSet.XMSS_SHA2_10_256 => "http://www.w3.org/2021/04/xmldsig-more#xmss-sha2-10-256", // DevSkim: ignore DS137138 @@ -665,10 +661,10 @@ public void RequestFutureSignatures(int count) } /// - /// TODO + /// Computes the XMSS signature for the specified data. /// - /// TODO - /// TODO + /// The data to sign. + /// The XMSS signature for the specified data. public byte[] Sign(ReadOnlySpan data) { var signature = new byte[Defines.XMSS_SIGNATURE_SIZE(ParameterSet.AsOID())]; @@ -692,12 +688,12 @@ public unsafe byte[] Sign(void* data, nuint dataLength) } /// - /// TODO + /// Computes the XMSS signature for the specified data into the provided buffer. /// - /// TODO - /// TODO - /// TODO - /// TODO + /// The data to sign. + /// The buffer to receive the signature. + /// The total number of bytes written to . + /// The buffer in is too small to hold the signature. public int Sign(ReadOnlySpan data, Span destination) { return TrySign(data, destination, out var bytesWritten) ? bytesWritten @@ -719,12 +715,13 @@ public unsafe int Sign(void* data, nuint dataLength, Span destination) } /// - /// TODO + /// Attempts to compute the XMSS digital signature for the specified read-only span of bytes into the provided destination by using the current key. /// - /// TODO - /// TODO - /// TODO - /// TODO + /// The data to be signed. + /// The buffer to receive the signature. + /// When this method returns, the total number of bytes written into . + /// This parameter is treated as uninitialized. + /// if is big enough to receive the signature; otherwise, . public bool TrySign(ReadOnlySpan data, Span destination, out int bytesWritten) { unsafe @@ -783,11 +780,11 @@ public unsafe bool TrySign(void* data, nuint dataLength, Span destination, #region Verify /// - /// TODO + /// Verifies that a digital signature is appropriate for the current key and provided data. /// - /// TODO - /// TODO - /// TODO + /// The signed data. + /// The signature to be verified. + /// if the signature is valid for the provided data; otherwise, . public bool Verify(Stream data, ReadOnlySpan signature) { ArgumentNullException.ThrowIfNull(data); @@ -838,11 +835,11 @@ public bool Verify(Stream data, ReadOnlySpan signature) } /// - /// TODO + /// Verifies that a digital signature is appropriate for the current key and provided data. /// - /// TODO - /// TODO - /// TODO + /// The signed data. + /// The signature to be verified. + /// if the signature is valid for the provided data; otherwise, . public bool Verify(ReadOnlySpan data, ReadOnlySpan signature) { ObjectDisposedException.ThrowIf(IsDisposed, this); @@ -1109,10 +1106,27 @@ void ImportXmssPublicKey(XmssParameterSet parameterSet, in XmssPublicKey publicK } /// - /// TODO + /// Imports an RFC 7468 PEM-encoded key, replacing the keys for this object. /// - /// TODO - /// TODO + /// The PEM text of the key to import. + /// + /// does not contain a PEM-encoded key with a recognized label. + /// + /// -or- + /// + /// contains multiple PEM-encoded keys with a recognized label. + /// + /// + /// Unsupported or malformed PEM-encoded objects will be ignored. + /// If multiple supported PEM labels are found, an exception is raised to prevent importing a key when the key is ambiguous. + /// + /// This method supports the following PEM labels: + /// + /// PUBLIC KEY + /// XMSS PUBLIC KEY + /// CERTIFICATE + /// + /// public override void ImportFromPem(ReadOnlySpan input) { PemFields? foundFields = default;