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

Replace SPKI hashing with a managed implementation #97559

Merged
merged 4 commits into from
Jan 29, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 0 additions & 1 deletion src/coreclr/nativeaot/BuildIntegration/WindowsAPIs.txt
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,6 @@ crypt32!CryptGetOIDFunctionValue
crypt32!CryptHashCertificate
crypt32!CryptHashCertificate2
crypt32!CryptHashMessage
crypt32!CryptHashPublicKeyInfo
vcsjones marked this conversation as resolved.
Show resolved Hide resolved
crypt32!CryptHashToBeSigned
crypt32!CryptImportPKCS8
crypt32!CryptImportPublicKeyInfo
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -1545,8 +1545,6 @@
Link="Common\Interop\Windows\Crypt32\Interop.CryptEncodeObject_CertEncodingType.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CryptFormatObject.cs"
Link="Common\Interop\Windows\Crypt32\Interop.CryptFormatObject.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CryptHashPublicKeyInfo.cs"
Link="Common\Interop\Windows\Crypt32\Interop.CryptHashPublicKeyInfo.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CryptImportPublicKeyInfoEx2.cs"
Link="Common\Interop\Windows\Crypt32\Interop.CryptImportPublicKeyInfoEx2.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CryptImportPublicKeyInfoFlags.cs"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,5 @@ internal interface IX509Pal
void DecodeX509EnhancedKeyUsageExtension(byte[] encoded, out OidCollection usages);
byte[] EncodeX509SubjectKeyIdentifierExtension(ReadOnlySpan<byte> subjectKeyIdentifier);
void DecodeX509SubjectKeyIdentifierExtension(byte[] encoded, out byte[] subjectKeyIdentifier);
byte[] ComputeCapiSha1OfPublicKey(PublicKey key);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -210,37 +210,6 @@ internal static byte[] DecodeX509SubjectKeyIdentifierExtension(byte[] encoded)
return contents.ToArray();
}

[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA5350", Justification = "SHA1 is required for Compat")]
public virtual byte[] ComputeCapiSha1OfPublicKey(PublicKey key)
{
// The CapiSha1 value is the SHA-1 of the SubjectPublicKeyInfo field, inclusive
// of the DER structural bytes.

SubjectPublicKeyInfoAsn spki = default;
spki.Algorithm = new AlgorithmIdentifierAsn { Algorithm = key.Oid!.Value!, Parameters = key.EncodedParameters.RawData };
spki.SubjectPublicKey = key.EncodedKeyValue.RawData;

AsnWriter writer = new AsnWriter(AsnEncodingRules.DER);
spki.Encode(writer);

byte[] rented = CryptoPool.Rent(writer.GetEncodedLength());

try
{
if (!writer.TryEncode(rented, out int bytesWritten))
{
Debug.Fail("TryEncode failed with a pre-allocated buffer");
throw new CryptographicException();
}

return SHA1.HashData(rented.AsSpan(0, bytesWritten));
}
finally
{
CryptoPool.Return(rented, clearSize: 0); // SubjectPublicKeyInfo is not sensitive.
}
}

private static byte ReverseBitOrder(byte b)
{
return (byte)(unchecked(b * 0x0202020202ul & 0x010884422010ul) % 1023);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ public static PublicKey CreateFromSubjectPublicKeyInfo(ReadOnlySpan<byte> source
}
}

private AsnWriter EncodeSubjectPublicKeyInfo()
internal AsnWriter EncodeSubjectPublicKeyInfo()
{
SubjectPublicKeyInfoAsn spki = new SubjectPublicKeyInfoAsn
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,6 @@ public static SafeHandle ToLpstrArray(this OidCollection? oids, out int numOids)
}
}

public static byte[] ValueAsAscii(this Oid oid)
{
return Encoding.ASCII.GetBytes(oid.Value!);
}

