Skip to content

Commit 5314b8c

Browse files
Refactor SLH-DSA tests (#114981)
1 parent 006ad21 commit 5314b8c

21 files changed

+870
-729
lines changed

src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/SlhDsa/SlhDsaAlgorithmTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
namespace System.Security.Cryptography.SLHDsa.Tests
77
{
8-
public class SlhDsaAlgorithmTests
8+
public static class SlhDsaAlgorithmTests
99
{
1010
[Fact]
1111
public static void AlgorithmsHaveExpectedParameters()

src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/SlhDsa/SlhDsaApiTests.cs

Lines changed: 0 additions & 171 deletions
This file was deleted.
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using Xunit;
7+
8+
namespace System.Security.Cryptography.SLHDsa.Tests
9+
{
10+
public static class SlhDsaContractTests
11+
{
12+
public static IEnumerable<object[]> ArgumentValidationData =>
13+
from algorithm in SlhDsaTestData.AlgorithmsRaw
14+
from shouldDispose in new[] { true, false }
15+
select new object[] { algorithm, shouldDispose };
16+
17+
[Theory]
18+
[MemberData(nameof(ArgumentValidationData))]
19+
public static void NullArgumentValidation(SlhDsaAlgorithm algorithm, bool shouldDispose)
20+
{
21+
using SlhDsa slhDsa = SlhDsaMockImplementation.Create(algorithm);
22+
23+
if (shouldDispose)
24+
{
25+
// Test that argument validation exceptions take precedence over ObjectDisposedException
26+
slhDsa.Dispose();
27+
}
28+
29+
AssertExtensions.Throws<ArgumentNullException>("pbeParameters", () => slhDsa.ExportEncryptedPkcs8PrivateKey(ReadOnlySpan<byte>.Empty, null));
30+
AssertExtensions.Throws<ArgumentNullException>("pbeParameters", () => slhDsa.ExportEncryptedPkcs8PrivateKey(ReadOnlySpan<char>.Empty, null));
31+
AssertExtensions.Throws<ArgumentNullException>("pbeParameters", () => slhDsa.ExportEncryptedPkcs8PrivateKeyPem(ReadOnlySpan<byte>.Empty, null));
32+
AssertExtensions.Throws<ArgumentNullException>("pbeParameters", () => slhDsa.ExportEncryptedPkcs8PrivateKeyPem(ReadOnlySpan<char>.Empty, null));
33+
AssertExtensions.Throws<ArgumentNullException>("pbeParameters", () => slhDsa.TryExportEncryptedPkcs8PrivateKey(ReadOnlySpan<byte>.Empty, null, Span<byte>.Empty, out _));
34+
AssertExtensions.Throws<ArgumentNullException>("pbeParameters", () => slhDsa.TryExportEncryptedPkcs8PrivateKey(ReadOnlySpan<char>.Empty, null, Span<byte>.Empty, out _));
35+
}
36+
37+
[Theory]
38+
[MemberData(nameof(ArgumentValidationData))]
39+
public static void ArgumentValidation(SlhDsaAlgorithm algorithm, bool shouldDispose)
40+
{
41+
using SlhDsa slhDsa = SlhDsaMockImplementation.Create(algorithm);
42+
43+
int publicKeySize = algorithm.PublicKeySizeInBytes;
44+
int secretKeySize = algorithm.SecretKeySizeInBytes;
45+
int signatureSize = algorithm.SignatureSizeInBytes;
46+
47+
if (shouldDispose)
48+
{
49+
// Test that argument validation exceptions take precedence over ObjectDisposedException
50+
slhDsa.Dispose();
51+
}
52+
53+
AssertExtensions.Throws<ArgumentException>("destination", () => slhDsa.ExportSlhDsaPublicKey(new byte[publicKeySize - 1]));
54+
AssertExtensions.Throws<ArgumentException>("destination", () => slhDsa.ExportSlhDsaSecretKey(new byte[secretKeySize - 1]));
55+
AssertExtensions.Throws<ArgumentException>("destination", () => slhDsa.SignData(ReadOnlySpan<byte>.Empty, new byte[signatureSize - 1], ReadOnlySpan<byte>.Empty));
56+
57+
// Context length must be less than 256
58+
AssertExtensions.Throws<ArgumentOutOfRangeException>("context", () => slhDsa.SignData(ReadOnlySpan<byte>.Empty, Span<byte>.Empty, new byte[256]));
59+
AssertExtensions.Throws<ArgumentOutOfRangeException>("context", () => slhDsa.VerifyData(ReadOnlySpan<byte>.Empty, Span<byte>.Empty, new byte[256]));
60+
}
61+
62+
public static IEnumerable<object[]> ApiWithDestinationSpanTestData =>
63+
from algorithm in SlhDsaTestData.AlgorithmsRaw
64+
from destinationLargerThanRequired in new[] { true, false }
65+
select new object[] { algorithm, destinationLargerThanRequired };
66+
67+
private const int PaddingSize = 10;
68+
69+
[Theory]
70+
[MemberData(nameof(ApiWithDestinationSpanTestData))]
71+
public static void ExportSlhDsaPublicKey_CallsCore(SlhDsaAlgorithm algorithm, bool destinationLargerThanRequired)
72+
{
73+
using SlhDsaMockImplementation slhDsa = SlhDsaMockImplementation.Create(algorithm);
74+
75+
int publicKeySize = algorithm.PublicKeySizeInBytes;
76+
byte[] publicKey = CreatePaddedFilledArray(publicKeySize, 42);
77+
78+
// Extra bytes in destination buffer should not be touched
79+
int extraBytes = destinationLargerThanRequired ? PaddingSize / 2 : 0;
80+
Memory<byte> destination = publicKey.AsMemory(PaddingSize, publicKeySize + extraBytes);
81+
82+
slhDsa.ExportSlhDsaPublicKeyCoreHook = _ => { };
83+
slhDsa.AddDestinationBufferIsSameAssertion(destination[..publicKeySize]);
84+
slhDsa.AddFillDestination(1);
85+
86+
slhDsa.ExportSlhDsaPublicKey(destination.Span);
87+
Assert.Equal(1, slhDsa.ExportSlhDsaPublicKeyCoreCallCount);
88+
AssertExpectedFill(publicKey, fillElement: 1, paddingElement: 42, PaddingSize, publicKeySize);
89+
}
90+
91+
[Theory]
92+
[MemberData(nameof(ApiWithDestinationSpanTestData))]
93+
public static void ExportSlhDsaSecretKey_CallsCore(SlhDsaAlgorithm algorithm, bool destinationLargerThanRequired)
94+
{
95+
using SlhDsaMockImplementation slhDsa = SlhDsaMockImplementation.Create(algorithm);
96+
97+
int secretKeySize = algorithm.SecretKeySizeInBytes;
98+
byte[] secretKey = CreatePaddedFilledArray(secretKeySize, 42);
99+
100+
// Extra bytes in destination buffer should not be touched
101+
int extraBytes = destinationLargerThanRequired ? PaddingSize / 2 : 0;
102+
Memory<byte> destination = secretKey.AsMemory(PaddingSize, secretKeySize + extraBytes);
103+
104+
slhDsa.ExportSlhDsaSecretKeyCoreHook = _ => { };
105+
slhDsa.AddDestinationBufferIsSameAssertion(destination[..secretKeySize]);
106+
slhDsa.AddFillDestination(1);
107+
108+
slhDsa.ExportSlhDsaSecretKey(destination.Span);
109+
Assert.Equal(1, slhDsa.ExportSlhDsaSecretKeyCoreCallCount);
110+
AssertExpectedFill(secretKey, fillElement: 1, paddingElement: 42, PaddingSize, secretKeySize);
111+
}
112+
113+
[Theory]
114+
[MemberData(nameof(ApiWithDestinationSpanTestData))]
115+
public static void SignData_CallsCore(SlhDsaAlgorithm algorithm, bool destinationLargerThanRequired)
116+
{
117+
using SlhDsaMockImplementation slhDsa = SlhDsaMockImplementation.Create(algorithm);
118+
119+
int signatureSize = algorithm.SignatureSizeInBytes;
120+
byte[] signature = CreatePaddedFilledArray(signatureSize, 42);
121+
122+
// Extra bytes in destination buffer should not be touched
123+
int extraBytes = destinationLargerThanRequired ? PaddingSize / 2 : 0;
124+
Memory<byte> destination = signature.AsMemory(PaddingSize, signatureSize + extraBytes);
125+
byte[] testData = [2];
126+
byte[] testContext = [3];
127+
128+
slhDsa.SignDataCoreHook = (_, _, _) => { };
129+
slhDsa.AddDataBufferIsSameAssertion(testData);
130+
slhDsa.AddContextBufferIsSameAssertion(testContext);
131+
slhDsa.AddDestinationBufferIsSameAssertion(destination[..signatureSize]);
132+
slhDsa.AddFillDestination(1);
133+
134+
slhDsa.SignData(testData, signature.AsSpan(PaddingSize, signatureSize + extraBytes), testContext);
135+
Assert.Equal(1, slhDsa.SignDataCoreCallCount);
136+
AssertExpectedFill(signature, fillElement: 1, paddingElement: 42, PaddingSize, signatureSize);
137+
}
138+
139+
[Theory]
140+
[MemberData(nameof(SlhDsaTestData.AlgorithmsData), MemberType = typeof(SlhDsaTestData))]
141+
public static void VerifyData_CallsCore(SlhDsaAlgorithm algorithm)
142+
{
143+
using SlhDsaMockImplementation slhDsa = SlhDsaMockImplementation.Create(algorithm);
144+
145+
int signatureSize = algorithm.SignatureSizeInBytes;
146+
byte[] testSignature = CreatePaddedFilledArray(signatureSize, 42);
147+
byte[] testData = [2];
148+
byte[] testContext = [3];
149+
bool returnValue = false;
150+
151+
slhDsa.VerifyDataCoreHook = (_, _, _) => returnValue;
152+
slhDsa.AddDataBufferIsSameAssertion(testData);
153+
slhDsa.AddContextBufferIsSameAssertion(testContext);
154+
slhDsa.AddSignatureBufferIsSameAssertion(testSignature.AsMemory(PaddingSize, signatureSize));
155+
156+
// Since `returnValue` is true, this shows the Core method doesn't get called for the wrong sized signature.
157+
returnValue = true;
158+
AssertExtensions.FalseExpression(slhDsa.VerifyData(testData, testSignature.AsSpan(PaddingSize, signatureSize - 1), testContext));
159+
Assert.Equal(0, slhDsa.VerifyDataCoreCallCount);
160+
161+
AssertExtensions.FalseExpression(slhDsa.VerifyData(testData, testSignature.AsSpan(PaddingSize, signatureSize + 1), testContext));
162+
Assert.Equal(0, slhDsa.VerifyDataCoreCallCount);
163+
164+
// But does for the right one.
165+
AssertExtensions.TrueExpression(slhDsa.VerifyData(testData, testSignature.AsSpan(PaddingSize, signatureSize), testContext));
166+
Assert.Equal(1, slhDsa.VerifyDataCoreCallCount);
167+
168+
// And just to prove that the Core method controls the answer...
169+
returnValue = false;
170+
AssertExtensions.FalseExpression(slhDsa.VerifyData(testData, testSignature.AsSpan(PaddingSize, signatureSize), testContext));
171+
Assert.Equal(2, slhDsa.VerifyDataCoreCallCount);
172+
}
173+
174+
[Theory]
175+
[MemberData(nameof(SlhDsaTestData.AlgorithmsData), MemberType = typeof(SlhDsaTestData))]
176+
public static void Dispose_CallsVirtual(SlhDsaAlgorithm algorithm)
177+
{
178+
SlhDsaMockImplementation slhDsa = SlhDsaMockImplementation.Create(algorithm);
179+
bool disposeCalled = false;
180+
181+
// First Dispose call should invoke overridden Dispose should be called
182+
slhDsa.DisposeHook = (bool disposing) =>
183+
{
184+
AssertExtensions.TrueExpression(disposing);
185+
disposeCalled = true;
186+
};
187+
188+
slhDsa.Dispose();
189+
AssertExtensions.TrueExpression(disposeCalled);
190+
191+
// Subsequent Dispose calls should be a no-op
192+
slhDsa.DisposeHook = _ => Assert.Fail();
193+
194+
slhDsa.Dispose();
195+
slhDsa.Dispose(); // no throw
196+
197+
SlhDsaTestHelpers.VerifyDisposed(slhDsa);
198+
}
199+
200+
private static void AssertExpectedFill(ReadOnlySpan<byte> source, byte fillElement, byte paddingElement, int startIndex, int length)
201+
{
202+
// Ensure that the data was filled correctly
203+
AssertExtensions.FilledWith(fillElement, source.Slice(startIndex, length));
204+
205+
// And that the padding was not touched
206+
AssertExtensions.FilledWith(paddingElement, source.Slice(0, startIndex));
207+
AssertExtensions.FilledWith(paddingElement, source.Slice(startIndex + length));
208+
}
209+
210+
private static byte[] CreatePaddedFilledArray(int size, byte filling)
211+
{
212+
byte[] publicKey = new byte[size + 2 * PaddingSize];
213+
publicKey.AsSpan().Fill(filling);
214+
return publicKey;
215+
}
216+
}
217+
}

0 commit comments

Comments
 (0)