Skip to content

Commit bdf1d42

Browse files
authored
De-duplicate PQC type helpers and unify source files
1 parent 655b0ea commit bdf1d42

File tree

10 files changed

+173
-264
lines changed

10 files changed

+173
-264
lines changed

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

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System;
5+
using System.Diagnostics;
56
using System.Diagnostics.CodeAnalysis;
67
using System.Formats.Asn1;
78
using System.Runtime.CompilerServices;
@@ -168,5 +169,47 @@ internal static CryptographicException CreateAlgorithmUnknownException(string al
168169
throw new CryptographicException(
169170
SR.Format(SR.Cryptography_UnknownAlgorithmIdentifier, algorithmId));
170171
}
172+
173+
#if !BUILDING_PKCS
174+
internal static string EncodeAsnWriterToPem(string label, AsnWriter writer, bool clear = true)
175+
{
176+
#if NET10_0_OR_GREATER
177+
return writer.Encode(label, static (label, span) => PemEncoding.WriteString(label, span));
178+
#else
179+
int length = writer.GetEncodedLength();
180+
byte[] rent = CryptoPool.Rent(length);
181+
182+
try
183+
{
184+
int written = writer.Encode(rent);
185+
Debug.Assert(written == length);
186+
return PemEncoding.WriteString(label, rent.AsSpan(0, written));
187+
}
188+
finally
189+
{
190+
CryptoPool.Return(rent, clear ? length : 0);
191+
}
192+
#endif
193+
}
194+
#endif
195+
196+
internal static void ThrowIfAsnInvalidLength(ReadOnlySpan<byte> data)
197+
{
198+
int bytesRead;
199+
200+
try
201+
{
202+
AsnDecoder.ReadEncodedValue(data, AsnEncodingRules.BER, out _, out _, out bytesRead);
203+
}
204+
catch (AsnContentException ace)
205+
{
206+
throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, ace);
207+
}
208+
209+
if (bytesRead != data.Length)
210+
{
211+
throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
212+
}
213+
}
171214
}
172215
}

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

Lines changed: 7 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ public string ExportSubjectPublicKeyInfoPem()
251251
AsnWriter writer = ExportSubjectPublicKeyInfoCore();
252252

253253
// SPKI does not contain sensitive data.
254-
return EncodeAsnWriterToPem(PemLabels.SpkiPublicKey, writer, clear: false);
254+
return Helpers.EncodeAsnWriterToPem(PemLabels.SpkiPublicKey, writer, clear: false);
255255
}
256256

257257
/// <summary>
@@ -638,7 +638,7 @@ public string ExportEncryptedPkcs8PrivateKeyPem(
638638
AsnWriter writer = ExportEncryptedPkcs8PrivateKeyCore(password, pbeParameters);
639639

640640
// Skip clear since the data is already encrypted.
641-
return EncodeAsnWriterToPem(PemLabels.EncryptedPkcs8PrivateKey, writer, clear: false);
641+
return Helpers.EncodeAsnWriterToPem(PemLabels.EncryptedPkcs8PrivateKey, writer, clear: false);
642642
}
643643

644644
/// <summary>
@@ -682,7 +682,7 @@ public string ExportEncryptedPkcs8PrivateKeyPem(
682682
AsnWriter writer = ExportEncryptedPkcs8PrivateKeyCore(passwordBytes, pbeParameters);
683683

684684
// Skip clear since the data is already encrypted.
685-
return EncodeAsnWriterToPem(PemLabels.EncryptedPkcs8PrivateKey, writer, clear: false);
685+
return Helpers.EncodeAsnWriterToPem(PemLabels.EncryptedPkcs8PrivateKey, writer, clear: false);
686686
}
687687

