Skip to content

Commit

Permalink
Add additional hash algorithms for X.509 SKI calculation
Browse files Browse the repository at this point in the history
  • Loading branch information
vcsjones authored and pull[bot] committed Nov 21, 2024
1 parent ec7da0f commit 1560501
Show file tree
Hide file tree
Showing 5 changed files with 273 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,19 @@ public static void CollectionEqual<T>(IEnumerable<T> expected, IEnumerable<T> ac
}
}

/// <summary>
/// Validates that the actual span is not equal to the expected span.
/// </summary>
/// <param name="expected">The sequence that <paramref name="actual"/> should be not be equal to.</param>
/// <param name="actual">The actual sequence.</param>
public static void SequenceNotEqual<T>(ReadOnlySpan<T> expected, ReadOnlySpan<T> actual) where T : IEquatable<T>
{
if (expected.SequenceEqual(actual))
{
throw new XunitException($"Expected: Contents of expected to differ from actual but were the same.");
}
}

/// <summary>
/// Validates that the actual span is equal to the expected span.
/// If this fails, determine where the differences are and create an exception with that information.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3612,6 +3612,12 @@ public enum X509SubjectKeyIdentifierHashAlgorithm
Sha1 = 0,
ShortSha1 = 1,
CapiSha1 = 2,
Sha256 = 3,
Sha384 = 4,
Sha512 = 5,
ShortSha256 = 6,
ShortSha384 = 7,
ShortSha512 = 8,
}
[System.FlagsAttribute]
public enum X509VerificationFlags
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,11 +186,32 @@ private static byte[] GenerateSubjectKeyIdentifierFromPublicKey(PublicKey key, X
case X509SubjectKeyIdentifierHashAlgorithm.CapiSha1:
// CAPI SHA1 is the SHA-1 hash over the whole SubjectPublicKeyInfo
return HashSubjectPublicKeyInfo(key, HashAlgorithmName.SHA1);
case X509SubjectKeyIdentifierHashAlgorithm.Sha256:
return HashSubjectPublicKeyInfo(key, HashAlgorithmName.SHA256);
case X509SubjectKeyIdentifierHashAlgorithm.Sha384:
return HashSubjectPublicKeyInfo(key, HashAlgorithmName.SHA384);
case X509SubjectKeyIdentifierHashAlgorithm.Sha512:
return HashSubjectPublicKeyInfo(key, HashAlgorithmName.SHA512);
case X509SubjectKeyIdentifierHashAlgorithm.ShortSha256:
return HashSubjectPublicKeyLeft160Bits(key, HashAlgorithmName.SHA256);
case X509SubjectKeyIdentifierHashAlgorithm.ShortSha384:
return HashSubjectPublicKeyLeft160Bits(key, HashAlgorithmName.SHA384);
case X509SubjectKeyIdentifierHashAlgorithm.ShortSha512:
return HashSubjectPublicKeyLeft160Bits(key, HashAlgorithmName.SHA512);
default:
throw new ArgumentException(SR.Format(SR.Arg_EnumIllegalVal, algorithm), nameof(algorithm));
}
}

private static byte[] HashSubjectPublicKeyLeft160Bits(PublicKey key, HashAlgorithmName hashAlgorithmName)
{
const int TruncateSize = 160 / 8;
Span<byte> hash = stackalloc byte[512 / 8]; // Largest known hash is 512-bits.
int written = CryptographicOperations.HashData(hashAlgorithmName, key.EncodedKeyValue.RawData, hash);
Debug.Assert(written >= TruncateSize);
return hash.Slice(0, TruncateSize).ToArray();
}

