Skip to content

Commit

Permalink
Provide PEM implementations for AsymmetricAlgorithm.
Browse files Browse the repository at this point in the history
Now that the Encoding and Primitive assemblies are unified, an implementation
for PKCS#8 and SPKI keys can be provided in the abstract AsymmetricAlgorithm.
Derived types will continue to override these members to provide support
for additional PEM labels.
  • Loading branch information
vcsjones authored Nov 9, 2021
1 parent 920a4a3 commit d39d805
Show file tree
Hide file tree
Showing 9 changed files with 483 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1173,20 +1173,9 @@ public int GetMaxSignatureSize(DSASignatureFormat signatureFormat)
/// </remarks>
public override void ImportFromPem(ReadOnlySpan<char> input)
{
PemKeyImportHelpers.ImportPem(input, label => {
if (label.SequenceEqual(PemLabels.Pkcs8PrivateKey))
{
return ImportPkcs8PrivateKey;
}
else if (label.SequenceEqual(PemLabels.SpkiPublicKey))
{
return ImportSubjectPublicKeyInfo;
}
else
{
return null;
}
});
// Implementation has been pushed down to AsymmetricAlgorithm. The
// override remains for compatibility.
base.ImportFromPem(input);
}

/// <summary>
Expand Down Expand Up @@ -1255,7 +1244,9 @@ public override void ImportFromPem(ReadOnlySpan<char> input)
/// </remarks>
public override void ImportFromEncryptedPem(ReadOnlySpan<char> input, ReadOnlySpan<char> password)
{
PemKeyImportHelpers.ImportEncryptedPem<char>(input, password, ImportEncryptedPkcs8PrivateKey);
// Implementation has been pushed down to AsymmetricAlgorithm. The
// override remains for compatibility.
base.ImportFromEncryptedPem(input, password);
}

