Skip to content

Commit 97c11f9

Browse files
authored
Add quantum ciphers using BouncyCastle (#499)
Closes #474.
1 parent e4036b0 commit 97c11f9

27 files changed

+1161
-575
lines changed

Directory.Packages.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
<PackageVersion Include="Microsoft.Sbom.Targets" Version="4.1.5" />
1616
<PackageVersion Include="ModelContextProtocol" Version="0.5.0-preview.1" />
1717
<PackageVersion Include="NSec.Cryptography" Version="25.4.0" />
18-
<PackageVersion Include="Portable.BouncyCastle" Version="1.9.0" />
18+
<PackageVersion Include="BouncyCastle.Cryptography" Version="2.6.2" />
1919
<PackageVersion Include="SIL.ReleaseTasks" Version="3.2.0" />
2020
<PackageVersion Include="System.Collections.Immutable" Version="10.0.1" />
2121
<PackageVersion Include="System.Formats.Cbor" Version="10.0.3" />

NuGet.config

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
<certificate fingerprint="5a2901d6ada3d18260b9c6dfe2133c95d74b9eef6ae0e5dc334c8454d1477df4" hashAlgorithm="SHA256" allowUntrustedRoot="false" />
2121
<certificate fingerprint="0E5F38F57DC1BCC806D8494F4F90FBCEDD988B46760709CBEEC6F4219AA6157D" hashAlgorithm="SHA256" allowUntrustedRoot="false" />
2222
<certificate fingerprint="1F4B311D9ACC115C8DC8018B5A49E00FCE6DA8E2855F9F014CA6F34570BC482D" hashAlgorithm="SHA256" allowUntrustedRoot="false" />
23-
<owners>ModelContextProtocol;ModelContextProtocolOfficial;rsuter;patrik;wtfsck;alirezanet;danielpalme;stryker-mutator;MarkZither;AnthonyLloyd;pshkarin;Microsoft;9ee1;commandlineparser;DotLiquid;roastedamoeba;NightOwl888;FlorianRappl;wtfsck;zzzprojects;SharpDevelop;jedisct1;xoofx;ApacheLuceneNet;Microsoft;dotnetfoundation;albi05;jd.cain.jr;joelhulen;aarnott;AndreyAkinshin;dotnetrdf;kurt;codito;kurtmkurtm;nsec;clairernovotny;Metalnem;sil-lsdev;sedatk;spectresystems;winsharpfuzz;</owners>
23+
<owners>LegionOfTheBouncyCastle;ModelContextProtocol;ModelContextProtocolOfficial;rsuter;patrik;wtfsck;alirezanet;danielpalme;stryker-mutator;MarkZither;AnthonyLloyd;pshkarin;Microsoft;9ee1;commandlineparser;DotLiquid;roastedamoeba;NightOwl888;FlorianRappl;wtfsck;zzzprojects;SharpDevelop;jedisct1;xoofx;ApacheLuceneNet;Microsoft;dotnetfoundation;albi05;jd.cain.jr;joelhulen;aarnott;AndreyAkinshin;dotnetrdf;kurt;codito;kurtmkurtm;nsec;Metalnem;sil-lsdev;sedatk;spectresystems;winsharpfuzz;</owners>
2424
</repository>
2525
</trustedSigners>
2626
</configuration>

src/Verifiable.BouncyCastle/BouncyCastleCryptographicFunctions.cs

Lines changed: 248 additions & 26 deletions
Large diffs are not rendered by default.

src/Verifiable.BouncyCastle/BouncyCastleKeyMaterialCreator.cs

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,84 @@ public static PublicPrivateKeyMaterial<PublicKeyMemory, PrivateKeyMemory> Create
9393
}
9494

9595

