File: System\Security\Cryptography\X509Certificates\X509AuthorityInformationAccessExtension.cs
Web Access
Project: src\src\libraries\System.Security.Cryptography\src\System.Security.Cryptography.csproj (System.Security.Cryptography)
// 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.Security.Cryptography.X509Certificates.Asn1;
 
namespace System.Security.Cryptography.X509Certificates
{
    /// <summary>
    ///   Represents the Authority Information Access X.509 Extension (1.3.6.1.5.5.7.1.1).
    /// </summary>
    public sealed class X509AuthorityInformationAccessExtension : X509Extension
    {
        private AccessDescriptionAsn[]? _decoded;
 
        /// <summary>
        ///   Initializes a new instance of the <see cref="X509AuthorityInformationAccessExtension" />
        ///   class.
        /// </summary>
        public X509AuthorityInformationAccessExtension()
            : base(Oids.AuthorityInformationAccessOid)
        {
            _decoded = Array.Empty<AccessDescriptionAsn>();
        }
 
        /// <summary>
        ///   Initializes a new instance of the <see cref="X509AuthorityInformationAccessExtension" />
        ///   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 Information Access extension.
        /// </exception>
        public X509AuthorityInformationAccessExtension(byte[] rawData, bool critical = false)
            : base(Oids.AuthorityInformationAccessOid, rawData, critical)
        {
            _decoded = Decode(RawData);
        }
 
        /// <summary>
        ///   Initializes a new instance of the <see cref="X509AuthorityInformationAccessExtension" />
        ///   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 Information Access extension.
        /// </exception>
        public X509AuthorityInformationAccessExtension(ReadOnlySpan<byte> rawData, bool critical = false)
            : base(Oids.AuthorityInformationAccessOid, rawData, critical)
        {
            _decoded = Decode(RawData);
        }
 
        /// <summary>
        ///   Initializes a new instance of the <see cref="X509AuthorityInformationAccessExtension" />
        ///   class from a collection of OCSP and CAIssuer values.
        /// </summary>
        /// <param name="ocspUris">
        ///   A collection of OCSP URI values to embed in the extension.
        /// </param>
        /// <param name="caIssuersUris">
        ///   A collection of CAIssuers URI values to embed in the extension.
        /// </param>
        /// <param name="critical">
        ///   <see langword="true" /> if the extension is critical;
        ///   otherwise, <see langword="false" />.
        /// </param>
        /// <exception cref="ArgumentException">
        ///   Both <paramref name="ocspUris"/> and <paramref name="caIssuersUris"/> are
        ///   either <see langword="null" /> or empty.
        /// </exception>
        /// <exception cref="CryptographicException">
        ///   One of the values in <paramref name="ocspUris"/> or <paramref name="caIssuersUris"/>
        ///   contains characters outside of the International Alphabet 5 (IA5) character space
        ///   (which is equivalent to 7-bit US-ASCII).
        /// </exception>
        public X509AuthorityInformationAccessExtension(
            IEnumerable<string>? ocspUris,
            IEnumerable<string>? caIssuersUris,
            bool critical = false)
            : base(Oids.AuthorityInformationAccessOid, Encode(ocspUris, caIssuersUris), critical, skipCopy: true)
        {
            _decoded = Decode(RawData);
        }
 
        /// <inheritdoc />
        public override void CopyFrom(AsnEncodedData asnEncodedData)
        {
            base.CopyFrom(asnEncodedData);
            _decoded = null;
        }
 
        /// <summary>
        ///   Enumerates the AccessDescription values described in this extension,
        ///   filtering the results to include only items using the specified access method
        ///   and having a content data type of URI.
        /// </summary>
        /// <param name="accessMethodOid">
        ///   The dotted-decimal form of the access method for filtering.
        /// </param>
        /// <remarks>
        ///   This method does not validate or ensure that the produced values are valid URIs,
        ///   merely that they were encoded as a URI.
        /// </remarks>
        /// <returns>
        ///   The URI-typed access location values that correspond to the requested access method.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        ///   <paramref name="accessMethodOid"/> is <see langword="null" />.
        /// </exception>
        /// <exception cref="CryptographicException">
        ///   The contents of the extension could not be decoded successfully.
        /// </exception>
        /// <seealso cref="EnumerateCAIssuersUris"/>
        /// <seealso cref="EnumerateOcspUris"/>
        public IEnumerable<string> EnumerateUris(string accessMethodOid)
        {
            ArgumentNullException.ThrowIfNull(accessMethodOid);
 
            _decoded ??= Decode(RawData);
 
            return EnumerateUrisCore(accessMethodOid);
        }
 