688688
/// <inheritdoc cref="ExportEncryptedPkcs8PrivateKeyPem(ReadOnlySpan{char}, PbeParameters)"/>
@@ -835,7 +835,7 @@ public static MLDsa GenerateKey(MLDsaAlgorithm algorithm)
835835
/// </exception>
836836
public static MLDsa ImportSubjectPublicKeyInfo(ReadOnlySpan<byte> source)
837837
{
838-
ThrowIfInvalidLength(source);
838+
Helpers.ThrowIfAsnInvalidLength(source);
839839
ThrowIfNotSupported();
840840

841841
unsafe
@@ -904,7 +904,7 @@ public static MLDsa ImportSubjectPublicKeyInfo(byte[] source)
904904
/// </exception>
905905
public static MLDsa ImportPkcs8PrivateKey(ReadOnlySpan<byte> source)
906906
{
907-
ThrowIfInvalidLength(source);
907+
Helpers.ThrowIfAsnInvalidLength(source);
908908
ThrowIfNotSupported();
909909

910910
KeyFormatHelper.ReadPkcs8(KnownOids, source, MLDsaKeyReader, out int read, out MLDsa dsa);
@@ -967,7 +967,7 @@ public static MLDsa ImportPkcs8PrivateKey(byte[] source)
967967
/// </exception>
968968
public static MLDsa ImportEncryptedPkcs8PrivateKey(ReadOnlySpan<byte> passwordBytes, ReadOnlySpan<byte> source)
969969
{
970-
ThrowIfInvalidLength(source);
970+
Helpers.ThrowIfAsnInvalidLength(source);
971971
ThrowIfNotSupported();
972972

973973
return KeyFormatHelper.DecryptPkcs8(
@@ -1016,7 +1016,7 @@ public static MLDsa ImportEncryptedPkcs8PrivateKey(ReadOnlySpan<byte> passwordBy
10161016
/// </exception>
10171017
public static MLDsa ImportEncryptedPkcs8PrivateKey(ReadOnlySpan<char> password, ReadOnlySpan<byte> source)
10181018
{
1019-
ThrowIfInvalidLength(source);
1019+
Helpers.ThrowIfAsnInvalidLength(source);
10201020
ThrowIfNotSupported();
10211021

10221022
return KeyFormatHelper.DecryptPkcs8(
@@ -1668,45 +1668,7 @@ internal static void ThrowIfNotSupported()
16681668
}
16691669
}
16701670

1671-
private static void ThrowIfInvalidLength(ReadOnlySpan<byte> data)
1672-
{
1673-
int bytesRead;
1674-
1675-
try
1676-
{
1677-
AsnDecoder.ReadEncodedValue(data, AsnEncodingRules.BER, out _, out _, out bytesRead);
1678-
}
1679-
catch (AsnContentException ace)
1680-
{
1681-
throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, ace);
1682-
}
1683-
1684-
if (bytesRead != data.Length)
1685-
{
1686-
throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
1687-
}
1688-
}
1689-
1690-
private static string EncodeAsnWriterToPem(string label, AsnWriter writer, bool clear = true)
1691-
{
1692-
#if NET10_0_OR_GREATER
1693-
return writer.Encode(label, static (label, span) => PemEncoding.WriteString(label, span));
1694-
#else
1695-
int length = writer.GetEncodedLength();
1696-
byte[] rent = CryptoPool.Rent(length);
16971671

1698-
try
1699-
{
1700-
int written = writer.Encode(rent);
1701-
Debug.Assert(written == length);
1702-
return PemEncoding.WriteString(label, rent.AsSpan(0, written));
1703-
}
1704-
finally
1705-
{
1706-
CryptoPool.Return(rent, clear ? length : 0);
1707-
}
1708-
#endif
1709-
}
17101672

17111673
private delegate TResult ExportPkcs8PrivateKeyFunc<TResult>(ReadOnlySpan<byte> pkcs8);
17121674
}

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

Lines changed: 8 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -660,7 +660,7 @@ public string ExportSubjectPublicKeyInfoPem()
660660
ThrowIfDisposed();
661661
AsnWriter writer = ExportSubjectPublicKeyInfoCore();
662662
// SPKI does not contain sensitive data.
663-
return EncodeAsnWriterToPem(PemLabels.SpkiPublicKey, writer, clear: false);
663+
return Helpers.EncodeAsnWriterToPem(PemLabels.SpkiPublicKey, writer, clear: false);
664664
}
665665

666666
/// <summary>
@@ -1064,7 +1064,7 @@ public string ExportEncryptedPkcs8PrivateKeyPem(ReadOnlySpan<byte> passwordBytes
10641064
KeyFormatHelper.WriteEncryptedPkcs8);
10651065

10661066
// Skip clear since the data is already encrypted.
1067-
return EncodeAsnWriterToPem(PemLabels.EncryptedPkcs8PrivateKey, writer, clear: false);
1067+
return Helpers.EncodeAsnWriterToPem(PemLabels.EncryptedPkcs8PrivateKey, writer, clear: false);
10681068
}
10691069

10701070
/// <summary>
@@ -1105,7 +1105,7 @@ public string ExportEncryptedPkcs8PrivateKeyPem(ReadOnlySpan<char> password, Pbe
11051105
KeyFormatHelper.WriteEncryptedPkcs8);
11061106

11071107
// Skip clear since the data is already encrypted.
1108-
return EncodeAsnWriterToPem(PemLabels.EncryptedPkcs8PrivateKey, writer, clear: false);
1108+
return Helpers.EncodeAsnWriterToPem(PemLabels.EncryptedPkcs8PrivateKey, writer, clear: false);
11091109
}
11101110

