Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions src/libraries/Common/src/System/Security/Cryptography/Helpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Formats.Asn1;
using System.Runtime.CompilerServices;
Expand Down Expand Up @@ -168,5 +169,47 @@ internal static CryptographicException CreateAlgorithmUnknownException(string al
throw new CryptographicException(
SR.Format(SR.Cryptography_UnknownAlgorithmIdentifier, algorithmId));
}

#if !BUILDING_PKCS
internal static string EncodeAsnWriterToPem(string label, AsnWriter writer, bool clear = true)
{
#if NET10_0_OR_GREATER
return writer.Encode(label, static (label, span) => PemEncoding.WriteString(label, span));
#else
int length = writer.GetEncodedLength();
byte[] rent = CryptoPool.Rent(length);

try
{
int written = writer.Encode(rent);
Debug.Assert(written == length);
return PemEncoding.WriteString(label, rent.AsSpan(0, written));
}
finally
{
CryptoPool.Return(rent, clear ? length : 0);
}
#endif
}
#endif

internal static void ThrowIfAsnInvalidLength(ReadOnlySpan<byte> data)
{
int bytesRead;

try
{
AsnDecoder.ReadEncodedValue(data, AsnEncodingRules.BER, out _, out _, out bytesRead);
}
catch (AsnContentException ace)
{
throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, ace);
}

if (bytesRead != data.Length)
{
throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
}
}
}
}
52 changes: 7 additions & 45 deletions src/libraries/Common/src/System/Security/Cryptography/MLDsa.cs
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ public string ExportSubjectPublicKeyInfoPem()
AsnWriter writer = ExportSubjectPublicKeyInfoCore();

// SPKI does not contain sensitive data.
return EncodeAsnWriterToPem(PemLabels.SpkiPublicKey, writer, clear: false);
return Helpers.EncodeAsnWriterToPem(PemLabels.SpkiPublicKey, writer, clear: false);
}

/// <summary>
Expand Down Expand Up @@ -638,7 +638,7 @@ public string ExportEncryptedPkcs8PrivateKeyPem(
AsnWriter writer = ExportEncryptedPkcs8PrivateKeyCore(password, pbeParameters);

// Skip clear since the data is already encrypted.
return EncodeAsnWriterToPem(PemLabels.EncryptedPkcs8PrivateKey, writer, clear: false);
return Helpers.EncodeAsnWriterToPem(PemLabels.EncryptedPkcs8PrivateKey, writer, clear: false);
}

/// <summary>
Expand Down Expand Up @@ -682,7 +682,7 @@ public string ExportEncryptedPkcs8PrivateKeyPem(
AsnWriter writer = ExportEncryptedPkcs8PrivateKeyCore(passwordBytes, pbeParameters);

// Skip clear since the data is already encrypted.
return EncodeAsnWriterToPem(PemLabels.EncryptedPkcs8PrivateKey, writer, clear: false);
return Helpers.EncodeAsnWriterToPem(PemLabels.EncryptedPkcs8PrivateKey, writer, clear: false);
}