        /// <summary>
        ///   Enumerates the AccessDescription values described in this extension,
        ///   filtering the results to include only items using the specified access method
        ///   and having a content data type of URI.
        /// </summary>
        /// <param name="accessMethodOid">
        ///   The object identifier representing the access method for filtering.
        /// </param>
        /// <remarks>
        ///   This method does not validate or ensure that the produced values are valid URIs,
        ///   merely that they were encoded as a URI.
        /// </remarks>
        /// <returns>
        ///   The URI-typed access location values that correspond to the requested access method.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        ///   <paramref name="accessMethodOid"/> is <see langword="null" />.
        /// </exception>
        /// <exception cref="ArgumentException">
        ///   The <see cref="Oid.Value"/> property of the <paramref name="accessMethodOid"/> parameter is
        ///   <see langword="null" /> or the empty string.
        /// </exception>
        /// <exception cref="CryptographicException">
        ///   The contents of the extension could not be decoded successfully.
        /// </exception>
        /// <seealso cref="EnumerateCAIssuersUris"/>
        /// <seealso cref="EnumerateOcspUris"/>
        public IEnumerable<string> EnumerateUris(Oid accessMethodOid)
        {
            ArgumentNullException.ThrowIfNull(accessMethodOid);
            ArgumentException.ThrowIfNullOrEmpty(accessMethodOid.Value);
 
            return EnumerateUris(accessMethodOid.Value);
        }
 
        private IEnumerable<string> EnumerateUrisCore(string accessMethodOid)
        {
            Debug.Assert(_decoded is not null);
 
            for (int i = 0; i < _decoded.Length; i++)
            {
                string? uri = GetUri(accessMethodOid, ref _decoded[i]);
 
                if (uri is not null)
                {
                    yield return uri;
                }
            }
 
            static string? GetUri(string accessMethodOid, ref AccessDescriptionAsn desc)
            {
                if (string.Equals(accessMethodOid, desc.AccessMethod))
                {
                    return desc.AccessLocation.Uri;
                }
 
                return null;
            }
        }
 
        /// <summary>
        ///   Enumerates the AccessDescription values whose AccessMethod is CAIssuers
        ///   (1.3.6.1.5.5.7.48.2) and content data type is URI.
        /// </summary>
        /// <returns>
        ///   The URIs corresponding to the locations for the issuing Certificate Authority
        ///   certificate.
        /// </returns>
        /// <exception cref="CryptographicException">
        ///   The contents of the extension could not be decoded successfully.
        /// </exception>
        /// <seealso cref="EnumerateUris(string)" />
        public IEnumerable<string> EnumerateCAIssuersUris()
        {
            return EnumerateUris(Oids.CertificateAuthorityIssuers);
        }
 
        /// <summary>
        ///   Enumerates the AccessDescription values whose AccessMethod is OCSP
        ///   (1.3.6.1.5.5.7.48.1) and content data type is URI.
        /// </summary>
        /// <returns>
        ///   The URIs corresponding to the locations for the issuing Certificate Authority
        ///   certificate.
        /// </returns>
        /// <exception cref="CryptographicException">
        ///   The contents of the extension could not be decoded successfully.
        /// </exception>
        /// <seealso cref="EnumerateUris(string)" />
        public IEnumerable<string> EnumerateOcspUris()
        {
            return EnumerateUris(Oids.OcspEndpoint);
        }
 
        private static AccessDescriptionAsn[] Decode(byte[] authorityInfoAccessSyntax)
        {
            try
            {
                AsnValueReader reader = new AsnValueReader(authorityInfoAccessSyntax, AsnEncodingRules.DER);
                AsnValueReader descriptions = reader.ReadSequence();
                reader.ThrowIfNotEmpty();
 
                int count = 0;
                AsnValueReader counter = descriptions;
 
                while (counter.HasData)
                {
                    count++;
                    counter.ReadEncodedValue();
                }
 
                AccessDescriptionAsn[] decoded = new AccessDescriptionAsn[count];
                count = 0;
 
                while (descriptions.HasData)
                {
                    AccessDescriptionAsn.Decode(
                        ref descriptions,
                        authorityInfoAccessSyntax,
                        out decoded[count]);
 
                    count++;
                }
 
                return decoded;
            }
            catch (AsnContentException e)
            {
                throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e);
            }
        }
 
        private static byte[] Encode(
            IEnumerable<string>? ocspUris,
            IEnumerable<string>? caIssuersUris)
        {
            AsnWriter writer = new AsnWriter(AsnEncodingRules.DER);
            bool empty = true;
 
            static void WriteAccessMethod(AsnWriter writer, string oid, string value)
            {
                if (value is null)
                {
                    throw new ArgumentException(SR.Cryptography_X509_AIA_NullValue);
                }
 
                writer.PushSequence();
                writer.WriteObjectIdentifier(oid);
 
                try
                {
                    writer.WriteCharacterString(
                        UniversalTagNumber.IA5String,
                        value,
                        new Asn1Tag(TagClass.ContextSpecific, 6));
                }
                catch (System.Text.EncoderFallbackException e)
                {
                    throw new CryptographicException(SR.Cryptography_Invalid_IA5String, e);
                }
 
                writer.PopSequence();
            }
 
            writer.PushSequence();
 
            if (ocspUris is not null)
            {
                foreach (string uri in ocspUris)
                {
                    WriteAccessMethod(writer, Oids.OcspEndpoint, uri);
                    empty = false;
                }
            }
 
            if (caIssuersUris is not null)
            {
                foreach (string uri in caIssuersUris)
                {
                    WriteAccessMethod(writer, Oids.CertificateAuthorityIssuers, uri);
                    empty = false;
                }
            }
 
            writer.PopSequence();
 
            if (empty)
            {
                throw new ArgumentException(SR.Cryptography_X509_AIA_MustNotBuildEmpty);
            }
 
            return writer.Encode();
        }
    }
}