Skip to content

Commit e4036b0

Browse files
authored
Improve cryptographic key material tests (#498)
1 parent f62eba2 commit e4036b0

14 files changed

+1076
-388
lines changed

src/Verifiable.BouncyCastle/BouncyCastleCryptographicFunctions.cs

Lines changed: 365 additions & 8 deletions
Large diffs are not rendered by default.

src/Verifiable.BouncyCastle/BouncyCastleKeyMaterialCreator.cs

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using Org.BouncyCastle.Asn1.Sec;
1+
using Org.BouncyCastle.Asn1.Pkcs;
2+
using Org.BouncyCastle.Asn1.Sec;
23
using Org.BouncyCastle.Crypto;
34
using Org.BouncyCastle.Crypto.Generators;
45
using Org.BouncyCastle.Crypto.Parameters;
@@ -91,7 +92,7 @@ public static PublicPrivateKeyMaterial<PublicKeyMemory, PrivateKeyMemory> Create
9192
return new PublicPrivateKeyMaterial<PublicKeyMemory, PrivateKeyMemory>(publicKeyMemory, privateKeyMemory);
9293
}
9394

94-
95+
9596
private static PublicPrivateKeyMaterial<PublicKeyMemory, PrivateKeyMemory> CreateEcKeys(
9697
string secCurveName,
9798
Tag publicKeyTag,
@@ -157,25 +158,31 @@ private static PublicPrivateKeyMaterial<PublicKeyMemory, PrivateKeyMemory> Creat
157158
generator.Init(keyGenParam);
158159
var keyPair = generator.GenerateKeyPair();
159160

160-
// Cast to RsaPublic/PrivateKeyParameters
161161
var publicKeyParam = (RsaKeyParameters)keyPair.Public;
162162
var privateKeyParam = (RsaPrivateCrtKeyParameters)keyPair.Private;
163163

164-
// Get public key modulus and private key 'D' bytes
165-
byte[] modulusBytes = publicKeyParam.Modulus.ToByteArray();
166-
byte[] privateKeyBytes = privateKeyParam.Exponent.ToByteArray();
167-
168-
// Encode public key modulus
164+
//Encode the public key modulus in the DID-compatible format.
165+
byte[] modulusBytes = publicKeyParam.Modulus.ToByteArrayUnsigned();
169166
byte[] derEncodedPublicKey = RsaUtilities.Encode(modulusBytes);
170167

168+
//Serialize the private key as PKCS#1 DER, compatible with both backends.
169+
byte[] privateKeyBytes = RsaPrivateKeyStructure.GetInstance(new RsaPrivateKeyStructure(
170+
privateKeyParam.Modulus,
171+
privateKeyParam.PublicExponent,
172+
privateKeyParam.Exponent,
173+
privateKeyParam.P,
174+
privateKeyParam.Q,
175+
privateKeyParam.DP,
176+
privateKeyParam.DQ,
177+
privateKeyParam.QInv)).GetDerEncoded();
178+
171179
var (publicKeyTag, privateKeyTag) = GetTags(keySizeInBits);
172180
var publicKeyMemory = new PublicKeyMemory(AsPooledMemory(derEncodedPublicKey, memoryPool), publicKeyTag);
173181
var privateKeyMemory = new PrivateKeyMemory(AsPooledMemory(privateKeyBytes, memoryPool), privateKeyTag);
174182

175-
// Clear sensitive data from memory
176183
Array.Clear(modulusBytes, 0, modulusBytes.Length);
177-
Array.Clear(privateKeyBytes, 0, privateKeyBytes.Length);
178184
Array.Clear(derEncodedPublicKey, 0, derEncodedPublicKey.Length);
185+
Array.Clear(privateKeyBytes, 0, privateKeyBytes.Length);
179186

180187
return new PublicPrivateKeyMaterial<PublicKeyMemory, PrivateKeyMemory>(publicKeyMemory, privateKeyMemory);
181188
}

src/Verifiable.Microsoft/MicrosoftCryptographicFunctions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ private static ValueTask<bool> VerifyECDsa(ReadOnlySpan<byte> dataToVerify, Read
170170
return ValueTask.FromResult(false);
171171
}
172172

