Skip to content

Commit d50bb8e

Browse files
EC-DSA Composite ML-DSA for net8+ (#118232)
Co-authored-by: Copilot <[email protected]>
1 parent e911894 commit d50bb8e

File tree

12 files changed

+1551
-894
lines changed

12 files changed

+1551
-894
lines changed
Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
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.Buffers;
5+
using System.Diagnostics;
6+
using System.Formats.Asn1;
7+
using System.Runtime.InteropServices;
8+
using System.Security.Cryptography.Asn1;
9+
using Internal.Cryptography;
10+
11+
namespace System.Security.Cryptography
12+
{
13+
internal sealed partial class CompositeMLDsaManaged
14+
{
15+
private sealed class ECDsaComponent : ComponentAlgorithm
16+
#if DESIGNTIMEINTERFACES
17+
#pragma warning disable SA1001 // Commas should be spaced correctly
18+
, IComponentAlgorithmFactory<ECDsaComponent, ECDsaAlgorithm>
19+
#pragma warning restore SA1001 // Commas should be spaced correctly
20+
#endif
21+
{
22+
private readonly ECDsaAlgorithm _algorithm;
23+
24+
private ECDsa _ecdsa;
25+
26+
private ECDsaComponent(ECDsa ecdsa, ECDsaAlgorithm algorithm)
27+
{
28+
Debug.Assert(ecdsa != null);
29+
30+
_ecdsa = ecdsa;
31+
_algorithm = algorithm;
32+
}
33+
34+
// While some of our OSes support the brainpool curves, not all do.
35+
// Limit this implementation to the NIST curves until we have a better understanding
36+
// of where native implementations of composite are aligning.
37+
public static bool IsAlgorithmSupported(ECDsaAlgorithm algorithm) =>
38+
#if NET
39+
algorithm.CurveOid is Oids.secp256r1 or Oids.secp384r1 or Oids.secp521r1;
40+
#else
41+
false;
42+
#endif
43+
44+
public static ECDsaComponent GenerateKey(ECDsaAlgorithm algorithm)
45+
{
46+
#if NET
47+
return new ECDsaComponent(ECDsa.Create(algorithm.Curve), algorithm);
48+
#else
49+
throw new PlatformNotSupportedException();
50+
#endif
51+
}
52+
53+
public static unsafe ECDsaComponent ImportPrivateKey(ECDsaAlgorithm algorithm, ReadOnlySpan<byte> source)
54+
{
55+
Helpers.ThrowIfAsnInvalidLength(source);
56+
57+
fixed (byte* ptr = &MemoryMarshal.GetReference(source))
58+
{
59+
using (MemoryManager<byte> manager = new PointerMemoryManager<byte>(ptr, source.Length))
60+
{
61+
ECPrivateKey ecPrivateKey = ECPrivateKey.Decode(manager.Memory, AsnEncodingRules.BER);
62+
63+
if (ecPrivateKey.Version != 1)
64+
{
65+
throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
66+
}
67+
68+
// If domain parameters are present, validate that they match the composite ML-DSA algorithm.
69+
if (ecPrivateKey.Parameters is ECDomainParameters domainParameters)
70+
{
71+
if (domainParameters.Named is not string curveOid || curveOid != algorithm.CurveOid)
72+
{
73+
// The curve specified must be named and match the required curve for the composite ML-DSA algorithm.
74+
throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
75+
}
76+
}
77+
78+
byte[]? x = null;
79+
byte[]? y = null;
80+
81+
// If public key is present, add it to the parameters.
82+
if (ecPrivateKey.PublicKey is ReadOnlyMemory<byte> publicKey)
83+
{
84+
EccKeyFormatHelper.GetECPointFromUncompressedPublicKey(publicKey.Span, algorithm.KeySizeInBytes, out x, out y);
85+
}
86+
87+
byte[] d = new byte[ecPrivateKey.PrivateKey.Length];
88+
89+
using (PinAndClear.Track(d))
90+
{
91+
ecPrivateKey.PrivateKey.CopyTo(d);
92+
93+
#if NET
94+
ECParameters parameters = new ECParameters
95+
{
96+
Curve = algorithm.Curve,
97+
Q = new ECPoint
98+
{
99+
X = x,
100+
Y = y,
101+
},
102+
D = d
103+
};
104+
105+
parameters.Validate();
106+
107+
return new ECDsaComponent(ECDsa.Create(parameters), algorithm);
108+
#else
109+
throw new PlatformNotSupportedException();
110+
#endif
111+
}
112+
}
113+
}
114+
}
115+
116+
public static unsafe ECDsaComponent ImportPublicKey(ECDsaAlgorithm algorithm, ReadOnlySpan<byte> source)
117+
{
118+
int fieldWidth = algorithm.KeySizeInBytes;
119+
120+
if (source.Length != 1 + fieldWidth * 2)
121+
{
122+
Debug.Fail("Public key format is fixed size, so caller needs to provide exactly correct sized buffer.");
123+
throw new CryptographicException();
124+
}
125+
126+
// Implementation limitation.
127+
// 04 (Uncompressed ECPoint) is almost always used.
128+
if (source[0] != 0x04)
129+
{
130+
throw new CryptographicException(SR.Cryptography_NotValidPublicOrPrivateKey);
131+
}
132+
133+
#if NET
134+
ECParameters parameters = new ECParameters()
135+
{
136+
Curve = algorithm.Curve,
137+
Q = new ECPoint()
138+
{
139+
X = source.Slice(1, fieldWidth).ToArray(),
140+
Y = source.Slice(1 + fieldWidth).ToArray(),
141+
}
142+
};
143+
144+
return new ECDsaComponent(ECDsa.Create(parameters), algorithm);
145+
#else
146+
throw new PlatformNotSupportedException();
147+
#endif
148+
}
149+
150+
internal override bool TryExportPrivateKey(Span<byte> destination, out int bytesWritten)
151+
{
152+
#if NET
153+
ECParameters ecParameters = _ecdsa.ExportParameters(includePrivateParameters: true);
154+
155+
Debug.Assert(ecParameters.D != null);
156+
157+
using (PinAndClear.Track(ecParameters.D))
158+
{
159+
ecParameters.Validate();
160+
161+
if (ecParameters.D.Length != _algorithm.KeySizeInBytes)
162+
{
163+
Debug.Fail("Unexpected key size.");
164+
throw new CryptographicException();
165+
}
166+
167+
// The curve OID must match the composite ML-DSA algorithm.
168+
if (!ecParameters.Curve.IsNamed ||
169+
(ecParameters.Curve.Oid.Value != _algorithm.Curve.Oid.Value && ecParameters.Curve.Oid.FriendlyName != _algorithm.Curve.Oid.FriendlyName))
170+
{
171+
Debug.Fail("Unexpected curve OID.");
172+
throw new CryptographicException();
173+
}
174+
175+
return TryWriteKey(ecParameters.D, ecParameters.Q.X, ecParameters.Q.Y, _algorithm.CurveOid, destination, out bytesWritten);
176+
}
177+
#else
178+
throw new PlatformNotSupportedException();
179+
#endif
180+
181+
#if NET
182+
static bool TryWriteKey(byte[] d, byte[]? x, byte[]? y, string curveOid, Span<byte> destination, out int bytesWritten)
183+
{
184+
AsnWriter writer = new AsnWriter(AsnEncodingRules.DER);
185+
186+
try
187+
{
188+
// ECPrivateKey
189+
using (writer.PushSequence())
190+
{
191+
// version 1
192+
writer.WriteInteger(1);
193+
194+
// privateKey
195+
writer.WriteOctetString(d);
196+
197+
// domainParameters
198+
using (writer.PushSequence(new Asn1Tag(TagClass.ContextSpecific, 0, isConstructed: true)))
199+
{
200+
writer.WriteObjectIdentifier(curveOid);
201+
}
202+
203+
// publicKey
204+
if (x != null)
205+
{
206+
Debug.Assert(y != null);
207+
208+
using (writer.PushSequence(new Asn1Tag(TagClass.ContextSpecific, 1, isConstructed: true)))
209+
{
210+
EccKeyFormatHelper.WriteUncompressedPublicKey(x, y, writer);
211+
}
212+
}
213+
}
214+
215+
return writer.TryEncode(destination, out bytesWritten);
216+
}
217+
finally
218+
{
219+
writer.Reset();
220+
}
221+
}
222+
#endif
223+
}
224+
225+
internal override bool TryExportPublicKey(Span<byte> destination, out int bytesWritten)
226+
{
227+
#if NET
228+
int fieldWidth = _algorithm.KeySizeInBytes;
229+
230+
if (destination.Length < 1 + 2 * fieldWidth)
231+
{
232+
Debug.Fail("Public key format is fixed size, so caller needs to provide exactly correct sized buffer.");
233+
234+
bytesWritten = 0;
235+
return false;
236+
}
237+
238+
ECParameters ecParameters = _ecdsa.ExportParameters(includePrivateParameters: false);
239+
240+
ecParameters.Validate();
241+
242+
if (ecParameters.Q.X?.Length != fieldWidth)
243+
{
244+
Debug.Fail("Unexpected key size.");
245+
throw new CryptographicException();
246+
}
247+
248+
// Uncompressed ECPoint format
249+
destination[0] = 0x04;
250+
251+
ecParameters.Q.X.CopyTo(destination.Slice(1, fieldWidth));
252+
ecParameters.Q.Y.CopyTo(destination.Slice(1 + fieldWidth));
253+
254+
bytesWritten = 1 + 2 * fieldWidth;
255+
return true;
256+
#else
257+
throw new PlatformNotSupportedException();
258+
#endif
259+
}
260+
261+
internal override bool VerifyData(
262+
#if NET
263+
ReadOnlySpan<byte> data,
264+
#else
265+
byte[] data,
266+
#endif
267+
ReadOnlySpan<byte> signature)
268+
{
269+
#if NET
270+
return _ecdsa.VerifyData(data, signature, _algorithm.HashAlgorithmName, DSASignatureFormat.Rfc3279DerSequence);
271+
#else
272+
throw new PlatformNotSupportedException();
273+
#endif
274+
}
275+
276+
internal override int SignData(
277+
#if NET
278+
ReadOnlySpan<byte> data,
279+
#else
280+
byte[] data,
281+
#endif
282+
Span<byte> destination)
283+
{
284+
#if NET
285+
if (!_ecdsa.TrySignData(data, destination, _algorithm.HashAlgorithmName, DSASignatureFormat.Rfc3279DerSequence, out int bytesWritten))
286+
{
287+
Debug.Fail("Buffer size should have been validated by caller.");
288+
throw new CryptographicException();
289+
}
290+
291+
return bytesWritten;
292+
#else
293+
throw new PlatformNotSupportedException();
294+
#endif
295+
}
296+
297+
protected override void Dispose(bool disposing)
298+
{
299+
if (disposing)
300+
{
301+
_ecdsa?.Dispose();
302+
_ecdsa = null!;
303+
}
304+
305+
base.Dispose(disposing);
306+
}
307+
}
308+
}
309+
}

src/libraries/Common/src/System/Security/Cryptography/CompositeMLDsaManaged.RSA.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,10 @@ public static RsaComponent ImportPrivateKey(RsaAlgorithm algorithm, ReadOnlySpan
156156
throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e);
157157
}
158158
#endif
159+
if (rsa.KeySize != algorithm.KeySizeInBits)
160+
{
161+
throw new CryptographicException(SR.Argument_PrivateKeyWrongSizeForAlgorithm);
162+
}
159163

160164
if (bytesRead != source.Length)
161165
{
@@ -207,6 +211,10 @@ public static RsaComponent ImportPublicKey(RsaAlgorithm algorithm, ReadOnlySpan<
207211
throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e);
208212
}
209213
#endif
214+
if (rsa.KeySize != algorithm.KeySizeInBits)
215+
{
216+
throw new CryptographicException(SR.Argument_PublicKeyWrongSizeForAlgorithm);
217+
}
210218

211219
if (bytesRead != source.Length)
212220
{

0 commit comments

Comments
 (0)