Add X509AuthorityKeyIdentifierExtension
authorJeremy Barton <jbarton@microsoft.com>
Fri, 22 Jul 2022 21:45:53 +0000 (14:45 -0700)
committerGitHub <noreply@github.com>
Fri, 22 Jul 2022 21:45:53 +0000 (14:45 -0700)
Additionally fixes the recently added SubjectAltNames extension to use a shared OID instance.

15 files changed:
src/libraries/Common/src/System/Security/Cryptography/Oids.Shared.cs
src/libraries/Common/src/System/Security/Cryptography/Oids.cs
src/libraries/Common/tests/System/Security/Cryptography/X509Certificates/CertificateAuthority.cs
src/libraries/System.Security.Cryptography.X509Certificates/tests/ExtensionsTests/AuthorityKeyIdentifierTests.cs [new file with mode: 0644]
src/libraries/System.Security.Cryptography.X509Certificates/tests/ExtensionsTests/ComprehensiveTests.cs
src/libraries/System.Security.Cryptography.X509Certificates/tests/System.Security.Cryptography.X509Certificates.Tests.csproj
src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs
src/libraries/System.Security.Cryptography/src/Resources/Strings.resx
src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj
src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/CryptoConfig.cs
src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/OidLookup.NoFallback.cs
src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/X509AuthorityKeyIdentifierExtension.cs [new file with mode: 0644]
src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/X509Certificate2.cs
src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/X509SubjectAlternativeNameExtension.cs
src/libraries/System.Security.Cryptography/tests/CryptoConfigTests.cs

index 6181c29..72b6438 100644 (file)
@@ -27,7 +27,9 @@ namespace System.Security.Cryptography
         private static volatile Oid? s_basicConstraints2Oid;
         private static volatile Oid? s_enhancedKeyUsageOid;
         private static volatile Oid? s_keyUsageOid;
+        private static volatile Oid? s_subjectAltNameOid;
         private static volatile Oid? s_subjectKeyIdentifierOid;
+        private static volatile Oid? s_authorityKeyIdentifierOid;
         private static volatile Oid? s_authorityInformationAccessOid;
         private static volatile Oid? s_commonNameOid;
         private static volatile Oid? s_countryOrRegionOid;
@@ -58,7 +60,9 @@ namespace System.Security.Cryptography
         internal static Oid BasicConstraints2Oid => s_basicConstraints2Oid ??= InitializeOid(BasicConstraints2);
         internal static Oid EnhancedKeyUsageOid => s_enhancedKeyUsageOid ??= InitializeOid(EnhancedKeyUsage);
         internal static Oid KeyUsageOid => s_keyUsageOid ??= InitializeOid(KeyUsage);
+        internal static Oid AuthorityKeyIdentifierOid => s_authorityKeyIdentifierOid ??= InitializeOid(AuthorityKeyIdentifier);
         internal static Oid SubjectKeyIdentifierOid => s_subjectKeyIdentifierOid ??= InitializeOid(SubjectKeyIdentifier);
+        internal static Oid SubjectAltNameOid => s_subjectAltNameOid ??= InitializeOid(SubjectAltName);
         internal static Oid AuthorityInformationAccessOid => s_authorityInformationAccessOid ??= InitializeOid(AuthorityInformationAccess);
 
         internal static Oid CommonNameOid => s_commonNameOid ??= InitializeOid(CommonName);
index 8b530cd..eff9b17 100644 (file)
@@ -110,6 +110,7 @@ namespace System.Security.Cryptography
         internal const string CertPolicies = "2.5.29.32";
         internal const string AnyCertPolicy = "2.5.29.32.0";
         internal const string CertPolicyMappings = "2.5.29.33";
+        internal const string AuthorityKeyIdentifier = "2.5.29.35";
         internal const string CertPolicyConstraints = "2.5.29.36";
         internal const string EnhancedKeyUsage = "2.5.29.37";
         internal const string InhibitAnyPolicyExtension = "2.5.29.54";
index 7b8abe4..0140132 100644 (file)
@@ -42,7 +42,6 @@ namespace System.Security.Cryptography.X509Certificates.Tests.Common
         private static readonly Asn1Tag s_context0 = new Asn1Tag(TagClass.ContextSpecific, 0);
         private static readonly Asn1Tag s_context1 = new Asn1Tag(TagClass.ContextSpecific, 1);
         private static readonly Asn1Tag s_context2 = new Asn1Tag(TagClass.ContextSpecific, 2);
-        private static readonly Asn1Tag s_context4 = new Asn1Tag(TagClass.ContextSpecific, 4);
 
         private static readonly X500DistinguishedName s_nonParticipatingName =
             new X500DistinguishedName("CN=The Ghost in the Machine");
@@ -738,47 +737,15 @@ SingleResponse ::= SEQUENCE {
             X509SubjectKeyIdentifierExtension skid =
                 _cert.Extensions.OfType<X509SubjectKeyIdentifierExtension>().SingleOrDefault();
 
-            AsnWriter writer = new AsnWriter(AsnEncodingRules.DER);
-
-            // AuthorityKeyIdentifier
-            using (writer.PushSequence())
+            if (skid is null)
             {
-                if (skid == null)
-                {
-                    // authorityCertIssuer [1] GeneralNames (SEQUENCE OF)
-                    using (writer.PushSequence(s_context1))
-                    {
-                        // directoryName [4] Name
-                        byte[] dn = _cert.SubjectName.RawData;
-
-                        if (s_context4.Encode(dn) != 1)
-                        {
-                            throw new InvalidOperationException();
-                        }
-
-                        writer.WriteEncodedValue(dn);
-                    }
-
-                    // authorityCertSerialNumber [2] CertificateSerialNumber (INTEGER)
-                    writer.WriteInteger(_cert.SerialNumberBytes.Span, s_context2);
-                }
-                else
-                {
-                    // keyIdentifier [0] KeyIdentifier (OCTET STRING)
-                    AsnReader reader = new AsnReader(skid.RawData, AsnEncodingRules.BER);
-                    ReadOnlyMemory<byte> contents;
-
-                    if (!reader.TryReadPrimitiveOctetString(out contents))
-                    {
-                        throw new InvalidOperationException();
-                    }
-
-                    reader.ThrowIfNotEmpty();
-                    writer.WriteOctetString(contents.Span, s_context0);
-                }
+                return X509AuthorityKeyIdentifierExtension.CreateFromCertificate(
+                    _cert,
+                    includeKeyIdentifier: false,
+                    includeIssuerAndSerial: true);
             }
 
-            return new X509Extension("2.5.29.35", writer.Encode(), false);
+            return X509AuthorityKeyIdentifierExtension.CreateFromSubjectKeyIdentifier(skid);
         }
 
         private enum OcspResponseStatus
diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/tests/ExtensionsTests/AuthorityKeyIdentifierTests.cs b/src/libraries/System.Security.Cryptography.X509Certificates/tests/ExtensionsTests/AuthorityKeyIdentifierTests.cs
new file mode 100644 (file)
index 0000000..18a4ced
--- /dev/null
@@ -0,0 +1,779 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Test.Cryptography;
+using Xunit;
+
+namespace System.Security.Cryptography.X509Certificates.Tests.ExtensionsTests
+{
+    public static class AuthorityKeyIdentifierTests
+    {
+        [Fact]
+        public static void DefaultConstructor()
+        {
+            X509AuthorityKeyIdentifierExtension e = new X509AuthorityKeyIdentifierExtension();
+            string oidValue = e.Oid.Value;
+            Assert.Equal("2.5.29.35", oidValue);
+
+            Assert.Empty(e.RawData);
+            Assert.False(e.KeyIdentifier.HasValue, "e.KeyIdentifier.HasValue");
+            Assert.Null(e.NamedIssuer);
+            Assert.False(e.RawIssuer.HasValue, "e.RawIssuer.HasValue");
+            Assert.False(e.SerialNumber.HasValue, "e.SerialNumber.HasValue");
+        }
+
+        [Fact]
+        public static void RoundtripFull()
+        {
+            byte[] encoded = (
+                "303C80140235857ED35BD13609F22DE8A71F93DFEBD3F495A11AA41830163114" +
+                "301206035504030C0B49737375696E6743657274820852E6DEFA1D32A969").HexToByteArray();
+
+            X509AuthorityKeyIdentifierExtension akid = new X509AuthorityKeyIdentifierExtension(encoded, true);
+            Assert.True(akid.Critical, "akid.Critical");
+            Assert.True(akid.KeyIdentifier.HasValue, "akid.KeyIdentifier.HasValue");
+
+            Assert.Equal(
+                "0235857ED35BD13609F22DE8A71F93DFEBD3F495",
+                akid.KeyIdentifier.Value.ByteArrayToHex());
+
+            Assert.True(akid.RawIssuer.HasValue, "akid.RawIssuer.HasValue");
+            Assert.NotNull(akid.NamedIssuer);
+
+            Assert.Equal(
+                "A11AA41830163114301206035504030C0B49737375696E6743657274",
+                akid.RawIssuer.Value.ByteArrayToHex());
+            Assert.Equal(
+                "30163114301206035504030C0B49737375696E6743657274",
+                akid.NamedIssuer.RawData.ByteArrayToHex());
+
+            Assert.True(akid.SerialNumber.HasValue, "akid.SerialNumber.HasValue");
+            Assert.Equal("52E6DEFA1D32A969", akid.SerialNumber.Value.ByteArrayToHex());
+
+            X509AuthorityKeyIdentifierExtension akid2 = X509AuthorityKeyIdentifierExtension.Create(
+                akid.KeyIdentifier.Value.Span,
+                akid.NamedIssuer,
+                akid.SerialNumber.Value.Span);
+
+            Assert.False(akid2.Critical, "akid2.Critical");
+            AssertExtensions.SequenceEqual(akid.RawData, akid2.RawData);
+        }
+
+        [Fact]
+        public static void CreateEmptyFromCertificate()
+        {
+            X509AuthorityKeyIdentifierExtension akid;
+
+            using (X509Certificate2 cert = new X509Certificate2(TestData.MicrosoftDotComIssuerBytes))
+            {
+                akid = X509AuthorityKeyIdentifierExtension.CreateFromCertificate(
+                    cert,
+                    includeKeyIdentifier: false,
+                    includeIssuerAndSerial: false);
+            }
+
+            Assert.False(akid.Critical, "akid.Critical");
+            Assert.Equal("3000", akid.RawData.ByteArrayToHex());
+        }
+
+        [Fact]
+        public static void CreateKeyIdOnlyFromCertificate()
+        {
+            X509AuthorityKeyIdentifierExtension akid;
+
+            using (X509Certificate2 cert = new X509Certificate2(TestData.MicrosoftDotComIssuerBytes))
+            {
+                akid = X509AuthorityKeyIdentifierExtension.CreateFromCertificate(
+                    cert,
+                    includeKeyIdentifier: true,
+                    includeIssuerAndSerial: false);
+            }
+
+            Assert.False(akid.Critical, "akid.Critical");
+            Assert.Equal("30168014B5760C3011CEC792424D4CC75C2CC8A90CE80B64", akid.RawData.ByteArrayToHex());
+            Assert.False(akid.RawIssuer.HasValue, "akid.RawIssuer.HasValue");
+            Assert.Null(akid.NamedIssuer);
+            Assert.False(akid.SerialNumber.HasValue, "akid.SerialNumber.HasValue");
+            Assert.True(akid.KeyIdentifier.HasValue, "akid.KeyIdentifier.HasValue");
+
+            Assert.Equal(
+                "B5760C3011CEC792424D4CC75C2CC8A90CE80B64",
+                akid.KeyIdentifier.GetValueOrDefault().ByteArrayToHex());
+        }
+
+        [Fact]
+        public static void CreateIssuerAndSerialFromCertificate()
+        {
+            X509AuthorityKeyIdentifierExtension akid;
+            X500DistinguishedName issuerName;
+            ReadOnlyMemory<byte> serial;
+
+            using (X509Certificate2 cert = new X509Certificate2(TestData.MicrosoftDotComIssuerBytes))
+            {
+                issuerName = cert.IssuerName;
+                serial = cert.SerialNumberBytes;
+
+                akid = X509AuthorityKeyIdentifierExtension.CreateFromCertificate(
+                    cert,
+                    includeKeyIdentifier: false,
+                    includeIssuerAndSerial: true);
+            }
+
+            Assert.False(akid.Critical, "akid.Critical");
+            Assert.NotNull(akid.NamedIssuer);
+            AssertExtensions.SequenceEqual(issuerName.RawData, akid.NamedIssuer.RawData);
+            Assert.True(akid.SerialNumber.HasValue, "akid.SerialNumber.HasValue");
+            AssertExtensions.SequenceEqual(serial.Span, akid.SerialNumber.GetValueOrDefault().Span);
+            Assert.False(akid.KeyIdentifier.HasValue, "akid.KeyIdentifier.HasValue");
+
+            const string ExpectedHex =
+                "3072A15EA45C305A310B300906035504061302494531123010060355040A1309" +
+                "42616C74696D6F726531133011060355040B130A437962657254727573743122" +
+                "30200603550403131942616C74696D6F7265204379626572547275737420526F" +
+                "6F7482100F14965F202069994FD5C7AC788941E2";
+
+            Assert.Equal(ExpectedHex, akid.RawData.ByteArrayToHex());
+        }
+
+        [Fact]
+        public static void CreateFullFromCertificate()
+        {
+            X509AuthorityKeyIdentifierExtension akid;
+            X500DistinguishedName issuerName;
+            ReadOnlyMemory<byte> serial;
+
+            using (X509Certificate2 cert = new X509Certificate2(TestData.MicrosoftDotComIssuerBytes))
+            {
+                issuerName = cert.IssuerName;
+                serial = cert.SerialNumberBytes;
+
+                akid = X509AuthorityKeyIdentifierExtension.CreateFromCertificate(
+                    cert,
+                    includeKeyIdentifier: true,
+                    includeIssuerAndSerial: true);
+            }
+
+            Assert.False(akid.Critical, "akid.Critical");
+            Assert.NotNull(akid.NamedIssuer);
+            AssertExtensions.SequenceEqual(issuerName.RawData, akid.NamedIssuer.RawData);
+            Assert.True(akid.SerialNumber.HasValue, "akid.SerialNumber.HasValue");
+            AssertExtensions.SequenceEqual(serial.Span, akid.SerialNumber.GetValueOrDefault().Span);
+            Assert.True(akid.KeyIdentifier.HasValue, "akid.KeyIdentifier.HasValue");
+
+            Assert.Equal(
+                "B5760C3011CEC792424D4CC75C2CC8A90CE80B64",
+                akid.KeyIdentifier.GetValueOrDefault().ByteArrayToHex());
+
+            const string ExpectedHex =
+                "3081888014B5760C3011CEC792424D4CC75C2CC8A90CE80B64A15EA45C305A31" +
+                "0B300906035504061302494531123010060355040A130942616C74696D6F7265" +
+                "31133011060355040B130A437962657254727573743122302006035504031319" +
+                "42616C74696D6F7265204379626572547275737420526F6F7482100F14965F20" +
+                "2069994FD5C7AC788941E2";
+
+            Assert.Equal(ExpectedHex, akid.RawData.ByteArrayToHex());
+        }
+
+        [Theory]
+        [InlineData(false)]
+        [InlineData(true)]
+        public static void CreateFullWithNegativeSerialNumber(bool fromArray)
+        {
+            X509AuthorityKeyIdentifierExtension akid;
+            ReadOnlySpan<byte> skid = new byte[] { 0x01, 0x02, 0x04 };
+            X500DistinguishedName issuerName = new X500DistinguishedName("CN=Negative");
+            ReadOnlySpan<byte> serial = new byte[] { 0x80, 0x02 };
+
+            if (fromArray)
+            {
+                akid = X509AuthorityKeyIdentifierExtension.Create(
+                    skid.ToArray(),
+                    issuerName,
+                    serial.ToArray());
+            }
+            else
+            {
+                akid = X509AuthorityKeyIdentifierExtension.Create(skid, issuerName, serial);
+            }
+
+            Assert.False(akid.Critical, "akid.Critical");
+            Assert.NotNull(akid.NamedIssuer);
+            AssertExtensions.SequenceEqual(issuerName.RawData, akid.NamedIssuer.RawData);
+            Assert.True(akid.SerialNumber.HasValue, "akid.SerialNumber.HasValue");
+            AssertExtensions.SequenceEqual(serial, akid.SerialNumber.GetValueOrDefault().Span);
+            Assert.True(akid.KeyIdentifier.HasValue, "akid.KeyIdentifier.HasValue");
+            AssertExtensions.SequenceEqual(skid, akid.KeyIdentifier.GetValueOrDefault().Span);
+
+            const string ExpectedHex =
+                "30228003010204A117A41530133111300F060355040313084E65676174697665" +
+                "82028002";
+
+            Assert.Equal(ExpectedHex, akid.RawData.ByteArrayToHex());
+        }
+
+        [Fact]
+        public static void CreateFromKeyIdentifierPreservesLeadingZeros()
+        {
+            ReadOnlySpan<byte> encoded = new byte[]
+            {
+                // SEQUENCE( [0](
+                0x30, 0x16, 0x80, 0x14,
+                // keyId
+                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                0x00, 0x00, 0x01, 0x00,
+            };
+
+            ReadOnlySpan<byte> keyId = encoded.Slice(4);
+
+            X509AuthorityKeyIdentifierExtension akid;
+
+            // From ROSpan
+            akid = X509AuthorityKeyIdentifierExtension.CreateFromSubjectKeyIdentifier(keyId);
+            AssertExtensions.SequenceEqual(encoded, akid.RawData);
+            AssertExtensions.SequenceEqual(keyId, akid.KeyIdentifier.GetValueOrDefault().Span);
+
+            // From array
+            akid = X509AuthorityKeyIdentifierExtension.CreateFromSubjectKeyIdentifier(keyId.ToArray());
+            AssertExtensions.SequenceEqual(encoded, akid.RawData);
+            AssertExtensions.SequenceEqual(keyId, akid.KeyIdentifier.GetValueOrDefault().Span);
+
+            // From SKI
+            var skid = new X509SubjectKeyIdentifierExtension(keyId, critical: false);
+            akid = X509AuthorityKeyIdentifierExtension.CreateFromSubjectKeyIdentifier(skid);
+            AssertExtensions.SequenceEqual(encoded, akid.RawData);
+            AssertExtensions.SequenceEqual(keyId, akid.KeyIdentifier.GetValueOrDefault().Span);
+        }
+
+        [Fact]
+        public static void CreateFullPreservesKeyIdLeadingZeros()
+        {
+            ReadOnlySpan<byte> encoded = new byte[]
+            {
+                0x30, 0x26, 0x80, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x80, 0xA1, 0x14, 0xA4,
+                0x12, 0x30, 0x10, 0x31, 0x0E, 0x30, 0x0C, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x05, 0x48, 0x65,
+                0x6C, 0x6C, 0x6F, 0x82, 0x03, 0x00, 0xEE, 0x7B,
+            };
+
+            ReadOnlySpan<byte> keyId = encoded.Slice(4, 9);
+            X500DistinguishedName issuerName = new X500DistinguishedName("CN=Hello");
+            ReadOnlySpan<byte> serial = new byte[] { 0x00, 0xEE, 0x7B };
+
+            X509AuthorityKeyIdentifierExtension akid;
+
+            // From ROSpan
+            akid = X509AuthorityKeyIdentifierExtension.Create(keyId, issuerName, serial);
+            AssertExtensions.SequenceEqual(encoded, akid.RawData);
+            AssertExtensions.SequenceEqual(keyId, akid.KeyIdentifier.GetValueOrDefault().Span);
+            AssertExtensions.SequenceEqual(issuerName.RawData, akid.NamedIssuer.RawData);
+            AssertExtensions.SequenceEqual(serial, akid.SerialNumber.GetValueOrDefault().Span);
+
+            // From Arrays
+            akid = X509AuthorityKeyIdentifierExtension.Create(keyId.ToArray(), issuerName, serial.ToArray());
+            AssertExtensions.SequenceEqual(encoded, akid.RawData);
+            AssertExtensions.SequenceEqual(keyId, akid.KeyIdentifier.GetValueOrDefault().Span);
+            AssertExtensions.SequenceEqual(issuerName.RawData, akid.NamedIssuer.RawData);
+            AssertExtensions.SequenceEqual(serial, akid.SerialNumber.GetValueOrDefault().Span);
+        }
+
+        [Fact]
+        public static void DecodeWithTwoX500Names()
+        {
+            // This extension has two separate X500 names, one with CN=Hello, the other with CN=Goodbye
+            ReadOnlySpan<byte> encoded = new byte[]
+            {
+                0x30, 0x3C, 0x80, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x80, 0xA1, 0x2A, 0xA4,
+                0x12, 0x30, 0x10, 0x31, 0x0E, 0x30, 0x0C, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x05, 0x48, 0x65,
+                0x6C, 0x6C, 0x6F, 0xA4, 0x14, 0x30, 0x12, 0x31, 0x10, 0x30, 0x0E, 0x06, 0x03, 0x55, 0x04, 0x03,
+                0x13, 0x07, 0x47, 0x6F, 0x6F, 0x64, 0x62, 0x79, 0x65, 0x82, 0x03, 0x00, 0xEE, 0x7B,
+            };
+
+            X509AuthorityKeyIdentifierExtension akid = new X509AuthorityKeyIdentifierExtension(encoded);
+            Assert.False(akid.Critical, "akid.Critical");
+            Assert.Equal("000000000000000880", akid.KeyIdentifier.GetValueOrDefault().Span.ByteArrayToHex());
+            Assert.Equal("00EE7B", akid.SerialNumber.GetValueOrDefault().Span.ByteArrayToHex());
+            Assert.Null(akid.NamedIssuer);
+            AssertExtensions.SequenceEqual(encoded.Slice(13, 44), akid.RawIssuer.GetValueOrDefault().Span);
+        }
+
+        [Fact]
+        public static void DecodeWithThreeX500Names()
+        {
+            // This extension has three separate X500 names: CN=Hello; CN=Middle; CN=Goodbye
+            ReadOnlySpan<byte> encoded = new byte[]
+            {
+                0x30, 0x6F, 0x80, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x80, 0xA1, 0x5D, 0xA4,
+                0x12, 0x30, 0x10, 0x31, 0x0E, 0x30, 0x0C, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x05, 0x48, 0x65,
+                0x6C, 0x6C, 0x6F, 0xA4, 0x13, 0x30, 0x11, 0x31, 0x0F, 0x30, 0x0D, 0x06, 0x03, 0x55, 0x04, 0x03,
+                0x13, 0x06, 0x4D, 0x69, 0x64, 0x64, 0x6C, 0x65, 0xA4, 0x14, 0x30, 0x12, 0x31, 0x10, 0x30, 0x0E,
+                0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x07, 0x47, 0x6F, 0x6F, 0x64, 0x62, 0x79, 0x65, 0x86, 0x1C,
+                0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x63, 0x65, 0x72, 0x74, 0x2E, 0x65, 0x78, 0x61, 0x6D,
+                0x70, 0x6C, 0x65, 0x2F, 0x63, 0x65, 0x72, 0x74, 0x2E, 0x63, 0x72, 0x74, 0x82, 0x03, 0x00, 0xEE,
+                0x7B,
+            };
+
+            X509AuthorityKeyIdentifierExtension akid = new X509AuthorityKeyIdentifierExtension(encoded);
+            Assert.False(akid.Critical, "akid.Critical");
+            Assert.Equal("000000000000000880", akid.KeyIdentifier.GetValueOrDefault().Span.ByteArrayToHex());
+            Assert.Equal("00EE7B", akid.SerialNumber.GetValueOrDefault().Span.ByteArrayToHex());
+            Assert.Null(akid.NamedIssuer);
+            AssertExtensions.SequenceEqual(encoded.Slice(13, 95), akid.RawIssuer.GetValueOrDefault().Span);
+        }
+
+        [Fact]
+        public static void DecodeWithComplexIssuer()
+        {
+            // This extension value has
+            // * A key identifier
+            // * A complex authorityCertIssuer
+            //   * One X500 name (CN=Hello)
+            //   * One URI (http://cert.example/cert.crt)
+            // * A serial number
+            ReadOnlySpan<byte> encoded = new byte[]
+            {
+                0x30, 0x44, 0x80, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x80, 0xA1, 0x32, 0xA4,
+                0x12, 0x30, 0x10, 0x31, 0x0E, 0x30, 0x0C, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x05, 0x48, 0x65,
+                0x6C, 0x6C, 0x6F, 0x86, 0x1C, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x63, 0x65, 0x72, 0x74,
+                0x2E, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, 0x2F, 0x63, 0x65, 0x72, 0x74, 0x2E, 0x63, 0x72,
+                0x74, 0x82, 0x03, 0x00, 0xEE, 0x7B,
+            };
+
+            X509AuthorityKeyIdentifierExtension akid = new X509AuthorityKeyIdentifierExtension(encoded, true);
+            Assert.True(akid.Critical, "akid.Critical");
+            Assert.Equal("000000000000000880", akid.KeyIdentifier.GetValueOrDefault().Span.ByteArrayToHex());
+            Assert.Equal("00EE7B", akid.SerialNumber.GetValueOrDefault().Span.ByteArrayToHex());
+            Assert.Equal("CN=Hello", akid.NamedIssuer.Name);
+            AssertExtensions.SequenceEqual(encoded.Slice(13, 52), akid.RawIssuer.GetValueOrDefault().Span);
+        }
+
+        [Fact]
+        public static void DecodeWithNoX500Names()
+        {
+            // This extension has no X500 names, but has a fetch URI (that no one supports)
+            ReadOnlySpan<byte> encoded = new byte[]
+            {
+                0x30, 0x30, 0x80, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x80, 0xA1, 0x1E, 0x86,
+                0x1C, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x63, 0x65, 0x72, 0x74, 0x2E, 0x65, 0x78, 0x61,
+                0x6D, 0x70, 0x6C, 0x65, 0x2F, 0x63, 0x65, 0x72, 0x74, 0x2E, 0x63, 0x72, 0x74, 0x82, 0x03, 0x00,
+                0xEE, 0x7B,
+            };
+
+            X509AuthorityKeyIdentifierExtension akid = new X509AuthorityKeyIdentifierExtension(encoded);
+            Assert.False(akid.Critical, "akid.Critical");
+            Assert.Equal("000000000000000880", akid.KeyIdentifier.GetValueOrDefault().Span.ByteArrayToHex());
+            Assert.Equal("00EE7B", akid.SerialNumber.GetValueOrDefault().Span.ByteArrayToHex());
+            Assert.Null(akid.NamedIssuer);
+            AssertExtensions.SequenceEqual(encoded.Slice(13, 32), akid.RawIssuer.GetValueOrDefault().Span);
+        }
+
+        [Fact]
+        public static void DecodeWithSerialNoIssuer()
+        {
+            // This extension has a keyID and serialNumber, but no issuerName.
+            // It's implied as invalid by the RFC and X.509, but the structure allows it.
+            ReadOnlySpan<byte> encoded = new byte[]
+            {
+                0x30, 0x10, 0x80, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x80, 0x82, 0x03, 0x00,
+                0xEE, 0x7B,
+            };
+
+            X509AuthorityKeyIdentifierExtension akid = new X509AuthorityKeyIdentifierExtension(encoded);
+            Assert.False(akid.Critical, "akid.Critical");
+            Assert.Equal("000000000000000880", akid.KeyIdentifier.GetValueOrDefault().Span.ByteArrayToHex());
+            Assert.Equal("00EE7B", akid.SerialNumber.GetValueOrDefault().Span.ByteArrayToHex());
+            Assert.Null(akid.NamedIssuer);
+            Assert.False(akid.RawIssuer.HasValue, "akid.RawIssuer.HasValue");
+        }
+
+        [Fact]
+        public static void DecodeWithIssuerNoSerial()
+        {
+            // This extension has a keyID and an issuerName, but no serialNumber.
+            // It's implied as invalid by the RFC and X.509, but the structure allows it.
+            ReadOnlySpan<byte> encoded = new byte[]
+            {
+                0x30, 0x21, 0x80, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x80, 0xA1, 0x14, 0xA4,
+                0x12, 0x30, 0x10, 0x31, 0x0E, 0x30, 0x0C, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x05, 0x48, 0x65,
+                0x6C, 0x6C, 0x6F,
+            };
+
+            X509AuthorityKeyIdentifierExtension akid = new X509AuthorityKeyIdentifierExtension(encoded);
+            Assert.False(akid.Critical, "akid.Critical");
+            Assert.Equal("000000000000000880", akid.KeyIdentifier.GetValueOrDefault().Span.ByteArrayToHex());
+            Assert.False(akid.SerialNumber.HasValue, "akid.SerialNumber.HasValue");
+            Assert.Equal("CN=Hello", akid.NamedIssuer.Name);
+            AssertExtensions.SequenceEqual(encoded.Slice(13, 22), akid.RawIssuer.GetValueOrDefault().Span);
+        }
+
+        [Fact]
+        public static void DecodeInvalid()
+        {
+            byte[] invalidEncoding = { 0x05, 0x00 };
+
+            Assert.Throws<CryptographicException>(
+                () => new X509AuthorityKeyIdentifierExtension(invalidEncoding));
+
+            Assert.Throws<CryptographicException>(
+                () => new X509AuthorityKeyIdentifierExtension(new ReadOnlySpan<byte>(invalidEncoding)));
+
+            X509Extension untypedExt = new X509Extension("0.1", invalidEncoding, critical: true);
+            X509AuthorityKeyIdentifierExtension ext = new X509AuthorityKeyIdentifierExtension();
+            ext.CopyFrom(untypedExt);
+
+            Assert.True(ext.Critical, "ext.Critical");
+            Assert.Equal("0.1", ext.Oid.Value);
+            Assert.Throws<CryptographicException>(() => ext.KeyIdentifier);
+            Assert.Throws<CryptographicException>(() => ext.SerialNumber);
+            Assert.Throws<CryptographicException>(() => ext.NamedIssuer);
+            Assert.Throws<CryptographicException>(() => ext.RawIssuer);
+        }
+
+        [Fact]
+        public static void CreateFromCertificateWithNoSki()
+        {
+            using (ECDsa key = ECDsa.Create())
+            {
+                CertificateRequest req = new CertificateRequest("CN=Hi", key, HashAlgorithmName.SHA256);
+                DateTimeOffset now = DateTimeOffset.UnixEpoch;
+
+                using (X509Certificate2 cert = req.CreateSelfSigned(now.AddMinutes(-5), now.AddMinutes(5)))
+                {
+                    Assert.Throws<CryptographicException>(
+                        () => X509AuthorityKeyIdentifierExtension.CreateFromCertificate(
+                            cert,
+                            includeKeyIdentifier: true,
+                            includeIssuerAndSerial: false));
+
+                    Assert.Throws<CryptographicException>(
+                        () => X509AuthorityKeyIdentifierExtension.CreateFromCertificate(
+                            cert,
+                            includeKeyIdentifier: true,
+                            includeIssuerAndSerial: true));
+
+                    // Assert.NoThrow
+                    X509AuthorityKeyIdentifierExtension.CreateFromCertificate(
+                        cert,
+                        includeKeyIdentifier: false,
+                        includeIssuerAndSerial: true);
+
+                    X509AuthorityKeyIdentifierExtension.CreateFromCertificate(
+                        cert,
+                        includeKeyIdentifier: false,
+                        includeIssuerAndSerial: false);
+                }
+            }
+        }
+
+        [Fact]
+        public static void CreateFromLargeKeyIdentifier()
+        {
+            byte[] keyId = new byte[128];
+
+            X509AuthorityKeyIdentifierExtension akid =
+                X509AuthorityKeyIdentifierExtension.CreateFromSubjectKeyIdentifier(keyId);
+
+            AssertExtensions.SequenceEqual(keyId, akid.KeyIdentifier.GetValueOrDefault().Span);
+
+            byte[] rawData = akid.RawData;
+
+            AssertExtensions.SequenceEqual(keyId, rawData.AsSpan(6));
+            Assert.Equal("308183808180", rawData.AsSpan(0, 6).ByteArrayToHex());
+        }
+
+        [Theory]
+        [InlineData(false)]
+        [InlineData(true)]
+        public static void CreateIssuerAndSerial(bool fromArray)
+        {
+            X509AuthorityKeyIdentifierExtension akid;
+            X500DistinguishedName issuerName;
+            ReadOnlyMemory<byte> serial;
+
+            using (X509Certificate2 cert = new X509Certificate2(TestData.MicrosoftDotComIssuerBytes))
+            {
+                issuerName = cert.IssuerName;
+                serial = cert.SerialNumberBytes;
+            }
+
+            if (fromArray)
+            {
+                akid = X509AuthorityKeyIdentifierExtension.CreateFromIssuerNameAndSerialNumber(
+                    issuerName,
+                    serial.Span.ToArray());
+            }
+            else
+            {
+                akid = X509AuthorityKeyIdentifierExtension.CreateFromIssuerNameAndSerialNumber(
+                    issuerName,
+                    serial.Span);
+            }
+
+            Assert.False(akid.Critical, "akid.Critical");
+            Assert.NotNull(akid.NamedIssuer);
+            AssertExtensions.SequenceEqual(issuerName.RawData, akid.NamedIssuer.RawData);
+            Assert.True(akid.SerialNumber.HasValue, "akid.SerialNumber.HasValue");
+            AssertExtensions.SequenceEqual(serial.Span, akid.SerialNumber.GetValueOrDefault().Span);
+            Assert.False(akid.KeyIdentifier.HasValue, "akid.KeyIdentifier.HasValue");
+
+            const string ExpectedHex =
+                "3072A15EA45C305A310B300906035504061302494531123010060355040A1309" +
+                "42616C74696D6F726531133011060355040B130A437962657254727573743122" +
+                "30200603550403131942616C74696D6F7265204379626572547275737420526F" +
+                "6F7482100F14965F202069994FD5C7AC788941E2";
+
+            Assert.Equal(ExpectedHex, akid.RawData.ByteArrayToHex());
+        }
+
+        [Theory]
+        [InlineData(false)]
+        [InlineData(true)]
+        public static void CreateIssuerAndNegativeSerial(bool fromArray)
+        {
+            X509AuthorityKeyIdentifierExtension akid;
+            X500DistinguishedName issuerName = new X500DistinguishedName("CN=Negative");
+            ReadOnlySpan<byte> serial = new byte[] { 0x80, 0x02 };
+
+            if (fromArray)
+            {
+                akid = X509AuthorityKeyIdentifierExtension.CreateFromIssuerNameAndSerialNumber(
+                    issuerName,
+                    serial.ToArray());
+            }
+            else
+            {
+                akid = X509AuthorityKeyIdentifierExtension.CreateFromIssuerNameAndSerialNumber(
+                    issuerName,
+                    serial);
+            }
+
+            Assert.False(akid.Critical, "akid.Critical");
+            Assert.NotNull(akid.NamedIssuer);
+            AssertExtensions.SequenceEqual(issuerName.RawData, akid.NamedIssuer.RawData);
+            Assert.True(akid.SerialNumber.HasValue, "akid.SerialNumber.HasValue");
+            AssertExtensions.SequenceEqual(serial, akid.SerialNumber.GetValueOrDefault().Span);
+            Assert.False(akid.KeyIdentifier.HasValue, "akid.KeyIdentifier.HasValue");
+
+            const string ExpectedHex = "301DA117A41530133111300F060355040313084E6567617469766582028002";
+
+            Assert.Equal(ExpectedHex, akid.RawData.ByteArrayToHex());
+        }
+
+        [Fact]
+        public static void CreateFromSubjectKeyIdentifier_Validation()
+        {
+            Assert.Throws<ArgumentNullException>(
+                "subjectKeyIdentifier",
+                () => X509AuthorityKeyIdentifierExtension.CreateFromSubjectKeyIdentifier(
+                    (X509SubjectKeyIdentifierExtension)null));
+
+            Assert.Throws<ArgumentNullException>(
+                "subjectKeyIdentifier",
+                () => X509AuthorityKeyIdentifierExtension.CreateFromSubjectKeyIdentifier(
+                    (byte[])null));
+        }
+
+        [Fact]
+        public static void CreateFromIssuerAndSerial_Validation()
+        {
+            Assert.Throws<ArgumentNullException>(
+                "issuerName",
+                () => X509AuthorityKeyIdentifierExtension.CreateFromIssuerNameAndSerialNumber(
+                    null,
+                    Array.Empty<byte>()));
+
+            Assert.Throws<ArgumentNullException>(
+                "issuerName",
+                () => X509AuthorityKeyIdentifierExtension.CreateFromIssuerNameAndSerialNumber(
+                    null,
+                    ReadOnlySpan<byte>.Empty));
+
+            X500DistinguishedName dn = new X500DistinguishedName("CN=Hi");
+
+            Assert.Throws<ArgumentNullException>(
+                "serialNumber",
+                () => X509AuthorityKeyIdentifierExtension.CreateFromIssuerNameAndSerialNumber(
+                    dn,
+                    (byte[])null));
+        }
+
+        [Fact]
+        public static void Create_Validation()
+        {
+            Assert.Throws<ArgumentNullException>(
+                "keyIdentifier",
+                () => X509AuthorityKeyIdentifierExtension.Create(
+                    (byte[])null,
+                    null,
+                    (byte[])null));
+
+            Assert.Throws<ArgumentNullException>(
+                "issuerName",
+                () => X509AuthorityKeyIdentifierExtension.Create(
+                    Array.Empty<byte>(),
+                    null,
+                    (byte[])null));
+
+            X500DistinguishedName dn = new X500DistinguishedName("CN=Hi");
+
+            Assert.Throws<ArgumentNullException>(
+                "serialNumber",
+                () => X509AuthorityKeyIdentifierExtension.Create(
+                    Array.Empty<byte>(),
+                    dn,
+                    (byte[])null));
+
+            Assert.Throws<ArgumentNullException>(
+                "issuerName",
+                () => X509AuthorityKeyIdentifierExtension.Create(
+                    ReadOnlySpan<byte>.Empty, 
+                    null,
+                    ReadOnlySpan<byte>.Empty));
+        }
+
+        [Fact]
+        public static void CreateFromCertificate_Validation()
+        {
+            Assert.Throws<ArgumentNullException>(
+                "certificate",
+                () => X509AuthorityKeyIdentifierExtension.CreateFromCertificate(
+                    null,
+                    false,
+                    false));
+
+            Assert.Throws<ArgumentNullException>(
+                "certificate",
+                () => X509AuthorityKeyIdentifierExtension.CreateFromCertificate(
+                    null,
+                    false,
+                    true));
+
+            Assert.Throws<ArgumentNullException>(
+                "certificate",
+                () => X509AuthorityKeyIdentifierExtension.CreateFromCertificate(
+                    null,
+                    true,
+                    false));
+
+            Assert.Throws<ArgumentNullException>(
+                "certificate",
+                () => X509AuthorityKeyIdentifierExtension.CreateFromCertificate(
+                    null,
+                    true,
+                    true));
+        }
+
+        [Fact]
+        public static void CopyFrom_ReadKeyIdentifierFirst()
+        {
+            X509AuthorityKeyIdentifierExtension ext = new X509AuthorityKeyIdentifierExtension();
+            ext.CopyFrom(GetFullyValuedExtension());
+
+            ReadOnlyMemory<byte>? keyIdentifier = ext.KeyIdentifier;
+            Assert.Equal("000000000000000880", keyIdentifier.GetValueOrDefault().ByteArrayToHex());
+        }
+
+        [Fact]
+        public static void CopyFrom_ReadRawIssuerFirst()
+        {
+            X509AuthorityKeyIdentifierExtension ext = new X509AuthorityKeyIdentifierExtension();
+            ext.CopyFrom(GetFullyValuedExtension());
+
+            ReadOnlyMemory<byte>? rawIssuer = ext.RawIssuer;
+
+            Assert.Equal(
+                "A114A4123010310E300C0603550403130548656C6C6F",
+                rawIssuer.GetValueOrDefault().ByteArrayToHex());
+        }
+
+        [Fact]
+        public static void CopyFrom_ReadNamedIssuerFirst()
+        {
+            X509AuthorityKeyIdentifierExtension ext = new X509AuthorityKeyIdentifierExtension();
+            ext.CopyFrom(GetFullyValuedExtension());
+
+            X500DistinguishedName namedIssuer = ext.NamedIssuer;
+            Assert.NotNull(namedIssuer);
+            Assert.Equal("CN=Hello", namedIssuer.Name);
+        }
+
+        [Fact]
+        public static void CopyFrom_ReadSerialNumberFirst()
+        {
+            X509AuthorityKeyIdentifierExtension ext = new X509AuthorityKeyIdentifierExtension();
+            ext.CopyFrom(GetFullyValuedExtension());
+
+            ReadOnlyMemory<byte>? serial = ext.SerialNumber;
+            Assert.Equal("00EE7B", serial.GetValueOrDefault().ByteArrayToHex());
+        }
+
+        [Fact]
+        public static void CreateWithInvalidSerialNumber()
+        {
+            // This value has 9 leading zero bits, making it an invalid encoding for a BER/DER INTEGER.
+            byte[] tooManyZeros = { 0x00, 0x7F };
+            byte[] invalidValue = tooManyZeros;
+            
+            X500DistinguishedName dn = new X500DistinguishedName("CN=Bad Serial");
+
+            // Array
+            Assert.Throws<ArgumentException>(
+                "serialNumber",
+                () => X509AuthorityKeyIdentifierExtension.Create(invalidValue, dn, invalidValue));
+
+            // Span
+            Assert.Throws<ArgumentException>(
+                "serialNumber",
+                () => X509AuthorityKeyIdentifierExtension.Create(
+                    new ReadOnlySpan<byte>(invalidValue), dn, new ReadOnlySpan<byte>(invalidValue)));
+
+            // Array
+            Assert.Throws<ArgumentException>(
+                "serialNumber",
+                () => X509AuthorityKeyIdentifierExtension.CreateFromIssuerNameAndSerialNumber(dn, invalidValue));
+
+            // Span
+            Assert.Throws<ArgumentException>(
+                "serialNumber",
+                () => X509AuthorityKeyIdentifierExtension.CreateFromIssuerNameAndSerialNumber(
+                    dn, new ReadOnlySpan<byte>(invalidValue)));
+
+            // The leading 9 bits are all one, also invalid.
+            byte[] tooManyOnes = { 0xFF, 0x80 };
+            invalidValue = tooManyOnes;
+
+            // Array
+            Assert.Throws<ArgumentException>(
+                "serialNumber",
+                () => X509AuthorityKeyIdentifierExtension.Create(invalidValue, dn, invalidValue));
+
+            // Span
+            Assert.Throws<ArgumentException>(
+                "serialNumber",
+                () => X509AuthorityKeyIdentifierExtension.Create(
+                    new ReadOnlySpan<byte>(invalidValue), dn, new ReadOnlySpan<byte>(invalidValue)));
+
+            // Array
+            Assert.Throws<ArgumentException>(
+                "serialNumber",
+                () => X509AuthorityKeyIdentifierExtension.CreateFromIssuerNameAndSerialNumber(dn, invalidValue));
+
+            // Span
+            Assert.Throws<ArgumentException>(
+                "serialNumber",
+                () => X509AuthorityKeyIdentifierExtension.CreateFromIssuerNameAndSerialNumber(
+                    dn, new ReadOnlySpan<byte>(invalidValue)));
+        }
+
+        private static X509Extension GetFullyValuedExtension()
+        {
+            ReadOnlySpan<byte> encoded = new byte[]
+            {
+                0x30, 0x26, 0x80, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x80, 0xA1, 0x14, 0xA4,
+                0x12, 0x30, 0x10, 0x31, 0x0E, 0x30, 0x0C, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x05, 0x48, 0x65,
+                0x6C, 0x6C, 0x6F, 0x82, 0x03, 0x00, 0xEE, 0x7B,
+            };
+
+            return new X509Extension("2.5.29.35", encoded, critical: false);
+        }
+    }
+}
index 1647ffa..5e8ff97 100644 (file)
@@ -108,7 +108,17 @@ namespace System.Security.Cryptography.X509Certificates.Tests.ExtensionsTests
                     byte[] expected = "30168014cb11e8cad2b4165801c9372e331616b94c9a0a1f".HexToByteArray();
                     Assert.Equal(expected, akid.RawData);
 