96+
/// <summary>
97+
/// Creates ML-DSA-44 key material (NIST FIPS 204, security level 2).
98+
/// Public key: 1312 bytes. Private key: 2560 bytes (seed-expanded).
99+
/// </summary>
100+
/// <param name="memoryPool">The memory pool to allocate key buffers from.</param>
101+
/// <returns>A new key pair. The caller must dispose each key individually.</returns>
102+
public static PublicPrivateKeyMaterial<PublicKeyMemory, PrivateKeyMemory> CreateMlDsa44Keys(MemoryPool<byte> memoryPool)
103+
{
104+
ArgumentNullException.ThrowIfNull(memoryPool);
105+
return CreateMlDsaKeys(MLDsaParameters.ml_dsa_44, memoryPool, CryptoTags.MlDsa44PublicKey, CryptoTags.MlDsa44PrivateKey);
106+
}
107+
108+
109+
/// <summary>
110+
/// Creates ML-DSA-65 key material (NIST FIPS 204, security level 3).
111+
/// Public key: 1952 bytes. Private key: 4032 bytes (seed-expanded).
112+
/// </summary>
113+
/// <param name="memoryPool">The memory pool to allocate key buffers from.</param>
114+
/// <returns>A new key pair. The caller must dispose each key individually.</returns>
115+
public static PublicPrivateKeyMaterial<PublicKeyMemory, PrivateKeyMemory> CreateMlDsa65Keys(MemoryPool<byte> memoryPool)
116+
{
117+
ArgumentNullException.ThrowIfNull(memoryPool);
118+
return CreateMlDsaKeys(MLDsaParameters.ml_dsa_65, memoryPool, CryptoTags.MlDsa65PublicKey, CryptoTags.MlDsa65PrivateKey);
119+
}
120+
121+
122+
/// <summary>
123+
/// Creates ML-DSA-87 key material (NIST FIPS 204, security level 5).
124+
/// Public key: 2592 bytes. Private key: 4896 bytes (seed-expanded).
125+
/// </summary>
126+
/// <param name="memoryPool">The memory pool to allocate key buffers from.</param>
127+
/// <returns>A new key pair. The caller must dispose each key individually.</returns>
128+
public static PublicPrivateKeyMaterial<PublicKeyMemory, PrivateKeyMemory> CreateMlDsa87Keys(MemoryPool<byte> memoryPool)
129+
{
130+
ArgumentNullException.ThrowIfNull(memoryPool);
131+
return CreateMlDsaKeys(MLDsaParameters.ml_dsa_87, memoryPool, CryptoTags.MlDsa87PublicKey, CryptoTags.MlDsa87PrivateKey);
132+
}
133+
134+
135+
/// <summary>
136+
/// Creates ML-KEM-512 key material (NIST FIPS 203, security level 1).
137+
/// Public key: 800 bytes. Ciphertext: 768 bytes. Shared secret: 32 bytes.
138+
/// </summary>
139+
/// <param name="memoryPool">The memory pool to allocate key buffers from.</param>
140+
/// <returns>A new key pair. The caller must dispose each key individually.</returns>
141+
public static PublicPrivateKeyMaterial<PublicKeyMemory, PrivateKeyMemory> CreateMlKem512Keys(MemoryPool<byte> memoryPool)
142+
{
143+
ArgumentNullException.ThrowIfNull(memoryPool);
144+
return CreateMlKemKeys(MLKemParameters.ml_kem_512, memoryPool, CryptoTags.MlKem512PublicKey, CryptoTags.MlKem512PrivateKey);
145+
}
146+
147+
148+
/// <summary>
149+
/// Creates ML-KEM-768 key material (NIST FIPS 203, security level 3).
150+
/// Public key: 1184 bytes. Ciphertext: 1088 bytes. Shared secret: 32 bytes.
151+
/// </summary>
152+
/// <param name="memoryPool">The memory pool to allocate key buffers from.</param>
153+
/// <returns>A new key pair. The caller must dispose each key individually.</returns>
154+
public static PublicPrivateKeyMaterial<PublicKeyMemory, PrivateKeyMemory> CreateMlKem768Keys(MemoryPool<byte> memoryPool)
155+
{
156+
ArgumentNullException.ThrowIfNull(memoryPool);
157+
return CreateMlKemKeys(MLKemParameters.ml_kem_768, memoryPool, CryptoTags.MlKem768PublicKey, CryptoTags.MlKem768PrivateKey);
158+
}
159+
160+
161+
/// <summary>
162+
/// Creates ML-KEM-1024 key material (NIST FIPS 203, security level 5).
163+
/// Public key: 1568 bytes. Ciphertext: 1568 bytes. Shared secret: 32 bytes.
164+
/// </summary>
165+
/// <param name="memoryPool">The memory pool to allocate key buffers from.</param>
166+
/// <returns>A new key pair. The caller must dispose each key individually.</returns>
167+
public static PublicPrivateKeyMaterial<PublicKeyMemory, PrivateKeyMemory> CreateMlKem1024Keys(MemoryPool<byte> memoryPool)
168+
{
169+
ArgumentNullException.ThrowIfNull(memoryPool);
170+
return CreateMlKemKeys(MLKemParameters.ml_kem_1024, memoryPool, CryptoTags.MlKem1024PublicKey, CryptoTags.MlKem1024PrivateKey);
171+
}
172+
173+
96174
private static PublicPrivateKeyMaterial<PublicKeyMemory, PrivateKeyMemory> CreateEcKeys(
97175
string secCurveName,
98176
Tag publicKeyTag,
@@ -188,6 +266,62 @@ private static PublicPrivateKeyMaterial<PublicKeyMemory, PrivateKeyMemory> Creat
188266
}
189267

