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;