-                    Assert.IsType<X509Extension>(akid);
+                    X509AuthorityKeyIdentifierExtension rich =
+                        Assert.IsType<X509AuthorityKeyIdentifierExtension>(akid);
+
+                    Assert.False(rich.RawIssuer.HasValue, "akid.RawIssuer.HasValue");
+                    Assert.Null(rich.NamedIssuer);
+                    Assert.False(rich.SerialNumber.HasValue, "akid.SerialNumber.HasValue");
+                    Assert.True(rich.KeyIdentifier.HasValue, "akid.KeyIdentifier.HasValue");
+
+                    Assert.Equal(
+                        "CB11E8CAD2B4165801C9372E331616B94C9A0A1F",
+                        rich.KeyIdentifier.GetValueOrDefault().ByteArrayToHex());
                 }
 
                 {
index 93c6f59..34e5cca 100644 (file)
@@ -35,6 +35,7 @@
     <Compile Include="CtorTests.cs" />
     <Compile Include="ExportTests.cs" />
     <Compile Include="ExtensionsTests\AuthorityInformationAccessTests.cs" />
+    <Compile Include="ExtensionsTests\AuthorityKeyIdentifierTests.cs" />
     <Compile Include="ExtensionsTests\BasicConstraintsTests.cs" />
     <Compile Include="ExtensionsTests\ComprehensiveTests.cs" />
     <Compile Include="ExtensionsTests\EnhancedKeyUsageTests.cs" />
index c4140da..e3038f7 100644 (file)
@@ -2605,6 +2605,25 @@ namespace System.Security.Cryptography.X509Certificates
         public System.Collections.Generic.IEnumerable<string> EnumerateUris(System.Security.Cryptography.Oid accessMethodOid) { throw null; }
         public System.Collections.Generic.IEnumerable<string> EnumerateUris(string accessMethodOid) { throw null; }
     }