/// <inheritdoc cref="ExportEncryptedPkcs8PrivateKeyPem(ReadOnlySpan{char}, PbeParameters)"/>
Expand Down Expand Up @@ -835,7 +835,7 @@ public static MLDsa GenerateKey(MLDsaAlgorithm algorithm)
/// </exception>
public static MLDsa ImportSubjectPublicKeyInfo(ReadOnlySpan<byte> source)
{
ThrowIfInvalidLength(source);
Helpers.ThrowIfAsnInvalidLength(source);
ThrowIfNotSupported();

unsafe
Expand Down Expand Up @@ -904,7 +904,7 @@ public static MLDsa ImportSubjectPublicKeyInfo(byte[] source)
/// </exception>
public static MLDsa ImportPkcs8PrivateKey(ReadOnlySpan<byte> source)
{
ThrowIfInvalidLength(source);
Helpers.ThrowIfAsnInvalidLength(source);
ThrowIfNotSupported();

KeyFormatHelper.ReadPkcs8(KnownOids, source, MLDsaKeyReader, out int read, out MLDsa dsa);
Expand Down Expand Up @@ -967,7 +967,7 @@ public static MLDsa ImportPkcs8PrivateKey(byte[] source)
/// </exception>
public static MLDsa ImportEncryptedPkcs8PrivateKey(ReadOnlySpan<byte> passwordBytes, ReadOnlySpan<byte> source)
{
ThrowIfInvalidLength(source);
Helpers.ThrowIfAsnInvalidLength(source);
ThrowIfNotSupported();

return KeyFormatHelper.DecryptPkcs8(
Expand Down Expand Up @@ -1016,7 +1016,7 @@ public static MLDsa ImportEncryptedPkcs8PrivateKey(ReadOnlySpan<byte> passwordBy
/// </exception>
public static MLDsa ImportEncryptedPkcs8PrivateKey(ReadOnlySpan<char> password, ReadOnlySpan<byte> source)
{
ThrowIfInvalidLength(source);
Helpers.ThrowIfAsnInvalidLength(source);
ThrowIfNotSupported();

return KeyFormatHelper.DecryptPkcs8(
Expand Down Expand Up @@ -1668,45 +1668,7 @@ internal static void ThrowIfNotSupported()
}
}

private static void ThrowIfInvalidLength(ReadOnlySpan<byte> data)
{
int bytesRead;

try
{
AsnDecoder.ReadEncodedValue(data, AsnEncodingRules.BER, out _, out _, out bytesRead);
}
catch (AsnContentException ace)
{
throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, ace);
}

if (bytesRead != data.Length)
{
throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
}
}

private static string EncodeAsnWriterToPem(string label, AsnWriter writer, bool clear = true)
{
#if NET10_0_OR_GREATER
return writer.Encode(label, static (label, span) => PemEncoding.WriteString(label, span));
#else
int length = writer.GetEncodedLength();
byte[] rent = CryptoPool.Rent(length);

try
{
int written = writer.Encode(rent);
Debug.Assert(written == length);
return PemEncoding.WriteString(label, rent.AsSpan(0, written));
}
finally
{
CryptoPool.Return(rent, clear ? length : 0);
}
#endif
}

private delegate TResult ExportPkcs8PrivateKeyFunc<TResult>(ReadOnlySpan<byte> pkcs8);
}
Expand Down
50 changes: 8 additions & 42 deletions src/libraries/Common/src/System/Security/Cryptography/MLKem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -660,7 +660,7 @@ public string ExportSubjectPublicKeyInfoPem()
ThrowIfDisposed();
AsnWriter writer = ExportSubjectPublicKeyInfoCore();
// SPKI does not contain sensitive data.
return EncodeAsnWriterToPem(PemLabels.SpkiPublicKey, writer, clear: false);
return Helpers.EncodeAsnWriterToPem(PemLabels.SpkiPublicKey, writer, clear: false);
}

/// <summary>
Expand Down Expand Up @@ -1064,7 +1064,7 @@ public string ExportEncryptedPkcs8PrivateKeyPem(ReadOnlySpan<byte> passwordBytes
KeyFormatHelper.WriteEncryptedPkcs8);

// Skip clear since the data is already encrypted.
return EncodeAsnWriterToPem(PemLabels.EncryptedPkcs8PrivateKey, writer, clear: false);
return Helpers.EncodeAsnWriterToPem(PemLabels.EncryptedPkcs8PrivateKey, writer, clear: false);
}

/// <summary>
Expand Down Expand Up @@ -1105,7 +1105,7 @@ public string ExportEncryptedPkcs8PrivateKeyPem(ReadOnlySpan<char> password, Pbe
KeyFormatHelper.WriteEncryptedPkcs8);

// Skip clear since the data is already encrypted.
return EncodeAsnWriterToPem(PemLabels.EncryptedPkcs8PrivateKey, writer, clear: false);
return Helpers.EncodeAsnWriterToPem(PemLabels.EncryptedPkcs8PrivateKey, writer, clear: false);
}

