Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add X509Certificate2Collection.FindByThumbprint #109130

Merged
merged 4 commits into from
Oct 23, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -3311,6 +3311,9 @@ public void AddRange(System.Security.Cryptography.X509Certificates.X509Certifica
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("tvos")]
public string ExportPkcs7Pem() { throw null; }
public System.Security.Cryptography.X509Certificates.X509Certificate2Collection Find(System.Security.Cryptography.X509Certificates.X509FindType findType, object findValue, bool validOnly) { throw null; }
public System.Security.Cryptography.X509Certificates.X509Certificate2Collection FindByThumbprint(System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.ReadOnlySpan<byte> thumbprintBytes) { throw null; }
public System.Security.Cryptography.X509Certificates.X509Certificate2Collection FindByThumbprint(System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.ReadOnlySpan<char> thumbprintHex) { throw null; }
public System.Security.Cryptography.X509Certificates.X509Certificate2Collection FindByThumbprint(System.Security.Cryptography.HashAlgorithmName hashAlgorithm, string thumbprintHex) { throw null; }
public new System.Security.Cryptography.X509Certificates.X509Certificate2Enumerator GetEnumerator() { throw null; }
[System.ObsoleteAttribute("Loading certificate data through the constructor or Import is obsolete. Use X509CertificateLoader instead to load certificates.", DiagnosticId="SYSLIB0057", UrlFormat="https://aka.ms/dotnet-warnings/{0}")]
public void Import(byte[] rawData) { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,9 @@
<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="Argument_Thumbprint_Invalid" xml:space="preserve">
<value>The supplied thumbprint is not a valid hex-encoded digest.</value>
</data>
<data name="Argument_X500_EmailTooLong" xml:space="preserve">
<value>The email address cannot exceed 255 characters.</value>
</data>
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 System.Buffers;
using System.Collections.Generic;
using System.Diagnostics;
using System.Formats.Asn1;
Expand Down Expand Up @@ -587,6 +588,153 @@ public bool TryExportCertificatePems(Span<char> destination, out int charsWritte
return true;
}

/// <summary>
/// Searches the collection for certificates with a matching thumbprint.
/// </summary>
/// <param name="hashAlgorithm">The name of the hash algorithm to compute the thumbprint.</param>
/// <param name="thumbprintHex">The thumbprint to match, hex-encoded.</param>
/// <returns>A collection of certificates with a matching thumbprint.</returns>
/// <exception cref="ArgumentNullException">
/// <paramref name="thumbprintHex"/> is <see langword="null"/>.
/// </exception>
/// <exception cref="ArgumentException">
/// <para>
/// <paramref name="hashAlgorithm"/>.<see cref="HashAlgorithmName.Name"/> is <see langword="null"/> or empty.
/// </para>
/// <para>-or-</para>
/// <para>
/// <paramref name="thumbprintHex"/> contains invalid hexadecimal characters.
/// </para>
/// <para>-or-</para>
/// <para>
/// <paramref name="thumbprintHex"/> does not decode evenly and contains an odd number of characters.
/// </para>
/// </exception>
/// <exception cref="PlatformNotSupportedException">
/// <paramref name="hashAlgorithm"/> specifies a hash algorithm not supported by the current platform.
/// </exception>
/// <exception cref="CryptographicException">
/// <para>
/// <paramref name="hashAlgorithm"/> specifies an unknown hash algorithm.
/// </para>
/// <para>-or-</para>
/// <para>
/// An error occured while finding the certificates with a matching thumbprint.
/// </para>
/// </exception>
public X509Certificate2Collection FindByThumbprint(HashAlgorithmName hashAlgorithm, string thumbprintHex)
{
ArgumentNullException.ThrowIfNull(thumbprintHex);
return FindByThumbprint(hashAlgorithm, thumbprintHex.AsSpan());
}

/// <summary>
/// Searches the collection for certificates with a matching thumbprint.
/// </summary>
/// <param name="hashAlgorithm">The name of the hash algorithm to compute the thumbprint.</param>
/// <param name="thumbprintHex">The thumbprint to match, hex-encoded.</param>
/// <returns>A collection of certificates with a matching thumbprint.</returns>
/// <exception cref="ArgumentException">
/// <para>
/// <paramref name="hashAlgorithm"/>.<see cref="HashAlgorithmName.Name"/> is <see langword="null"/> or empty.
/// </para>
/// <para>-or-</para>
/// <para>
/// <paramref name="thumbprintHex"/> contains invalid hexadecimal characters.
/// </para>
/// <para>-or-</para>
/// <para>
/// <paramref name="thumbprintHex"/> does not decode evenly and contains an odd number of characters.
/// </para>
/// </exception>
/// <exception cref="PlatformNotSupportedException">
/// <paramref name="hashAlgorithm"/> specifies a hash algorithm not supported by the current platform.
/// </exception>
/// <exception cref="CryptographicException">
/// <para>
/// <paramref name="hashAlgorithm"/> specifies an unknown hash algorithm.
/// </para>
/// <para>-or-</para>
/// <para>
/// An error occured while finding the certificates with a matching thumbprint.
/// </para>
/// </exception>
public X509Certificate2Collection FindByThumbprint(HashAlgorithmName hashAlgorithm, ReadOnlySpan<char> thumbprintHex)
{
ArgumentException.ThrowIfNullOrEmpty(hashAlgorithm.Name, nameof(hashAlgorithm));
const int MaxThumbprintStackAlloc = 64; // SHA-2/3-512 is the largest thumbprint currently known.

// Make sure the buffer is big enough even if the input string is the incorrect length so we get the proper
// error out of the decoder.
int maxDecodedLength = checked(thumbprintHex.Length + 1) / 2;
Span<byte> thumbprint = maxDecodedLength > MaxThumbprintStackAlloc ?
new byte[maxDecodedLength] :
stackalloc byte[MaxThumbprintStackAlloc];

OperationStatus status = Convert.FromHexString(thumbprintHex, thumbprint, out _, out int bytesWritten);

switch (status)
{
case OperationStatus.InvalidData:
case OperationStatus.NeedMoreData:
throw new ArgumentException(SR.Argument_Thumbprint_Invalid, nameof(thumbprintHex));
case OperationStatus.DestinationTooSmall:
Debug.Fail("Precomputed buffer was not large enough");
throw new CryptographicException();
case OperationStatus.Done:
break;
}

return FindByThumbprintCore(hashAlgorithm, thumbprint.Slice(0, bytesWritten));
}

/// <summary>
/// Searches the collection for certificates with a matching thumbprint.
/// </summary>
/// <param name="hashAlgorithm">The name of the hash algorithm to compute the thumbprint.</param>
/// <param name="thumbprintBytes">The thumbprint to match.</param>
/// <returns>A collection of certificates with a matching thumbprint.</returns>
/// <exception cref="ArgumentException">
/// <paramref name="hashAlgorithm"/>.<see cref="HashAlgorithmName.Name"/> is <see langword="null"/> or empty.
/// </exception>
/// <exception cref="PlatformNotSupportedException">
/// <paramref name="hashAlgorithm"/> specifies a hash algorithm not supported by the current platform.
/// </exception>
/// <exception cref="CryptographicException">
/// <para>
/// <paramref name="hashAlgorithm"/> specifies an unknown hash algorithm.
/// </para>
/// <para>-or-</para>
/// <para>
/// An error occured while finding the certificates with a matching thumbprint.
/// </para>
/// </exception>
public X509Certificate2Collection FindByThumbprint(HashAlgorithmName hashAlgorithm, ReadOnlySpan<byte> thumbprintBytes)
{
ArgumentException.ThrowIfNullOrEmpty(hashAlgorithm.Name, nameof(hashAlgorithm));
return FindByThumbprintCore(hashAlgorithm, thumbprintBytes);
}

private X509Certificate2Collection FindByThumbprintCore(HashAlgorithmName hashAlgorithm, ReadOnlySpan<byte> thumbprintBytes)
{
const int MaxThumbprintStackAlloc = 64; // SHA-2/3-512 is the largest thumbprint currently known.
Span<byte> thumbprintBuffer = stackalloc byte[MaxThumbprintStackAlloc];

X509Certificate2Collection results = [];

foreach (X509Certificate2 cert in this)
{
int bytesWritten = CryptographicOperations.HashData(hashAlgorithm, cert.RawDataMemory.Span, thumbprintBuffer);

if (thumbprintBuffer.Slice(0, bytesWritten).SequenceEqual(thumbprintBytes))
{
results.Add(cert);
}
}

return results;
}

private int GetCertificatePemsSize()
{
checked
Expand Down
Loading
Loading