+    public sealed partial class X509AuthorityKeyIdentifierExtension : System.Security.Cryptography.X509Certificates.X509Extension
+    {
+        public X509AuthorityKeyIdentifierExtension() { }
+        public X509AuthorityKeyIdentifierExtension(byte[] rawData, bool critical = false) { }
+        public X509AuthorityKeyIdentifierExtension(System.ReadOnlySpan<byte> rawData, bool critical = false) { }
+        public System.ReadOnlyMemory<byte>? KeyIdentifier { get { throw null; } }
+        public System.Security.Cryptography.X509Certificates.X500DistinguishedName? NamedIssuer { get { throw null; } }
+        public System.ReadOnlyMemory<byte>? RawIssuer { get { throw null; } }
+        public System.ReadOnlyMemory<byte>? SerialNumber { get { throw null; } }
+        public override void CopyFrom(System.Security.Cryptography.AsnEncodedData asnEncodedData) { }
+        public static System.Security.Cryptography.X509Certificates.X509AuthorityKeyIdentifierExtension Create(byte[] keyIdentifier, System.Security.Cryptography.X509Certificates.X500DistinguishedName issuerName, byte[] serialNumber) { throw null; }
+        public static System.Security.Cryptography.X509Certificates.X509AuthorityKeyIdentifierExtension Create(System.ReadOnlySpan<byte> keyIdentifier, System.Security.Cryptography.X509Certificates.X500DistinguishedName issuerName, System.ReadOnlySpan<byte> serialNumber) { throw null; }
+        public static System.Security.Cryptography.X509Certificates.X509AuthorityKeyIdentifierExtension CreateFromCertificate(System.Security.Cryptography.X509Certificates.X509Certificate2 certificate, bool includeKeyIdentifier, bool includeIssuerAndSerial) { throw null; }
+        public static System.Security.Cryptography.X509Certificates.X509AuthorityKeyIdentifierExtension CreateFromIssuerNameAndSerialNumber(System.Security.Cryptography.X509Certificates.X500DistinguishedName issuerName, byte[] serialNumber) { throw null; }
+        public static System.Security.Cryptography.X509Certificates.X509AuthorityKeyIdentifierExtension CreateFromIssuerNameAndSerialNumber(System.Security.Cryptography.X509Certificates.X500DistinguishedName issuerName, System.ReadOnlySpan<byte> serialNumber) { throw null; }
+        public static System.Security.Cryptography.X509Certificates.X509AuthorityKeyIdentifierExtension CreateFromSubjectKeyIdentifier(byte[] subjectKeyIdentifier) { throw null; }
+        public static System.Security.Cryptography.X509Certificates.X509AuthorityKeyIdentifierExtension CreateFromSubjectKeyIdentifier(System.ReadOnlySpan<byte> subjectKeyIdentifier) { throw null; }
+        public static System.Security.Cryptography.X509Certificates.X509AuthorityKeyIdentifierExtension CreateFromSubjectKeyIdentifier(System.Security.Cryptography.X509Certificates.X509SubjectKeyIdentifierExtension subjectKeyIdentifier) { throw null; }
+    }
     public sealed partial class X509BasicConstraintsExtension : System.Security.Cryptography.X509Certificates.X509Extension
     {
         public X509BasicConstraintsExtension() { }
index cf4b84f..8fe3327 100644 (file)
   <data name="Argument_InvalidRandomRange" xml:space="preserve">
     <value>Range of random number does not contain at least one possibility.</value>
   </data>
+  <data name="Argument_InvalidSerialNumberBytes" xml:space="preserve">
+    <value>The provided serial number is invalid. Ensure the input is in big-endian byte order and that all redundant leading bytes have been removed.</value>
+  </data>
   <data name="Argument_InvalidValue" xml:space="preserve">
     <value>Value was invalid.</value>
   </data>
   <data name="Cryptography_X509_AIA_NullValue" xml:space="preserve">
     <value>One of the ocspUris or caIssuersUris enumerables contains a null value.</value>
   </data>
+  <data name="Cryptography_X509_AKID_NoSKID" xml:space="preserve">
+    <value>The provided certificate does not have a Subject Key Identifier extension.</value>
+  </data>
   <data name="Cryptography_X509_CertificateCorrupted" xml:space="preserve">
     <value>Certificate '{0}' is corrupted.</value>
   </data>
index 3a2bffb..9c0bde4 100644 (file)
     <Compile Include="System\Security\Cryptography\X509Certificates\X500RelativeDistinguishedName.cs" />
     <Compile Include="System\Security\Cryptography\X509Certificates\X501Attribute.cs" />
     <Compile Include="System\Security\Cryptography\X509Certificates\X509AuthorityInformationAccessExtension.cs" />
+    <Compile Include="System\Security\Cryptography\X509Certificates\X509AuthorityKeyIdentifierExtension.cs" />
     <Compile Include="System\Security\Cryptography\X509Certificates\X509BasicConstraintsExtension.cs" />
     <Compile Include="System\Security\Cryptography\X509Certificates\X509Certificate.cs" />
     <Compile Include="System\Security\Cryptography\X509Certificates\X509Certificate2.cs" />
index bc7ba49..6b52faa 100644 (file)
@@ -256,6 +256,7 @@ namespace System.Security.Cryptography
                 ht.Add("2.5.29.19", typeof(X509Certificates.X509BasicConstraintsExtension));
                 ht.Add("2.5.29.14", typeof(X509Certificates.X509SubjectKeyIdentifierExtension));
                 ht.Add("2.5.29.15", typeof(X509Certificates.X509KeyUsageExtension));
+                ht.Add("2.5.29.35", typeof(X509Certificates.X509AuthorityKeyIdentifierExtension));
                 ht.Add("2.5.29.37", typeof(X509Certificates.X509EnhancedKeyUsageExtension));
                 ht.Add(Oids.AuthorityInformationAccess, typeof(X509Certificates.X509AuthorityInformationAccessExtension));
                 ht.Add(Oids.SubjectAltName, typeof(X509Certificates.X509SubjectAlternativeNameExtension));
index 316865d..3e76a13 100644 (file)
@@ -34,7 +34,7 @@ namespace System.Security.Cryptography
         }
 
         /// <summary>Expected size of <see cref="s_extraFriendlyNameToOid"/>.</summary>