/// <summary>
Expand Down Expand Up @@ -1168,7 +1168,7 @@ public string ExportEncryptedPkcs8PrivateKeyPem(string password, PbeParameters p
/// </exception>
public static MLKem ImportSubjectPublicKeyInfo(ReadOnlySpan<byte> source)
{
ThrowIfTrailingData(source);
Helpers.ThrowIfAsnInvalidLength(source);
ThrowIfNotSupported();

KeyFormatHelper.ReadSubjectPublicKeyInfo(s_knownOids, source, SubjectPublicKeyReader, out int read, out MLKem kem);
Expand Down Expand Up @@ -1230,7 +1230,7 @@ public static MLKem ImportSubjectPublicKeyInfo(byte[] source)
/// </exception>
public static MLKem ImportPkcs8PrivateKey(ReadOnlySpan<byte> source)
{
ThrowIfTrailingData(source);
Helpers.ThrowIfAsnInvalidLength(source);
ThrowIfNotSupported();

KeyFormatHelper.ReadPkcs8(s_knownOids, source, MLKemKeyReader, out int read, out MLKem kem);
Expand Down Expand Up @@ -1288,7 +1288,7 @@ public static MLKem ImportPkcs8PrivateKey(byte[] source)
/// </exception>
public static MLKem ImportEncryptedPkcs8PrivateKey(ReadOnlySpan<byte> passwordBytes, ReadOnlySpan<byte> source)
{
ThrowIfTrailingData(source);
Helpers.ThrowIfAsnInvalidLength(source);
ThrowIfNotSupported();

return KeyFormatHelper.DecryptPkcs8(
Expand Down Expand Up @@ -1333,7 +1333,7 @@ public static MLKem ImportEncryptedPkcs8PrivateKey(ReadOnlySpan<byte> passwordBy
/// </exception>
public static MLKem ImportEncryptedPkcs8PrivateKey(ReadOnlySpan<char> password, ReadOnlySpan<byte> source)
{
ThrowIfTrailingData(source);
Helpers.ThrowIfAsnInvalidLength(source);
ThrowIfNotSupported();

return KeyFormatHelper.DecryptPkcs8(
Expand Down Expand Up @@ -1383,7 +1383,7 @@ public static MLKem ImportEncryptedPkcs8PrivateKey(string password, byte[] sourc
{
ArgumentNullException.ThrowIfNull(password);
ArgumentNullException.ThrowIfNull(source);
ThrowIfTrailingData(source);
Helpers.ThrowIfAsnInvalidLength(source);
ThrowIfNotSupported();

return KeyFormatHelper.DecryptPkcs8(
Expand Down Expand Up @@ -1744,19 +1744,6 @@ private static void MLKemKeyReader(
}
}

private static void ThrowIfTrailingData(ReadOnlySpan<byte> data)
{
// The only thing we are checking here is that TryReadEncodedValue was able to decode it and that, given
// the length of the data, that it the same length as the span. The encoding rules don't matter for length
// checking, so just use BER.
bool success = AsnDecoder.TryReadEncodedValue(data, AsnEncodingRules.BER, out _, out _, out _, out int bytesRead);

if (!success || bytesRead != data.Length)
{
throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
}
}

private protected void ThrowIfDisposed()
{
ObjectDisposedException.ThrowIf(_disposed, typeof(MLKem));
Expand Down Expand Up @@ -1822,27 +1809,6 @@ private TResult ExportPkcs8PrivateKeyCallback<TResult>(ExportPkcs8PrivateKeyFunc
return result;
}

private static string EncodeAsnWriterToPem(string label, AsnWriter writer, bool clear = true)
{
#if NET10_0_OR_GREATER
return writer.Encode(label, static (label, span) => PemEncoding.WriteString(label, span));
#else
int length = writer.GetEncodedLength();
byte[] rent = CryptoPool.Rent(length);

try
{
int written = writer.Encode(rent);
Debug.Assert(written == length);
return PemEncoding.WriteString(label, rent.AsSpan(0, written));
}
finally
{
CryptoPool.Return(rent, clear ? length : 0);
}
#endif
}

private protected static void ThrowIfNoSeed(bool hasSeed)
{
if (!hasSeed)
Expand Down
Loading
Loading