private static volatile Oid? s_authorityKeyIdentifierOid;
private static volatile Oid? s_authorityInformationAccessOid;
private static volatile Oid? s_crlNumberOid;
+ private static volatile Oid? s_crlDistributionPointOid;
private static volatile Oid? s_commonNameOid;
private static volatile Oid? s_countryOrRegionOid;
private static volatile Oid? s_localityNameOid;
internal static Oid SubjectAltNameOid => s_subjectAltNameOid ??= InitializeOid(SubjectAltName);
internal static Oid AuthorityInformationAccessOid => s_authorityInformationAccessOid ??= InitializeOid(AuthorityInformationAccess);
internal static Oid CrlNumberOid => s_crlNumberOid ??= InitializeOid(CrlNumber);
+ internal static Oid CrlDistributionPointsOid => s_crlDistributionPointOid ??= InitializeOid(CrlDistributionPoints);
internal static Oid CommonNameOid => s_commonNameOid ??= InitializeOid(CommonName);
internal static Oid CountryOrRegionNameOid => s_countryOrRegionOid ??= InitializeOid(CountryOrRegionName);
private static X509Extension CreateCdpExtension(string cdp)
{
- AsnWriter writer = new AsnWriter(AsnEncodingRules.DER);
-
- // SEQUENCE OF
- using (writer.PushSequence())
- {
- // DistributionPoint
- using (writer.PushSequence())
- {
- // Because DistributionPointName is a CHOICE type this tag is explicit.
- // (ITU-T REC X.680-201508 C.3.2.2(g)(3rd bullet))
- // distributionPoint [0] DistributionPointName
- using (writer.PushSequence(s_context0))
- {
- // [0] DistributionPointName (GeneralNames (SEQUENCE OF))
- using (writer.PushSequence(s_context0))
- {
- // GeneralName ([6] IA5String)
- writer.WriteCharacterString(
- UniversalTagNumber.IA5String,
- cdp,
- new Asn1Tag(TagClass.ContextSpecific, 6));
- }
- }
- }
- }
-
- return new X509Extension("2.5.29.31", writer.Encode(), false);
+ return CertificateRevocationListBuilder.BuildCrlDistributionPointExtension(new[] { cdp });
}
private X509Extension CreateAkidExtension()
public void AddEntry(System.Security.Cryptography.X509Certificates.X509Certificate2 certificate, System.DateTimeOffset? revocationTime = default(System.DateTimeOffset?), System.Security.Cryptography.X509Certificates.X509RevocationReason? reason = default(System.Security.Cryptography.X509Certificates.X509RevocationReason?)) { }
public byte[] Build(System.Security.Cryptography.X509Certificates.X500DistinguishedName issuerName, System.Security.Cryptography.X509Certificates.X509SignatureGenerator generator, System.Numerics.BigInteger crlNumber, System.DateTimeOffset nextUpdate, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.Security.Cryptography.X509Certificates.X509AuthorityKeyIdentifierExtension authorityKeyIdentifier, System.DateTimeOffset? thisUpdate = default(System.DateTimeOffset?)) { throw null; }
public byte[] Build(System.Security.Cryptography.X509Certificates.X509Certificate2 issuerCertificate, System.Numerics.BigInteger crlNumber, System.DateTimeOffset nextUpdate, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.Security.Cryptography.RSASignaturePadding? rsaSignaturePadding = null, System.DateTimeOffset? thisUpdate = default(System.DateTimeOffset?)) { throw null; }
+ public static System.Security.Cryptography.X509Certificates.X509Extension BuildCrlDistributionPointExtension(System.Collections.Generic.IEnumerable<string> uris, bool critical = false) { throw null; }
public static System.Security.Cryptography.X509Certificates.CertificateRevocationListBuilder Load(byte[] currentCrl, out System.Numerics.BigInteger currentCrlNumber) { throw null; }
public static System.Security.Cryptography.X509Certificates.CertificateRevocationListBuilder Load(System.ReadOnlySpan<byte> currentCrl, out System.Numerics.BigInteger currentCrlNumber, out int bytesConsumed) { throw null; }
public static System.Security.Cryptography.X509Certificates.CertificateRevocationListBuilder LoadPem(System.ReadOnlySpan<char> currentCrl, out System.Numerics.BigInteger currentCrlNumber) { throw null; }
<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_CDP_MustNotBuildEmpty" xml:space="preserve">
+ <value>The collection of distribution URIs must be non-empty.</value>
+ </data>
+ <data name="Cryptography_X509_CDP_NullValue" xml:space="preserve">
+ <value>One of the provided CRL Distribution Point URIs is a null value.</value>
+ </data>
<data name="Cryptography_X509_CertificateCorrupted" xml:space="preserve">
<value>Certificate '{0}' is corrupted.</value>
</data>
<Compile Include="System\Security\Cryptography\X509Certificates\CertificateRequest.cs" />
<Compile Include="System\Security\Cryptography\X509Certificates\CertificateRevocationListBuilder.cs" />
<Compile Include="System\Security\Cryptography\X509Certificates\CertificateRevocationListBuilder.Build.cs" />
+ <Compile Include="System\Security\Cryptography\X509Certificates\CertificateRevocationListBuilder.CdpExtension.cs" />
<Compile Include="System\Security\Cryptography\X509Certificates\CertificateRevocationListBuilder.Load.cs" />
<Compile Include="System\Security\Cryptography\X509Certificates\ChainPal.cs" />
<Compile Include="System\Security\Cryptography\X509Certificates\DSACertificateExtensions.cs" />
--- /dev/null
+// 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.Formats.Asn1;
+using Internal.Cryptography;
+
+namespace System.Security.Cryptography.X509Certificates
+{
+ public sealed partial class CertificateRevocationListBuilder
+ {
+ /// <summary>
+ /// Builds a CRL Distribution Point Extension with the specified retrieval URIs.
+ /// </summary>
+ /// <param name="uris">
+ /// The URIs to include as distribution points for the relevant Certificate
+ /// Revocation List (CRL).
+ /// </param>
+ /// <param name="critical">
+ /// <see langword="true" /> to mark the extension as critical;
+ /// otherwise, <see langword="false" />.
+ /// The default is <see langword="false" />.
+ /// </param>
+ /// <returns>
+ /// An object suitable for use as a CRL Distribution Point Extension.
+ /// </returns>
+ /// <exception cref="ArgumentNullException">
+ /// <paramref name="uris"/> is <see langword="null" />.
+ /// </exception>
+ /// <exception cref="ArgumentException">
+ /// <para>
+ /// <paramref name="uris"/> contains a <see langword="null" /> value.
+ /// </para>
+ /// <para>- or -</para>
+ /// <para>
+ /// <paramref name="uris"/> is empty.
+ /// </para>
+ /// </exception>
+ /// <exception cref="CryptographicException">
+ /// One of the values in <paramref name="uris"/>
+ /// contains characters outside of the International Alphabet 5 (IA5) character space
+ /// (which is equivalent to 7-bit US-ASCII).
+ /// </exception>
+ public static X509Extension BuildCrlDistributionPointExtension(
+ IEnumerable<string> uris,
+ bool critical = false)
+ {
+ ArgumentNullException.ThrowIfNull(uris);
+
+ // CRLDistributionPoints ::= SEQUENCE SIZE (1..MAX) OF DistributionPoint
+ //
+ // DistributionPoint::= SEQUENCE {
+ // distributionPoint[0] DistributionPointName OPTIONAL,
+ // reasons[1] ReasonFlags OPTIONAL,
+ // cRLIssuer[2] GeneralNames OPTIONAL }
+
+ // DistributionPointName::= CHOICE {
+ // fullName[0] GeneralNames,
+ // nameRelativeToCRLIssuer[1] RelativeDistinguishedName }
+
+ AsnWriter? writer = null;
+
+ foreach (string uri in uris)
+ {
+ if (uri is null)
+ {
+ throw new ArgumentException(SR.Cryptography_X509_CDP_NullValue, nameof(uris));
+ }
+
+ if (writer is null)
+ {
+ writer = new AsnWriter(AsnEncodingRules.DER);
+ // CRLDistributionPoints
+ writer.PushSequence();
+ }
+
+ // DistributionPoint
+ using (writer.PushSequence())
+ {
+ // DistributionPoint/DistributionPointName EXPLICIT [0]
+ using (writer.PushSequence(new Asn1Tag(TagClass.ContextSpecific, 0)))
+ {
+ // DistributionPointName/GeneralName
+ using (writer.PushSequence(new Asn1Tag(TagClass.ContextSpecific, 0)))
+ {
+ // GeneralName/Uri
+ try
+ {
+ writer.WriteCharacterString(
+ UniversalTagNumber.IA5String,
+ uri,
+ new Asn1Tag(TagClass.ContextSpecific, 6));
+ }
+ catch (System.Text.EncoderFallbackException e)
+ {
+ throw new CryptographicException(SR.Cryptography_Invalid_IA5String, e);
+ }
+ }
+ }
+ }
+ }
+
+ if (writer is null)
+ {
+ throw new ArgumentException(SR.Cryptography_X509_CDP_MustNotBuildEmpty, nameof(uris));
+ }
+
+ // CRLDistributionPoints
+ writer.PopSequence();
+
+ byte[] encoded = writer.Encode();
+ return new X509Extension(Oids.CrlDistributionPointsOid, encoded, critical);
+ }
+ }
+}
<Compile Include="TripleDesTests.cs" />
<Compile Include="ZeroMemoryTests.cs" />
<Compile Include="X509Certificates\ChainPolicyTests.cs" />
+ <Compile Include="X509Certificates\CrlDistPointBuilderTests.cs" />
<Compile Include="X509Certificates\X500DistinguishedNameBuilderTests.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'windows'">
--- /dev/null
+// 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
+{
+ public static class CrlDistPointBuilderTests
+ {
+ [Fact]
+ public static void NullEnumerable()
+ {
+ Assert.Throws<ArgumentNullException>(
+ "uris",
+ () => CertificateRevocationListBuilder.BuildCrlDistributionPointExtension(null));
+ }
+
+ [Fact]
+ public static void NullUriInEnumerable()
+ {
+ Assert.Throws<ArgumentException>(
+ "uris",
+ () => CertificateRevocationListBuilder.BuildCrlDistributionPointExtension(
+ new[]
+ {
+ "http://cert.example/ca1.crl",
+ null,
+ "http://cdn.cert.example/ca1.crl",
+ }));
+ }
+
+ [Fact]
+ public static void BuildEmpty()
+ {
+ Assert.Throws<ArgumentException>(
+ "uris",
+ () => CertificateRevocationListBuilder.BuildCrlDistributionPointExtension(
+ System.Linq.Enumerable.Empty<string>()));
+ }
+
+ [Fact]
+ public static void BuildOneEntry()
+ {
+ X509Extension ext = CertificateRevocationListBuilder.BuildCrlDistributionPointExtension(
+ new[]
+ {
+ "http://crl.microsoft.com/pki/crl/products/MicCodSigPCA_08-31-2010.crl",
+ });
+
+ Assert.False(ext.Critical, "ext.Critical");
+ Assert.Equal("2.5.29.31", ext.Oid.Value);
+
+ byte[] expected = (
+ "304d304ba049a0478645687474703a2f2f63726c2e6d6963726f736f" +
+ "66742e636f6d2f706b692f63726c2f70726f64756374732f4d696343" +
+ "6f645369675043415f30382d33312d323031302e63726c").HexToByteArray();
+
+ AssertExtensions.SequenceEqual(expected, ext.RawData);
+ }
+
+ [Fact]
+ public static void BuildTwoEntries()
+ {
+ // Recreate the encoding of the CDP extension from https://crt.sh/?id=3777044
+ // (the original wasn't marked as critical, but that doesn't affect the RawData value)
+ X509Extension ext = CertificateRevocationListBuilder.BuildCrlDistributionPointExtension(
+ new[]
+ {
+ "http://crl3.digicert.com/sha2-ev-server-g1.crl",
+ "http://crl4.digicert.com/sha2-ev-server-g1.crl",
+ },
+ critical: true);
+
+ Assert.True(ext.Critical, "ext.Critical");
+ Assert.Equal("2.5.29.31", ext.Oid.Value);
+
+ byte[] expected = (
+ "306C3034A032A030862E687474703A2F2F63726C332E64696769636572742E63" +
+ "6F6D2F736861322D65762D7365727665722D67312E63726C3034A032A030862E" +
+ "687474703A2F2F63726C342E64696769636572742E636F6D2F736861322D6576" +
+ "2D7365727665722D67312E63726C").HexToByteArray();
+
+ AssertExtensions.SequenceEqual(expected, ext.RawData);
+ }
+
+ [Fact]
+ public static void UriNotValidated()
+ {
+ X509Extension ext = CertificateRevocationListBuilder.BuildCrlDistributionPointExtension(
+ new[]
+ {
+ "!!!!",
+ });
+
+ Assert.Equal("300C300AA008A006860421212121", ext.RawData.ByteArrayToHex());
+ }
+
+ [Fact]
+ public static void OnlyAscii7Permitted()
+ {
+ Assert.Throws<CryptographicException>(
+ () => CertificateRevocationListBuilder.BuildCrlDistributionPointExtension(
+ new[]
+ {
+ // http://[nihongo].example/ca4.crl
+ "http://\u65E5\u672C\u8A8E.example/ca4.crl",
+ }));
+ }
+ }
+}