-        private const int ExtraFriendlyNameToOidCount = 12;
+        private const int ExtraFriendlyNameToOidCount = 13;
 
         // There are places inside the framework where Oid.FromFriendlyName is called
         // (to pass in an OID group restriction for Windows) and an exception is not tolerated.
@@ -55,6 +55,7 @@ namespace System.Security.Cryptography
                 { "X509v3 Key Usage", "2.5.29.15" },
                 { "X509v3 Subject Alternative Name", "2.5.29.17" },
                 { "X509v3 Basic Constraints", "2.5.29.19" },
+                { "X509v3 Authority Key Identifier", "2.5.29.35" },
                 { "X509v3 Extended Key Usage", "2.5.29.37" },
                 { "prime256v1", "1.2.840.10045.3.1.7" },
                 { "secp224r1", "1.3.132.0.33" },
diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/X509AuthorityKeyIdentifierExtension.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/X509AuthorityKeyIdentifierExtension.cs
new file mode 100644 (file)
index 0000000..dc748f6
--- /dev/null
@@ -0,0 +1,605 @@
+// 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 System.Diagnostics;
+using System.Formats.Asn1;
+using System.Runtime.InteropServices.ComTypes;
+using System.Security.Cryptography.Asn1;
+using System.Security.Cryptography.X509Certificates.Asn1;
+
+namespace System.Security.Cryptography.X509Certificates
+{
+    /// <summary>
+    ///   Represents the Authority Key Identifier X.509 Extension (2.5.29.35).
+    /// </summary>
+    public sealed class X509AuthorityKeyIdentifierExtension : X509Extension
+    {
+        private bool _decoded;
+        private X500DistinguishedName? _simpleIssuer;
+        private ReadOnlyMemory<byte>? _keyIdentifier;
+        private ReadOnlyMemory<byte>? _rawIssuer;
+        private ReadOnlyMemory<byte>? _serialNumber;
+
+        /// <summary>
+        ///   Initializes a new instance of the <see cref="X509AuthorityKeyIdentifierExtension" />
+        ///   class.
+        /// </summary>
+        public X509AuthorityKeyIdentifierExtension()
+            : base(Oids.AuthorityKeyIdentifierOid)
+        {
+            _decoded = true;
+        }
+
+        /// <summary>
+        ///   Initializes a new instance of the <see cref="X509AuthorityKeyIdentifierExtension" />
+        ///   class from an encoded representation of the extension and an optional critical marker.
+        /// </summary>
+        /// <param name="rawData">
+        ///   The encoded data used to create the extension.
+        /// </param>
+        /// <param name="critical">
+        ///   <see langword="true" /> if the extension is critical;
+        ///   otherwise, <see langword="false" />.
+        /// </param>
+        /// <exception cref="ArgumentNullException">
+        ///   <paramref name="rawData" /> is <see langword="null"/>.
+        /// </exception>
+        /// <exception cref="CryptographicException">
+        ///   <paramref name="rawData" /> did not decode as an Authority Key Identifier extension.
+        /// </exception>
+        public X509AuthorityKeyIdentifierExtension(byte[] rawData, bool critical = false)
+            : base(Oids.AuthorityKeyIdentifierOid, rawData, critical)
+        {
+            Decode(RawData);
+        }
+
+        /// <summary>
+        ///   Initializes a new instance of the <see cref="X509AuthorityKeyIdentifierExtension" />
+        ///   class from an encoded representation of the extension and an optional critical marker.
+        /// </summary>
+        /// <param name="rawData">
+        ///   The encoded data used to create the extension.
+        /// </param>
+        /// <param name="critical">
+        ///   <see langword="true" /> if the extension is critical;
+        ///   otherwise, <see langword="false" />.
+        /// </param>
+        /// <exception cref="CryptographicException">
+        ///   <paramref name="rawData" /> did not decode as an Authority Key Identifier extension.
+        /// </exception>
+        public X509AuthorityKeyIdentifierExtension(ReadOnlySpan<byte> rawData, bool critical = false)
+            : base(Oids.AuthorityKeyIdentifierOid, rawData, critical)
+        {
+            Decode(RawData);
+        }
+
+        /// <inheritdoc />
+        public override void CopyFrom(AsnEncodedData asnEncodedData)
+        {
+            base.CopyFrom(asnEncodedData);
+            _decoded = false;
+        }
+
+        /// <summary>
+        ///   Gets a value whose contents represent the subject key identifier value
+        ///   from this certificate's issuing Certificate Authority (CA), when specified.
+        /// </summary>
+        /// <value>
+        ///   The subject key identifier from this certificate's issuing Certificate Authority (CA).
+        /// </value>
+        public ReadOnlyMemory<byte>? KeyIdentifier
+        {
+            get
+            {
+                if (!_decoded)
+                {
+                    Decode(RawData);
+                }
+
+                return _keyIdentifier;
+            }
+        }
+
+        /// <summary>
+        ///   Gets the <see cref="X509Certificate2.IssuerName"/> value from this certificate's
+        ///   issuing Certificate Authority (CA), when available.
+        /// </summary>
+        /// <value>
+        ///   The <see cref="X509Certificate2.IssuerName"/> value from this certificate's
+        ///   issuing Certificate Authority (CA).
+        /// </value>
+        /// <remarks>
+        ///   This property is <see langword="null" /> if any of the following are true:
+        ///
+        ///   <list type="bullet">
+        ///     <item>The encoded extension does not include an <c>authorityCertIssuer</c> value.</item>
+        ///     <item>The <c>authorityCertIssuer</c> value contains no <c>directoryName</c> values.</item>
+        ///     <item>The <c>authorityCertIssuer</c> value contains multiple <c>directoryName</c> values.</item>
+        ///     <item>
+        ///       The <c>directoryName</c> value did not successfully decode as
+        ///       an <see cref="X500DistinguishedName"/>.
+        ///     </item>
+        ///   </list>
+        /// </remarks>
+        /// <seealso cref="RawIssuer" />
+        public X500DistinguishedName? NamedIssuer
+        {
+            get
+            {
+                if (!_decoded)
+                {
+                    Decode(RawData);
+                }
+
+                return _simpleIssuer;
+            }
+        }
+
+        /// <summary>
+        ///   Gets a value whose contents represent the encoded representation of the
+        ///   <c>authorityCertIssuer</c> field from the extension,
+        ///   or <see langword="null" /> when the extension does not contain an authority
+        ///   certificate issuer field.
+        /// </summary>
+        /// <value>
+        ///   The encoded <c>authorityCertIssuer</c> value.
+        /// </value>
+        public ReadOnlyMemory<byte>? RawIssuer
+        {
+            get
+            {
+                if (!_decoded)
+                {
+                    Decode(RawData);
+                }
+
+                return _rawIssuer;
+            }
+        }
+
+        /// <summary>
+        ///   Gets a value whose contents represent the serial number of this certificate's
+        ///   issuing Certificate Authority (CA).
+        /// </summary>
+        /// <value>
+        ///   The serial number from this certificate's issuing Certificate Authority (CA).
+        /// </value>
+        public ReadOnlyMemory<byte>? SerialNumber
+        {
+            get
+            {
+                if (!_decoded)
+                {
+                    Decode(RawData);
+                }
+
+                return _serialNumber;
+            }
+        }
+
+        /// <summary>
+        ///   Creates an <see cref="X509AuthorityKeyIdentifierExtension"/> that specifies
+        ///   the key identifier value from a subject key identifier extension.
+        /// </summary>
+        /// <param name="subjectKeyIdentifier">
+        ///   The subject key identifier extension from the Certificate Authority (CA) certificate
+        ///   that will sign this extension.
+        /// </param>
+        /// <returns>
+        ///   The configured extension.
+        /// </returns>
+        /// <exception cref="ArgumentNullException">
+        ///   <paramref name="subjectKeyIdentifier"/> is <see langword="null" />.
+        /// </exception>
+        public static X509AuthorityKeyIdentifierExtension CreateFromSubjectKeyIdentifier(
+            X509SubjectKeyIdentifierExtension subjectKeyIdentifier)
+        {
+            ArgumentNullException.ThrowIfNull(subjectKeyIdentifier);
+
+            return CreateFromSubjectKeyIdentifier(
+                subjectKeyIdentifier.SubjectKeyIdentifierBytes.Span);
+        }
+
+        /// <summary>
+        ///   Creates an <see cref="X509AuthorityKeyIdentifierExtension"/> that specifies
+        ///   the provided key identifier value.
+        /// </summary>
+        /// <param name="subjectKeyIdentifier">
+        ///   The subject key identifier value from the Certificate Authority (CA) certificate
+        ///   that will sign this extension.
+        /// </param>
+        /// <returns>
+        ///   The configured extension.
+        /// </returns>
+        /// <exception cref="ArgumentNullException">
+        ///   <paramref name="subjectKeyIdentifier"/> is <see langword="null" />.
+        /// </exception>
+        public static X509AuthorityKeyIdentifierExtension CreateFromSubjectKeyIdentifier(
+        byte[] subjectKeyIdentifier)
+        {
+            ArgumentNullException.ThrowIfNull(subjectKeyIdentifier);
+
+            return CreateFromSubjectKeyIdentifier(new ReadOnlySpan<byte>(subjectKeyIdentifier));
+        }
+
+        /// <summary>
+        ///   Creates an <see cref="X509AuthorityKeyIdentifierExtension"/> that specifies
+        ///   the provided key identifier value.
+        /// </summary>
+        /// <param name="subjectKeyIdentifier">
+        ///   The subject key identifier value from the Certificate Authority (CA) certificate
+        ///   that will sign this extension.
+        /// </param>
+        /// <returns>
+        ///   The configured extension.
+        /// </returns>
+        /// <seealso cref="X509SubjectKeyIdentifierExtension.SubjectKeyIdentifierBytes"/>
+        public static X509AuthorityKeyIdentifierExtension CreateFromSubjectKeyIdentifier(
+            ReadOnlySpan<byte> subjectKeyIdentifier)
+        {
+            AsnWriter writer = new AsnWriter(AsnEncodingRules.DER);
+
+            using (writer.PushSequence())
+            {
+                writer.WriteOctetString(subjectKeyIdentifier, new Asn1Tag(TagClass.ContextSpecific, 0));
+            }
+
+            // Most KeyIdentifier values are computed from SHA-1 (20 bytes), which produces a 24-byte
+            // value for this extension.
+            // Let's go ahead and be really generous before moving to redundant array allocation.
+            Span<byte> stackSpan = stackalloc byte[64];
+            scoped ReadOnlySpan<byte> encoded;
+
+            if (writer.TryEncode(stackSpan, out int written))
+            {
+                encoded = stackSpan.Slice(0, written);
+            }
+            else
+            {
+                encoded = writer.Encode();
+            }
+
+            return new X509AuthorityKeyIdentifierExtension(encoded);
+        }
+
+        /// <summary>
+        ///   Creates an <see cref="X509AuthorityKeyIdentifierExtension"/> that specifies
+        ///   the provided issuer name and serial number.
+        /// </summary>
+        /// <param name="issuerName">
+        ///   The issuer name value from the Certificate Authority (CA) certificate that will
+        ///   sign this extension.
+        /// </param>
+        /// <param name="serialNumber">
+        ///   The serial number value from the Certificate Authority (CA) certificate that will
+        ///   sign this extension.
+        /// </param>
+        /// <returns>
+        ///   The configured extension.
+        /// </returns>
+        /// <exception cref="ArgumentNullException">
+        ///   <paramref name="issuerName"/> or <paramref name="serialNumber"/> is <see langword="null" />.
+        /// </exception>
+        /// <exception cref="ArgumentException">
+        ///   <paramref name="serialNumber"/> is invalid because the leading 9 bits are either
+        ///   all zero or all one.
+        /// </exception>
+        public static X509AuthorityKeyIdentifierExtension CreateFromIssuerNameAndSerialNumber(
+            X500DistinguishedName issuerName,
+            byte[] serialNumber)
+        {
+            ArgumentNullException.ThrowIfNull(issuerName);
+            ArgumentNullException.ThrowIfNull(serialNumber);
+
+            return CreateFromIssuerNameAndSerialNumber(issuerName, new ReadOnlySpan<byte>(serialNumber));
+        }
+
+        /// <summary>
+        ///   Creates an <see cref="X509AuthorityKeyIdentifierExtension"/> that specifies
+        ///   the provided issuer name and serial number.
+        /// </summary>
+        /// <param name="issuerName">
+        ///   The issuer name value from the Certificate Authority (CA) certificate that will
+        ///   sign this extension.
+        /// </param>
+        /// <param name="serialNumber">
+        ///   The serial number value from the Certificate Authority (CA) certificate that will
+        ///   sign this extension.
+        /// </param>
+        /// <returns>
+        ///   The configured extension.
+        /// </returns>
+        /// <exception cref="ArgumentNullException">
+        ///   <paramref name="issuerName"/> is <see langword="null" />.
+        /// </exception>
+        /// <exception cref="ArgumentException">
+        ///   <paramref name="serialNumber"/> is invalid because the leading 9 bits are either
+        ///   all zero or all one.
+        /// </exception>
+        public static X509AuthorityKeyIdentifierExtension CreateFromIssuerNameAndSerialNumber(
+            X500DistinguishedName issuerName,
+            ReadOnlySpan<byte> serialNumber)
+        {
+            ArgumentNullException.ThrowIfNull(issuerName);
+
+            AsnWriter writer = new AsnWriter(AsnEncodingRules.DER);
+
+            using (writer.PushSequence())
+            {
+                using (writer.PushSequence(new Asn1Tag(TagClass.ContextSpecific, 1)))
+                using (writer.PushSequence(new Asn1Tag(TagClass.ContextSpecific, 4)))
+                {
+                    writer.WriteEncodedValue(issuerName.RawData);
+                }
+
+                try
+                {
+                    writer.WriteInteger(serialNumber, new Asn1Tag(TagClass.ContextSpecific, 2));
+                }
+                catch (ArgumentException)
+                {
+                    throw new ArgumentException(SR.Argument_InvalidSerialNumberBytes, nameof(serialNumber));
+                }
+            }
+
+            return new X509AuthorityKeyIdentifierExtension(writer.Encode());
+        }
+
+
+        /// <summary>
+        ///   Creates an <see cref="X509AuthorityKeyIdentifierExtension"/> that specifies
+        ///   the provided key identifier, issuer name and serial number.
+        /// </summary>
+        /// <param name="keyIdentifier">
+        ///   The subject key identifier value from the Certificate Authority (CA) certificate
+        ///   that will sign this extension.
+        /// </param>
+        /// <param name="issuerName">
+        ///   The issuer name value from the Certificate Authority (CA) certificate that will
+        ///   sign this extension.
+        /// </param>
+        /// <param name="serialNumber">
+        ///   The serial number value from the Certificate Authority (CA) certificate that will
+        ///   sign this extension.
+        /// </param>
+        /// <returns>
+        ///   The configured extension.
+        /// </returns>
+        /// <exception cref="ArgumentNullException">
+        ///   <paramref name="keyIdentifier" />, <paramref name="issuerName"/> or
+        ///   <paramref name="serialNumber"/> is <see langword="null" />.
+        /// </exception>
+        /// <exception cref="ArgumentException">
+        ///   <paramref name="serialNumber"/> is invalid because the leading 9 bits are either
+        ///   all zero or all one.
+        /// </exception>
+        public static X509AuthorityKeyIdentifierExtension Create(
+            byte[] keyIdentifier,
+            X500DistinguishedName issuerName,
+            byte[] serialNumber)
+        {
+            ArgumentNullException.ThrowIfNull(keyIdentifier);
+            ArgumentNullException.ThrowIfNull(issuerName);
+            ArgumentNullException.ThrowIfNull(serialNumber);
+
+            return Create(
+                new ReadOnlySpan<byte>(keyIdentifier),
+                issuerName,
+                new ReadOnlySpan<byte>(serialNumber));
+        }
+
+        /// <summary>
+        ///   Creates an <see cref="X509AuthorityKeyIdentifierExtension"/> that specifies
+        ///   the provided key identifier, issuer name and serial number.
+        /// </summary>
+        /// <param name="keyIdentifier">
+        ///   The subject key identifier value from the Certificate Authority (CA) certificate
+        ///   that will sign this extension.
+        /// </param>
+        /// <param name="issuerName">
+        ///   The issuer name value from the Certificate Authority (CA) certificate that will
+        ///   sign this extension.
+        /// </param>
+        /// <param name="serialNumber">
+        ///   The serial number value from the Certificate Authority (CA) certificate that will
+        ///   sign this extension.
+        /// </param>
+        /// <returns>
+        ///   The configured extension.
+        /// </returns>
+        /// <exception cref="ArgumentNullException">
+        ///   <paramref name="issuerName"/> is <see langword="null" />.
+        /// </exception>
+        /// <exception cref="ArgumentException">
+        ///   <paramref name="serialNumber"/> is invalid because the leading 9 bits are either
+        ///   all zero or all one.
+        /// </exception>
+        public static X509AuthorityKeyIdentifierExtension Create(
+            ReadOnlySpan<byte> keyIdentifier,
+            X500DistinguishedName issuerName,
+            ReadOnlySpan<byte> serialNumber)
+        {
+            ArgumentNullException.ThrowIfNull(issuerName);
+
+            AsnWriter writer = new AsnWriter(AsnEncodingRules.DER);
+
+            using (writer.PushSequence())
+            {
+                writer.WriteOctetString(keyIdentifier, new Asn1Tag(TagClass.ContextSpecific, 0));
+
+                using (writer.PushSequence(new Asn1Tag(TagClass.ContextSpecific, 1)))
+                using (writer.PushSequence(new Asn1Tag(TagClass.ContextSpecific, 4)))
+                {
+                    writer.WriteEncodedValue(issuerName.RawData);
+                }
+
+                try
+                {
+                    writer.WriteInteger(serialNumber, new Asn1Tag(TagClass.ContextSpecific, 2));
+                }
+                catch (ArgumentException)
+                {
+                    throw new ArgumentException(SR.Argument_InvalidSerialNumberBytes, nameof(serialNumber));
+                }
+            }
+
+            return new X509AuthorityKeyIdentifierExtension(writer.Encode());
+        }
+
+        /// <summary>
+        ///   Creates an <see cref="X509AuthorityKeyIdentifierExtension"/> based on values
+        ///   from the provided certificate.
+        /// </summary>
+        /// <param name="certificate">
+        ///   The Certificate Authority (CA) certificate that will sign this extension.
+        /// </param>
+        /// <param name="includeKeyIdentifier">
+        ///   <see langword="true" /> to include the Subject Key Identifier value from the certificate
+        ///   as the key identifier value in this extension; otherwise, <see langword="false" />.
+        /// </param>
+        /// <param name="includeIssuerAndSerial">
+        ///   <see langword="true" /> to include the certificate's issuer name and serial number
+        ///   in this extension; otherwise, <see langword="false" />.
+        /// </param>
+        /// <returns>
+        ///   The configured extension.
+        /// </returns>
+        /// <exception cref="ArgumentNullException">
+        ///   <paramref name="certificate"/> is <see langword="null" />.
+        /// </exception>
+        /// <exception cref="CryptographicException">
+        ///   <paramref name="includeKeyIdentifier" /> is <see langword="true" />, but
+        ///   <paramref name="certificate" /> does not contain a Subject Key Identifier extension.
+        /// </exception>
+        public static X509AuthorityKeyIdentifierExtension CreateFromCertificate(
+            X509Certificate2 certificate,
+            bool includeKeyIdentifier,
+            bool includeIssuerAndSerial)
+        {
+            ArgumentNullException.ThrowIfNull(certificate);
+
+            if (includeKeyIdentifier)
+            {
+                X509SubjectKeyIdentifierExtension? skid =
+                    (X509SubjectKeyIdentifierExtension?)certificate.Extensions[Oids.SubjectKeyIdentifier];
+
+                if (skid is null)
+                {
+                    throw new CryptographicException(SR.Cryptography_X509_AKID_NoSKID);
+                }
+
+                ReadOnlySpan<byte> skidBytes = skid.SubjectKeyIdentifierBytes.Span;
+
+                if (includeIssuerAndSerial)
+                {
+                    return Create(
+                        skidBytes,
+                        certificate.IssuerName,
+                        certificate.SerialNumberBytes.Span);
+                }
+
+                return CreateFromSubjectKeyIdentifier(skidBytes);
+            }
+            else if (includeIssuerAndSerial)
+            {
+                return CreateFromIssuerNameAndSerialNumber(
+                    certificate.IssuerName,
+                    certificate.SerialNumberBytes.Span);
+            }
+
+            ReadOnlySpan<byte> emptyExtension = new byte[] { 0x30, 0x00 };
+            return new X509AuthorityKeyIdentifierExtension(emptyExtension);
+        }
+
+        private void Decode(ReadOnlySpan<byte> rawData)
+        {
+            _keyIdentifier = null;
+            _simpleIssuer = null;
+            _rawIssuer = null;
+            _serialNumber = null;
+
+            // https://datatracker.ietf.org/doc/html/rfc3280#section-4.2.1.1
+            // AuthorityKeyIdentifier ::= SEQUENCE {
+            //    keyIdentifier[0] KeyIdentifier OPTIONAL,
+            //    authorityCertIssuer[1] GeneralNames OPTIONAL,
+            //    authorityCertSerialNumber[2] CertificateSerialNumber OPTIONAL  }
+            //
+            // KeyIdentifier::= OCTET STRING
+
+            try
+            {
+                AsnValueReader reader = new AsnValueReader(rawData, AsnEncodingRules.DER);
+                AsnValueReader aki = reader.ReadSequence();
+                reader.ThrowIfNotEmpty();
+
+                Asn1Tag nextTag = default;
+
+                if (aki.HasData)
+                {
+                    nextTag = aki.PeekTag();
+                }
+
+                if (nextTag.HasSameClassAndValue(new Asn1Tag(TagClass.ContextSpecific, 0)))
+                {
+                    _keyIdentifier = aki.ReadOctetString(nextTag);
+
+                    if (aki.HasData)
+                    {
+                        nextTag = aki.PeekTag();
+                    }
+                }
+
+                if (nextTag.HasSameClassAndValue(new Asn1Tag(TagClass.ContextSpecific, 1)))
+                {
+                    byte[] rawIssuer = aki.PeekEncodedValue().ToArray();
+                    _rawIssuer = rawIssuer;
+
+                    AsnValueReader generalNames = aki.ReadSequence(nextTag);
+                    bool foundIssuer = false;
+
+                    // Walk all of the entities to make sure they decode legally, so no early abort.
+                    while (generalNames.HasData)
+                    {
+                        GeneralNameAsn.Decode(ref generalNames, rawIssuer, out GeneralNameAsn decoded);
+
+                        if (decoded.DirectoryName.HasValue)
+                        {
+                            if (!foundIssuer)
+                            {
+                                // Only ever try reading the first one.
+                                // Don't just use a null check or we would load the last of an odd number.
+                                foundIssuer = true;
+
+                                _simpleIssuer = new X500DistinguishedName(
+                                    decoded.DirectoryName.GetValueOrDefault().Span);
+                            }
+                            else
+                            {
+                                _simpleIssuer = null;
+                            }
+                        }
+                    }
+
+                    if (aki.HasData)
+                    {
+                        nextTag = aki.PeekTag();
+                    }
+                }
+
+                if (nextTag.HasSameClassAndValue(new Asn1Tag(TagClass.ContextSpecific, 2)))
+                {
+                    _serialNumber = aki.ReadIntegerBytes(nextTag).ToArray();
+                }
+
+                aki.ThrowIfNotEmpty();
+            }
+            catch (AsnContentException e)
+            {
+                throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e);
+            }
+
+            _decoded = true;
+        }
+    }
+}
index 9fc8c2e..0b86f65 100644 (file)
@@ -1489,6 +1489,7 @@ namespace System.Security.Cryptography.X509Certificates
                 Oids.KeyUsage => new X509KeyUsageExtension(),
                 Oids.EnhancedKeyUsage => new X509EnhancedKeyUsageExtension(),
                 Oids.SubjectKeyIdentifier => new X509SubjectKeyIdentifierExtension(),