public unsafe delegate void DecodedObjectReceiver(void* pvDecodedObject, int cbDecodedObject);
public unsafe delegate TResult DecodedObjectReceiver<TResult>(void* pvDecodedObject, int cbDecodedObject);
public unsafe delegate TResult DecodedObjectReceiver<TState, TResult>(void* pvDecodedObject, int cbDecodedObject, TState state);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,50 +184,5 @@ static delegate (void* pvDecoded, int cbDecoded)
});
}
}

public byte[] ComputeCapiSha1OfPublicKey(PublicKey key)
{
unsafe
{
fixed (byte* pszOidValue = key.Oid.ValueAsAscii())
{
byte[] encodedParameters = key.EncodedParameters.RawData;
fixed (byte* pEncodedParameters = encodedParameters)
{
byte[] encodedKeyValue = key.EncodedKeyValue.RawData;
fixed (byte* pEncodedKeyValue = encodedKeyValue)
{
Interop.Crypt32.CERT_PUBLIC_KEY_INFO publicKeyInfo = new Interop.Crypt32.CERT_PUBLIC_KEY_INFO()
{
Algorithm = new Interop.Crypt32.CRYPT_ALGORITHM_IDENTIFIER()
{
pszObjId = new IntPtr(pszOidValue),
Parameters = new Interop.Crypt32.DATA_BLOB(new IntPtr(pEncodedParameters), (uint)encodedParameters.Length),
},

PublicKey = new Interop.Crypt32.CRYPT_BIT_BLOB()
{
cbData = encodedKeyValue.Length,
pbData = new IntPtr(pEncodedKeyValue),
cUnusedBits = 0,
},
};

int cb = 20;
byte[] buffer = new byte[cb];
if (!Interop.Crypt32.CryptHashPublicKeyInfo(IntPtr.Zero, AlgId.CALG_SHA1, 0, Interop.Crypt32.CertEncodingType.All, ref publicKeyInfo, buffer, ref cb))
throw Marshal.GetHRForLastWin32Error().ToCryptographicException();
if (cb < buffer.Length)
{
byte[] newBuffer = new byte[cb];
Buffer.BlockCopy(buffer, 0, newBuffer, 0, cb);
buffer = newBuffer;
}
return buffer;
}
}
}
}
}
}
}
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.Diagnostics;
using System.Formats.Asn1;
using Internal.Cryptography;

namespace System.Security.Cryptography.X509Certificates
Expand Down Expand Up @@ -145,11 +146,35 @@ private static byte[] GenerateSubjectKeyIdentifierFromPublicKey(PublicKey key, X
}

case X509SubjectKeyIdentifierHashAlgorithm.CapiSha1:
return X509Pal.Instance.ComputeCapiSha1OfPublicKey(key);

// CAPI SHA1 is the SHA-1 hash over the whole SubjectPublicKeyInfo
return HashSubjectPublicKeyInfo(key, HashAlgorithmName.SHA1);
default:
throw new ArgumentException(SR.Format(SR.Arg_EnumIllegalVal, algorithm), nameof(algorithm));
}
}

private static byte[] HashSubjectPublicKeyInfo(PublicKey key, HashAlgorithmName hashAlgorithmName)
vcsjones marked this conversation as resolved.
Show resolved Hide resolved
{
Span<byte> hash = stackalloc byte[512 / 8]; // Largest known hash is 512-bits.
AsnWriter writer = key.EncodeSubjectPublicKeyInfo();

// An RSA 4096 SPKI is going to be about 550 bytes. 640 for a little extra space. Anything bigger will rent.
const int MaxSpkiStackSize = 640;
byte[]? rented = null;
int encodedLength = writer.GetEncodedLength();
Span<byte> spkiBuffer = encodedLength <= MaxSpkiStackSize ?
stackalloc byte[MaxSpkiStackSize] :
(rented = CryptoPool.Rent(encodedLength));

int spkiWritten = writer.Encode(spkiBuffer);
int hashWritten = CryptographicOperations.HashData(hashAlgorithmName, spkiBuffer.Slice(0, spkiWritten), hash);

if (rented is not null)
{
CryptoPool.Return(rented, clearSize: 0); // SPKI is public so no need to zero it.
}

return hash.Slice(0, hashWritten).ToArray();
}
}
}
Loading