190268

269+
/// <summary>
270+
/// Creates ML-DSA key material for the given parameter set. The keys are serialized
271+
/// as raw encoded bytes via <c>GetEncoded()</c>.
272+
/// </summary>
273+
private static PublicPrivateKeyMaterial<PublicKeyMemory, PrivateKeyMemory> CreateMlDsaKeys(
274+
MLDsaParameters parameters,
275+
MemoryPool<byte> memoryPool,
276+
Tag publicKeyTag,
277+
Tag privateKeyTag)
278+
{
279+
var keyGenParameters = new MLDsaKeyGenerationParameters(random, parameters);
280+
var keyPairGen = new MLDsaKeyPairGenerator();
281+
keyPairGen.Init(keyGenParameters);
282+
283+
AsymmetricCipherKeyPair keyPair = keyPairGen.GenerateKeyPair();
284+
byte[] publicKeyBytes = ((MLDsaPublicKeyParameters)keyPair.Public).GetEncoded();
285+
byte[] privateKeyBytes = ((MLDsaPrivateKeyParameters)keyPair.Private).GetEncoded();
286+
287+
var publicKeyMemory = new PublicKeyMemory(AsPooledMemory(publicKeyBytes, memoryPool), publicKeyTag);
288+
var privateKeyMemory = new PrivateKeyMemory(AsPooledMemory(privateKeyBytes, memoryPool), privateKeyTag);
289+
290+
Array.Clear(publicKeyBytes, 0, publicKeyBytes.Length);
291+
Array.Clear(privateKeyBytes, 0, privateKeyBytes.Length);
292+
293+
return new PublicPrivateKeyMaterial<PublicKeyMemory, PrivateKeyMemory>(publicKeyMemory, privateKeyMemory);
294+
}
295+
296+
297+
/// <summary>
298+
/// Creates ML-KEM key material for the given parameter set. The keys are serialized
299+
/// as raw encoded bytes via <c>GetEncoded()</c>.
300+
/// </summary>
301+
private static PublicPrivateKeyMaterial<PublicKeyMemory, PrivateKeyMemory> CreateMlKemKeys(
302+
MLKemParameters parameters,
303+
MemoryPool<byte> memoryPool,
304+
Tag publicKeyTag,
305+
Tag privateKeyTag)
306+
{
307+
var keyGenParameters = new MLKemKeyGenerationParameters(random, parameters);
308+
var keyPairGen = new MLKemKeyPairGenerator();
309+
keyPairGen.Init(keyGenParameters);
310+
311+
AsymmetricCipherKeyPair keyPair = keyPairGen.GenerateKeyPair();
312+
byte[] publicKeyBytes = ((MLKemPublicKeyParameters)keyPair.Public).GetEncoded();
313+
byte[] privateKeyBytes = ((MLKemPrivateKeyParameters)keyPair.Private).GetEncoded();
314+
315+
var publicKeyMemory = new PublicKeyMemory(AsPooledMemory(publicKeyBytes, memoryPool), publicKeyTag);
316+
var privateKeyMemory = new PrivateKeyMemory(AsPooledMemory(privateKeyBytes, memoryPool), privateKeyTag);
317+
318+
Array.Clear(publicKeyBytes, 0, publicKeyBytes.Length);
319+
Array.Clear(privateKeyBytes, 0, privateKeyBytes.Length);
320+
321+
return new PublicPrivateKeyMaterial<PublicKeyMemory, PrivateKeyMemory>(publicKeyMemory, privateKeyMemory);
322+
}
323+
324+
191325
private static IMemoryOwner<byte> AsPooledMemory(byte[] keyBytes, MemoryPool<byte> memoryPool)
192326
{
193327
ArgumentNullException.ThrowIfNull(keyBytes);

src/Verifiable.BouncyCastle/Verifiable.BouncyCastle.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
</PropertyGroup>
99

1010
<ItemGroup>
11-
<PackageReference Include="Portable.BouncyCastle" />
11+
<PackageReference Include="BouncyCastle.Cryptography" />
1212
</ItemGroup>
1313

1414
<ItemGroup>

src/Verifiable.BouncyCastle/packages.lock.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22
"version": 2,
33
"dependencies": {
44
"net10.0": {
5+
"BouncyCastle.Cryptography": {
6+
"type": "Direct",
7+
"requested": "[2.6.2, )",
8+
"resolved": "2.6.2",
9+
"contentHash": "7oWOcvnntmMKNzDLsdxAYqApt+AjpRpP2CShjMfIa3umZ42UQMvH0tl1qAliYPNYO6vTdcGMqnRrCPmsfzTI1w=="
10+
},
511
"Microsoft.CodeAnalysis.BannedApiAnalyzers": {
612
"type": "Direct",
713
"requested": "[3.12.0-beta1.25218.8, )",
@@ -14,12 +20,6 @@
1420
"resolved": "4.1.5",
1521
"contentHash": "i5z+cNu/cOcdO0AgFB8aXk8w6In2H+haaDfSgd9ImvQIK+rSHavHZIogVoAZLL8jLwYx4bAcs5b7EyuMMG4mQQ=="
1622
},
17-
"Portable.BouncyCastle": {
18-
"type": "Direct",
19-
"requested": "[1.9.0, )",
20-
"resolved": "1.9.0",
21-
"contentHash": "eZZBCABzVOek+id9Xy04HhmgykF0wZg9wpByzrWN7q8qEI0Qen9b7tfd7w8VA3dOeesumMG7C5ZPy0jk7PSRHw=="
22-
},
2323
"SIL.ReleaseTasks": {
2424
"type": "Direct",
2525
"requested": "[3.2.0, )",

src/Verifiable.Cbor/Sd/SdCwtPipeline.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,9 @@ internal static async ValueTask<ReadOnlyMemory<byte>> Sign(
7171
Signature signature = await signingDelegate(
7272
privateKey.AsReadOnlyMemory(),
7373
sigStructure,
74-
memoryPool).ConfigureAwait(false);
74+
memoryPool,
75+
context: null,
76+
cancellationToken: cancellationToken).ConfigureAwait(false);
7577

7678
//Serialize COSE_Sign1 = #6.18([protected, unprotected, payload, signature]).
7779
byte[] coseSign1 = SerializeCoseSign1(

src/Verifiable.Core/Model/Credentials/CredentialCoseExtensions.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,9 @@ public static async ValueTask<CoseCredentialVerificationResult> VerifyCoseAsync(
149149
bool isValid = await verificationDelegate(
150150
toBeSigned,
151151
message.Signature,
152-
publicKey.AsReadOnlyMemory()).ConfigureAwait(false);
152+
publicKey.AsReadOnlyMemory(),
153+
context: null,
154+
cancellationToken: cancellationToken).ConfigureAwait(false);
153155

154156
if(!isValid)
155157
{

src/Verifiable.Core/Model/Credentials/CredentialJwsExtensions.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,9 @@ public static async ValueTask<JwsMessage> SignJwsAsync(
122122
Signature signature = await signingDelegate(
123123
privateKey.AsReadOnlyMemory(),
124124
signingInputMemory,
125-
memoryPool).ConfigureAwait(false);
125+
memoryPool,
126+
context: null,
127+
cancellationToken: cancellationToken).ConfigureAwait(false);
126128

127129
var signatureComponent = new JwsSignatureComponent(
128130
headerSegment,
@@ -195,7 +197,9 @@ public static async ValueTask<JwsCredentialVerificationResult> VerifyJwsAsync(
195197
bool isValid = await verificationDelegate(
196198
verifyInputMemory,
197199
signatureBytesOwner.Memory,
198-
publicKey.AsReadOnlyMemory()).ConfigureAwait(false);
200+
publicKey.AsReadOnlyMemory(),
201+
context: null,
202+
cancellationToken: cancellationToken).ConfigureAwait(false);
199203

200204
return new JwsCredentialVerificationResult(isValid, header, credential);
201205
}
@@ -255,7 +259,9 @@ public static async ValueTask<JwsCredentialVerificationResult> VerifyJwsAsync(
255259
bool isValid = await verificationDelegate(
256260
verifyInputMemory,
257261
signature.Signature.AsReadOnlyMemory(),
258-
publicKey.AsReadOnlyMemory()).ConfigureAwait(false);
262+
publicKey.AsReadOnlyMemory(),
263+
context: null,
264+
cancellationToken: cancellationToken).ConfigureAwait(false);
259265

260266
VerifiableCredential credential = credentialDeserializer(message.Payload.Span);
261267
var header = new Dictionary<string, object>(signature.ProtectedHeader);

0 commit comments

Comments
 (0)