173-
173+
174174
private static ValueTask<Signature> SignECDsa(ReadOnlySpan<byte> privateKeyBytes, ReadOnlySpan<byte> dataToSign, MemoryPool<byte> signaturePool, ECCurve curve, HashAlgorithmName hashAlgorithmName, Tag signatureTag)
175175
{
176176
var key = ECDsa.Create(new ECParameters

src/Verifiable.NSec/NSecAlgorithms.cs renamed to src/Verifiable.NSec/NSecCryptographicFunctions.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@
1010
namespace Verifiable.NSec;
1111

1212
/// <summary>
13-
/// Adapter functions for NSec cryptographic operations matching <see cref="SigningDelegate"/>
13+
/// Adapter functions for NSec cryptographic operations matching <see cref="SigningDelegate"/>
1414
/// and <see cref="VerificationDelegate"/> signatures.
1515
/// </summary>
16-
public static class NSecAlgorithms
16+
public static class NSecCryptographicFunctions
1717
{
1818
/// <summary>
1919
/// Signs data using Ed25519 via NSec.
@@ -60,4 +60,4 @@ public static ValueTask<bool> VerifyEd25519Async(
6060
var publicKey = PublicKey.Import(SignatureAlgorithm.Ed25519, publicKeyMaterial.Span, KeyBlobFormat.RawPublicKey);
6161
return ValueTask.FromResult(SignatureAlgorithm.Ed25519.Verify(publicKey, dataToVerify.Span, signature.Span));
6262
}
63-
}
63+
}

src/Verifiable.NSec/NSecKeyGenerator.cs renamed to src/Verifiable.NSec/NSecKeyMaterialCreator.cs

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,40 @@
77

88
namespace Verifiable.NSec
99
{
10+
/// <summary>
11+
/// Creates key material for NSec-supported algorithms. The caller is responsible
12+
/// for disposing the individual <see cref="PublicKeyMemory"/> and <see cref="PrivateKeyMemory"/>
13+
/// instances returned within the key material.
14+
/// </summary>
1015
[SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "The caller is responsible for disposing the returned key material instances.")]
11-
public static class NSecKeyCreator
16+
public static class NSecKeyMaterialCreator
1217
{
18+
/// <summary>
19+
/// Creates fresh Ed25519 key material using the NSec cryptographic backend.
20+
/// </summary>
21+
/// <param name="memoryPool">The memory pool to allocate key buffers from.</param>
22+
/// <returns>A new key pair. The caller is responsible for disposing each key individually.</returns>
1323
public static PublicPrivateKeyMaterial<PublicKeyMemory, PrivateKeyMemory> CreateEd25519Keys(MemoryPool<byte> memoryPool)
1424
{
1525
return CreateKeys(SignatureAlgorithm.Ed25519, memoryPool, CryptoTags.Ed25519PublicKey, CryptoTags.Ed25519PrivateKey);
1626
}
1727

28+
29+
/// <summary>
30+
/// Creates fresh X25519 key material using the NSec cryptographic backend.
31+
/// </summary>
32+
/// <param name="memoryPool">The memory pool to allocate key buffers from.</param>
33+
/// <returns>A new key pair. The caller is responsible for disposing each key individually.</returns>
1834
public static PublicPrivateKeyMaterial<PublicKeyMemory, PrivateKeyMemory> CreateX25519Keys(MemoryPool<byte> memoryPool)
1935
{
2036
return CreateKeys(KeyAgreementAlgorithm.X25519, memoryPool, CryptoTags.X25519PublicKey, CryptoTags.X25519PrivateKey);
2137
}
2238

23-
2439

40+
/// <summary>
41+
/// Creates key material for the given NSec algorithm by generating a key pair,
42+
/// exporting the raw bytes, and wrapping them in pooled memory.
43+
/// </summary>
2544
private static PublicPrivateKeyMaterial<PublicKeyMemory, PrivateKeyMemory> CreateKeys(
2645
Algorithm algorithm,
2746
MemoryPool<byte> memoryPool,
@@ -32,7 +51,7 @@ private static PublicPrivateKeyMaterial<PublicKeyMemory, PrivateKeyMemory> Creat
3251
{
3352
var publicKeyBytes = key.Export(KeyBlobFormat.RawPublicKey);
3453
var privateKeyBytes = key.Export(KeyBlobFormat.RawPrivateKey);
35-
54+
3655
var publicKeyMemory = new PublicKeyMemory(AsPooledMemory(publicKeyBytes, memoryPool), publicKeyTag);
3756
var privateKeyMemory = new PrivateKeyMemory(AsPooledMemory(privateKeyBytes, memoryPool), privateKeyTag);
3857
Array.Clear(publicKeyBytes, 0, publicKeyBytes.Length);
@@ -56,4 +75,4 @@ private static IMemoryOwner<byte> AsPooledMemory(byte[] keyBytes, MemoryPool<byt
5675
return keyBuffer;
5776
}
5877
}
59-
}
78+
}
Lines changed: 187 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,221 @@
1-
using System.Buffers;
21
using System.Text;
32
using Verifiable.BouncyCastle;
43
using Verifiable.Cryptography;
54

65
namespace Verifiable.Tests.Cryptography
76
{
87
/// <summary>
9-
/// These test specifically BouncyCastle as the cryptographic provider.
8+
/// Tests BouncyCastle as the cryptographic provider across all supported algorithms.
109
/// </summary>
1110
[TestClass]
1211
internal sealed class BouncyCastleCryptographicTests
1312
{
13+
public TestContext TestContext { get; set; } = null!;
14+
1415
/// <summary>
15-
/// Used in tests as test data.
16+
/// Shared test payload used for all sign-and-verify tests.
1617
/// </summary>
17-
private byte[] TestData { get; } = Encoding.UTF8.GetBytes("This is a test string.");
18+
private static byte[] TestData { get; } = Encoding.UTF8.GetBytes("BouncyCastle cryptographic test payload.");
1819

1920

2021
[TestMethod]
21-
public void CanGenerateKeyPairEd255019()
22+
public void Ed25519KeyPairHasNonEmptyMaterial()
2223
{
23-
var keys = BouncyCastleKeyMaterialCreator.CreateEd25519Keys(MemoryPool<byte>.Shared);
24-
Assert.IsGreaterThan(0, keys.PublicKey.AsReadOnlySpan().Length);
25-
Assert.IsGreaterThan(0, keys.PrivateKey.AsReadOnlySpan().Length);
24+
var keys = BouncyCastleKeyMaterialCreator.CreateEd25519Keys(SensitiveMemoryPool<byte>.Shared);
25+
using var publicKey = keys.PublicKey;
26+
using var privateKey = keys.PrivateKey;
27+
28+
Assert.IsGreaterThan(0, publicKey.AsReadOnlySpan().Length);
29+
Assert.IsGreaterThan(0, privateKey.AsReadOnlySpan().Length);
2630
}
2731

2832

2933
[TestMethod]
30-
public async ValueTask CanSignAndVerifyEd255019()
34+
public async Task Ed25519SignatureVerifies()
3135
{
32-
var keys = BouncyCastleKeyMaterialCreator.CreateEd25519Keys(MemoryPool<byte>.Shared);
33-
var publicKey = keys.PublicKey;
34-
var privateKey = keys.PrivateKey;
36+
var keys = BouncyCastleKeyMaterialCreator.CreateEd25519Keys(SensitiveMemoryPool<byte>.Shared);
37+
using var publicKey = keys.PublicKey;
38+
using var privateKey = keys.PrivateKey;
39+
40+
ReadOnlyMemory<byte> data = TestData;
41+
using var signature = await BouncyCastleCryptographicFunctions
42+
.SignEd25519Async(privateKey.AsReadOnlyMemory(), data, SensitiveMemoryPool<byte>.Shared)
43+
.ConfigureAwait(false);
3544

36-
var data = (ReadOnlyMemory<byte>)TestData;
37-
using var signature = await privateKey.SignAsync(data, BouncyCastleCryptographicFunctions.SignEd25519Async, MemoryPool<byte>.Shared)
45+
bool isVerified = await BouncyCastleCryptographicFunctions
46+
.VerifyEd25519Async(data, signature.AsReadOnlyMemory(), publicKey.AsReadOnlyMemory())
3847
.ConfigureAwait(false);
39-
Assert.IsTrue(await publicKey.VerifyAsync(data, signature, BouncyCastleCryptographicFunctions.VerifyEd25519Async)
40-
.ConfigureAwait(false));
48+
49+
Assert.IsTrue(isVerified);
4150
}
4251

4352

4453
[TestMethod]
45-
public async ValueTask CanCreateIdentifiedKeyAndVerify()
54+
public async Task Ed25519IdentifiedKeySignatureVerifies()
4655
{
47-
var keys = BouncyCastleKeyMaterialCreator.CreateEd25519Keys(MemoryPool<byte>.Shared);
56+
var keys = BouncyCastleKeyMaterialCreator.CreateEd25519Keys(SensitiveMemoryPool<byte>.Shared);
57+
using var publicKey = new PublicKey(keys.PublicKey, "ed25519-test", BouncyCastleCryptographicFunctions.VerifyEd25519Async);
58+
using var privateKey = new PrivateKey(keys.PrivateKey, "ed25519-test", BouncyCastleCryptographicFunctions.SignEd25519Async);
4859

49-
using var publicKey = new PublicKey(keys.PublicKey, "Test-1", BouncyCastleCryptographicFunctions.VerifyEd25519Async);
50-
using var privateKey = new PrivateKey(keys.PrivateKey, "Test-1", BouncyCastleCryptographicFunctions.SignEd25519Async);
60+
ReadOnlyMemory<byte> data = TestData;
61+
using var signature = await privateKey.SignAsync(data, SensitiveMemoryPool<byte>.Shared).ConfigureAwait(false);
5162

52-
var data = (ReadOnlyMemory<byte>)TestData;
53-
using var signature = await privateKey.SignAsync(data, MemoryPool<byte>.Shared).ConfigureAwait(false);
5463
Assert.IsTrue(await publicKey.VerifyAsync(data, signature).ConfigureAwait(false));
5564
}
65+
66+
67+
[TestMethod]
68+
public async Task P256SignatureVerifies()
69+
{
70+
var keys = BouncyCastleKeyMaterialCreator.CreateP256Keys(SensitiveMemoryPool<byte>.Shared);
71+
using var publicKey = keys.PublicKey;
72+
using var privateKey = keys.PrivateKey;
73+
74+
ReadOnlyMemory<byte> data = TestData;
75+
using var signature = await BouncyCastleCryptographicFunctions
76+
.SignP256Async(privateKey.AsReadOnlyMemory(), data, SensitiveMemoryPool<byte>.Shared)
77+
.ConfigureAwait(false);
78+
79+
bool isVerified = await BouncyCastleCryptographicFunctions
80+
.VerifyP256Async(data, signature.AsReadOnlyMemory(), publicKey.AsReadOnlyMemory())
81+
.ConfigureAwait(false);
82+
83+
Assert.IsTrue(isVerified);
84+
}
85+
86+
87+
[TestMethod]
88+
public async Task P384SignatureVerifies()
89+
{
90+
var keys = BouncyCastleKeyMaterialCreator.CreateP384Keys(SensitiveMemoryPool<byte>.Shared);
91+
using var publicKey = keys.PublicKey;
92+
using var privateKey = keys.PrivateKey;
93+
94+
ReadOnlyMemory<byte> data = TestData;
95+
using var signature = await BouncyCastleCryptographicFunctions
96+
.SignP384Async(privateKey.AsReadOnlyMemory(), data, SensitiveMemoryPool<byte>.Shared)
97+
.ConfigureAwait(false);
98+
99+
bool isVerified = await BouncyCastleCryptographicFunctions
100+
.VerifyP384Async(data, signature.AsReadOnlyMemory(), publicKey.AsReadOnlyMemory())
101+
.ConfigureAwait(false);
102+
103+
Assert.IsTrue(isVerified);
104+
}
105+
106+
107+
[TestMethod]
108+
public async Task P521SignatureVerifies()
109+
{
110+
var keys = BouncyCastleKeyMaterialCreator.CreateP521Keys(SensitiveMemoryPool<byte>.Shared);
111+
using var publicKey = keys.PublicKey;
112+
using var privateKey = keys.PrivateKey;
113+
114+
ReadOnlyMemory<byte> data = TestData;
115+
using var signature = await BouncyCastleCryptographicFunctions
116+
.SignP521Async(privateKey.AsReadOnlyMemory(), data, SensitiveMemoryPool<byte>.Shared)
117+
.ConfigureAwait(false);
118+
119+
bool isVerified = await BouncyCastleCryptographicFunctions
120+
.VerifyP521Async(data, signature.AsReadOnlyMemory(), publicKey.AsReadOnlyMemory())
121+
.ConfigureAwait(false);
122+
123+
Assert.IsTrue(isVerified);
124+
}
125+
126+
127+
[TestMethod]
128+
public async Task Secp256k1SignatureVerifies()
129+
{
130+
var keys = BouncyCastleKeyMaterialCreator.CreateSecp256k1Keys(SensitiveMemoryPool<byte>.Shared);
131+
using var publicKey = keys.PublicKey;
132+
using var privateKey = keys.PrivateKey;
133+
134+
ReadOnlyMemory<byte> data = TestData;
135+
using var signature = await BouncyCastleCryptographicFunctions
136+
.SignSecp256k1Async(privateKey.AsReadOnlyMemory(), data, SensitiveMemoryPool<byte>.Shared)
137+
.ConfigureAwait(false);
138+
139+
bool isVerified = await BouncyCastleCryptographicFunctions
140+
.VerifySecp256k1Async(data, signature.AsReadOnlyMemory(), publicKey.AsReadOnlyMemory())
141+
.ConfigureAwait(false);
142+
143+
Assert.IsTrue(isVerified);
144+
}
145+
146+
147+
[TestMethod]
148+
public async Task Rsa2048SignatureVerifies()
149+
{
150+
var keys = BouncyCastleKeyMaterialCreator.CreateRsa2048Keys(SensitiveMemoryPool<byte>.Shared);
151+
using var publicKey = keys.PublicKey;
152+
using var privateKey = keys.PrivateKey;
153+
154+
ReadOnlyMemory<byte> data = TestData;
155+
using var signature = await BouncyCastleCryptographicFunctions
156+
.SignRsa2048Async(privateKey.AsReadOnlyMemory(), data, SensitiveMemoryPool<byte>.Shared)
157+
.ConfigureAwait(false);
158+
159+
bool isVerified = await BouncyCastleCryptographicFunctions
160+
.VerifyRsa2048Async(data, signature.AsReadOnlyMemory(), publicKey.AsReadOnlyMemory())
161+
.ConfigureAwait(false);
162+
163+
Assert.IsTrue(isVerified);
164+
}
165+
166+
167+
[TestMethod]
168+
public async Task Rsa4096SignatureVerifies()
169+
{
170+
var keys = BouncyCastleKeyMaterialCreator.CreateRsa4096Keys(SensitiveMemoryPool<byte>.Shared);
171+
using var publicKey = keys.PublicKey;
172+
using var privateKey = keys.PrivateKey;
173+
174+
ReadOnlyMemory<byte> data = TestData;
175+
using var signature = await BouncyCastleCryptographicFunctions
176+
.SignRsa4096Async(privateKey.AsReadOnlyMemory(), data, SensitiveMemoryPool<byte>.Shared)
177+
.ConfigureAwait(false);
178+
179+
bool isVerified = await BouncyCastleCryptographicFunctions
180+
.VerifyRsa4096Async(data, signature.AsReadOnlyMemory(), publicKey.AsReadOnlyMemory())
181+
.ConfigureAwait(false);
182+
183+
Assert.IsTrue(isVerified);
184+
}
185+
186+
187+
[TestMethod]
188+
public void X25519KeyPairHasNonEmptyMaterial()
189+
{
190+
var keys = BouncyCastleKeyMaterialCreator.CreateX25519Keys(SensitiveMemoryPool<byte>.Shared);
191+
using var publicKey = keys.PublicKey;
192+
using var privateKey = keys.PrivateKey;
193+
194+
Assert.IsGreaterThan(0, publicKey.AsReadOnlySpan().Length);
195+
Assert.IsGreaterThan(0, privateKey.AsReadOnlySpan().Length);
196+
}
197+
198+
199+
[TestMethod]
200+
public async Task X25519SharedSecretDerivationProducesSameResultForBothParties()
201+
{
202+
var aliceKeys = BouncyCastleKeyMaterialCreator.CreateX25519Keys(SensitiveMemoryPool<byte>.Shared);
203+
using var alicePublicKey = aliceKeys.PublicKey;
204+
using var alicePrivateKey = aliceKeys.PrivateKey;
205+
206+
var bobKeys = BouncyCastleKeyMaterialCreator.CreateX25519Keys(SensitiveMemoryPool<byte>.Shared);
207+
using var bobPublicKey = bobKeys.PublicKey;
208+
using var bobPrivateKey = bobKeys.PrivateKey;
209+
210+
using var aliceSecret = await BouncyCastleCryptographicFunctions
211+
.DeriveX25519SharedSecretAsync(alicePrivateKey.AsReadOnlyMemory(), bobPublicKey.AsReadOnlyMemory(), SensitiveMemoryPool<byte>.Shared)
212+
.ConfigureAwait(false);
213+
214+
using var bobSecret = await BouncyCastleCryptographicFunctions
215+
.DeriveX25519SharedSecretAsync(bobPrivateKey.AsReadOnlyMemory(), alicePublicKey.AsReadOnlyMemory(), SensitiveMemoryPool<byte>.Shared)
216+
.ConfigureAwait(false);
217+
218+
Assert.IsTrue(aliceSecret.Memory.Span.SequenceEqual(bobSecret.Memory.Span));
219+
}
56220
}
57-
}
221+
}

0 commit comments

Comments
 (0)