// 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 { ValueAsnReader reader = new ValueAsnReader(authorityInfoAccessSyntax, AsnEncodingRules.DER); ValueAsnReader descriptions = reader.ReadSequence(); reader.ThrowIfNotEmpty(); int count = 0; ValueAsnReader 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(); } } } |