private static byte[] HashSubjectPublicKeyInfo(PublicKey key, HashAlgorithmName hashAlgorithmName)
{
Span<byte> hash = stackalloc byte[512 / 8]; // Largest known hash is 512-bits.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,35 @@ public enum X509SubjectKeyIdentifierHashAlgorithm
Sha1 = 0,
ShortSha1 = 1,
CapiSha1 = 2,

/// <summary>
/// The SHA-256 hash over the SubjectPublicKeyInfo as described in RFC 7093.
/// </summary>
Sha256 = 3,

/// <summary>
/// The SHA-384 hash over the SubjectPublicKeyInfo as described in RFC 7093.
/// </summary>
Sha384 = 4,

/// <summary>
/// The SHA-512 hash over the SubjectPublicKeyInfo as described in RFC 7093.
/// </summary>
Sha512 = 5,

/// <summary>
/// The SHA-256 hash over the subjectPublicKey truncated to the leftmost 160-bits as described in RFC 7093.
/// </summary>
ShortSha256 = 6,

/// <summary>
/// The SHA-384 hash over the subjectPublicKey truncated to the leftmost 160-bits as described in RFC 7093.
/// </summary>
ShortSha384 = 7,

/// <summary>
/// The SHA-512 hash over the subjectPublicKey truncated to the leftmost 160-bits as described in RFC 7093.
/// </summary>
ShortSha512 = 8,
}
}
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.Collections.Generic;
using Test.Cryptography;
using Xunit;

