Skip to content

Commit 138d5fe

Browse files
CopilotPranavSenthilnathanvcsjones
authored
[release/10.0] Composite ML-DSA Draft 12 and 13 updates (#120601, #120961) (#121555)
Backport of #120601 and #120961 to release/10.0 # Description Backports Draft 12 and Draft 13 spec changes for Composite ML-DSA. This PR combines two related updates: **Draft 12 changes (#120601):** - Mandate parameters field in ECPrivateKey (previously omitted) - `CompositeMLDsaAlgorithm.cs`: Calculate parameters field size for EC curves (P256/P384/P521/brainpool variants) - `CompositeMLDsaManaged.ECDsa.cs`: Validate parameters presence and curve match; write parameters with context-specific tag [0] - `CompositeMLDsaManaged.cs`: Update spec references from draft-08 to draft-12 - Test updates: Add validation for wrong/missing/implicit/explicit curves; update expected key sizes per spec Table 4 **Draft 13 changes (#120961):** - Update OIDs from experimental range (2.16.840.1.114027.80.9.1.*) to official IANA-assigned range (1.3.6.1.5.5.7.6.*) - `Oids.cs`: Update all Composite ML-DSA OID constants to new range - `CompositeMLDsaManaged.cs`: Add "ECDSA" to domain separation strings (e.g., "COMPSIG-MLDSA65-P256-SHA512" → "COMPSIG-MLDSA65-ECDSA-P256-SHA512") - Test data and helpers: Update to reflect new OIDs and domain strings # Customer Impact Without these fixes, Composite ML-DSA keys generated in .NET 10 would not conform to Draft 12 and Draft 13 of the IETF spec, causing interoperability failures with other implementations following the updated standards. # Regression No. This updates implementation to match spec evolution from Draft 8 to Draft 13. # Testing All 1,015 CompositeMLDsa tests pass. Added test coverage for: - Wrong curve OID rejection - Missing parameters rejection - Implicit curve parameters rejection - Explicit curve parameters rejection - Correct parameter serialization for all supported curves - New OID and domain string validation # Risk Low. Changes are confined to Composite ML-DSA implementation (preview feature). Validates existing behavior is maintained while adding required spec compliance. Breaking changes are intentional and necessary for spec conformance. <!-- START COPILOT CODING AGENT TIPS --> --- ✨ Let Copilot coding agent [set things up for you](https://github.com/dotnet/runtime/issues/new?title=✨+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot) — coding agent works faster and does higher quality work when set up for your repo. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Pranav Senthilnathan <pranas@microsoft.com> Co-authored-by: Kevin Jones <vcsjones@github.com>
1 parent e93e023 commit 138d5fe

File tree

7 files changed

+324
-170
lines changed

7 files changed

+324
-170
lines changed

src/libraries/Common/src/System/Security/Cryptography/CompositeMLDsaAlgorithm.cs

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -473,23 +473,67 @@ private static CompositeMLDsaAlgorithm CreateECDsa(
473473
// publicKey [1] BIT STRING OPTIONAL
474474
// }
475475

476+
// version
477+
476478
int versionSizeInBytes =
477479
1 + // Tag for INTEGER
478480
1 + // Length field
479481
1; // Value (always 1)
480482

483+
// privateKey
484+
481485
int privateKeySizeInBytes =
482486
1 + // Tag for OCTET STRING
483487
GetDerLengthLength(keySizeInBytes) + // Length field
484488
keySizeInBytes; // Value
485489

486-
// parameters and publicKey must be omitted for Composite ML-DSA
490+
// parameters
491+
492+
int namedCurveSizeInBytes =
493+
oid switch
494+
{
495+
Oids.MLDsa44WithECDsaP256PreHashSha256 or
496+
Oids.MLDsa65WithECDsaP256PreHashSha512 =>
497+
// 1.2.840.10045.3.1.7
498+
// 06 08 2A 86 48 CE 3D 03 01 07
499+
10,
500+
Oids.MLDsa65WithECDsaP384PreHashSha512 or
501+
Oids.MLDsa87WithECDsaP384PreHashSha512 =>
502+
// 1.3.132.0.34
503+
// 06 05 2B 81 04 00 22
504+
7,
505+
Oids.MLDsa87WithECDsaP521PreHashSha512 =>
506+
// 1.3.132.0.35
507+
// 06 05 2B 81 04 00 23
508+
7,
509+
Oids.MLDsa65WithECDsaBrainpoolP256r1PreHashSha512 =>
510+
// 1.3.36.3.3.2.8.1.1.7
511+
// 06 09 2B 24 03 03 02 08 01 01 07
512+
11,
513+
Oids.MLDsa87WithECDsaBrainpoolP384r1PreHashSha512 =>
514+
// 1.3.36.3.3.2.8.1.1.11
515+
// 06 09 2B 24 03 03 02 08 01 01 0B
516+
11,
517+
_ => AssertAndThrow(oid),
518+
};
519+
520+
static int AssertAndThrow(string oid)
521+
{
522+
Debug.Fail($"Unsupported OID: {oid}.");
523+
throw new CryptographicException();
524+
}
525+
526+
int parametersSizeInBytes =
527+
1 + // Context-specific tag for [0]
528+
GetDerLengthLength(namedCurveSizeInBytes) + // Length field
529+
namedCurveSizeInBytes; // Value
530+
531+
// publicKey must be omitted for Composite ML-DSA
487532

488533
int ecPrivateKeySizeInBytes =
489-
1 + // Tag for SEQUENCE
490-
GetDerLengthLength(versionSizeInBytes + privateKeySizeInBytes) + // Length field
491-
versionSizeInBytes + // Version
492-
privateKeySizeInBytes;
534+
1 + // Tag for SEQUENCE
535+
GetDerLengthLength(versionSizeInBytes + privateKeySizeInBytes + parametersSizeInBytes) + // Length field
536+
versionSizeInBytes + privateKeySizeInBytes + parametersSizeInBytes; // Value
493537

494538
return new CompositeMLDsaAlgorithm(
495539
name,

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

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,17 @@ public static unsafe ECDsaComponent ImportPrivateKey(ECDsaAlgorithm algorithm, R
7272
ECPrivateKey ecPrivateKey = ECPrivateKey.Decode(manager.Memory, AsnEncodingRules.BER);
7373

7474
if (ecPrivateKey.Version != 1 ||
75-
ecPrivateKey.Parameters is not null ||
7675
ecPrivateKey.PublicKey is not null)
7776
{
7877
throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
7978
}
8079

80+
if (ecPrivateKey.Parameters?.Named != algorithm.CurveOidValue)
81+
{
82+
// The curve specified must be named and match the required curve for the Composite ML-DSA algorithm.
83+
throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
84+
}
85+
8186
byte[] d = new byte[ecPrivateKey.PrivateKey.Length];
8287

8388
using (PinAndClear.Track(d))
@@ -206,7 +211,7 @@ internal override bool TryExportPrivateKey(Span<byte> destination, out int bytes
206211

207212
try
208213
{
209-
WriteKey(ecParameters.D, writer);
214+
WriteKey(ecParameters.D, _algorithm.CurveOidValue, writer);
210215
return writer.TryEncode(destination, out bytesWritten);
211216
}
212217
finally
@@ -239,7 +244,7 @@ internal override bool TryExportPrivateKey(Span<byte> destination, out int bytes
239244
throw new CryptographicException();
240245
}
241246

242-
WriteKey(d, writer);
247+
WriteKey(d, _algorithm.CurveOidValue, writer);
243248
return true;
244249
});
245250
});
@@ -252,7 +257,7 @@ internal override bool TryExportPrivateKey(Span<byte> destination, out int bytes
252257
}
253258
#endif
254259

255-
static void WriteKey(byte[] d, AsnWriter writer)
260+
static void WriteKey(byte[] d, string curveOid, AsnWriter writer)
256261
{
257262
// ECPrivateKey
258263
using (writer.PushSequence())
@@ -262,6 +267,12 @@ static void WriteKey(byte[] d, AsnWriter writer)
262267

263268
// privateKey
264269
writer.WriteOctetString(d);
270+
271+
// parameters
272+
using (writer.PushSequence(new Asn1Tag(TagClass.ContextSpecific, 0, isConstructed: true)))
273+
{
274+
writer.WriteObjectIdentifier(curveOid);
275+
}
265276
}
266277
}
267278
}

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

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ internal static CompositeMLDsa GenerateKeyImpl(CompositeMLDsaAlgorithm algorithm
6262

6363
AlgorithmMetadata metadata = s_algorithmMetadata[algorithm];
6464

65-
// draft-ietf-lamps-pq-composite-sigs-08, 4.1
65+
// draft-ietf-lamps-pq-composite-sigs-12, 4.1
6666
// 1. Generate component keys
6767
//
6868
// mldsaSeed = Random(32)
@@ -115,7 +115,7 @@ internal static CompositeMLDsa ImportCompositeMLDsaPublicKeyImpl(CompositeMLDsaA
115115

116116
AlgorithmMetadata metadata = s_algorithmMetadata[algorithm];
117117

118-
// draft-ietf-lamps-pq-composite-sigs-08, 5.1
118+
// draft-ietf-lamps-pq-composite-sigs-12, 5.1
119119
// 1. Parse each constituent encoded public key.
120120
// The length of the mldsaKey is known based on the
121121
// size of the ML-DSA component key length specified
@@ -167,7 +167,7 @@ internal static CompositeMLDsa ImportCompositeMLDsaPrivateKeyImpl(CompositeMLDsa
167167

168168
AlgorithmMetadata metadata = s_algorithmMetadata[algorithm];
169169

170-
// draft-ietf-lamps-pq-composite-sigs-08, 5.2
170+
// draft-ietf-lamps-pq-composite-sigs-12, 5.2
171171
// 1. Parse each constituent encoded key.
172172
//
173173
// mldsaSeed = bytes[:32]
@@ -203,7 +203,7 @@ static CryptographicException FailAndGetException()
203203

204204
protected override int SignDataCore(ReadOnlySpan<byte> data, ReadOnlySpan<byte> context, Span<byte> destination)
205205
{
206-
// draft-ietf-lamps-pq-composite-sigs-08, 4.2
206+
// draft-ietf-lamps-pq-composite-sigs-12, 4.2
207207
// 1. If len(ctx) > 255:
208208
// return error
209209

@@ -287,7 +287,7 @@ protected override int SignDataCore(ReadOnlySpan<byte> data, ReadOnlySpan<byte>
287287

288288
protected override bool VerifyDataCore(ReadOnlySpan<byte> data, ReadOnlySpan<byte> context, ReadOnlySpan<byte> signature)
289289
{
290-
// draft-ietf-lamps-pq-composite-sigs-08, 4.3
290+
// draft-ietf-lamps-pq-composite-sigs-12, 4.3
291291
// 1. If len(ctx) > 255
292292
// return error
293293

@@ -375,7 +375,7 @@ protected override bool TryExportPkcs8PrivateKeyCore(Span<byte> destination, out
375375

376376
protected override int ExportCompositeMLDsaPublicKeyCore(Span<byte> destination)
377377
{
378-
// draft-ietf-lamps-pq-composite-sigs-08, 5.1
378+
// draft-ietf-lamps-pq-composite-sigs-12, 5.1
379379
// 1. Combine and output the encoded public key
380380
//
381381
// output mldsaPK || tradPK
@@ -397,7 +397,7 @@ protected override int ExportCompositeMLDsaPublicKeyCore(Span<byte> destination)
397397

398398
protected override int ExportCompositeMLDsaPrivateKeyCore(Span<byte> destination)
399399
{
400-
// draft-ietf-lamps-pq-composite-sigs-08, 5.2
400+
// draft-ietf-lamps-pq-composite-sigs-12, 5.2
401401
// 1. Combine and output the encoded private key.
402402
//
403403
// output mldsaSeed || tradSK
@@ -626,23 +626,23 @@ private static Dictionary<CompositeMLDsaAlgorithm, AlgorithmMetadata> CreateAlgo
626626
new AlgorithmMetadata(
627627
MLDsaAlgorithm.MLDsa65,
628628
ECDsaAlgorithm.CreateP256(HashAlgorithmName.SHA256),
629-
[.."COMPSIG-MLDSA65-P256-SHA512"u8],
629+
[.."COMPSIG-MLDSA65-ECDSA-P256-SHA512"u8],
630630
HashAlgorithmName.SHA512)
631631
},
632632
{
633633
CompositeMLDsaAlgorithm.MLDsa65WithECDsaP384,
634634
new AlgorithmMetadata(
635635
MLDsaAlgorithm.MLDsa65,
636636
ECDsaAlgorithm.CreateP384(HashAlgorithmName.SHA384),
637-
[.."COMPSIG-MLDSA65-P384-SHA512"u8],
637+
[.."COMPSIG-MLDSA65-ECDSA-P384-SHA512"u8],
638638
HashAlgorithmName.SHA512)
639639
},
640640
{
641641
CompositeMLDsaAlgorithm.MLDsa65WithECDsaBrainpoolP256r1,
642642
new AlgorithmMetadata(
643643
MLDsaAlgorithm.MLDsa65,
644644
ECDsaAlgorithm.CreateBrainpoolP256r1(HashAlgorithmName.SHA256),
645-
[.."COMPSIG-MLDSA65-BP256-SHA512"u8],
645+
[.."COMPSIG-MLDSA65-ECDSA-BP256-SHA512"u8],
646646
HashAlgorithmName.SHA512)
647647
},
648648
{
@@ -658,15 +658,15 @@ private static Dictionary<CompositeMLDsaAlgorithm, AlgorithmMetadata> CreateAlgo
658658
new AlgorithmMetadata(
659659
MLDsaAlgorithm.MLDsa87,
660660
ECDsaAlgorithm.CreateP384(HashAlgorithmName.SHA384),
661-
[.."COMPSIG-MLDSA87-P384-SHA512"u8],
661+
[.."COMPSIG-MLDSA87-ECDSA-P384-SHA512"u8],
662662
HashAlgorithmName.SHA512)
663663
},
664664
{
665665
CompositeMLDsaAlgorithm.MLDsa87WithECDsaBrainpoolP384r1,
666666
new AlgorithmMetadata(
667667
MLDsaAlgorithm.MLDsa87,
668668
ECDsaAlgorithm.CreateBrainpoolP384r1(HashAlgorithmName.SHA384),
669-
[.."COMPSIG-MLDSA87-BP384-SHA512"u8],
669+
[.."COMPSIG-MLDSA87-ECDSA-BP384-SHA512"u8],
670670
HashAlgorithmName.SHA512)
671671
},
672672
{
@@ -698,7 +698,7 @@ private static Dictionary<CompositeMLDsaAlgorithm, AlgorithmMetadata> CreateAlgo
698698
new AlgorithmMetadata(
699699
MLDsaAlgorithm.MLDsa87,
700700
ECDsaAlgorithm.CreateP521(HashAlgorithmName.SHA512),
701-
[.."COMPSIG-MLDSA87-P521-SHA512"u8],
701+
[.."COMPSIG-MLDSA87-ECDSA-P521-SHA512"u8],
702702
HashAlgorithmName.SHA512)
703703
}
704704
};

src/libraries/Common/src/System/Security/Cryptography/Oids.cs

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -135,24 +135,24 @@ internal static partial class Oids
135135
internal const string Mgf1 = "1.2.840.113549.1.1.8";
136136
internal const string PSpecified = "1.2.840.113549.1.1.9";
137137

138-
internal const string MLDsa44WithRSA2048PssPreHashSha256 = "2.16.840.1.114027.80.9.1.20";
139-
internal const string MLDsa44WithRSA2048Pkcs15PreHashSha256 = "2.16.840.1.114027.80.9.1.21";
140-
internal const string MLDsa44WithEd25519PreHashSha512 = "2.16.840.1.114027.80.9.1.22";
141-
internal const string MLDsa44WithECDsaP256PreHashSha256 = "2.16.840.1.114027.80.9.1.23";
142-
internal const string MLDsa65WithRSA3072PssPreHashSha512 = "2.16.840.1.114027.80.9.1.24";
143-
internal const string MLDsa65WithRSA3072Pkcs15PreHashSha512 = "2.16.840.1.114027.80.9.1.25";
144-
internal const string MLDsa65WithRSA4096PssPreHashSha512 = "2.16.840.1.114027.80.9.1.26";
145-
internal const string MLDsa65WithRSA4096Pkcs15PreHashSha512 = "2.16.840.1.114027.80.9.1.27";
146-
internal const string MLDsa65WithECDsaP256PreHashSha512 = "2.16.840.1.114027.80.9.1.28";
147-
internal const string MLDsa65WithECDsaP384PreHashSha512 = "2.16.840.1.114027.80.9.1.29";
148-
internal const string MLDsa65WithECDsaBrainpoolP256r1PreHashSha512 = "2.16.840.1.114027.80.9.1.30";
149-
internal const string MLDsa65WithEd25519PreHashSha512 = "2.16.840.1.114027.80.9.1.31";
150-
internal const string MLDsa87WithECDsaP384PreHashSha512 = "2.16.840.1.114027.80.9.1.32";
151-
internal const string MLDsa87WithECDsaBrainpoolP384r1PreHashSha512 = "2.16.840.1.114027.80.9.1.33";
152-
internal const string MLDsa87WithEd448PreHashShake256_512 = "2.16.840.1.114027.80.9.1.34";
153-
internal const string MLDsa87WithRSA3072PssPreHashSha512 = "2.16.840.1.114027.80.9.1.35";
154-
internal const string MLDsa87WithRSA4096PssPreHashSha512 = "2.16.840.1.114027.80.9.1.36";
155-
internal const string MLDsa87WithECDsaP521PreHashSha512 = "2.16.840.1.114027.80.9.1.37";
138+
internal const string MLDsa44WithRSA2048PssPreHashSha256 = "1.3.6.1.5.5.7.6.37";
139+
internal const string MLDsa44WithRSA2048Pkcs15PreHashSha256 = "1.3.6.1.5.5.7.6.38";
140+
internal const string MLDsa44WithEd25519PreHashSha512 = "1.3.6.1.5.5.7.6.39";
141+
internal const string MLDsa44WithECDsaP256PreHashSha256 = "1.3.6.1.5.5.7.6.40";
142+
internal const string MLDsa65WithRSA3072PssPreHashSha512 = "1.3.6.1.5.5.7.6.41";
143+
internal const string MLDsa65WithRSA3072Pkcs15PreHashSha512 = "1.3.6.1.5.5.7.6.42";
144+
internal const string MLDsa65WithRSA4096PssPreHashSha512 = "1.3.6.1.5.5.7.6.43";
145+
internal const string MLDsa65WithRSA4096Pkcs15PreHashSha512 = "1.3.6.1.5.5.7.6.44";
146+
internal const string MLDsa65WithECDsaP256PreHashSha512 = "1.3.6.1.5.5.7.6.45";
147+
internal const string MLDsa65WithECDsaP384PreHashSha512 = "1.3.6.1.5.5.7.6.46";
148+
internal const string MLDsa65WithECDsaBrainpoolP256r1PreHashSha512 = "1.3.6.1.5.5.7.6.47";
149+
internal const string MLDsa65WithEd25519PreHashSha512 = "1.3.6.1.5.5.7.6.48";
150+
internal const string MLDsa87WithECDsaP384PreHashSha512 = "1.3.6.1.5.5.7.6.49";
151+
internal const string MLDsa87WithECDsaBrainpoolP384r1PreHashSha512 = "1.3.6.1.5.5.7.6.50";
152+
internal const string MLDsa87WithEd448PreHashShake256_512 = "1.3.6.1.5.5.7.6.51";
153+
internal const string MLDsa87WithRSA3072PssPreHashSha512 = "1.3.6.1.5.5.7.6.52";
154+
internal const string MLDsa87WithRSA4096PssPreHashSha512 = "1.3.6.1.5.5.7.6.53";
155+
internal const string MLDsa87WithECDsaP521PreHashSha512 = "1.3.6.1.5.5.7.6.54";
156156

157157
// PKCS#7
158158
internal const string NoSignature = "1.3.6.1.5.5.7.6.2";

0 commit comments

Comments
 (0)