+                Oids.AuthorityKeyIdentifier => new X509AuthorityKeyIdentifierExtension(),
                 Oids.AuthorityInformationAccess => new X509AuthorityInformationAccessExtension(),
                 Oids.SubjectAltName => new X509SubjectAlternativeNameExtension(),
                 _ => null,
index 79434cb..c678121 100644 (file)
@@ -13,19 +13,19 @@ namespace System.Security.Cryptography.X509Certificates
     {
         private List<GeneralNameAsn>? _decoded;
 
-        public X509SubjectAlternativeNameExtension() : base(Oids.SubjectAltName)
+        public X509SubjectAlternativeNameExtension() : base(Oids.SubjectAltNameOid)
         {
             _decoded = new List<GeneralNameAsn>(0);
         }
 
         public X509SubjectAlternativeNameExtension(byte[] rawData, bool critical = false)
-            : base(Oids.SubjectAltName, rawData, critical)
+            : base(Oids.SubjectAltNameOid, rawData, critical)
         {
             _decoded = Decode(RawData);
         }
 
         public X509SubjectAlternativeNameExtension(ReadOnlySpan<byte> rawData, bool critical = false)
-            : base(Oids.SubjectAltName, rawData, critical)
+            : base(Oids.SubjectAltNameOid, rawData, critical)
         {
             _decoded = Decode(RawData);
         }
index 21f0dea..e59e1ea 100644 (file)
@@ -481,6 +481,7 @@ namespace System.Security.Cryptography.Tests
                     yield return new object[] { "2.5.29.14", "System.Security.Cryptography.X509Certificates.X509SubjectKeyIdentifierExtension", true };
                     yield return new object[] { "2.5.29.15", "System.Security.Cryptography.X509Certificates.X509KeyUsageExtension", true };
                     yield return new object[] { "2.5.29.17", "System.Security.Cryptography.X509Certificates.X509SubjectAlternativeNameExtension", true };
+                    yield return new object[] { "2.5.29.35", "System.Security.Cryptography.X509Certificates.X509AuthorityKeyIdentifierExtension", true };
                     yield return new object[] { "2.5.29.37", "System.Security.Cryptography.X509Certificates.X509EnhancedKeyUsageExtension", true };
                     yield return new object[] { "X509Chain", "System.Security.Cryptography.X509Certificates.X509Chain", true };