Expand All @@ -9,6 +10,10 @@ namespace System.Security.Cryptography.X509Certificates.Tests.ExtensionsTests
[SkipOnPlatform(TestPlatforms.Browser, "Browser doesn't support X.509 certificates")]
public static class SubjectKeyIdentifierTests
{
private const string EcPublicKey = "1.2.840.10045.2.1";
private static ReadOnlySpan<byte> NistP256r1 => [0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07];
private static ReadOnlySpan<byte> BrainpoolP256r1 => [0x06, 0x09, 0x2b, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x07];

[Fact]
public static void DefaultConstructor()
{
Expand Down Expand Up @@ -148,22 +153,215 @@ public static void DecodeFromBER()
Assert.Equal(skid, Convert.ToHexString(ext.SubjectKeyIdentifierBytes.Span));
}

[Theory]
[MemberData(nameof(Rfc7093Examples))]
public static void EncodeDecode_Rfc7093_Examples(
byte[] subjectPublicKeyInfo,
X509SubjectKeyIdentifierHashAlgorithm algorithm,
string expectedDer,
string expectedIdentifier)
{
EncodeDecodeSubjectPublicKeyInfo(
subjectPublicKeyInfo,
algorithm,
false,
Convert.FromHexString(expectedDer),
expectedIdentifier);
}

[Theory]
[MemberData(nameof(Rfc7093Vectors))]
public static void EncodeDecode_Rfc7093_TestVectors(
byte[] key,
byte[] parameters,
X509SubjectKeyIdentifierHashAlgorithm algorithm,
string expectedDer,
string expectedIdentifier)
{
EncodeDecodePublicKey(
new PublicKey(new Oid("1.2.3.4"), new AsnEncodedData(parameters), new AsnEncodedData(key)),
algorithm,
false,
Convert.FromHexString(expectedDer),
expectedIdentifier);
}

[Theory]
[InlineData(X509SubjectKeyIdentifierHashAlgorithm.ShortSha256)]
[InlineData(X509SubjectKeyIdentifierHashAlgorithm.ShortSha384)]
[InlineData(X509SubjectKeyIdentifierHashAlgorithm.ShortSha512)]
public static void Rfc7093_Truncated_SubjectPublicKeyOnly(X509SubjectKeyIdentifierHashAlgorithm algorithm)
{
ReadOnlySpan<byte> ecKey =
[
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
];

PublicKey nistP256Key = new PublicKey(new Oid(EcPublicKey), new AsnEncodedData(NistP256r1), new AsnEncodedData(ecKey));
PublicKey brainboolP256Key = new PublicKey(new Oid(EcPublicKey), new AsnEncodedData(BrainpoolP256r1), new AsnEncodedData(ecKey));

X509SubjectKeyIdentifierExtension nistP256Extension = new(nistP256Key, algorithm, critical: false);
X509SubjectKeyIdentifierExtension brainpoolP256Extension = new(brainboolP256Key, algorithm, critical: false);

// Although the PublicKeys have different parameters by their curve, the key material is the same, so the
// hash should not differ.
AssertExtensions.SequenceEqual(
nistP256Extension.SubjectKeyIdentifierBytes.Span,
brainpoolP256Extension.SubjectKeyIdentifierBytes.Span);
}

[Theory]
[InlineData(X509SubjectKeyIdentifierHashAlgorithm.Sha256)]
[InlineData(X509SubjectKeyIdentifierHashAlgorithm.Sha384)]
[InlineData(X509SubjectKeyIdentifierHashAlgorithm.Sha512)]
public static void Rfc7093_SubjectPublicKeyInfo(X509SubjectKeyIdentifierHashAlgorithm algorithm)
{
ReadOnlySpan<byte> ecKey =
[
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
];

PublicKey nistP256Key = new PublicKey(new Oid(EcPublicKey), new AsnEncodedData(NistP256r1), new AsnEncodedData(ecKey));
PublicKey brainboolP256Key = new PublicKey(new Oid(EcPublicKey), new AsnEncodedData(BrainpoolP256r1), new AsnEncodedData(ecKey));

X509SubjectKeyIdentifierExtension nistP256Extension = new(nistP256Key, algorithm, critical: false);
X509SubjectKeyIdentifierExtension brainpoolP256Extension = new(brainboolP256Key, algorithm, critical: false);

// Although the PublicKeys have the same key, their parameters are different, thus should produce different
// hashes.
AssertExtensions.SequenceNotEqual(
nistP256Extension.SubjectKeyIdentifierBytes.Span,
brainpoolP256Extension.SubjectKeyIdentifierBytes.Span);
}

public static IEnumerable<object[]> Rfc7093Examples()
{
byte[] example =
[
0x30, 0x59,
0x30, 0x13,
0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01,
0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07,
0x03, 0x42, 0x00,
0x04,
0x7F, 0x7F, 0x35, 0xA7, 0x97, 0x94, 0xC9, 0x50, 0x06, 0x0B, 0x80, 0x29, 0xFC, 0x8F, 0x36, 0x3A,
0x28, 0xF1, 0x11, 0x59, 0x69, 0x2D, 0x9D, 0x34, 0xE6, 0xAC, 0x94, 0x81, 0x90, 0x43, 0x47, 0x35,
0xF8, 0x33, 0xB1, 0xA6, 0x66, 0x52, 0xDC, 0x51, 0x43, 0x37, 0xAF, 0xF7, 0xF5, 0xC9, 0xC7, 0x5D,
0x67, 0x0C, 0x01, 0x9D, 0x95, 0xA5, 0xD6, 0x39, 0xB7, 0x27, 0x44, 0xC6, 0x4A, 0x91, 0x28, 0xBB,
];

// Method 1 example from RFC 7093
yield return new object[]
{
example,
X509SubjectKeyIdentifierHashAlgorithm.ShortSha256,
"0414BF37B3E5808FD46D54B28E846311BCCE1CAD2E1A",
"BF37B3E5808FD46D54B28E846311BCCE1CAD2E1A",
};

// Method 4 example from RFC 7093
yield return new object[]
{
example,
X509SubjectKeyIdentifierHashAlgorithm.Sha256,
"04206D20896AB8BD833B6B66554BD59B20225D8A75A296088148399D7BF763D57405",
"6D20896AB8BD833B6B66554BD59B20225D8A75A296088148399D7BF763D57405",
};
}

public static IEnumerable<object[]> Rfc7093Vectors()
{
byte[] key = [1, 2, 3, 4];
byte[] parameters = [4, 4, 5, 6, 7, 8];

yield return new object[]
{
key,
parameters,
X509SubjectKeyIdentifierHashAlgorithm.Sha256,
"04200B710654AEB48CE6A1FF80C0F3E83FAD8DB63B7E1004BE8F3EEC10E95CF3C620",
"0B710654AEB48CE6A1FF80C0F3E83FAD8DB63B7E1004BE8F3EEC10E95CF3C620",
};

yield return new object[]
{
key,
parameters,
X509SubjectKeyIdentifierHashAlgorithm.Sha384,
"0430150E43FE7ACE471CDB3910809145AD44B5B7E641A0364D608A1C106C9AD47963BAFE05E431B7782D791DE1B7E25F69DA",
"150E43FE7ACE471CDB3910809145AD44B5B7E641A0364D608A1C106C9AD47963BAFE05E431B7782D791DE1B7E25F69DA",
};

yield return new object[]
{
key,
parameters,
X509SubjectKeyIdentifierHashAlgorithm.Sha512,
"0440493CF4FC4B5CF15C17D5FCF4F85CFD1CFBCF29BEA538B8063733922A43693FEECAE70A11BEE932E23C32350C1F624DB16962A6AE6EF4B29BB3BFAD838048006F",
"493CF4FC4B5CF15C17D5FCF4F85CFD1CFBCF29BEA538B8063733922A43693FEECAE70A11BEE932E23C32350C1F624DB16962A6AE6EF4B29BB3BFAD838048006F",
};

yield return new object[]
{
key,
parameters,
X509SubjectKeyIdentifierHashAlgorithm.ShortSha256,
"04149F64A747E1B97F131FABB6B447296C9B6F0201E7",
"9F64A747E1B97F131FABB6B447296C9B6F0201E7",
};

yield return new object[]
{
key,
parameters,
X509SubjectKeyIdentifierHashAlgorithm.ShortSha384,
"04145A667D62430A8C253EBAE433333904DC6E1D41DC",
"5A667D62430A8C253EBAE433333904DC6E1D41DC",
};

yield return new object[]
{
key,
parameters,
X509SubjectKeyIdentifierHashAlgorithm.ShortSha512,
"0414A7C976DB1723ADB41274178DC82E9B777941AB20",
"A7C976DB1723ADB41274178DC82E9B777941AB20",
};
}

private static void EncodeDecode(
byte[] certBytes,
X509SubjectKeyIdentifierHashAlgorithm algorithm,
bool critical,
byte[] expectedDer,
string expectedIdentifier)
{
PublicKey pk;

using (var cert = new X509Certificate2(certBytes))
{
pk = cert.PublicKey;
EncodeDecodePublicKey(cert.PublicKey, algorithm, critical, expectedDer, expectedIdentifier);
}
}

X509SubjectKeyIdentifierExtension ext =
new X509SubjectKeyIdentifierExtension(pk, algorithm, critical);
private static void EncodeDecodeSubjectPublicKeyInfo(
byte[] spkiBytes,
X509SubjectKeyIdentifierHashAlgorithm algorithm,
bool critical,
byte[] expectedDer,
string expectedIdentifier)
{
PublicKey publicKey = PublicKey.CreateFromSubjectPublicKeyInfo(spkiBytes, out _);
EncodeDecodePublicKey(publicKey, algorithm, critical, expectedDer, expectedIdentifier);
}
private static void EncodeDecodePublicKey(
PublicKey publicKey,
X509SubjectKeyIdentifierHashAlgorithm algorithm,
bool critical,
byte[] expectedDer,
string expectedIdentifier)
{
X509SubjectKeyIdentifierExtension ext = new X509SubjectKeyIdentifierExtension(publicKey, algorithm, critical);

byte[] rawData = ext.RawData;
Assert.Equal(expectedDer, rawData);
Expand Down

0 comments on commit 1560501

Please sign in to comment.