// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Buffers; using System.Collections.Generic; using System.Diagnostics; using System.Formats.Asn1; using System.Numerics; using System.Security.Cryptography.Asn1; namespace System.Security.Cryptography.X509Certificates { public sealed partial class CertificateRevocationListBuilder { /// <summary> /// Decodes the specified Certificate Revocation List (CRL) and produces /// a <see cref="CertificateRevocationListBuilder" /> with all of the revocation /// entries from the decoded CRL. /// </summary> /// <param name="currentCrl"> /// The DER-encoded CRL to decode. /// </param> /// <param name="currentCrlNumber"> /// When this method returns, contains the CRL sequence number from the decoded CRL. /// This parameter is treated as uninitialized. /// </param> /// <returns> /// A new builder that has the same revocation entries as the decoded CRL. /// </returns> /// <exception cref="ArgumentNullException"> /// <paramref name="currentCrl" /> is <see langword="null" />. /// </exception> /// <exception cref="CryptographicException"> /// <para> /// <paramref name="currentCrl" /> could not be decoded. /// </para> /// <para>- or -</para> /// <para> /// <paramref name="currentCrl" /> decoded successfully, but decoding did not /// need all of the bytes provided in the array. /// </para> /// </exception> public static CertificateRevocationListBuilder Load(byte[] currentCrl, out BigInteger currentCrlNumber) { ArgumentNullException.ThrowIfNull(currentCrl); CertificateRevocationListBuilder ret = Load( new ReadOnlySpan<byte>(currentCrl), out BigInteger crlNumber, out int bytesConsumed); if (bytesConsumed != currentCrl.Length) { throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); } currentCrlNumber = crlNumber; return ret; } /// <summary> /// Decodes the specified Certificate Revocation List (CRL) and produces /// a <see cref="CertificateRevocationListBuilder" /> with all of the revocation /// entries from the decoded CRL. /// </summary> /// <param name="currentCrl"> /// The DER-encoded CRL to decode. /// </param> /// <param name="currentCrlNumber"> /// When this method returns, contains the CRL sequence number from the decoded CRL. /// This parameter is treated as uninitialized. /// </param> /// <param name="bytesConsumed"> /// When this method returns, contains the number of bytes that were read from /// <paramref name="currentCrl"/> while decoding. /// </param> /// <returns> /// A new builder that has the same revocation entries as the decoded CRL. /// </returns> /// <exception cref="CryptographicException"> /// <paramref name="currentCrl" /> could not be decoded. /// </exception> public static CertificateRevocationListBuilder Load( ReadOnlySpan<byte> currentCrl, out BigInteger currentCrlNumber, out int bytesConsumed) { List<RevokedCertificate> list = new(); BigInteger crlNumber = 0; int payloadLength; try { ValueAsnReader reader = new ValueAsnReader(currentCrl, AsnEncodingRules.DER); payloadLength = reader.PeekEncodedValue().Length; ValueAsnReader certificateList = reader.ReadSequence(); ValueAsnReader tbsCertList = certificateList.ReadSequence(); AlgorithmIdentifierAsn.Decode(ref certificateList, ReadOnlyMemory<byte>.Empty, out _); if (!certificateList.TryReadPrimitiveBitString(out _, out _)) { throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); } certificateList.ThrowIfNotEmpty(); int version = 0; if (tbsCertList.PeekTag().HasSameClassAndValue(Asn1Tag.Integer)) { // https://datatracker.ietf.org/doc/html/rfc5280#section-5.1 says the only // version values are v1 (0) and v2 (1). // // Since v1 (0) is supposed to not write down the version value, v2 (1) is the // only legal value to read. if (!tbsCertList.TryReadInt32(out version) || version != 1) { throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); } } AlgorithmIdentifierAsn.Decode(ref tbsCertList, ReadOnlyMemory<byte>.Empty, out _); // X500DN tbsCertList.ReadSequence(); // thisUpdate ReadX509Time(ref tbsCertList); // nextUpdate ReadX509TimeOpt(ref tbsCertList); ValueAsnReader revokedCertificates = default; if (tbsCertList.HasData && tbsCertList.PeekTag().HasSameClassAndValue(Asn1Tag.Sequence)) { revokedCertificates = tbsCertList.ReadSequence(); } if (version > 0 && tbsCertList.HasData) { ValueAsnReader crlExtensionsExplicit = tbsCertList.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, 0)); ValueAsnReader crlExtensions = crlExtensionsExplicit.ReadSequence(); crlExtensionsExplicit.ThrowIfNotEmpty(); while (crlExtensions.HasData) { ValueAsnReader extension = crlExtensions.ReadSequence(); Oid? extnOid = Oids.GetSharedOrNullOid(ref extension); if (extnOid is null) { extension.ReadObjectIdentifier(); } if (extension.PeekTag().HasSameClassAndValue(Asn1Tag.Boolean)) { extension.ReadBoolean(); } if (!extension.TryReadPrimitiveOctetString(out ReadOnlySpan<byte> extnValue)) { throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); } // Since we're only matching against OIDs that come from GetSharedOrNullOid // we can use ReferenceEquals and skip the Value string equality check in // the Oid.ValueEquals extension method (as it will always be preempted by // the ReferenceEquals or will evaulate to false). if (ReferenceEquals(extnOid, Oids.CrlNumberOid)) { ValueAsnReader crlNumberReader = new ValueAsnReader( extnValue, AsnEncodingRules.DER); crlNumber = crlNumberReader.ReadInteger(); crlNumberReader.ThrowIfNotEmpty(); } } } tbsCertList.ThrowIfNotEmpty(); while (revokedCertificates.HasData) { RevokedCertificate revokedCertificate = new RevokedCertificate(ref revokedCertificates, version); list.Add(revokedCertificate); } } catch (AsnContentException e) { throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e); } bytesConsumed = payloadLength; currentCrlNumber = crlNumber; return new CertificateRevocationListBuilder(list); } /// <summary> /// Decodes the specified Certificate Revocation List (CRL) and produces /// a <see cref="CertificateRevocationListBuilder" /> with all of the revocation /// entries from the decoded CRL. /// </summary> /// <param name="currentCrl"> /// The PEM-encoded CRL to decode. /// </param> /// <param name="currentCrlNumber"> /// When this method returns, contains the CRL sequence number from the decoded CRL. /// This parameter is treated as uninitialized. /// </param> /// <returns> /// A new builder that has the same revocation entries as the decoded CRL. /// </returns> /// <remarks> /// This loads the first well-formed PEM found with an <c>X509 CRL</c> label. /// </remarks> /// <exception cref="ArgumentNullException"> /// <paramref name="currentCrl" /> is <see langword="null" />. /// </exception> /// <exception cref="CryptographicException"> /// <para> /// <paramref name="currentCrl" /> did not contain a well-formed PEM payload with /// an <c>X509 CRL</c> label. /// </para> /// <para>- or -</para> /// <para> /// <paramref name="currentCrl" /> could not be decoded. /// </para> /// </exception> public static CertificateRevocationListBuilder LoadPem(string currentCrl, out BigInteger currentCrlNumber) { ArgumentNullException.ThrowIfNull(currentCrl); return LoadPem(currentCrl.AsSpan(), out currentCrlNumber); } /// <summary> /// Decodes the specified Certificate Revocation List (CRL) and produces /// a <see cref="CertificateRevocationListBuilder" /> with all of the revocation /// entries from the decoded CRL. /// </summary> /// <param name="currentCrl"> /// The PEM-encoded CRL to decode. /// </param> /// <param name="currentCrlNumber"> /// When this method returns, contains the CRL sequence number from the decoded CRL. /// This parameter is treated as uninitialized. /// </param> /// <returns> /// A new builder that has the same revocation entries as the decoded CRL. /// </returns> /// <remarks> /// This loads the first well-formed PEM found with an <c>X509 CRL</c> label. /// </remarks> /// <exception cref="CryptographicException"> /// <para> /// <paramref name="currentCrl" /> did not contain a well-formed PEM payload with /// an <c>X509 CRL</c> label. /// </para> /// <para>- or -</para> /// <para> /// <paramref name="currentCrl" /> could not be decoded. /// </para> /// </exception> public static CertificateRevocationListBuilder LoadPem(ReadOnlySpan<char> currentCrl, out BigInteger currentCrlNumber) { foreach ((ReadOnlySpan<char> contents, PemFields fields) in PemEnumerator.Utf16(currentCrl)) { if (contents[fields.Label].SequenceEqual(PemLabels.X509CertificateRevocationList)) { byte[] rented = ArrayPool<byte>.Shared.Rent(fields.DecodedDataLength); if (!Convert.TryFromBase64Chars(contents[fields.Base64Data], rented, out int bytesWritten)) { Debug.Fail("Base64Decode failed, but PemEncoding said it was legal"); throw new UnreachableException(); } CertificateRevocationListBuilder ret = Load( rented.AsSpan(0, bytesWritten), out currentCrlNumber, out int bytesConsumed); ArrayPool<byte>.Shared.Return(rented); if (bytesConsumed != bytesWritten) { throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); } return ret; } } throw new CryptographicException(SR.Cryptography_NoPemOfLabel, PemLabels.X509CertificateRevocationList); } } } |