Skip to content

Commit d09a42b

Browse files
authored
Seed in the skeleton of ML-DSA based on current prototyping
This seeds a lot of untested boilerplate code as non-public types in System.Security.Cryptography. When we start work on ML-DSA, we can take this, make MLDsa and MLDsaAlgorithm public, start to write tests, and wire up an implementation. We can also take this and clone+customize it for the other PQC algorithms. It does not define all span-vs-array overloads for all spanified members, just one candidate per method group. As the PQC experiment needs to make it further before we can commit to the shape, it is both starting without API Review, and pre-emptively applying the Experimental attribute.
1 parent f63d96e commit d09a42b

12 files changed

+1765
-0
lines changed

docs/project/list-of-diagnostics.md

+1
Original file line numberDiff line numberDiff line change
@@ -314,3 +314,4 @@ Diagnostic id values for experimental APIs must not be recycled, as that could s
314314
| __`SYSLIB5003`__ | .NET 9 | TBD | `System.Runtime.Intrinsics.Arm.Sve` is experimental |
315315
| __`SYSLIB5004`__ | .NET 9 | TBD | `X86Base.DivRem` is experimental since performance is not as optimized as `T.DivRem` |
316316
| __`SYSLIB5005`__ | .NET 9 | TBD | `System.Formats.Nrbf` is experimental |
317+
| __`SYSLIB5006`__ | .NET 10 | TBD | Types for Post-Quantum Cryptography (PQC) are experimental. |

src/libraries/Common/src/System/Experimentals.cs

+3
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ internal static class Experimentals
3030

3131
// System.Formats.Nrbf was experimental, do not reuse "SYSLIB5005"
3232

33+
// Types for Post-Quantum Cryptography (PQC) are experimental.
34+
internal const string PostQuantumCryptographyDiagId = "SYSLIB5006";
35+
3336
// When adding a new diagnostic ID, add it to the table in docs\project\list-of-diagnostics.md as well.
3437
// Keep new const identifiers above this comment.
3538
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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+
namespace System.Security.Cryptography
5+
{
6+
#if DESIGNTIMEINTERFACES
7+
internal interface IImportExportShape<TSelf> where TSelf : class, IImportExportShape<TSelf>
8+
{
9+
static abstract TSelf ImportSubjectPublicKeyInfo(ReadOnlySpan<byte> source);
10+
static abstract TSelf ImportPkcs8PrivateKey(ReadOnlySpan<byte> source);
11+
static abstract TSelf ImportEncryptedPkcs8PrivateKey(ReadOnlySpan<byte> passwordBytes, ReadOnlySpan<byte> source);
12+
static abstract TSelf ImportEncryptedPkcs8PrivateKey(ReadOnlySpan<char> password, ReadOnlySpan<byte> source);
13+
14+
static abstract TSelf ImportFromPem(ReadOnlySpan<char> source);
15+
static abstract TSelf ImportFromEncryptedPem(ReadOnlySpan<char> source, ReadOnlySpan<char> password);
16+
static abstract TSelf ImportFromEncryptedPem(ReadOnlySpan<char> source, ReadOnlySpan<byte> passwordBytes);
17+
18+
byte[] ExportSubjectPublicKeyInfo();
19+
bool TryExportSubjectPublicKeyInfo(Span<byte> destination, out int bytesWritten);
20+
string ExportSubjectPublicKeyInfoPem();
21+
22+
byte[] ExportPkcs8PrivateKey();
23+
bool TryExportPkcs8PrivateKey(Span<byte> destination, out int bytesWritten);
24+
string ExportPkcs8PrivateKeyPem();
25+
26+
byte[] ExportEncryptedPkcs8PrivateKey(ReadOnlySpan<char> password, PbeParameters pbeParameters);
27+
bool TryExportEncryptedPkcs8PrivateKey(
28+
ReadOnlySpan<char> password, PbeParameters pbeParameters, Span<byte> destination, out int bytesWritten);
29+
string ExportEncryptedPkcs8PrivateKeyPem(ReadOnlySpan<char> password, PbeParameters pbeParameters);
30+
31+
byte[] ExportEncryptedPkcs8PrivateKey(ReadOnlySpan<byte> passwordBytes, PbeParameters pbeParameters);
32+
bool TryExportEncryptedPkcs8PrivateKey(
33+
ReadOnlySpan<byte> passwordBytes, PbeParameters pbeParameters, Span<byte> destination, out int bytesWritten);
34+
string ExportEncryptedPkcs8PrivateKeyPem(ReadOnlySpan<byte> passwordBytes, PbeParameters pbeParameters);
35+
}
36+
#endif
37+
}

src/libraries/Common/src/System/Security/Cryptography/KeyFormatHelper.Encrypted.cs

+64
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ namespace System.Security.Cryptography
1212
{
1313
internal static partial class KeyFormatHelper
1414
{
15+
internal delegate TRet ReadOnlySpanFunc<TIn, TRet>(ReadOnlySpan<TIn> span);
16+
1517
internal static unsafe void ReadEncryptedPkcs8<TRet>(
1618
string[] validOids,
1719
ReadOnlySpan<byte> source,
@@ -272,6 +274,68 @@ internal static ArraySegment<byte> DecryptPkcs8(
272274
out bytesRead);
273275
}
274276

277+
internal static unsafe T DecryptPkcs8<T>(
278+
ReadOnlySpan<char> password,
279+
ReadOnlySpan<byte> source,
280+
ReadOnlySpanFunc<byte, T> keyReader,
281+
out int bytesRead)
282+
{
283+
fixed (byte* pointer = source)
284+
{
285+
using (PointerMemoryManager<byte> manager = new(pointer, source.Length))
286+
{
287+
ArraySegment<byte> decrypted = DecryptPkcs8(password, manager.Memory, out bytesRead);
288+
289+
try
290+
{
291+
AsnValueReader reader = new(decrypted, AsnEncodingRules.BER);
292+
reader.ReadEncodedValue();
293+
reader.ThrowIfNotEmpty();
294+
return keyReader(decrypted);
295+
}
296+
catch (AsnContentException e)
297+
{
298+
throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e);
299+
}
300+
finally
301+
{
302+
CryptoPool.Return(decrypted);
303+
}
304+
}
305+
}
306+
}
307+
308+
internal static unsafe T DecryptPkcs8<T>(
309+
ReadOnlySpan<byte> passwordBytes,
310+
ReadOnlySpan<byte> source,
311+
ReadOnlySpanFunc<byte, T> keyReader,
312+
out int bytesRead)
313+
{
314+
fixed (byte* pointer = source)
315+
{
316+
using (PointerMemoryManager<byte> manager = new(pointer, source.Length))
317+
{
318+
ArraySegment<byte> decrypted = KeyFormatHelper.DecryptPkcs8(passwordBytes, manager.Memory, out bytesRead);
319+
AsnValueReader reader = new(decrypted, AsnEncodingRules.BER);
320+
reader.ReadEncodedValue();
321+
322+
try
323+
{
324+
reader.ThrowIfNotEmpty();
325+
return keyReader(decrypted);
326+
}
327+
catch (AsnContentException e)
328+
{
329+
throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e);
330+
}
331+
finally
332+
{
333+
CryptoPool.Return(decrypted);
334+
}
335+
}
336+
}
337+
}
338+
275339
private static ArraySegment<byte> DecryptPkcs8(
276340
ReadOnlySpan<char> inputPassword,
277341
ReadOnlySpan<byte> inputPasswordBytes,

0 commit comments

Comments
 (0)