-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #131 from dorssel/memory_state
Add memory state manager
- Loading branch information
Showing
4 changed files
with
331 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,185 @@ | ||
// SPDX-FileCopyrightText: 2025 Frans van Dorsselaer | ||
// | ||
// SPDX-License-Identifier: MIT | ||
|
||
using Dorssel.Security.Cryptography; | ||
|
||
namespace UnitTests; | ||
|
||
[TestClass] | ||
sealed class XmssMemoryStateManagerTests | ||
{ | ||
[TestMethod] | ||
public void Constructor() | ||
{ | ||
using var stateManager = new XmssMemoryStateManager(); | ||
} | ||
|
||
[TestMethod] | ||
public void Store() | ||
{ | ||
using var stateManager = new XmssMemoryStateManager(); | ||
|
||
stateManager.Store(XmssKeyPart.Public, [1]); | ||
} | ||
|
||
[TestMethod] | ||
public void Store_Exists() | ||
{ | ||
using var stateManager = new XmssMemoryStateManager(); | ||
|
||
stateManager.Store(XmssKeyPart.Public, [1]); | ||
|
||
Assert.ThrowsException<InvalidOperationException>(() => | ||
{ | ||
stateManager.Store(XmssKeyPart.Public, [2]); | ||
}); | ||
} | ||
|
||
[TestMethod] | ||
public void StoreStoreStatefulPart() | ||
{ | ||
using var stateManager = new XmssMemoryStateManager(); | ||
stateManager.Store(XmssKeyPart.PrivateStateful, [1]); | ||
|
||
stateManager.StoreStatefulPart([1], [2]); | ||
} | ||
|
||
[TestMethod] | ||
public void StoreStoreStatefulPart_ExpectedAndDataMismatch() | ||
{ | ||
using var stateManager = new XmssMemoryStateManager(); | ||
stateManager.Store(XmssKeyPart.PrivateStateful, [1]); | ||
|
||
Assert.ThrowsException<ArgumentException>(() => | ||
{ | ||
stateManager.StoreStatefulPart([1], [2, 3]); | ||
}); | ||
} | ||
|
||
[TestMethod] | ||
public void StoreStoreStatefulPart_FileNotExists() | ||
{ | ||
using var stateManager = new XmssMemoryStateManager(); | ||
|
||
Assert.ThrowsException<InvalidOperationException>(() => | ||
{ | ||
stateManager.StoreStatefulPart([1], [2]); | ||
}); | ||
} | ||
|
||
[TestMethod] | ||
public void StoreStoreStatefulPart_FileSizeMismatch() | ||
{ | ||
using var stateManager = new XmssMemoryStateManager(); | ||
stateManager.Store(XmssKeyPart.PrivateStateful, [1]); | ||
|
||
Assert.ThrowsException<ArgumentException>(() => | ||
{ | ||
stateManager.StoreStatefulPart([1, 2], [3, 4]); | ||
}); | ||
} | ||
|
||
[TestMethod] | ||
public void StoreStoreStatefulPart_FileContentMismatch() | ||
{ | ||
using var stateManager = new XmssMemoryStateManager(); | ||
stateManager.Store(XmssKeyPart.PrivateStateful, [1]); | ||
|
||
Assert.ThrowsException<ArgumentException>(() => | ||
{ | ||
stateManager.StoreStatefulPart([2], [3]); | ||
}); | ||
} | ||
|
||
[TestMethod] | ||
public void Load() | ||
{ | ||
var data = new byte[] { 1, 2, 3 }; | ||
|
||
using var stateManager = new XmssMemoryStateManager(); | ||
stateManager.Store(XmssKeyPart.Public, data); | ||
|
||
var read = new byte[data.Length]; | ||
stateManager.Load(XmssKeyPart.Public, read); | ||
|
||
CollectionAssert.AreEqual(data, read); | ||
} | ||
|
||
[TestMethod] | ||
public void Load_WrongSize() | ||
{ | ||
var data = new byte[] { 1, 2, 3 }; | ||
|
||
using var stateManager = new XmssMemoryStateManager(); | ||
stateManager.Store(XmssKeyPart.Public, data); | ||
|
||
var read = new byte[data.Length - 1]; | ||
Assert.ThrowsException<ArgumentException>(() => | ||
{ | ||
stateManager.Load(XmssKeyPart.Public, read); | ||
}); | ||
} | ||
|
||
[TestMethod] | ||
public void Load_PartNotExists() | ||
{ | ||
using var stateManager = new XmssMemoryStateManager(); | ||
|
||
Assert.ThrowsException<InvalidOperationException>(() => | ||
{ | ||
stateManager.Load(XmssKeyPart.Public, new byte[1]); | ||
}); | ||
} | ||
|
||
[TestMethod] | ||
public void Load_UnknownPart() | ||
{ | ||
var data = new byte[] { 1, 2, 3 }; | ||
|
||
using var stateManager = new XmssMemoryStateManager(); | ||
|
||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => | ||
{ | ||
stateManager.Load(Enum.GetValues<XmssKeyPart>().Max() + 1, new byte[1]); | ||
}); | ||
} | ||
|
||
[TestMethod] | ||
public void DeletePublicPart() | ||
{ | ||
using var stateManager = new XmssMemoryStateManager(); | ||
stateManager.Store(XmssKeyPart.Public, [1]); | ||
|
||
stateManager.DeletePublicPart(); | ||
|
||
|
||
} | ||
|
||
[TestMethod] | ||
public void DeletePublicPart_PartNotExists() | ||
{ | ||
using var stateManager = new XmssMemoryStateManager(); | ||
|
||
stateManager.DeletePublicPart(); | ||
} | ||
|
||
[TestMethod] | ||
public void DeleteAll() | ||
{ | ||
using var stateManager = new XmssMemoryStateManager(); | ||
stateManager.Store(XmssKeyPart.PrivateStateless, [1]); | ||
stateManager.Store(XmssKeyPart.PrivateStateful, [2]); | ||
stateManager.Store(XmssKeyPart.Public, [3]); | ||
|
||
stateManager.DeleteAll(); | ||
} | ||
|
||
[TestMethod] | ||
public void DeleteAll_PartsNotExist() | ||
{ | ||
using var stateManager = new XmssMemoryStateManager(); | ||
|
||
stateManager.DeleteAll(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
// SPDX-FileCopyrightText: 2025 Frans van Dorsselaer | ||
// | ||
// SPDX-License-Identifier: MIT | ||
|
||
using System.Diagnostics; | ||
using System.Security.Cryptography; | ||
|
||
namespace Dorssel.Security.Cryptography; | ||
|
||
/// <summary> | ||
/// TODO | ||
/// </summary> | ||
public sealed class XmssMemoryStateManager() | ||
: IXmssStateManager, IDisposable | ||
{ | ||
readonly Dictionary<XmssKeyPart, byte[]?> State = new() | ||
{ | ||
{ XmssKeyPart.PrivateStateless, null }, | ||
{ XmssKeyPart.PrivateStateful, null }, | ||
{ XmssKeyPart.Public, null }, | ||
}; | ||
|
||
[StackTraceHidden] | ||
void ThrowIfInvalidPart(XmssKeyPart part) | ||
{ | ||
if (!State.ContainsKey(part)) | ||
{ | ||
throw new ArgumentOutOfRangeException(nameof(part)); | ||
} | ||
} | ||
|
||
/// <inheritdoc/> | ||
public void Store(XmssKeyPart part, ReadOnlySpan<byte> data) | ||
{ | ||
lock (State) | ||
{ | ||
ThrowIfInvalidPart(part); | ||
|
||
ObjectDisposedException.ThrowIf(IsDisposed, this); | ||
|
||
if (State[part] is not null) | ||
{ | ||
throw new InvalidOperationException("Part already exists."); | ||
} | ||
State[part] = data.ToArray(); | ||
} | ||
} | ||
|
||
/// <inheritdoc/> | ||
public void StoreStatefulPart(ReadOnlySpan<byte> expected, ReadOnlySpan<byte> data) | ||
{ | ||
if (data.Length != expected.Length) | ||
{ | ||
throw new ArgumentException("Expected data and new data must have the same size."); | ||
} | ||
|
||
lock (State) | ||
{ | ||
ObjectDisposedException.ThrowIf(IsDisposed, this); | ||
|
||
if (State[XmssKeyPart.PrivateStateful] is not byte[] oldData) | ||
{ | ||
throw new InvalidOperationException("Part does not exist."); | ||
} | ||
if (!expected.SequenceEqual(oldData)) | ||
{ | ||
throw new ArgumentException("Expected content mismatch.", nameof(expected)); | ||
} | ||
State[XmssKeyPart.PrivateStateful] = data.ToArray(); | ||
} | ||
} | ||
|
||
/// <inheritdoc/> | ||
public void Load(XmssKeyPart part, Span<byte> destination) | ||
{ | ||
lock (State) | ||
{ | ||
ThrowIfInvalidPart(part); | ||
|
||
ObjectDisposedException.ThrowIf(IsDisposed, this); | ||
|
||
if (State[part] is not byte[] data) | ||
{ | ||
throw new InvalidOperationException("Part does not exist."); | ||
} | ||
if (data.Length != destination.Length) | ||
{ | ||
throw new ArgumentException("Part size mismatch.", nameof(destination)); | ||
} | ||
data.CopyTo(destination); | ||
} | ||
} | ||
|
||
/// <inheritdoc/> | ||
public void DeletePublicPart() | ||
{ | ||
lock (State) | ||
{ | ||
ObjectDisposedException.ThrowIf(IsDisposed, this); | ||
|
||
State[XmssKeyPart.Public] = null; | ||
} | ||
} | ||
|
||
/// <inheritdoc/> | ||
public void DeleteAll() | ||
{ | ||
lock (State) | ||
{ | ||
ObjectDisposedException.ThrowIf(IsDisposed, this); | ||
|
||
CryptographicOperations.ZeroMemory(State[XmssKeyPart.PrivateStateless]); | ||
CryptographicOperations.ZeroMemory(State[XmssKeyPart.PrivateStateful]); | ||
State[XmssKeyPart.PrivateStateless] = null; | ||
State[XmssKeyPart.PrivateStateful] = null; | ||
State[XmssKeyPart.Public] = null; | ||
} | ||
} | ||
|
||
bool IsDisposed; | ||
|
||
/// <inheritdoc/> | ||
public void Dispose() | ||
{ | ||
lock (State) | ||
{ | ||
if (!IsDisposed) | ||
{ | ||
DeleteAll(); | ||
IsDisposed = true; | ||
} | ||
} | ||
} | ||
} |