11111111
/// <summary>
@@ -1168,7 +1168,7 @@ public string ExportEncryptedPkcs8PrivateKeyPem(string password, PbeParameters p
11681168
/// </exception>
11691169
public static MLKem ImportSubjectPublicKeyInfo(ReadOnlySpan<byte> source)
11701170
{
1171-
ThrowIfTrailingData(source);
1171+
Helpers.ThrowIfAsnInvalidLength(source);
11721172
ThrowIfNotSupported();
11731173

11741174
KeyFormatHelper.ReadSubjectPublicKeyInfo(s_knownOids, source, SubjectPublicKeyReader, out int read, out MLKem kem);
@@ -1230,7 +1230,7 @@ public static MLKem ImportSubjectPublicKeyInfo(byte[] source)
12301230
/// </exception>
12311231
public static MLKem ImportPkcs8PrivateKey(ReadOnlySpan<byte> source)
12321232
{
1233-
ThrowIfTrailingData(source);
1233+
Helpers.ThrowIfAsnInvalidLength(source);
12341234
ThrowIfNotSupported();
12351235

12361236
KeyFormatHelper.ReadPkcs8(s_knownOids, source, MLKemKeyReader, out int read, out MLKem kem);
@@ -1288,7 +1288,7 @@ public static MLKem ImportPkcs8PrivateKey(byte[] source)
12881288
/// </exception>
12891289
public static MLKem ImportEncryptedPkcs8PrivateKey(ReadOnlySpan<byte> passwordBytes, ReadOnlySpan<byte> source)
12901290
{
1291-
ThrowIfTrailingData(source);
1291+
Helpers.ThrowIfAsnInvalidLength(source);
12921292
ThrowIfNotSupported();
12931293

12941294
return KeyFormatHelper.DecryptPkcs8(
@@ -1333,7 +1333,7 @@ public static MLKem ImportEncryptedPkcs8PrivateKey(ReadOnlySpan<byte> passwordBy
13331333
/// </exception>
13341334
public static MLKem ImportEncryptedPkcs8PrivateKey(ReadOnlySpan<char> password, ReadOnlySpan<byte> source)
13351335
{
1336-
ThrowIfTrailingData(source);
1336+
Helpers.ThrowIfAsnInvalidLength(source);
13371337
ThrowIfNotSupported();
13381338

13391339
return KeyFormatHelper.DecryptPkcs8(
@@ -1383,7 +1383,7 @@ public static MLKem ImportEncryptedPkcs8PrivateKey(string password, byte[] sourc
13831383
{
13841384
ArgumentNullException.ThrowIfNull(password);
13851385
ArgumentNullException.ThrowIfNull(source);
1386-
ThrowIfTrailingData(source);
1386+
Helpers.ThrowIfAsnInvalidLength(source);
13871387
ThrowIfNotSupported();
13881388

13891389
return KeyFormatHelper.DecryptPkcs8(
@@ -1744,19 +1744,6 @@ private static void MLKemKeyReader(
17441744
}
17451745
}
17461746

1747-
private static void ThrowIfTrailingData(ReadOnlySpan<byte> data)
1748-
{
1749-
// The only thing we are checking here is that TryReadEncodedValue was able to decode it and that, given
1750-
// the length of the data, that it the same length as the span. The encoding rules don't matter for length
1751-
// checking, so just use BER.
1752-
bool success = AsnDecoder.TryReadEncodedValue(data, AsnEncodingRules.BER, out _, out _, out _, out int bytesRead);
1753-
1754-
if (!success || bytesRead != data.Length)
1755-
{
1756-
throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
1757-
}
1758-
}
1759-
17601747
private protected void ThrowIfDisposed()
17611748
{
17621749
ObjectDisposedException.ThrowIf(_disposed, typeof(MLKem));
@@ -1822,27 +1809,6 @@ private TResult ExportPkcs8PrivateKeyCallback<TResult>(ExportPkcs8PrivateKeyFunc
18221809
return result;
18231810
}
18241811

1825-
private static string EncodeAsnWriterToPem(string label, AsnWriter writer, bool clear = true)
1826-
{
1827-
#if NET10_0_OR_GREATER
1828-
return writer.Encode(label, static (label, span) => PemEncoding.WriteString(label, span));
1829-
#else
1830-
int length = writer.GetEncodedLength();
1831-
byte[] rent = CryptoPool.Rent(length);
1832-
1833-
try
1834-
{
1835-
int written = writer.Encode(rent);
1836-
Debug.Assert(written == length);
1837-
return PemEncoding.WriteString(label, rent.AsSpan(0, written));
1838-
}
1839-
finally
1840-
{
1841-
CryptoPool.Return(rent, clear ? length : 0);
1842-
}
1843-
#endif
1844-
}
1845-
18461812
private protected static void ThrowIfNoSeed(bool hasSeed)
18471813
{
18481814
if (!hasSeed)

0 commit comments

Comments
 (0)