Skip to content

Commit d587aca

Browse files
authored
Merge pull request #131 from dorssel/memory_state
Add memory state manager
2 parents 8bae681 + 8845be6 commit d587aca

File tree

4 files changed

+331
-15
lines changed

4 files changed

+331
-15
lines changed

Examples/ConsoleApp/Program.cs

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,16 @@ static async Task Main()
2626
#pragma warning restore IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code
2727
Console.WriteLine($"Created by CryptoConfig('XMSS'): {alg is not null}");
2828
}
29+
30+
var message = new byte[] { 1, 2, 3 };
31+
byte[] signature;
32+
string publicKeyPem;
33+
2934
{
3035
Console.WriteLine("Generating new key...");
3136

32-
var stateManager = new XmssFileStateManager(@"C:\test");
33-
stateManager.DeleteAll();
3437
using var xmss = new Xmss();
35-
xmss.GeneratePrivateKey(stateManager, XmssParameterSet.XMSS_SHA2_10_256, false);
38+
xmss.GeneratePrivateKey(new XmssEphemeralStateManager(), XmssParameterSet.XMSS_SHA2_10_256, false);
3639
{
3740
// this is special for XMSS, a long-running (cancelable) process
3841

@@ -63,19 +66,13 @@ await xmss.CalculatePublicKeyAsync(progress =>
6366
}
6467
}
6568
Console.WriteLine();
69+
70+
publicKeyPem = xmss.ExportSubjectPublicKeyInfoPem();
6671
Console.WriteLine("Public Key:");
67-
Console.WriteLine(xmss.ExportSubjectPublicKeyInfoPem());
68-
}
69-
var message = new byte[] { 1, 2, 3 };
70-
byte[] signature;
71-
string publicKeyPem;
72-
{
73-
Console.WriteLine("Signing a message...");
72+
Console.WriteLine(publicKeyPem);
7473

75-
using var xmss = new Xmss();
76-
xmss.ImportPrivateKey(new XmssFileStateManager(@"C:\test"));
74+
Console.WriteLine("Signing a message...");
7775
signature = xmss.Sign(message);
78-
publicKeyPem = xmss.ExportSubjectPublicKeyInfoPem();
7976
}
8077
{
8178
using var xmss = new Xmss();

UnitTests/UnitTests/MemoryStateManager.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public void StoreStatefulPart(ReadOnlySpan<byte> expected, ReadOnlySpan<byte> da
5353

5454
if (State[XmssKeyPart.PrivateStateful] is not byte[] oldData)
5555
{
56-
throw new InvalidOperationException("Part does not exists.");
56+
throw new InvalidOperationException("Part does not exist.");
5757
}
5858
if (!expected.SequenceEqual(oldData))
5959
{
@@ -68,7 +68,7 @@ public void Load(XmssKeyPart part, Span<byte> destination)
6868

6969
if (State[part] is not byte[] data)
7070
{
71-
throw new ArgumentException("Part does not exist.", nameof(part));
71+
throw new InvalidOperationException("Part does not exist.");
7272
}
7373
if (data.Length != destination.Length)
7474
{
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
// SPDX-FileCopyrightText: 2025 Frans van Dorsselaer
2+
//
3+
// SPDX-License-Identifier: MIT
4+
5+
using Dorssel.Security.Cryptography;
6+
7+
namespace UnitTests;
8+
9+
[TestClass]
10+
sealed class XmssMemoryStateManagerTests
11+
{
12+
[TestMethod]
13+
public void Constructor()
14+
{
15+
using var stateManager = new XmssMemoryStateManager();
16+
}
17+
18+
[TestMethod]
19+
public void Store()
20+
{
21+
using var stateManager = new XmssMemoryStateManager();
22+
23+
stateManager.Store(XmssKeyPart.Public, [1]);
24+
}
25+
26+
[TestMethod]
27+
public void Store_Exists()
28+
{
29+
using var stateManager = new XmssMemoryStateManager();
30+
31+
stateManager.Store(XmssKeyPart.Public, [1]);
32+
33+
Assert.ThrowsException<InvalidOperationException>(() =>
34+
{
35+
stateManager.Store(XmssKeyPart.Public, [2]);
36+
});
37+
}
38+
39+
[TestMethod]
40+
public void StoreStoreStatefulPart()
41+
{
42+
using var stateManager = new XmssMemoryStateManager();
43+
stateManager.Store(XmssKeyPart.PrivateStateful, [1]);
44+
45+
stateManager.StoreStatefulPart([1], [2]);
46+
}
47+
48+
[TestMethod]
49+
public void StoreStoreStatefulPart_ExpectedAndDataMismatch()
50+
{
51+
using var stateManager = new XmssMemoryStateManager();
52+
stateManager.Store(XmssKeyPart.PrivateStateful, [1]);
53+
54+
Assert.ThrowsException<ArgumentException>(() =>
55+
{
56+
stateManager.StoreStatefulPart([1], [2, 3]);
57+
});
58+
}
59+
60+
[TestMethod]
61+
public void StoreStoreStatefulPart_FileNotExists()
62+
{
63+
using var stateManager = new XmssMemoryStateManager();
64+
65+
Assert.ThrowsException<InvalidOperationException>(() =>
66+
{
67+
stateManager.StoreStatefulPart([1], [2]);
68+
});
69+
}
70+
71+
[TestMethod]
72+
public void StoreStoreStatefulPart_FileSizeMismatch()
73+
{
74+
using var stateManager = new XmssMemoryStateManager();
75+
stateManager.Store(XmssKeyPart.PrivateStateful, [1]);
76+
77+
Assert.ThrowsException<ArgumentException>(() =>
78+
{
79+
stateManager.StoreStatefulPart([1, 2], [3, 4]);
80+
});
81+
}
82+
83+
[TestMethod]
84+
public void StoreStoreStatefulPart_FileContentMismatch()
85+
{
86+
using var stateManager = new XmssMemoryStateManager();
87+
stateManager.Store(XmssKeyPart.PrivateStateful, [1]);
88+
89+
Assert.ThrowsException<ArgumentException>(() =>
90+
{
91+
stateManager.StoreStatefulPart([2], [3]);
92+
});
93+
}
94+
95+
[TestMethod]
96+
public void Load()
97+
{
98+
var data = new byte[] { 1, 2, 3 };
99+
100+
using var stateManager = new XmssMemoryStateManager();
101+
stateManager.Store(XmssKeyPart.Public, data);
102+
103+
var read = new byte[data.Length];
104+
stateManager.Load(XmssKeyPart.Public, read);
105+
106+
CollectionAssert.AreEqual(data, read);
107+
}
108+
109+
[TestMethod]
110+
public void Load_WrongSize()
111+
{
112+
var data = new byte[] { 1, 2, 3 };
113+
114+
using var stateManager = new XmssMemoryStateManager();
115+
stateManager.Store(XmssKeyPart.Public, data);
116+
117+
var read = new byte[data.Length - 1];
118+
Assert.ThrowsException<ArgumentException>(() =>
119+
{
120+
stateManager.Load(XmssKeyPart.Public, read);
121+
});
122+
}
123+
124+
[TestMethod]
125+
public void Load_PartNotExists()
126+
{
127+
using var stateManager = new XmssMemoryStateManager();
128+
129+
Assert.ThrowsException<InvalidOperationException>(() =>
130+
{
131+
stateManager.Load(XmssKeyPart.Public, new byte[1]);
132+
});
133+
}
134+
135+
[TestMethod]
136+
public void Load_UnknownPart()
137+
{
138+
var data = new byte[] { 1, 2, 3 };
139+
140+
using var stateManager = new XmssMemoryStateManager();
141+
142+
Assert.ThrowsException<ArgumentOutOfRangeException>(() =>
143+
{
144+
stateManager.Load(Enum.GetValues<XmssKeyPart>().Max() + 1, new byte[1]);
145+
});
146+
}
147+
148+
[TestMethod]
149+
public void DeletePublicPart()
150+
{
151+
using var stateManager = new XmssMemoryStateManager();
152+
stateManager.Store(XmssKeyPart.Public, [1]);
153+
154+
stateManager.DeletePublicPart();
155+
156+
157+
}
158+
159+
[TestMethod]
160+
public void DeletePublicPart_PartNotExists()
161+
{
162+
using var stateManager = new XmssMemoryStateManager();
163+
164+
stateManager.DeletePublicPart();
165+
}
166+
167+
[TestMethod]
168+
public void DeleteAll()
169+
{
170+
using var stateManager = new XmssMemoryStateManager();
171+
stateManager.Store(XmssKeyPart.PrivateStateless, [1]);
172+
stateManager.Store(XmssKeyPart.PrivateStateful, [2]);
173+
stateManager.Store(XmssKeyPart.Public, [3]);
174+
175+
stateManager.DeleteAll();
176+
}
177+
178+
[TestMethod]
179+
public void DeleteAll_PartsNotExist()
180+
{
181+
using var stateManager = new XmssMemoryStateManager();
182+
183+
stateManager.DeleteAll();
184+
}
185+
}

Xmss/XmssMemoryStateManager.cs

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
// SPDX-FileCopyrightText: 2025 Frans van Dorsselaer
2+
//
3+
// SPDX-License-Identifier: MIT
4+
5+
using System.Diagnostics;
6+
using System.Security.Cryptography;
7+
8+
namespace Dorssel.Security.Cryptography;
9+
10+
/// <summary>
11+
/// TODO
12+
/// </summary>
13+
public sealed class XmssMemoryStateManager()
14+
: IXmssStateManager, IDisposable
15+
{
16+
readonly Dictionary<XmssKeyPart, byte[]?> State = new()
17+
{
18+
{ XmssKeyPart.PrivateStateless, null },
19+
{ XmssKeyPart.PrivateStateful, null },
20+
{ XmssKeyPart.Public, null },
21+
};
22+
23+
[StackTraceHidden]
24+
void ThrowIfInvalidPart(XmssKeyPart part)
25+
{
26+
if (!State.ContainsKey(part))
27+
{
28+
throw new ArgumentOutOfRangeException(nameof(part));
29+
}
30+
}
31+
32+
/// <inheritdoc/>
33+
public void Store(XmssKeyPart part, ReadOnlySpan<byte> data)
34+
{
35+
lock (State)
36+
{
37+
ThrowIfInvalidPart(part);
38+
39+
ObjectDisposedException.ThrowIf(IsDisposed, this);
40+
41+
if (State[part] is not null)
42+
{
43+
throw new InvalidOperationException("Part already exists.");
44+
}
45+
State[part] = data.ToArray();
46+
}
47+
}
48+
49+
/// <inheritdoc/>
50+
public void StoreStatefulPart(ReadOnlySpan<byte> expected, ReadOnlySpan<byte> data)
51+
{
52+
if (data.Length != expected.Length)
53+
{
54+
throw new ArgumentException("Expected data and new data must have the same size.");
55+
}
56+
57+
lock (State)
58+
{
59+
ObjectDisposedException.ThrowIf(IsDisposed, this);
60+
61+
if (State[XmssKeyPart.PrivateStateful] is not byte[] oldData)
62+
{
63+
throw new InvalidOperationException("Part does not exist.");
64+
}
65+
if (!expected.SequenceEqual(oldData))
66+
{
67+
throw new ArgumentException("Expected content mismatch.", nameof(expected));
68+
}
69+
State[XmssKeyPart.PrivateStateful] = data.ToArray();
70+
}
71+
}
72+
73+
/// <inheritdoc/>
74+
public void Load(XmssKeyPart part, Span<byte> destination)
75+
{
76+
lock (State)
77+
{
78+
ThrowIfInvalidPart(part);
79+
80+
ObjectDisposedException.ThrowIf(IsDisposed, this);
81+
82+
if (State[part] is not byte[] data)
83+
{
84+
throw new InvalidOperationException("Part does not exist.");
85+
}
86+
if (data.Length != destination.Length)
87+
{
88+
throw new ArgumentException("Part size mismatch.", nameof(destination));
89+
}
90+
data.CopyTo(destination);
91+
}
92+
}
93+
94+
/// <inheritdoc/>
95+
public void DeletePublicPart()
96+
{
97+
lock (State)
98+
{
99+
ObjectDisposedException.ThrowIf(IsDisposed, this);
100+
101+
State[XmssKeyPart.Public] = null;
102+
}
103+
}
104+
105+
/// <inheritdoc/>
106+
public void DeleteAll()
107+
{
108+
lock (State)
109+
{
110+
ObjectDisposedException.ThrowIf(IsDisposed, this);
111+
112+
CryptographicOperations.ZeroMemory(State[XmssKeyPart.PrivateStateless]);
113+
CryptographicOperations.ZeroMemory(State[XmssKeyPart.PrivateStateful]);
114+
State[XmssKeyPart.PrivateStateless] = null;
115+
State[XmssKeyPart.PrivateStateful] = null;
116+
State[XmssKeyPart.Public] = null;
117+
}
118+
}
119+
120+
bool IsDisposed;
121+
122+
/// <inheritdoc/>
123+
public void Dispose()
124+
{
125+
lock (State)
126+
{
127+
if (!IsDisposed)
128+
{
129+
DeleteAll();
130+
IsDisposed = true;
131+
}
132+
}
133+
}
134+
}

0 commit comments

Comments
 (0)