/// <summary>
Expand Down Expand Up @@ -1325,7 +1316,9 @@ public override void ImportFromEncryptedPem(ReadOnlySpan<char> input, ReadOnlySp
/// </remarks>
public override void ImportFromEncryptedPem(ReadOnlySpan<char> input, ReadOnlySpan<byte> passwordBytes)
{
PemKeyImportHelpers.ImportEncryptedPem<byte>(input, passwordBytes, ImportEncryptedPkcs8PrivateKey);
// Implementation has been pushed down to AsymmetricAlgorithm. The
// override remains for compatibility.
base.ImportFromEncryptedPem(input, passwordBytes);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -555,7 +555,9 @@ public override void ImportFromPem(ReadOnlySpan<char> input)
/// </remarks>
public override void ImportFromEncryptedPem(ReadOnlySpan<char> input, ReadOnlySpan<char> password)
{
PemKeyImportHelpers.ImportEncryptedPem<char>(input, password, ImportEncryptedPkcs8PrivateKey);
// Implementation has been pushed down to AsymmetricAlgorithm. The
// override remains for compatibility.
base.ImportFromEncryptedPem(input, password);
}

/// <summary>
Expand Down Expand Up @@ -625,7 +627,9 @@ public override void ImportFromEncryptedPem(ReadOnlySpan<char> input, ReadOnlySp
/// </remarks>
public override void ImportFromEncryptedPem(ReadOnlySpan<char> input, ReadOnlySpan<byte> passwordBytes)
{
PemKeyImportHelpers.ImportEncryptedPem<byte>(input, passwordBytes, ImportEncryptedPkcs8PrivateKey);
// Implementation has been pushed down to AsymmetricAlgorithm. The
// override remains for compatibility.
base.ImportFromEncryptedPem(input, passwordBytes);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1419,7 +1419,9 @@ public override void ImportFromPem(ReadOnlySpan<char> input)
/// </remarks>
public override void ImportFromEncryptedPem(ReadOnlySpan<char> input, ReadOnlySpan<char> password)
{
PemKeyImportHelpers.ImportEncryptedPem<char>(input, password, ImportEncryptedPkcs8PrivateKey);
// Implementation has been pushed down to AsymmetricAlgorithm. The
// override remains for compatibility.
base.ImportFromEncryptedPem(input, password);
}

/// <summary>
Expand Down Expand Up @@ -1489,7 +1491,9 @@ public override void ImportFromEncryptedPem(ReadOnlySpan<char> input, ReadOnlySp
/// </remarks>
public override void ImportFromEncryptedPem(ReadOnlySpan<char> input, ReadOnlySpan<byte> passwordBytes)
{
PemKeyImportHelpers.ImportEncryptedPem<byte>(input, passwordBytes, ImportEncryptedPkcs8PrivateKey);
// Implementation has been pushed down to AsymmetricAlgorithm. The
// override remains for compatibility.
base.ImportFromEncryptedPem(input, passwordBytes);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -769,7 +769,9 @@ public override void ImportFromPem(ReadOnlySpan<char> input)
/// </remarks>
public override void ImportFromEncryptedPem(ReadOnlySpan<char> input, ReadOnlySpan<char> password)
{
PemKeyImportHelpers.ImportEncryptedPem<char>(input, password, ImportEncryptedPkcs8PrivateKey);
// Implementation has been pushed down to AsymmetricAlgorithm. The
// override remains for compatibility.
base.ImportFromEncryptedPem(input, password);
}

/// <summary>
Expand Down Expand Up @@ -839,7 +841,9 @@ public override void ImportFromEncryptedPem(ReadOnlySpan<char> input, ReadOnlySp
/// </remarks>
public override void ImportFromEncryptedPem(ReadOnlySpan<char> input, ReadOnlySpan<byte> passwordBytes)
{
PemKeyImportHelpers.ImportEncryptedPem<byte>(input, passwordBytes, ImportEncryptedPkcs8PrivateKey);
// Implementation has been pushed down to AsymmetricAlgorithm. The
// override remains for compatibility.
base.ImportFromEncryptedPem(input, passwordBytes);
}

private static void ClearPrivateParameters(in RSAParameters rsaParameters)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,15 @@
<data name="Argument_PemEncoding_EncodedSizeTooLarge" xml:space="preserve">
<value>The encoded PEM size is too large to represent as a signed 32-bit integer.</value>
</data>
<data name="Argument_PemImport_NoPemFound" xml:space="preserve">
<value>No supported key formats were found. Check that the input represents the contents of a PEM-encoded key file, not the path to such a file.</value>
</data>
<data name="Argument_PemImport_AmbiguousPem" xml:space="preserve">
<value>The input contains multiple keys, but only one key can be imported.</value>
</data>
<data name="Argument_PemImport_EncryptedPem" xml:space="preserve">
<value>An encrypted key was found, but no password was provided. Use ImportFromEncryptedPem to import this key.</value>
</data>
<data name="ArgumentOutOfRange_Index" xml:space="preserve">
<value>Index was out of range. Must be non-negative and less than the size of the collection.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,16 @@
Link="Common\System\Obsoletions.cs" />
<Compile Include="$(CommonPath)Internal\Cryptography\Helpers.cs"
Link="Internal\Cryptography\Helpers.cs" />
<Compile Include="$(CommonPath)Internal\Cryptography\PemKeyImportHelpers.cs"
Link="Internal\Cryptography\PemKeyImportHelpers.cs" />
<Compile Include="$(CommonPath)System\Security\Cryptography\CryptoPool.cs"
Link="Common\System\Security\Cryptography\CryptoPool.cs" />
<Compile Include="$(CommonPath)System\Security\Cryptography\KeySizeHelpers.cs"
Link="Common\System\Security\Cryptography\KeySizeHelpers.cs" />
<Compile Include="$(CommonPath)System\Security\Cryptography\Oids.cs"
Link="Common\System\Security\Cryptography\Oids.cs" />
<Compile Include="$(CommonPath)System\Security\Cryptography\PemLabels.cs"
Link="Common\System\Security\Cryptography\PemLabels.cs" />
<Compile Include="$(CommonPath)System\Threading\Tasks\TaskToApm.cs"
Link="Common\System\Threading\Tasks\TaskToApm.cs" />
<Compile Include="System\Security\Cryptography\AsnEncodedData.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Internal.Cryptography;
using System.Diagnostics.CodeAnalysis;

namespace System.Security.Cryptography
Expand Down Expand Up @@ -165,55 +166,218 @@ public virtual bool TryExportSubjectPublicKeyInfo(Span<byte> destination, out in
throw new NotImplementedException(SR.NotSupported_SubclassOverride);

/// <summary>
/// When overridden in a derived class, imports an encrypted RFC 7468
/// PEM-encoded key, replacing the keys for this object.
/// Imports an encrypted RFC 7468 PEM-encoded key, replacing the keys for this object.
/// </summary>
/// <param name="input">The PEM text of the encrypted key to import.</param>
/// <param name="password">
/// The password to use for decrypting the key material.
/// </param>
/// <exception cref="ArgumentException">
/// <para>
/// <paramref name="input"/> does not contain a PEM-encoded key with a recognized label.
/// </para>
/// <para>
/// -or-
/// </para>
/// <para>
/// <paramref name="input"/> contains multiple PEM-encoded keys with a recognized label.
/// </para>
/// </exception>
/// <exception cref="CryptographicException">
/// <para>
/// The password is incorrect.
/// </para>
/// <para>
/// -or-
/// </para>
/// <para>
/// The base-64 decoded contents of the PEM text from <paramref name="input" />
/// do not represent an ASN.1-BER-encoded PKCS#8 EncryptedPrivateKeyInfo structure.
/// </para>
/// <para>
/// -or-
/// </para>
/// <para>
/// The base-64 decoded contents of the PEM text from <paramref name="input" />
/// indicate the key is for an algorithm other than the algorithm
/// represented by this instance.
/// </para>
/// <para>
/// -or-
/// </para>
/// <para>
/// The base-64 decoded contents of the PEM text from <paramref name="input" />
/// represent the key in a format that is not supported.
/// </para>
/// <para>
/// -or-
/// </para>
/// <para>
/// The algorithm-specific key import failed.
/// </para>
/// </exception>
/// <exception cref="NotImplementedException">
/// A derived type has not overridden this member.
/// A derived type has not provided an implementation for
/// <see cref="ImportEncryptedPkcs8PrivateKey(ReadOnlySpan{char}, ReadOnlySpan{byte}, out int)" />.
/// </exception>
/// <remarks>
/// Because each algorithm may have algorithm-specific PEM labels, the
/// default behavior will throw <see cref="NotImplementedException" />.
/// <para>
/// When the base-64 decoded contents of <paramref name="input" /> indicate an algorithm that uses PBKDF1
/// (Password-Based Key Derivation Function 1) or PBKDF2 (Password-Based Key Derivation Function 2),
/// the password is converted to bytes via the UTF-8 encoding.
/// </para>
/// <para>
/// Unsupported or malformed PEM-encoded objects will be ignored. If multiple supported PEM labels
/// are found, an exception is thrown to prevent importing a key when
/// the key is ambiguous.
/// </para>
/// <para>This method supports the <c>ENCRYPTED PRIVATE KEY</c> PEM label.</para>
/// <para>
/// Types that override this method may support additional PEM labels.
/// </para>
/// </remarks>
public virtual void ImportFromEncryptedPem(ReadOnlySpan<char> input, ReadOnlySpan<char> password) =>
throw new NotImplementedException(SR.NotSupported_SubclassOverride);
public virtual void ImportFromEncryptedPem(ReadOnlySpan<char> input, ReadOnlySpan<char> password)
{
PemKeyImportHelpers.ImportEncryptedPem<char>(input, password, ImportEncryptedPkcs8PrivateKey);
}

/// <summary>
/// When overridden in a derived class, imports an encrypted RFC 7468
/// PEM-encoded key, replacing the keys for this object.
/// Imports an encrypted RFC 7468 PEM-encoded key, replacing the keys for this object.
/// </summary>
/// <param name="input">The PEM text of the encrypted key to import.</param>
/// <param name="passwordBytes">
/// The bytes to use as a password when decrypting the key material.
/// </param>
/// <exception cref="ArgumentException">
/// <para>
/// <paramref name="input"/> does not contain a PEM-encoded key with a recognized label.
/// </para>
/// <para>
/// -or-
/// </para>
/// <para>
/// <paramref name="input"/> contains multiple PEM-encoded keys with a recognized label.
/// </para>
/// </exception>
/// <exception cref="CryptographicException">
/// <para>
/// The password is incorrect.
/// </para>
/// <para>
/// -or-
/// </para>
/// <para>
/// The base-64 decoded contents of the PEM text from <paramref name="input" />
/// do not represent an ASN.1-BER-encoded PKCS#8 EncryptedPrivateKeyInfo structure.
/// </para>
/// <para>
/// -or-
/// </para>
/// <para>
/// The base-64 decoded contents of the PEM text from <paramref name="input" />
/// indicate the key is for an algorithm other than the algorithm
/// represented by this instance.
/// </para>
/// <para>
/// -or-
/// </para>
/// <para>
/// The base-64 decoded contents of the PEM text from <paramref name="input" />
/// represent the key in a format that is not supported.
/// </para>
/// <para>
/// -or-
/// </para>
/// <para>
/// The algorithm-specific key import failed.
/// </para>
/// </exception>
/// <exception cref="NotImplementedException">
/// A derived type has not overridden this member.
/// A derived type has not provided an implementation for
/// <see cref="ImportEncryptedPkcs8PrivateKey(ReadOnlySpan{byte}, ReadOnlySpan{byte}, out int)" />.
/// </exception>
/// <remarks>
/// Because each algorithm may have algorithm-specific PEM labels, the
/// default behavior will throw <see cref="NotImplementedException" />.
/// <para>
/// The password bytes are passed directly into the Key Derivation Function (KDF)
/// used by the algorithm indicated by <c>pbeParameters</c>. This enables compatibility
/// with other systems which use a text encoding other than UTF-8 when processing
/// passwords with PBKDF2 (Password-Based Key Derivation Function 2).
/// </para>
/// <para>
/// Unsupported or malformed PEM-encoded objects will be ignored. If multiple supported PEM labels
/// are found, an exception is thrown to prevent importing a key when
/// the key is ambiguous.
/// </para>
/// <para>This method supports the <c>ENCRYPTED PRIVATE KEY</c> PEM label.</para>
/// <para>
/// Types that override this method may support additional PEM labels.
/// </para>
/// </remarks>
public virtual void ImportFromEncryptedPem(ReadOnlySpan<char> input, ReadOnlySpan<byte> passwordBytes) =>
throw new NotImplementedException(SR.NotSupported_SubclassOverride);
public virtual void ImportFromEncryptedPem(ReadOnlySpan<char> input, ReadOnlySpan<byte> passwordBytes)
{
PemKeyImportHelpers.ImportEncryptedPem<byte>(input, passwordBytes, ImportEncryptedPkcs8PrivateKey);
}

/// <summary>
/// When overridden in a derived class, imports an RFC 7468 textually
/// encoded key, replacing the keys for this object.
/// Imports an RFC 7468 textually encoded key, replacing the keys for this object.
/// </summary>
/// <param name="input">The text of the PEM key to import.</param>
/// <exception cref="ArgumentException">
/// <para>
/// <paramref name="input"/> does not contain a PEM-encoded key with a recognized label.
/// </para>
/// <para>
/// -or-
/// </para>
/// <para>
/// <paramref name="input"/> contains multiple PEM-encoded keys with a recognized label.
/// </para>
/// <para>
/// -or-
/// </para>
/// <para>
/// <paramref name="input"/> contains an encrypted PEM-encoded key.
/// </para>
/// </exception>
/// <exception cref="NotImplementedException">
/// A derived type has not overridden this member.
/// A derived type has not provided an implementation for <see cref="ImportPkcs8PrivateKey" />
/// or <see cref="ImportSubjectPublicKeyInfo" />.
/// </exception>
/// <remarks>
/// Because each algorithm may have algorithm-specific PEM labels, the
/// default behavior will throw <see cref="NotImplementedException" />.
/// <para>
/// Unsupported or malformed PEM-encoded objects will be ignored. If multiple supported PEM labels
/// are found, an exception is raised to prevent importing a key when
/// the key is ambiguous.
/// </para>
/// <para>
/// This method supports the following PEM labels:
/// <list type="bullet">
/// <item><description>PUBLIC KEY</description></item>
/// <item><description>PRIVATE KEY</description></item>
/// </list>
/// </para>
/// <para>
/// Types that override this method may support additional PEM labels.
/// </para>
/// </remarks>
public virtual void ImportFromPem(ReadOnlySpan<char> input) =>
throw new NotImplementedException(SR.NotSupported_SubclassOverride);
public virtual void ImportFromPem(ReadOnlySpan<char> input)
{
PemKeyImportHelpers.ImportPem(input, label =>
{
if (label.SequenceEqual(PemLabels.Pkcs8PrivateKey))
{
return ImportPkcs8PrivateKey;
}
else if (label.SequenceEqual(PemLabels.SpkiPublicKey))
{
return ImportSubjectPublicKeyInfo;
}
else
{
return null;
}
});
}

private delegate bool TryExportPbe<T>(
ReadOnlySpan<T> password,
Expand Down
Loading

0 comments on commit d39d805

Please sign in to comment.