Skip to content

Commit

Permalink
Replace SPKI hashing with a managed implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
vcsjones authored Jan 29, 2024
1 parent 8b082e1 commit 92999f4
Show file tree
Hide file tree
Showing 8 changed files with 28 additions and 109 deletions.

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)
{
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();
}
}
}

0 comments on commit 92999f4

Please sign in to comment.