From cae3cedc4b20f84bf5f384bc67bedc4cd61d9057 Mon Sep 17 00:00:00 2001 From: Jeremy Barton Date: Thu, 28 Jul 2022 14:39:20 -0700 Subject: [PATCH 1/4] Add the ability to load a PKCS10 into CertificateRequest --- .../Cryptography/Asn1/PssParamsAsn.manual.cs | 64 ++ .../System/Security/Cryptography/Helpers.cs | 14 + .../System/Security/Cryptography/PemLabels.cs | 1 + .../System.Security.Cryptography.Pkcs.csproj | 4 + .../Cryptography/Pkcs/CmsSignature.RSA.cs | 43 +- .../CertificateRequestApiTests.cs | 327 ++++++++ .../CertificateRequestLoadTests.cs | 745 ++++++++++++++++++ .../CertificateRequestUsageTests.cs | 149 +++- .../PrivateKeyAssociationTests.cs | 23 - .../X509Sha1SignatureGenerators.cs | 76 ++ ...Cryptography.X509Certificates.Tests.csproj | 2 + .../tests/TestData.cs | 21 + .../ref/System.Security.Cryptography.cs | 15 + .../src/Resources/Strings.resx | 33 + .../src/System.Security.Cryptography.csproj | 6 + .../CertificateRequest.Load.cs | 393 +++++++++ .../X509Certificates/CertificateRequest.cs | 278 ++++++- .../CertificateRequestLoadOptions.cs | 30 + .../X509Certificates/PublicKey.cs | 27 +- .../X509Certificates/X509Certificate2.cs | 5 +- 20 files changed, 2152 insertions(+), 104 deletions(-) create mode 100644 src/libraries/Common/src/System/Security/Cryptography/Asn1/PssParamsAsn.manual.cs create mode 100644 src/libraries/System.Security.Cryptography.X509Certificates/tests/CertificateCreation/CertificateRequestLoadTests.cs create mode 100644 src/libraries/System.Security.Cryptography.X509Certificates/tests/CertificateCreation/X509Sha1SignatureGenerators.cs create mode 100644 src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/CertificateRequest.Load.cs create mode 100644 src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/CertificateRequestLoadOptions.cs diff --git a/src/libraries/Common/src/System/Security/Cryptography/Asn1/PssParamsAsn.manual.cs b/src/libraries/Common/src/System/Security/Cryptography/Asn1/PssParamsAsn.manual.cs new file mode 100644 index 00000000000000..b80004f5efa50c --- /dev/null +++ b/src/libraries/Common/src/System/Security/Cryptography/Asn1/PssParamsAsn.manual.cs @@ -0,0 +1,64 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Formats.Asn1; +using Internal.Cryptography; + +namespace System.Security.Cryptography.Asn1 +{ + internal partial struct PssParamsAsn + { + internal RSASignaturePadding GetSignaturePadding( + int? digestValueLength = null) + { + if (TrailerField != 1) + { + throw new CryptographicException(SR.Cryptography_Pkcs_InvalidSignatureParameters); + } + + if (MaskGenAlgorithm.Algorithm != Oids.Mgf1) + { + throw new CryptographicException( + SR.Cryptography_Pkcs_PssParametersMgfNotSupported, + MaskGenAlgorithm.Algorithm); + } + + if (MaskGenAlgorithm.Parameters == null) + { + throw new CryptographicException(SR.Cryptography_Pkcs_InvalidSignatureParameters); + } + + AlgorithmIdentifierAsn mgfParams = AlgorithmIdentifierAsn.Decode( + MaskGenAlgorithm.Parameters.Value, + AsnEncodingRules.DER); + + if (mgfParams.Algorithm != HashAlgorithm.Algorithm) + { + throw new CryptographicException( + SR.Format( + SR.Cryptography_Pkcs_PssParametersMgfHashMismatch, + mgfParams.Algorithm, + HashAlgorithm.Algorithm)); + } + + int saltSize = digestValueLength.GetValueOrDefault(); + + if (!digestValueLength.HasValue) + { + saltSize = Helpers.HashOidToByteLength(HashAlgorithm.Algorithm); + } + + if (SaltLength != saltSize) + { + throw new CryptographicException( + SR.Format( + SR.Cryptography_Pkcs_PssParametersSaltMismatch, + SaltLength, + HashAlgorithm.Algorithm)); + } + + // When RSASignaturePadding supports custom salt sizes this return will look different. + return RSASignaturePadding.Pss; + } + } +} diff --git a/src/libraries/Common/src/System/Security/Cryptography/Helpers.cs b/src/libraries/Common/src/System/Security/Cryptography/Helpers.cs index 23e64a23a089d9..685a7e1beed628 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/Helpers.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/Helpers.cs @@ -64,5 +64,19 @@ internal static bool TryCopyToDestination(this ReadOnlySpan source, Span 256 >> 3, + Oids.Sha384 => 384 >> 3, + Oids.Sha512 => 512 >> 3, + Oids.Sha1 => 160 >> 3, + Oids.Md5 => 128 >> 3, + _ => throw new CryptographicException(SR.Format(SR.Cryptography_UnknownHashAlgorithm, hashOid)), + }; + } } } diff --git a/src/libraries/Common/src/System/Security/Cryptography/PemLabels.cs b/src/libraries/Common/src/System/Security/Cryptography/PemLabels.cs index 6e843b7af9c1b1..4d2e306437f14a 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/PemLabels.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/PemLabels.cs @@ -14,5 +14,6 @@ internal static class PemLabels internal const string X509Certificate = "CERTIFICATE"; internal const string Pkcs7Certificate = "PKCS7"; internal const string X509CertificateRevocationList = "X509 CRL"; + internal const string Pkcs10CertificateRequest = "CERTIFICATE REQUEST"; } } diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/System.Security.Cryptography.Pkcs.csproj b/src/libraries/System.Security.Cryptography.Pkcs/src/System.Security.Cryptography.Pkcs.csproj index 1bbf74d2fe6698..be33237e616414 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/src/System.Security.Cryptography.Pkcs.csproj +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/System.Security.Cryptography.Pkcs.csproj @@ -415,6 +415,10 @@ System.Security.Cryptography.Pkcs.EnvelopedCms Common\System\Security\Cryptography\Asn1\PssParamsAsn.xml + + Common\System\Security\Cryptography\Asn1\PssParamsAsn.manual.cs + Common\System\Security\Cryptography\Asn1\PssParamsAsn.xml + Common\System\Security\Cryptography\Asn1\PssParamsAsn.xml.cs Common\System\Security\Cryptography\Asn1\PssParamsAsn.xml diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSignature.RSA.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSignature.RSA.cs index 1ac9a248d35e7f..12adeda2360f6e 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSignature.RSA.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/System/Security/Cryptography/Pkcs/CmsSignature.RSA.cs @@ -309,47 +309,8 @@ protected override RSASignaturePadding GetSignaturePadding( digestAlgorithmOid)); } - if (pssParams.TrailerField != 1) - { - throw new CryptographicException(SR.Cryptography_Pkcs_InvalidSignatureParameters); - } - - if (pssParams.SaltLength != digestValueLength) - { - throw new CryptographicException( - SR.Format( - SR.Cryptography_Pkcs_PssParametersSaltMismatch, - pssParams.SaltLength, - digestAlgorithmName.Name)); - } - - if (pssParams.MaskGenAlgorithm.Algorithm != Oids.Mgf1) - { - throw new CryptographicException( - SR.Cryptography_Pkcs_PssParametersMgfNotSupported, - pssParams.MaskGenAlgorithm.Algorithm); - } - - if (pssParams.MaskGenAlgorithm.Parameters == null) - { - throw new CryptographicException(SR.Cryptography_Pkcs_InvalidSignatureParameters); - } - - AlgorithmIdentifierAsn mgfParams = AlgorithmIdentifierAsn.Decode( - pssParams.MaskGenAlgorithm.Parameters.Value, - AsnEncodingRules.DER); - - if (mgfParams.Algorithm != digestAlgorithmOid) - { - throw new CryptographicException( - SR.Format( - SR.Cryptography_Pkcs_PssParametersMgfHashMismatch, - mgfParams.Algorithm, - digestAlgorithmOid)); - } - - // When RSASignaturePadding supports custom salt sizes this return will look different. - return RSASignaturePadding.Pss; + RSASignaturePadding padding = pssParams.GetSignaturePadding(digestValueLength); + return padding; } protected override bool Sign( diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/tests/CertificateCreation/CertificateRequestApiTests.cs b/src/libraries/System.Security.Cryptography.X509Certificates/tests/CertificateCreation/CertificateRequestApiTests.cs index 1cf2e93872ed1d..f79783cb08ed3b 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/tests/CertificateCreation/CertificateRequestApiTests.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/tests/CertificateCreation/CertificateRequestApiTests.cs @@ -241,5 +241,332 @@ public static void CtorValidation_PublicKey_X500DN() "hashAlgorithm", () => new CertificateRequest(subjectName, publicKey, new HashAlgorithmName(""))); } + + [Fact] + public static void NullAttributeInCollection() + { + using (ECDsa key = ECDsa.Create(EccTestData.Secp384r1Data.KeyParameters)) + { + CertificateRequest req = new CertificateRequest( + "CN=Test", + key, + HashAlgorithmName.SHA384); + + req.OtherRequestAttributes.Add(null); + + X509SignatureGenerator gen = X509SignatureGenerator.CreateForECDsa(key); + InvalidOperationException ex; + + ex = Assert.Throws(() => req.CreateSigningRequest()); + Assert.Contains(nameof(CertificateRequest.OtherRequestAttributes), ex.Message); + + ex = Assert.Throws(() => req.CreateSigningRequest(gen)); + Assert.Contains(nameof(CertificateRequest.OtherRequestAttributes), ex.Message); + } + } + + [Fact] + public static void NullOidInAttributeInCollection() + { + using (ECDsa key = ECDsa.Create(EccTestData.Secp384r1Data.KeyParameters)) + { + CertificateRequest req = new CertificateRequest( + "CN=Test", + key, + HashAlgorithmName.SHA384); + + req.OtherRequestAttributes.Add(new AsnEncodedData((Oid)null, Array.Empty())); + + X509SignatureGenerator gen = X509SignatureGenerator.CreateForECDsa(key); + InvalidOperationException ex; + + ex = Assert.Throws(() => req.CreateSigningRequest()); + Assert.Contains(nameof(CertificateRequest.OtherRequestAttributes), ex.Message); + + ex = Assert.Throws(() => req.CreateSigningRequest(gen)); + Assert.Contains(nameof(CertificateRequest.OtherRequestAttributes), ex.Message); + } + } + + [Fact] + public static void NullOidValueInAttributeInCollection() + { + using (ECDsa key = ECDsa.Create(EccTestData.Secp384r1Data.KeyParameters)) + { + CertificateRequest req = new CertificateRequest( + "CN=Test", + key, + HashAlgorithmName.SHA384); + + req.OtherRequestAttributes.Add(new AsnEncodedData(new Oid(null, null), Array.Empty())); + + X509SignatureGenerator gen = X509SignatureGenerator.CreateForECDsa(key); + InvalidOperationException ex; + + ex = Assert.Throws(() => req.CreateSigningRequest()); + Assert.Contains(nameof(CertificateRequest.OtherRequestAttributes), ex.Message); + + ex = Assert.Throws(() => req.CreateSigningRequest(gen)); + Assert.Contains(nameof(CertificateRequest.OtherRequestAttributes), ex.Message); + } + } + + [Fact] + public static void ExtensionRequestInAttributeInCollection() + { + using (ECDsa key = ECDsa.Create(EccTestData.Secp384r1Data.KeyParameters)) + { + CertificateRequest req = new CertificateRequest( + "CN=Test", + key, + HashAlgorithmName.SHA384); + + req.OtherRequestAttributes.Add( + new AsnEncodedData( + new Oid("1.2.840.113549.1.9.14", null), + Array.Empty())); + + X509SignatureGenerator gen = X509SignatureGenerator.CreateForECDsa(key); + InvalidOperationException ex; + + ex = Assert.Throws(() => req.CreateSigningRequest()); + Assert.Contains(nameof(CertificateRequest.OtherRequestAttributes), ex.Message); + Assert.Contains(nameof(CertificateRequest.CertificateExtensions), ex.Message); + + ex = Assert.Throws(() => req.CreateSigningRequest(gen)); + Assert.Contains(nameof(CertificateRequest.OtherRequestAttributes), ex.Message); + Assert.Contains(nameof(CertificateRequest.CertificateExtensions), ex.Message); + } + } + + [Fact] + public static void PublicKeyConstructor_CannotSelfSign() + { + byte[] spki; + + using (ECDsa key = ECDsa.Create(EccTestData.Secp384r1Data.KeyParameters)) + { + spki = key.ExportSubjectPublicKeyInfo(); + } + + PublicKey publicKey = PublicKey.CreateFromSubjectPublicKeyInfo(spki, out _); + + CertificateRequest req = new CertificateRequest( + new X500DistinguishedName("CN=Test"), + publicKey, + HashAlgorithmName.SHA384); + + InvalidOperationException ex; + + ex = Assert.Throws(() => req.CreateSigningRequest()); + Assert.Contains(nameof(X509SignatureGenerator), ex.Message); + + DateTimeOffset notBefore = DateTimeOffset.UtcNow; + DateTimeOffset notAfter = notBefore.AddMinutes(1); + + ex = Assert.Throws(() => req.CreateSelfSigned(notBefore, notAfter)); + Assert.Contains(nameof(X509SignatureGenerator), ex.Message); + } + + [Fact] + public static void InvalidDerInAttribute() + { + using (ECDsa key = ECDsa.Create(EccTestData.Secp384r1Data.KeyParameters)) + { + CertificateRequest req = new CertificateRequest( + "CN=Test", + key, + HashAlgorithmName.SHA384); + + // This is "legal DER", but contains more than one value, which is invalid in context. + ReadOnlySpan invalidEncoding = new byte[] + { + // PrintableString("123") + 0x13, 0x03, 0x31, 0x32, 0x33, + // NULL + 0x05, 0x00, + }; + + req.OtherRequestAttributes.Add( + new AsnEncodedData( + new Oid("1.2.840.113549.1.9.7", null), + invalidEncoding)); + + X509SignatureGenerator gen = X509SignatureGenerator.CreateForECDsa(key); + + Assert.Throws(() => req.CreateSigningRequest()); + Assert.Throws(() => req.CreateSigningRequest(gen)); + } + } + + [Fact] + public static void LoadNullArray() + { + Assert.Throws( + "pkcs10", + () => CertificateRequest.LoadSigningRequest((byte[])null, HashAlgorithmName.SHA256)); + } + + [Fact] + public static void LoadNullPemString() + { + Assert.Throws( + "pkcs10Pem", + () => CertificateRequest.LoadSigningRequestPem((string)null, HashAlgorithmName.SHA256)); + } + + [Fact] + public static void LoadWithDefaultHashAlgorithm() + { + Assert.Throws( + "signerHashAlgorithm", + () => CertificateRequest.LoadSigningRequest(Array.Empty(), default(HashAlgorithmName))); + + { + int consumed = -1; + + Assert.Throws( + "signerHashAlgorithm", + () => CertificateRequest.LoadSigningRequest( + ReadOnlySpan.Empty, + default(HashAlgorithmName), + out consumed)); + + Assert.Equal(-1, consumed); + } + + Assert.Throws( + "signerHashAlgorithm", + () => CertificateRequest.LoadSigningRequestPem(string.Empty, default(HashAlgorithmName))); + + Assert.Throws( + "signerHashAlgorithm", + () => CertificateRequest.LoadSigningRequestPem( + ReadOnlySpan.Empty, + default(HashAlgorithmName))); + } + + [Fact] + public static void LoadWithEmptyHashAlgorithm() + { + HashAlgorithmName hashAlgorithm = new HashAlgorithmName(""); + + Assert.Throws( + "signerHashAlgorithm", + () => CertificateRequest.LoadSigningRequest(Array.Empty(), hashAlgorithm)); + + { + int consumed = -1; + + Assert.Throws( + "signerHashAlgorithm", + () => CertificateRequest.LoadSigningRequest( + ReadOnlySpan.Empty, + hashAlgorithm, + out consumed)); + + Assert.Equal(-1, consumed); + } + + Assert.Throws( + "signerHashAlgorithm", + () => CertificateRequest.LoadSigningRequestPem(string.Empty, hashAlgorithm)); + + Assert.Throws( + "signerHashAlgorithm", + () => CertificateRequest.LoadSigningRequestPem( + ReadOnlySpan.Empty, + hashAlgorithm)); + } + + [Theory] + [InlineData(-1)] + [InlineData(4)] + public static void LoadWithUnknownOptions(int optionsValue) + { + CertificateRequestLoadOptions options = (CertificateRequestLoadOptions)optionsValue; + HashAlgorithmName hashAlgorithm = HashAlgorithmName.SHA256; + ArgumentOutOfRangeException ex; + + ex = Assert.Throws( + "options", + () => CertificateRequest.LoadSigningRequest(Array.Empty(), hashAlgorithm, options)); + + Assert.Equal(options, ex.ActualValue); + + { + int consumed = -1; + + ex = Assert.Throws( + "options", + () => CertificateRequest.LoadSigningRequest( + ReadOnlySpan.Empty, + hashAlgorithm, + out consumed, + options)); + + Assert.Equal(-1, consumed); + Assert.Equal(options, ex.ActualValue); + } + + ex = Assert.Throws( + "options", + () => CertificateRequest.LoadSigningRequestPem(string.Empty, hashAlgorithm, options)); + + Assert.Equal(options, ex.ActualValue); + + ex = Assert.Throws( + "options", + () => CertificateRequest.LoadSigningRequestPem( + ReadOnlySpan.Empty, + hashAlgorithm, + options)); + + Assert.Equal(options, ex.ActualValue); + } + + [Theory] + [InlineData("SHA256")] + [InlineData("SHA384")] + [InlineData("SHA512")] + [InlineData("SHA1")] + public static void VerifySignature_ECDsa(string hashAlgorithm) + { + HashAlgorithmName hashAlgorithmName = new HashAlgorithmName(hashAlgorithm); + + using (ECDsa key = ECDsa.Create()) + { + CertificateRequest first = new CertificateRequest( + "CN=Test", + key, + hashAlgorithmName); + + byte[] pkcs10; + + if (hashAlgorithm == "SHA1") + { + pkcs10 = first.CreateSigningRequest(new ECDsaSha1SignatureGenerator(key)); + } + else + { + pkcs10 = first.CreateSigningRequest(); + } + + // Assert.NoThrow + CertificateRequest.LoadSigningRequest(pkcs10, hashAlgorithmName, out _); + + pkcs10[^1] ^= 0xFF; + + Assert.Throws( + () => CertificateRequest.LoadSigningRequest(pkcs10, hashAlgorithmName, out _)); + + // Assert.NoThrow + CertificateRequest.LoadSigningRequest( + pkcs10, + hashAlgorithmName, + out _, + CertificateRequestLoadOptions.SkipSignatureValidation); + } + } } } diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/tests/CertificateCreation/CertificateRequestLoadTests.cs b/src/libraries/System.Security.Cryptography.X509Certificates/tests/CertificateCreation/CertificateRequestLoadTests.cs new file mode 100644 index 00000000000000..70ddd99a24193f --- /dev/null +++ b/src/libraries/System.Security.Cryptography.X509Certificates/tests/CertificateCreation/CertificateRequestLoadTests.cs @@ -0,0 +1,745 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Net; +using Test.Cryptography; +using Xunit; + +namespace System.Security.Cryptography.X509Certificates.Tests.CertificateCreation +{ + public static class CertificateRequestLoadTests + { + [Theory] + [InlineData(CertificateRequestLoadOptions.Default, false)] + [InlineData(CertificateRequestLoadOptions.UnsafeLoadCertificateExtensions, false)] + [InlineData(CertificateRequestLoadOptions.Default, true)] + [InlineData(CertificateRequestLoadOptions.UnsafeLoadCertificateExtensions, true)] + public static void LoadBigExponentRequest_Span(CertificateRequestLoadOptions options, bool oversized) + { + byte[] pkcs10 = TestData.BigExponentPkcs10Bytes; + + if (oversized) + { + Array.Resize(ref pkcs10, pkcs10.Length + 22); + } + + CertificateRequest req = CertificateRequest.LoadSigningRequest( + new ReadOnlySpan(pkcs10), + HashAlgorithmName.SHA256, + out int bytesConsumed, + options); + + Assert.Equal(TestData.BigExponentPkcs10Bytes.Length, bytesConsumed); + Assert.Equal(HashAlgorithmName.SHA256, req.HashAlgorithm); + VerifyBigExponentRequest(req, options); + } + + [Theory] + [InlineData(CertificateRequestLoadOptions.Default)] + [InlineData(CertificateRequestLoadOptions.UnsafeLoadCertificateExtensions)] + public static void LoadBigExponentRequest_Bytes(CertificateRequestLoadOptions options) + { + CertificateRequest req = CertificateRequest.LoadSigningRequest( + TestData.BigExponentPkcs10Bytes, + HashAlgorithmName.SHA384, + options); + + Assert.Equal(HashAlgorithmName.SHA384, req.HashAlgorithm); + VerifyBigExponentRequest(req, options); + } + + [Theory] + [InlineData(CertificateRequestLoadOptions.Default)] + [InlineData(CertificateRequestLoadOptions.UnsafeLoadCertificateExtensions)] + public static void LoadBigExponentRequest_Bytes_Oversized(CertificateRequestLoadOptions options) + { + byte[] pkcs10 = TestData.BigExponentPkcs10Bytes; + Array.Resize(ref pkcs10, pkcs10.Length + 1); + + Assert.Throws( + () => CertificateRequest.LoadSigningRequest( + pkcs10, + HashAlgorithmName.SHA384, + options)); + } + + [Theory] + [InlineData(CertificateRequestLoadOptions.Default, false)] + [InlineData(CertificateRequestLoadOptions.UnsafeLoadCertificateExtensions, false)] + [InlineData(CertificateRequestLoadOptions.Default, true)] + [InlineData(CertificateRequestLoadOptions.UnsafeLoadCertificateExtensions, true)] + public static void LoadBigExponentRequest_PemString(CertificateRequestLoadOptions options, bool multiPem) + { + string pem = TestData.BigExponentPkcs10Pem; + + if (multiPem) + { + pem = $@" +-----BEGIN UNRELATED----- +abcd +-----END UNRELATED----- +-----BEGIN CERTIFICATE REQUEST----- +!!!!!INVALID!!!!! +-----END CERTIFICATE REQUEST----- +-----BEGIN MORE UNRELATED----- +efgh +-----END MORE UNRELATED----- +{pem} +-----BEGIN CERTIFICATE REQUEST----- +!!!!!INVALID!!!!! +-----END CERTIFICATE REQUEST-----"; + } + + CertificateRequest req = CertificateRequest.LoadSigningRequestPem( + pem, + HashAlgorithmName.SHA512, + options); + + Assert.Equal(HashAlgorithmName.SHA512, req.HashAlgorithm); + VerifyBigExponentRequest(req, options); + } + + [Theory] + [InlineData(CertificateRequestLoadOptions.Default, false)] + [InlineData(CertificateRequestLoadOptions.UnsafeLoadCertificateExtensions, false)] + [InlineData(CertificateRequestLoadOptions.Default, true)] + [InlineData(CertificateRequestLoadOptions.UnsafeLoadCertificateExtensions, true)] + public static void LoadBigExponentRequest_PemSpam(CertificateRequestLoadOptions options, bool multiPem) + { + string pem = TestData.BigExponentPkcs10Pem; + + if (multiPem) + { + pem = $@" +-----BEGIN UNRELATED----- +abcd +-----END UNRELATED----- +Free Floating Text +-----BEGIN CERTIFICATE REQUEST----- +!!!!!INVALID!!!!! +-----END CERTIFICATE REQUEST----- +-----BEGIN MORE UNRELATED----- +efgh +-----END MORE UNRELATED----- +More Text. +{pem} +-----BEGIN CERTIFICATE REQUEST----- +!!!!!INVALID!!!!! +-----END CERTIFICATE REQUEST-----"; + } + + CertificateRequest req = CertificateRequest.LoadSigningRequestPem( + pem.AsSpan(), + HashAlgorithmName.SHA1, + options); + + Assert.Equal(HashAlgorithmName.SHA1, req.HashAlgorithm); + VerifyBigExponentRequest(req, options); + } + + [Fact] + public static void HashAlgorithmLaxInLoad() + { + HashAlgorithmName hashAlgorithm = new HashAlgorithmName("I promise to be a hash algorithm"); + + CertificateRequest req = CertificateRequest.LoadSigningRequest( + TestData.BigExponentPkcs10Bytes, + hashAlgorithm); + + Assert.Equal(hashAlgorithm, req.HashAlgorithm); + } + + [Fact] + public static void LoadPem_NoMatch() + { + CryptographicException ex; + + const string NoMatchPem = @" +-----BEGIN CERTIFICATE REQUEST----- +%% Not Base64 %% +-----END CERTIFICATE REQUEST----- +-----BEGIN CERTIFICATE----- +AQAB +-----END CERTIFICATE-----"; + + ex = Assert.Throws( + () => CertificateRequest.LoadSigningRequestPem(NoMatchPem, HashAlgorithmName.SHA256)); + + Assert.Contains("CERTIFICATE REQUEST", ex.Message); + + ex = Assert.Throws( + () => CertificateRequest.LoadSigningRequestPem( + NoMatchPem.AsSpan(), + HashAlgorithmName.SHA256)); + + Assert.Contains("CERTIFICATE REQUEST", ex.Message); + } + + [Fact] + public static void LoadWithAttributes() + { + // Generated by `openssl req -new -keyin bigexponent.pem` where bigexponent.pem + // represents the PKCS8 of TestData.RsaBigExponentParams + // All default values were taken, except + // Challenge password: 1234 (vs unspecified) + // An optional company name: Fabrikam (vs unspecified) + const string Pkcs10Pem = + "-----BEGIN CERTIFICATE REQUEST-----\n" + + "MIICujCCAaICAQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx\n" + + "ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASQwDQYJKoZIhvcN\n" + + "AQEBBQADggERADCCAQwCggEBAK+BwcvYID9iSlOe1mCBdTcjk6KDfUiQ5IoZ3tNp\n" + + "cxFWIJaNa+DT2qOKp3e+Au4La5O3JOjcwStjK0+oC7ySW85iT0ynzGBjBrOUA+KM\n" + + "ky0k3VRv/k72o38QdwsiFeqMu1v0J+jE2Jt56zODdRAMX4PlXem0Rm3fvu5CU5rv\n" + + "M+8Ye3dgw7GhshA8LYFEVkoMEDmgnIXPa1l061FvyNZiPJSuOloLs7THkpV9QyOR\n" + + "Vmzz4qUq+wwUK54GgbiXJnGvK4LdOQo5uTnPcZVoaH5JkKYwUMp3aNzWs3iELxj9\n" + + "sfbZ/wlrr3vrmNz5MNZvz9UD9Y1Bv/RiEuJOOvxF6kK9iEcCBQIAAARBoC4wEwYJ\n" + + "KoZIhvcNAQkHMQYMBDEyMzQwFwYJKoZIhvcNAQkCMQoMCEZhYnJpa2FtMA0GCSqG\n" + + "SIb3DQEBCwUAA4IBAQCr1X8D+ZkJqBmuZVEYqLPvNvie+KBycxgiJ08ZaV/dyndZ\n" + + "cudn6G9K0hiIwwGrfI5gbIb7QdPi64g3l9VdIrdH3yvQ6AcOZ644paiUUpe3u93l\n" + + "DTY+BGN7C0reJwL7ehalIrtS7hLKAQerg/qS7JO9aLRTbIXR52BQIUs9htYeATC5\n" + + "VHHssrOZpIHqIN4oaZbE0BwZm0ap6RVD80Oexko8pjiz9XNmtUWadeXXtezuOWTb\n" + + "duuJlh31kITIrbWVoMawMRq6JwNTPAFyiDMB/EFIvjxpUoS5yJe14bT8Hw2XvAFK\n" + + "Z9jOhHEPmAsasfRRSwr6CXyIKqo1HVT1ARPgHKHX\n" + + "-----END CERTIFICATE REQUEST-----"; + + CertificateRequest req = CertificateRequest.LoadSigningRequestPem( + Pkcs10Pem, + HashAlgorithmName.SHA384); + + Assert.Equal(2, req.OtherRequestAttributes.Count); + + AsnEncodedData attr = req.OtherRequestAttributes[0]; + Assert.Equal("1.2.840.113549.1.9.7", attr.Oid.Value); + Assert.Equal("0C0431323334", attr.RawData.ByteArrayToHex()); + + attr = req.OtherRequestAttributes[1]; + Assert.Equal("1.2.840.113549.1.9.2", attr.Oid.Value); + Assert.Equal("0C0846616272696B616D", attr.RawData.ByteArrayToHex()); + } + + [Fact] + public static void LoadUnsortedAttributes() + { + // This is TestData.BigExponentPkcs10Bytes, except + // * A challenge password was added after the extensions requests + // * The signature was changed to just 1 bit, to cut down on space. + const string Pkcs10Pem = @" +-----BEGIN CERTIFICATE REQUEST----- +MIICJTCCAg4CAQAwgYoxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u +MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp +b24xIDAeBgNVBAsTFy5ORVQgRnJhbWV3b3JrIChDb3JlRlgpMRIwEAYDVQQDEwls +b2NhbGhvc3QwggEkMA0GCSqGSIb3DQEBAQUAA4IBEQAwggEMAoIBAQCvgcHL2CA/ +YkpTntZggXU3I5Oig31IkOSKGd7TaXMRViCWjWvg09qjiqd3vgLuC2uTtyTo3MEr +YytPqAu8klvOYk9Mp8xgYwazlAPijJMtJN1Ub/5O9qN/EHcLIhXqjLtb9CfoxNib +eeszg3UQDF+D5V3ptEZt377uQlOa7zPvGHt3YMOxobIQPC2BRFZKDBA5oJyFz2tZ +dOtRb8jWYjyUrjpaC7O0x5KVfUMjkVZs8+KlKvsMFCueBoG4lyZxryuC3TkKObk5 +z3GVaGh+SZCmMFDKd2jc1rN4hC8Y/bH22f8Ja69765jc+TDWb8/VA/WNQb/0YhLi +Tjr8RepCvYhHAgUCAAAEQaBUMD0GCSqGSIb3DQEJDjEwMC4wLAYDVR0RBCUwI4cE +fwAAAYcQAAAAAAAAAAAAAAAAAAAAAYIJbG9jYWxob3N0MBMGCSqGSIb3DQEJBzEG +DAQxMjM0MA0GCSqGSIb3DQEBCwUAAwIHgA== +-----END CERTIFICATE REQUEST-----"; + + Assert.Throws( + () => CertificateRequest.LoadSigningRequestPem( + Pkcs10Pem, + HashAlgorithmName.SHA256, + CertificateRequestLoadOptions.SkipSignatureValidation)); + } + + [Fact] + public static void LoadDuplicateExtensionRequests() + { + // This is TestData.BigExponentPkcs10Bytes, except + // * A challenge password was added before the extensions requests + // * The extensions requests attribute was cloned (appears twice) + // * The signature was changed to just 1 bit, to cut down on space. + const string Pkcs10Pem = @" +-----BEGIN CERTIFICATE REQUEST----- +MIICZTCCAk4CAQAwgYoxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u +MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp +b24xIDAeBgNVBAsTFy5ORVQgRnJhbWV3b3JrIChDb3JlRlgpMRIwEAYDVQQDEwls +b2NhbGhvc3QwggEkMA0GCSqGSIb3DQEBAQUAA4IBEQAwggEMAoIBAQCvgcHL2CA/ +YkpTntZggXU3I5Oig31IkOSKGd7TaXMRViCWjWvg09qjiqd3vgLuC2uTtyTo3MEr +YytPqAu8klvOYk9Mp8xgYwazlAPijJMtJN1Ub/5O9qN/EHcLIhXqjLtb9CfoxNib +eeszg3UQDF+D5V3ptEZt377uQlOa7zPvGHt3YMOxobIQPC2BRFZKDBA5oJyFz2tZ +dOtRb8jWYjyUrjpaC7O0x5KVfUMjkVZs8+KlKvsMFCueBoG4lyZxryuC3TkKObk5 +z3GVaGh+SZCmMFDKd2jc1rN4hC8Y/bH22f8Ja69765jc+TDWb8/VA/WNQb/0YhLi +Tjr8RepCvYhHAgUCAAAEQaCBkzATBgkqhkiG9w0BCQcxBgwEMTIzNDA9BgkqhkiG +9w0BCQ4xMDAuMCwGA1UdEQQlMCOHBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAGCCWxv +Y2FsaG9zdDA9BgkqhkiG9w0BCQ4xMDAuMCwGA1UdEQQlMCOHBH8AAAGHEAAAAAAA +AAAAAAAAAAAAAAGCCWxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAMCB4A= +-----END CERTIFICATE REQUEST----- +"; + + CryptographicException ex = Assert.Throws( + () => CertificateRequest.LoadSigningRequestPem( + Pkcs10Pem, + HashAlgorithmName.SHA256, + CertificateRequestLoadOptions.SkipSignatureValidation)); + + Assert.Contains("Extension Request", ex.Message); + } + + [Fact] + public static void LoadMultipleExtensionRequestsInOneAttribute() + { + // This is TestData.BigExponentPkcs10Bytes, except + // * A challenge password was added before the extensions requests + // * The extensions requests attribute value was cloned within the one attribute node + // * The signature was changed to just 1 bit, to cut down on space. + const string Pkcs10Pem = @" +-----BEGIN CERTIFICATE REQUEST----- +MIICVjCCAj8CAQAwgYoxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u +MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp +b24xIDAeBgNVBAsTFy5ORVQgRnJhbWV3b3JrIChDb3JlRlgpMRIwEAYDVQQDEwls +b2NhbGhvc3QwggEkMA0GCSqGSIb3DQEBAQUAA4IBEQAwggEMAoIBAQCvgcHL2CA/ +YkpTntZggXU3I5Oig31IkOSKGd7TaXMRViCWjWvg09qjiqd3vgLuC2uTtyTo3MEr +YytPqAu8klvOYk9Mp8xgYwazlAPijJMtJN1Ub/5O9qN/EHcLIhXqjLtb9CfoxNib +eeszg3UQDF+D5V3ptEZt377uQlOa7zPvGHt3YMOxobIQPC2BRFZKDBA5oJyFz2tZ +dOtRb8jWYjyUrjpaC7O0x5KVfUMjkVZs8+KlKvsMFCueBoG4lyZxryuC3TkKObk5 +z3GVaGh+SZCmMFDKd2jc1rN4hC8Y/bH22f8Ja69765jc+TDWb8/VA/WNQb/0YhLi +Tjr8RepCvYhHAgUCAAAEQaCBhDATBgkqhkiG9w0BCQcxBgwEMTIzNDBtBgkqhkiG +9w0BCQ4xYDAuMCwGA1UdEQQlMCOHBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAGCCWxv +Y2FsaG9zdDAuMCwGA1UdEQQlMCOHBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAGCCWxv +Y2FsaG9zdDANBgkqhkiG9w0BAQsFAAMCB4A= +-----END CERTIFICATE REQUEST-----"; + + CryptographicException ex = Assert.Throws( + () => CertificateRequest.LoadSigningRequestPem( + Pkcs10Pem, + HashAlgorithmName.SHA256, + CertificateRequestLoadOptions.SkipSignatureValidation)); + + Assert.Contains("Extension Request", ex.Message); + } + + [Theory] + [InlineData("SHA256")] + [InlineData("SHA384")] + [InlineData("SHA512")] + [InlineData("SHA1")] + public static void VerifySignature_RSA_PKCS1(string hashAlgorithm) + { + HashAlgorithmName hashAlgorithmName = new HashAlgorithmName(hashAlgorithm); + + using (RSA key = RSA.Create()) + { + CertificateRequest first = new CertificateRequest( + "CN=Test", + key, + hashAlgorithmName, + RSASignaturePadding.Pkcs1); + + byte[] pkcs10; + + if (hashAlgorithm == "SHA1") + { + pkcs10 = first.CreateSigningRequest(new RSASha1Pkcs1SignatureGenerator(key)); + } + else + { + pkcs10 = first.CreateSigningRequest(); + } + + // Assert.NoThrow + CertificateRequest.LoadSigningRequest(pkcs10, hashAlgorithmName, out _); + + pkcs10[^1] ^= 0xFF; + + Assert.Throws( + () => CertificateRequest.LoadSigningRequest(pkcs10, hashAlgorithmName, out _)); + + // Assert.NoThrow + CertificateRequest.LoadSigningRequest( + pkcs10, + hashAlgorithmName, + out _, + CertificateRequestLoadOptions.SkipSignatureValidation); + } + } + + [Theory] + [InlineData("SHA256")] + [InlineData("SHA384")] + [InlineData("SHA512")] + [InlineData("SHA1")] + public static void VerifySignature_RSA_PSS(string hashAlgorithm) + { + HashAlgorithmName hashAlgorithmName = new HashAlgorithmName(hashAlgorithm); + + using (RSA key = RSA.Create()) + { + CertificateRequest first = new CertificateRequest( + "CN=Test", + key, + hashAlgorithmName, + RSASignaturePadding.Pss); + + byte[] pkcs10; + + if (hashAlgorithm == "SHA1") + { + pkcs10 = first.CreateSigningRequest(new RSASha1PssSignatureGenerator(key)); + } + else + { + pkcs10 = first.CreateSigningRequest(); + } + + // Assert.NoThrow + CertificateRequest.LoadSigningRequest(pkcs10, hashAlgorithmName, out _); + + pkcs10[^1] ^= 0xFF; + + Assert.Throws( + () => CertificateRequest.LoadSigningRequest(pkcs10, hashAlgorithmName, out _)); + + // Assert.NoThrow + CertificateRequest.LoadSigningRequest( + pkcs10, + hashAlgorithmName, + out _, + CertificateRequestLoadOptions.SkipSignatureValidation); + } + } + + [Theory] + [InlineData("SHA256")] + [InlineData("SHA1")] + public static void VerifySignature_DSA(string hashAlgorithm) + { + HashAlgorithmName hashAlgorithmName = new HashAlgorithmName(hashAlgorithm); + + using (DSA key = DSA.Create(TestData.GetDSA1024Params())) + { + DSAX509SignatureGenerator generator = new DSAX509SignatureGenerator(key); + + CertificateRequest first = new CertificateRequest( + new X500DistinguishedName("CN=Test"), + generator.PublicKey, + hashAlgorithmName); + + byte[] pkcs10 = first.CreateSigningRequest(generator); + + // The inbox version doesn't support DSA + Assert.Throws( + () => CertificateRequest.LoadSigningRequest(pkcs10, hashAlgorithmName, out _)); + + // Assert.NoThrow + CertificateRequest.LoadSigningRequest( + pkcs10, + hashAlgorithmName, + out _, + CertificateRequestLoadOptions.SkipSignatureValidation); + } + } + + [Fact] + public static void LoadAndSignRequest_NoRSAPadding() + { + DateTimeOffset now = DateTimeOffset.UtcNow; + DateTimeOffset notBefore = now.AddMonths(-1); + DateTimeOffset notAfter = now.AddMonths(1); + + using (RSA rootKey = RSA.Create(TestData.RsaBigExponentParams)) + { + CertificateRequest rootReq = new CertificateRequest( + "CN=Root", + rootKey, + HashAlgorithmName.SHA256, + RSASignaturePadding.Pkcs1); + + rootReq.CertificateExtensions.Add( + X509BasicConstraintsExtension.CreateForCertificateAuthority()); + + using (X509Certificate2 rootCert = rootReq.CreateSelfSigned(notBefore, notAfter)) + { + CertificateRequest req = CertificateRequest.LoadSigningRequestPem( + TestData.BigExponentPkcs10Pem, + HashAlgorithmName.SHA384); + + byte[] serial = new byte[] { 0x02, 0x04, 0x06, 0x08 }; + + Exception ex = Assert.Throws( + () => req.Create(rootCert, notBefore, notAfter, serial)); + + Assert.Contains(nameof(RSASignaturePadding), ex.Message); + Assert.Contains(nameof(X509SignatureGenerator), ex.Message); + + X509SignatureGenerator gen = + X509SignatureGenerator.CreateForRSA(rootKey, RSASignaturePadding.Pkcs1); + + X509Certificate2 issued = req.Create( + rootCert.SubjectName, + gen, + notBefore, + notAfter, + serial); + + using (issued) + { + Assert.Equal("1.2.840.113549.1.1.12", issued.SignatureAlgorithm.Value); + } + } + } + } + + [Fact] + public static void LoadAndSignRequest_WithRSAPadding() + { + DateTimeOffset now = DateTimeOffset.UtcNow; + DateTimeOffset notBefore = now.AddMonths(-1); + DateTimeOffset notAfter = now.AddMonths(1); + + using (RSA rootKey = RSA.Create(TestData.RsaBigExponentParams)) + { + CertificateRequest rootReq = new CertificateRequest( + "CN=Root", + rootKey, + HashAlgorithmName.SHA256, + RSASignaturePadding.Pkcs1); + + rootReq.CertificateExtensions.Add( + X509BasicConstraintsExtension.CreateForCertificateAuthority()); + + using (X509Certificate2 rootCert = rootReq.CreateSelfSigned(notBefore, notAfter)) + { + CertificateRequest req = CertificateRequest.LoadSigningRequestPem( + TestData.BigExponentPkcs10Pem, + HashAlgorithmName.SHA512, + signerSignaturePadding: RSASignaturePadding.Pkcs1); + + byte[] serial = new byte[] { 0x02, 0x04, 0x06, 0x08 }; + + X509Certificate2 issued = req.Create( + rootCert, + notBefore, + notAfter, + serial); + + using (issued) + { + Assert.Equal("1.2.840.113549.1.1.13", issued.SignatureAlgorithm.Value); + } + + // Using a generator overrides the decision + + X509SignatureGenerator gen = + X509SignatureGenerator.CreateForRSA(rootKey, RSASignaturePadding.Pss); + + issued = req.Create( + rootCert.SubjectName, + gen, + notBefore, + notAfter, + serial); + + using (issued) + { + Assert.Equal("1.2.840.113549.1.1.10", issued.SignatureAlgorithm.Value); + } + } + } + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void LoadAndSignRequest_ECDsaIgnoresRSAPadding(bool specifyAnyways) + { + DateTimeOffset now = DateTimeOffset.UtcNow; + DateTimeOffset notBefore = now.AddMonths(-1); + DateTimeOffset notAfter = now.AddMonths(1); + + using (ECDsa rootKey = ECDsa.Create(ECCurve.NamedCurves.nistP384)) + { + CertificateRequest rootReq = new CertificateRequest( + "CN=Root", + rootKey, + HashAlgorithmName.SHA384); + + rootReq.CertificateExtensions.Add( + X509BasicConstraintsExtension.CreateForCertificateAuthority()); + + using (X509Certificate2 rootCert = rootReq.CreateSelfSigned(notBefore, notAfter)) + { + RSASignaturePadding padding = specifyAnyways ? RSASignaturePadding.Pss : null; + + // A PKCS10 for an ECDSA key with no attributes at all. + const string Pkcs10Pem = @" +-----BEGIN CERTIFICATE REQUEST----- + MIIBCjCBkgIBADATMREwDwYDVQQDEwhOb3QgUm9vdDB2MBAGByqGSM49AgEGBSuB + BAAiA2IABATbHVzs8lyAElJbPxYW0PJWosOg6bdkQQvem8Qq8EXMGCLk13Hibxzb + eViS8ZTTq84sgRYpDhEQHwufix/MQ0gECe93LN1X6DZQgvvy1FVGm8XNtPTrZgGO + GwZ3IQmaBqAAMAoGCCqGSM49BAMDA2cAMGQCMHtDz2m+GnrwjJ9H7/UE578cePe1 + 1luBYpJcXKCAusDxsnvC8fAOkjXI6rwp9AVcjAIwIKoBVpkgyOzTDs+rEBJEQaKa + WK1BHMwWl7lY6Z0WrMIQuGsdljzpbeLk8h7Kdcbm + -----END CERTIFICATE REQUEST-----"; + + CertificateRequest req = CertificateRequest.LoadSigningRequestPem( + Pkcs10Pem, + HashAlgorithmName.SHA512, + signerSignaturePadding: padding); + + byte[] serial = new byte[] { 0x02, 0x04, 0x06, 0x08 }; + + X509Certificate2 issued = req.Create( + rootCert, + notBefore, + notAfter, + serial); + + using (issued) + { + Assert.Equal("1.2.840.10045.4.3.4", issued.SignatureAlgorithm.Value); + } + } + } + } + + [Fact] + public static void LoadRequestWithDuplicateAttributes() + { + // The output from CertificateRequestUsageTests.CreateSigningRequestWithDuplicateAttributes + const string Pkcs10Pem = + "-----BEGIN CERTIFICATE REQUEST-----\n" + + "MIIC/TCCAeUCAQAwgYoxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u\n" + + "MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp\n" + + "b24xIDAeBgNVBAsTFy5ORVQgRnJhbWV3b3JrIChDb3JlRlgpMRIwEAYDVQQDEwls\n" + + "b2NhbGhvc3QwggEkMA0GCSqGSIb3DQEBAQUAA4IBEQAwggEMAoIBAQCvgcHL2CA/\n" + + "YkpTntZggXU3I5Oig31IkOSKGd7TaXMRViCWjWvg09qjiqd3vgLuC2uTtyTo3MEr\n" + + "YytPqAu8klvOYk9Mp8xgYwazlAPijJMtJN1Ub/5O9qN/EHcLIhXqjLtb9CfoxNib\n" + + "eeszg3UQDF+D5V3ptEZt377uQlOa7zPvGHt3YMOxobIQPC2BRFZKDBA5oJyFz2tZ\n" + + "dOtRb8jWYjyUrjpaC7O0x5KVfUMjkVZs8+KlKvsMFCueBoG4lyZxryuC3TkKObk5\n" + + "z3GVaGh+SZCmMFDKd2jc1rN4hC8Y/bH22f8Ja69765jc+TDWb8/VA/WNQb/0YhLi\n" + + "Tjr8RepCvYhHAgUCAAAEQaArMBMGCSqGSIb3DQEJBzEGDAQxMjM0MBQGCSqGSIb3\n" + + "DQEJBzEHDAUxMjM0NTANBgkqhkiG9w0BAQsFAAOCAQEAB3lwd8z6XGmX6mbOo3Xm\n" + + "+ZyW4glQtJ51FAXA1zy83y5Uqyf85ZtTFl6UPw970x8KlSlY/9eMhyo/LORAwQql\n" + + "J8oga5ho2clJF62IJX9/Ih6JlmcMfyi9qEQaqsY/Og4IBSvxQo39SGzGFLv9mhxa\n" + + "R1YWoVggsbs638ph/T8Upz/GKb/0tBnGBThRZJip7HLugzzvSJGnirpp0fZhnwWM\n" + + "l1IlddN5/AdZ86j/r5RNlDKDHlwqI3UJ5Olb1iVFt00d/vwVRM09V1ZNIpiCmPv6\n" + + "MJG3L+NUKOpSUDXn9qtCxB0pd1MaZVit5EvJI98sKZhILRz3S5KXTxf+kBjNxC98\n" + + "AQ==\n" + + "-----END CERTIFICATE REQUEST-----"; + + CertificateRequest req = + CertificateRequest.LoadSigningRequestPem(Pkcs10Pem, HashAlgorithmName.SHA256); + + Assert.Equal(2, req.OtherRequestAttributes.Count); + + AsnEncodedData attr = req.OtherRequestAttributes[0]; + Assert.Equal("1.2.840.113549.1.9.7", attr.Oid.Value); + Assert.Equal("0C0431323334", attr.RawData.ByteArrayToHex()); + + attr = req.OtherRequestAttributes[1]; + Assert.Equal("1.2.840.113549.1.9.7", attr.Oid.Value); + Assert.Equal("0C053132333435", attr.RawData.ByteArrayToHex()); + } + + [Fact] + public static void LoadRequestWithAttributeValues() + { + // The output from CertificateRequestUsageTests.CreateSigningRequestWithDuplicateAttributes, + // but modified to be + // + // Attribute + // id: ChallengePassword + // values: + // cp1 + // cp2 + // + // instead of + // + // Attribute + // id: ChallengePassword + // values: + // cp1 + // Attribute + // id: ChallengePassword + // values: + // cp2 + // + // And then made the signature 0 bits long rather than really compute it. + const string Pkcs10Pem = @" +-----BEGIN CERTIFICATE REQUEST----- +MIIB7DCCAdYCAQAwgYoxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u +MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp +b24xIDAeBgNVBAsTFy5ORVQgRnJhbWV3b3JrIChDb3JlRlgpMRIwEAYDVQQDEwls +b2NhbGhvc3QwggEkMA0GCSqGSIb3DQEBAQUAA4IBEQAwggEMAoIBAQCvgcHL2CA/ +YkpTntZggXU3I5Oig31IkOSKGd7TaXMRViCWjWvg09qjiqd3vgLuC2uTtyTo3MEr +YytPqAu8klvOYk9Mp8xgYwazlAPijJMtJN1Ub/5O9qN/EHcLIhXqjLtb9CfoxNib +eeszg3UQDF+D5V3ptEZt377uQlOa7zPvGHt3YMOxobIQPC2BRFZKDBA5oJyFz2tZ +dOtRb8jWYjyUrjpaC7O0x5KVfUMjkVZs8+KlKvsMFCueBoG4lyZxryuC3TkKObk5 +z3GVaGh+SZCmMFDKd2jc1rN4hC8Y/bH22f8Ja69765jc+TDWb8/VA/WNQb/0YhLi +Tjr8RepCvYhHAgUCAAAEQaAcMBoGCSqGSIb3DQEJBzENDAQxMjM0DAUxMjM0NTAN +BgkqhkiG9w0BAQsFAAMBAA== +-----END CERTIFICATE REQUEST-----"; + + CertificateRequest req = + CertificateRequest.LoadSigningRequestPem( + Pkcs10Pem, + HashAlgorithmName.SHA256, + CertificateRequestLoadOptions.SkipSignatureValidation); + + Assert.Equal(2, req.OtherRequestAttributes.Count); + + AsnEncodedData attr = req.OtherRequestAttributes[0]; + Assert.Equal("1.2.840.113549.1.9.7", attr.Oid.Value); + Assert.Equal("0C0431323334", attr.RawData.ByteArrayToHex()); + + attr = req.OtherRequestAttributes[1]; + Assert.Equal("1.2.840.113549.1.9.7", attr.Oid.Value); + Assert.Equal("0C053132333435", attr.RawData.ByteArrayToHex()); + } + + private static void VerifyBigExponentRequest( + CertificateRequest req, + CertificateRequestLoadOptions options) + { + VerifyBigExponentRequest( + req, + (options & CertificateRequestLoadOptions.UnsafeLoadCertificateExtensions) != 0); + } + + private static void VerifyBigExponentRequest(CertificateRequest req, bool loadExtensions) + { + Assert.Equal("1.2.840.113549.1.1.1", req.PublicKey.Oid.Value); + Assert.Equal("0500", req.PublicKey.EncodedParameters.RawData.ByteArrayToHex()); + Assert.Null(req.PublicKey.EncodedParameters.Oid); + Assert.Null(req.PublicKey.EncodedKeyValue.Oid); + + Assert.Equal( + "3082010C0282010100AF81C1CBD8203F624A539ED6608175372393A2837D4890" + + "E48A19DED36973115620968D6BE0D3DAA38AA777BE02EE0B6B93B724E8DCC12B" + + "632B4FA80BBC925BCE624F4CA7CC606306B39403E28C932D24DD546FFE4EF6A3" + + "7F10770B2215EA8CBB5BF427E8C4D89B79EB338375100C5F83E55DE9B4466DDF" + + "BEEE42539AEF33EF187B7760C3B1A1B2103C2D8144564A0C1039A09C85CF6B59" + + "74EB516FC8D6623C94AE3A5A0BB3B4C792957D432391566CF3E2A52AFB0C142B" + + "9E0681B8972671AF2B82DD390A39B939CF719568687E4990A63050CA7768DCD6" + + "B378842F18FDB1F6D9FF096BAF7BEB98DCF930D66FCFD503F58D41BFF46212E2" + + "4E3AFC45EA42BD884702050200000441", + req.PublicKey.EncodedKeyValue.RawData.ByteArrayToHex()); + + Assert.Equal( + "CN=localhost, OU=.NET Framework (CoreFX), O=Microsoft Corporation, L=Redmond, S=Washington, C=US", + req.SubjectName.Name); + + if (loadExtensions) + { + Assert.Equal(1, req.CertificateExtensions.Count); + + X509SubjectAlternativeNameExtension san = + Assert.IsType(req.CertificateExtensions[0]); + + Assert.Equal(new[] { IPAddress.Loopback, IPAddress.IPv6Loopback }, san.EnumerateIPAddresses()); + Assert.Equal(new[] { "localhost" }, san.EnumerateDnsNames()); + } + else + { + Assert.Empty(req.CertificateExtensions); + } + + Assert.Empty(req.OtherRequestAttributes); + } + } +} diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/tests/CertificateCreation/CertificateRequestUsageTests.cs b/src/libraries/System.Security.Cryptography.X509Certificates/tests/CertificateCreation/CertificateRequestUsageTests.cs index d753f8e477da74..aa8624f5a73523 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/tests/CertificateCreation/CertificateRequestUsageTests.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/tests/CertificateCreation/CertificateRequestUsageTests.cs @@ -19,6 +19,8 @@ public static void ReproduceBigExponentCsr() byte[] autoCsr; byte[] csr; + string csrPem; + string autoCsrPem; using (RSA rsa = RSA.Create()) { @@ -33,13 +35,17 @@ public static void ReproduceBigExponentCsr() request.CertificateExtensions.Add(sanExtension); autoCsr = request.CreateSigningRequest(); + autoCsrPem = request.CreateSigningRequestPem(); X509SignatureGenerator generator = X509SignatureGenerator.CreateForRSA(rsa, RSASignaturePadding.Pkcs1); csr = request.CreateSigningRequest(generator); + csrPem = request.CreateSigningRequestPem(generator); } - Assert.Equal(TestData.BigExponentPkcs10Bytes.ByteArrayToHex(), autoCsr.ByteArrayToHex()); - Assert.Equal(TestData.BigExponentPkcs10Bytes.ByteArrayToHex(), csr.ByteArrayToHex()); + AssertExtensions.SequenceEqual(TestData.BigExponentPkcs10Bytes, autoCsr); + AssertExtensions.SequenceEqual(TestData.BigExponentPkcs10Bytes, csr); + Assert.Equal(TestData.BigExponentPkcs10Pem, autoCsrPem); + Assert.Equal(TestData.BigExponentPkcs10Pem, csrPem); } [Fact] @@ -948,6 +954,145 @@ public static void FractionalSecondsNotWritten(bool selfSigned) } } + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void CreateSigningRequestWithAttributes(bool reversed) + { + // Generated by `openssl req -new -keyin bigexponent.pem` where bigexponent.pem + // represents the PKCS8 of TestData.RsaBigExponentParams + // All default values were taken, except + // Challenge password: 1234 (vs unspecified) + // An optional company name: Fabrikam (vs unspecified) + const string ExpectedPem = + "-----BEGIN CERTIFICATE REQUEST-----\n" + + "MIICujCCAaICAQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx\n" + + "ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASQwDQYJKoZIhvcN\n" + + "AQEBBQADggERADCCAQwCggEBAK+BwcvYID9iSlOe1mCBdTcjk6KDfUiQ5IoZ3tNp\n" + + "cxFWIJaNa+DT2qOKp3e+Au4La5O3JOjcwStjK0+oC7ySW85iT0ynzGBjBrOUA+KM\n" + + "ky0k3VRv/k72o38QdwsiFeqMu1v0J+jE2Jt56zODdRAMX4PlXem0Rm3fvu5CU5rv\n" + + "M+8Ye3dgw7GhshA8LYFEVkoMEDmgnIXPa1l061FvyNZiPJSuOloLs7THkpV9QyOR\n" + + "Vmzz4qUq+wwUK54GgbiXJnGvK4LdOQo5uTnPcZVoaH5JkKYwUMp3aNzWs3iELxj9\n" + + "sfbZ/wlrr3vrmNz5MNZvz9UD9Y1Bv/RiEuJOOvxF6kK9iEcCBQIAAARBoC4wEwYJ\n" + + "KoZIhvcNAQkHMQYMBDEyMzQwFwYJKoZIhvcNAQkCMQoMCEZhYnJpa2FtMA0GCSqG\n" + + "SIb3DQEBCwUAA4IBAQCr1X8D+ZkJqBmuZVEYqLPvNvie+KBycxgiJ08ZaV/dyndZ\n" + + "cudn6G9K0hiIwwGrfI5gbIb7QdPi64g3l9VdIrdH3yvQ6AcOZ644paiUUpe3u93l\n" + + "DTY+BGN7C0reJwL7ehalIrtS7hLKAQerg/qS7JO9aLRTbIXR52BQIUs9htYeATC5\n" + + "VHHssrOZpIHqIN4oaZbE0BwZm0ap6RVD80Oexko8pjiz9XNmtUWadeXXtezuOWTb\n" + + "duuJlh31kITIrbWVoMawMRq6JwNTPAFyiDMB/EFIvjxpUoS5yJe14bT8Hw2XvAFK\n" + + "Z9jOhHEPmAsasfRRSwr6CXyIKqo1HVT1ARPgHKHX\n" + + "-----END CERTIFICATE REQUEST-----"; + + string builtPem; + + using (RSA key = RSA.Create(TestData.RsaBigExponentParams)) + { + X500DistinguishedNameBuilder nameBuilder = new(); + nameBuilder.AddOrganizationName("Internet Widgits Pty Ltd"); + nameBuilder.AddStateOrProvinceName("Some-State"); + nameBuilder.AddCountryOrRegion("AU"); + + CertificateRequest req = new CertificateRequest( + nameBuilder.Build(), + key, + HashAlgorithmName.SHA256, + RSASignaturePadding.Pkcs1); + + // Unstructured Name: UTF8String("Fabrikam") + AsnEncodedData unstructuredNameAttr = new AsnEncodedData( + new Oid("1.2.840.113549.1.9.2", null), + "0C0846616272696B616D".HexToByteArray()); + + // ChallengePassword + AsnEncodedData cpAttr = new AsnEncodedData( + new Oid("1.2.840.113549.1.9.7", null), + "0C0431323334".HexToByteArray()); + + // Request attributes are in a SET OF, which means they get sorted. + // So both orders here produce the same output. + if (reversed) + { + req.OtherRequestAttributes.Add(unstructuredNameAttr); + req.OtherRequestAttributes.Add(cpAttr); + } + else + { + req.OtherRequestAttributes.Add(cpAttr); + req.OtherRequestAttributes.Add(unstructuredNameAttr); + } + + builtPem = req.CreateSigningRequestPem(); + } + + Assert.Equal(ExpectedPem, builtPem); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void CreateSigningRequestWithDuplicateAttributes(bool reversed) + { + const string ExpectedPem = + "-----BEGIN CERTIFICATE REQUEST-----\n" + + "MIIC/TCCAeUCAQAwgYoxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u\n" + + "MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp\n" + + "b24xIDAeBgNVBAsTFy5ORVQgRnJhbWV3b3JrIChDb3JlRlgpMRIwEAYDVQQDEwls\n" + + "b2NhbGhvc3QwggEkMA0GCSqGSIb3DQEBAQUAA4IBEQAwggEMAoIBAQCvgcHL2CA/\n" + + "YkpTntZggXU3I5Oig31IkOSKGd7TaXMRViCWjWvg09qjiqd3vgLuC2uTtyTo3MEr\n" + + "YytPqAu8klvOYk9Mp8xgYwazlAPijJMtJN1Ub/5O9qN/EHcLIhXqjLtb9CfoxNib\n" + + "eeszg3UQDF+D5V3ptEZt377uQlOa7zPvGHt3YMOxobIQPC2BRFZKDBA5oJyFz2tZ\n" + + "dOtRb8jWYjyUrjpaC7O0x5KVfUMjkVZs8+KlKvsMFCueBoG4lyZxryuC3TkKObk5\n" + + "z3GVaGh+SZCmMFDKd2jc1rN4hC8Y/bH22f8Ja69765jc+TDWb8/VA/WNQb/0YhLi\n" + + "Tjr8RepCvYhHAgUCAAAEQaArMBMGCSqGSIb3DQEJBzEGDAQxMjM0MBQGCSqGSIb3\n" + + "DQEJBzEHDAUxMjM0NTANBgkqhkiG9w0BAQsFAAOCAQEAB3lwd8z6XGmX6mbOo3Xm\n" + + "+ZyW4glQtJ51FAXA1zy83y5Uqyf85ZtTFl6UPw970x8KlSlY/9eMhyo/LORAwQql\n" + + "J8oga5ho2clJF62IJX9/Ih6JlmcMfyi9qEQaqsY/Og4IBSvxQo39SGzGFLv9mhxa\n" + + "R1YWoVggsbs638ph/T8Upz/GKb/0tBnGBThRZJip7HLugzzvSJGnirpp0fZhnwWM\n" + + "l1IlddN5/AdZ86j/r5RNlDKDHlwqI3UJ5Olb1iVFt00d/vwVRM09V1ZNIpiCmPv6\n" + + "MJG3L+NUKOpSUDXn9qtCxB0pd1MaZVit5EvJI98sKZhILRz3S5KXTxf+kBjNxC98\n" + + "AQ==\n" + + "-----END CERTIFICATE REQUEST-----"; + + string output; + + using (RSA key = RSA.Create(TestData.RsaBigExponentParams)) + { + CertificateRequest req = new CertificateRequest( + "CN=localhost, OU=.NET Framework (CoreFX), O=Microsoft Corporation, L=Redmond, S=Washington, C=US", + key, + HashAlgorithmName.SHA256, + RSASignaturePadding.Pkcs1); + + // 1234 + AsnEncodedData cpAttr1 = new AsnEncodedData( + new Oid("1.2.840.113549.1.9.7", null), + "0C0431323334".HexToByteArray()); + + // 12345 + AsnEncodedData cpAttr2 = new AsnEncodedData( + new Oid("1.2.840.113549.1.9.7", null), + "0C053132333435".HexToByteArray()); + + if (reversed) + { + req.OtherRequestAttributes.Add(cpAttr2); + req.OtherRequestAttributes.Add(cpAttr1); + } + else + { + req.OtherRequestAttributes.Add(cpAttr1); + req.OtherRequestAttributes.Add(cpAttr2); + } + + // ChallengePassword is defined as SINGLE VALUE TRUE, + // so if we understood it as a rich concept we would block it. + // But, we don't, so this problem gets passed on to the entity reading the request. + output = req.CreateSigningRequestPem(); + } + + Assert.Equal(ExpectedPem, output); + } + private class InvalidSignatureGenerator : X509SignatureGenerator { private readonly byte[] _signatureAlgBytes; diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/tests/CertificateCreation/PrivateKeyAssociationTests.cs b/src/libraries/System.Security.Cryptography.X509Certificates/tests/CertificateCreation/PrivateKeyAssociationTests.cs index 6763e700d6984e..83c075cf67d4ea 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/tests/CertificateCreation/PrivateKeyAssociationTests.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/tests/CertificateCreation/PrivateKeyAssociationTests.cs @@ -549,28 +549,5 @@ public static void ThirdPartyProvider_ECDsa() Assert.True(ecdsaOther.VerifyData(data, signature, hashAlgorithm)); } } - - private sealed class RSASha1Pkcs1SignatureGenerator : X509SignatureGenerator - { - private readonly X509SignatureGenerator _realRsaGenerator; - - internal RSASha1Pkcs1SignatureGenerator(RSA rsa) - { - _realRsaGenerator = X509SignatureGenerator.CreateForRSA(rsa, RSASignaturePadding.Pkcs1); - } - - protected override PublicKey BuildPublicKey() => _realRsaGenerator.PublicKey; - - public override byte[] GetSignatureAlgorithmIdentifier(HashAlgorithmName hashAlgorithm) - { - if (hashAlgorithm == HashAlgorithmName.SHA1) - return "300D06092A864886F70D0101050500".HexToByteArray(); - - throw new InvalidOperationException(); - } - - public override byte[] SignData(byte[] data, HashAlgorithmName hashAlgorithm) => - _realRsaGenerator.SignData(data, hashAlgorithm); - } } } diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/tests/CertificateCreation/X509Sha1SignatureGenerators.cs b/src/libraries/System.Security.Cryptography.X509Certificates/tests/CertificateCreation/X509Sha1SignatureGenerators.cs new file mode 100644 index 00000000000000..0ed6da88215eb7 --- /dev/null +++ b/src/libraries/System.Security.Cryptography.X509Certificates/tests/CertificateCreation/X509Sha1SignatureGenerators.cs @@ -0,0 +1,76 @@ +// 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; + +namespace System.Security.Cryptography.X509Certificates.Tests.CertificateCreation +{ + internal sealed class ECDsaSha1SignatureGenerator : X509SignatureGenerator + { + private readonly X509SignatureGenerator _realGenerator; + + internal ECDsaSha1SignatureGenerator(ECDsa ecdsa) + { + _realGenerator = CreateForECDsa(ecdsa); + } + + protected override PublicKey BuildPublicKey() => _realGenerator.PublicKey; + + public override byte[] GetSignatureAlgorithmIdentifier(HashAlgorithmName hashAlgorithm) + { + if (hashAlgorithm == HashAlgorithmName.SHA1) + return "300906072A8648CE3D0401".HexToByteArray(); + + throw new InvalidOperationException(); + } + + public override byte[] SignData(byte[] data, HashAlgorithmName hashAlgorithm) => + _realGenerator.SignData(data, hashAlgorithm); + } + + internal sealed class RSASha1Pkcs1SignatureGenerator : X509SignatureGenerator + { + private readonly X509SignatureGenerator _realRsaGenerator; + + internal RSASha1Pkcs1SignatureGenerator(RSA rsa) + { + _realRsaGenerator = CreateForRSA(rsa, RSASignaturePadding.Pkcs1); + } + + protected override PublicKey BuildPublicKey() => _realRsaGenerator.PublicKey; + + public override byte[] GetSignatureAlgorithmIdentifier(HashAlgorithmName hashAlgorithm) + { + if (hashAlgorithm == HashAlgorithmName.SHA1) + return "300D06092A864886F70D0101050500".HexToByteArray(); + + throw new InvalidOperationException(); + } + + public override byte[] SignData(byte[] data, HashAlgorithmName hashAlgorithm) => + _realRsaGenerator.SignData(data, hashAlgorithm); + } + + internal sealed class RSASha1PssSignatureGenerator : X509SignatureGenerator + { + private readonly X509SignatureGenerator _realRsaGenerator; + + internal RSASha1PssSignatureGenerator(RSA rsa) + { + _realRsaGenerator = CreateForRSA(rsa, RSASignaturePadding.Pss); + } + + protected override PublicKey BuildPublicKey() => _realRsaGenerator.PublicKey; + + public override byte[] GetSignatureAlgorithmIdentifier(HashAlgorithmName hashAlgorithm) + { + if (hashAlgorithm == HashAlgorithmName.SHA1) + return "300D06092A864886F70D01010A3000".HexToByteArray(); + + throw new InvalidOperationException(); + } + + public override byte[] SignData(byte[] data, HashAlgorithmName hashAlgorithm) => + _realRsaGenerator.SignData(data, hashAlgorithm); + } +} diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/tests/System.Security.Cryptography.X509Certificates.Tests.csproj b/src/libraries/System.Security.Cryptography.X509Certificates/tests/System.Security.Cryptography.X509Certificates.Tests.csproj index 77cee2990ffdc2..f9ea150bfae71e 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/tests/System.Security.Cryptography.X509Certificates.Tests.csproj +++ b/src/libraries/System.Security.Cryptography.X509Certificates/tests/System.Security.Cryptography.X509Certificates.Tests.csproj @@ -68,6 +68,7 @@ + @@ -77,6 +78,7 @@ + diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/tests/TestData.cs b/src/libraries/System.Security.Cryptography.X509Certificates/tests/TestData.cs index a0151384fe999a..92d8606e3093b4 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/tests/TestData.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/tests/TestData.cs @@ -2334,6 +2334,27 @@ internal struct ECDsaCngKeyValues "699B3C957C6DD22E9B63DBAE3B5AE62919F0EA3DF304C7DD9E0BBA0E7053605F" + "D066A788426159BB937C58E5A110461DC9364CA7CA").HexToByteArray(); + internal const string BigExponentPkcs10Pem = + "-----BEGIN CERTIFICATE REQUEST-----\n" + + "MIIDETCCAfkCAQAwgYoxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u\n" + + "MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp\n" + + "b24xIDAeBgNVBAsTFy5ORVQgRnJhbWV3b3JrIChDb3JlRlgpMRIwEAYDVQQDEwls\n" + + "b2NhbGhvc3QwggEkMA0GCSqGSIb3DQEBAQUAA4IBEQAwggEMAoIBAQCvgcHL2CA/\n" + + "YkpTntZggXU3I5Oig31IkOSKGd7TaXMRViCWjWvg09qjiqd3vgLuC2uTtyTo3MEr\n" + + "YytPqAu8klvOYk9Mp8xgYwazlAPijJMtJN1Ub/5O9qN/EHcLIhXqjLtb9CfoxNib\n" + + "eeszg3UQDF+D5V3ptEZt377uQlOa7zPvGHt3YMOxobIQPC2BRFZKDBA5oJyFz2tZ\n" + + "dOtRb8jWYjyUrjpaC7O0x5KVfUMjkVZs8+KlKvsMFCueBoG4lyZxryuC3TkKObk5\n" + + "z3GVaGh+SZCmMFDKd2jc1rN4hC8Y/bH22f8Ja69765jc+TDWb8/VA/WNQb/0YhLi\n" + + "Tjr8RepCvYhHAgUCAAAEQaA/MD0GCSqGSIb3DQEJDjEwMC4wLAYDVR0RBCUwI4cE\n" + + "fwAAAYcQAAAAAAAAAAAAAAAAAAAAAYIJbG9jYWxob3N0MA0GCSqGSIb3DQEBCwUA\n" + + "A4IBAQA7yufgLTqChDURDIplGX/xoCfsWso36+KbbnCTpL3Km9qOJE3AWEaqnxht\n" + + "LrvfZHS7Cez1o8EfOn5W2dSJw9SuLc9dUqv83+1tRiOvfH0uUqGJvEoL/F65bsFY\n" + + "qWspLfbkrcrlIzp+FZhETiP3MlJrcRciZuRXBvkO+rCUWnXURvCmVHx4jdga1vTR\n" + + "5/0OiIQIOvUgA9nNOLOhQPLlUs8/vwtMdx5XRcbabybc/Q/rh7n90vRySgneH7TF\n" + + "XkOfQ8bjeoZroZSUshDSlGmbPJV8bdIum2Pbrjta5ikZ8Oo98wTH3Z4Lug5wU2Bf\n" + + "0GaniEJhWbuTfFjloRBGHck2TKfK\n" + + "-----END CERTIFICATE REQUEST-----"; + internal static byte[] EmptySubjectCertificate = ( "308202A73082018FA003020102020103300D06092A864886F70D01010B050030" + "1F311D301B06035504031314456D707479205375626A65637420497373756572" + diff --git a/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs b/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs index 8321a27eb42010..e2db462113fcb1 100644 --- a/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs +++ b/src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs @@ -2451,10 +2451,12 @@ public sealed partial class CertificateRequest public CertificateRequest(System.Security.Cryptography.X509Certificates.X500DistinguishedName subjectName, System.Security.Cryptography.ECDsa key, System.Security.Cryptography.HashAlgorithmName hashAlgorithm) { } public CertificateRequest(System.Security.Cryptography.X509Certificates.X500DistinguishedName subjectName, System.Security.Cryptography.RSA key, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.Security.Cryptography.RSASignaturePadding padding) { } public CertificateRequest(System.Security.Cryptography.X509Certificates.X500DistinguishedName subjectName, System.Security.Cryptography.X509Certificates.PublicKey publicKey, System.Security.Cryptography.HashAlgorithmName hashAlgorithm) { } + public CertificateRequest(System.Security.Cryptography.X509Certificates.X500DistinguishedName subjectName, System.Security.Cryptography.X509Certificates.PublicKey publicKey, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.Security.Cryptography.RSASignaturePadding? rsaSignaturePadding = null) { } public CertificateRequest(string subjectName, System.Security.Cryptography.ECDsa key, System.Security.Cryptography.HashAlgorithmName hashAlgorithm) { } public CertificateRequest(string subjectName, System.Security.Cryptography.RSA key, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.Security.Cryptography.RSASignaturePadding padding) { } public System.Collections.ObjectModel.Collection CertificateExtensions { get { throw null; } } public System.Security.Cryptography.HashAlgorithmName HashAlgorithm { get { throw null; } } + public System.Collections.ObjectModel.Collection OtherRequestAttributes { get { throw null; } } public System.Security.Cryptography.X509Certificates.PublicKey PublicKey { get { throw null; } } public System.Security.Cryptography.X509Certificates.X500DistinguishedName SubjectName { get { throw null; } } public System.Security.Cryptography.X509Certificates.X509Certificate2 Create(System.Security.Cryptography.X509Certificates.X500DistinguishedName issuerName, System.Security.Cryptography.X509Certificates.X509SignatureGenerator generator, System.DateTimeOffset notBefore, System.DateTimeOffset notAfter, byte[] serialNumber) { throw null; } @@ -2464,6 +2466,19 @@ public CertificateRequest(string subjectName, System.Security.Cryptography.RSA k public System.Security.Cryptography.X509Certificates.X509Certificate2 CreateSelfSigned(System.DateTimeOffset notBefore, System.DateTimeOffset notAfter) { throw null; } public byte[] CreateSigningRequest() { throw null; } public byte[] CreateSigningRequest(System.Security.Cryptography.X509Certificates.X509SignatureGenerator signatureGenerator) { throw null; } + public string CreateSigningRequestPem() { throw null; } + public string CreateSigningRequestPem(System.Security.Cryptography.X509Certificates.X509SignatureGenerator signatureGenerator) { throw null; } + public static System.Security.Cryptography.X509Certificates.CertificateRequest LoadSigningRequest(byte[] pkcs10, System.Security.Cryptography.HashAlgorithmName signerHashAlgorithm, System.Security.Cryptography.X509Certificates.CertificateRequestLoadOptions options = System.Security.Cryptography.X509Certificates.CertificateRequestLoadOptions.Default, System.Security.Cryptography.RSASignaturePadding? signerSignaturePadding = null) { throw null; } + public static System.Security.Cryptography.X509Certificates.CertificateRequest LoadSigningRequest(System.ReadOnlySpan pkcs10, System.Security.Cryptography.HashAlgorithmName signerHashAlgorithm, out int bytesConsumed, System.Security.Cryptography.X509Certificates.CertificateRequestLoadOptions options = System.Security.Cryptography.X509Certificates.CertificateRequestLoadOptions.Default, System.Security.Cryptography.RSASignaturePadding? signerSignaturePadding = null) { throw null; } + public static System.Security.Cryptography.X509Certificates.CertificateRequest LoadSigningRequestPem(System.ReadOnlySpan pkcs10Pem, System.Security.Cryptography.HashAlgorithmName signerHashAlgorithm, System.Security.Cryptography.X509Certificates.CertificateRequestLoadOptions options = System.Security.Cryptography.X509Certificates.CertificateRequestLoadOptions.Default, System.Security.Cryptography.RSASignaturePadding? signerSignaturePadding = null) { throw null; } + public static System.Security.Cryptography.X509Certificates.CertificateRequest LoadSigningRequestPem(string pkcs10Pem, System.Security.Cryptography.HashAlgorithmName signerHashAlgorithm, System.Security.Cryptography.X509Certificates.CertificateRequestLoadOptions options = System.Security.Cryptography.X509Certificates.CertificateRequestLoadOptions.Default, System.Security.Cryptography.RSASignaturePadding? signerSignaturePadding = null) { throw null; } + } + [System.FlagsAttribute] + public enum CertificateRequestLoadOptions + { + Default = 0, + SkipSignatureValidation = 1, + UnsafeLoadCertificateExtensions = 2, } public sealed partial class CertificateRevocationListBuilder { diff --git a/src/libraries/System.Security.Cryptography/src/Resources/Strings.resx b/src/libraries/System.Security.Cryptography/src/Resources/Strings.resx index fb9fdee0f82741..d20ccd721f9787 100644 --- a/src/libraries/System.Security.Cryptography/src/Resources/Strings.resx +++ b/src/libraries/System.Security.Cryptography/src/Resources/Strings.resx @@ -228,6 +228,9 @@ An X509Extension with OID '{0}' has already been specified. + + The OtherRequestAttributes collection contains a PKCS#9 Extension Request Attribute. This attribute cannot be manually specified, it is generated via the CertificateExtensions property. + The issuer certificate does not have an appropriate value for the Basic Constraints extension. @@ -237,6 +240,18 @@ The provided issuer certificate does not have an associated private key. + + The PKCS#10 Certification Request contains multiple PKCS#9 Extension Request attributes. + + + The provided PKCS#10 Certification Request value has version '{0}', but version '{1}' is the maximum supported. + + + The collection in the '{0}' property contains a value that has a null Oid value. + + + The collection in the '{0}' property contains a null value. + This method cannot be used since no signing key was provided via a constructor, use an overload accepting an X509SignatureGenerator instead. @@ -249,6 +264,9 @@ The issuer certificate uses an RSA key but no RSASignaturePadding was provided to a constructor. If one cannot be provided, use the X509SignatureGenerator overload. + + The public key within the PKCS#10 Certification Request did not verify the embedded signature. + The specified feedback size '{0}' for CipherMode '{1}' is not supported. @@ -525,6 +543,21 @@ The provided PFX data contains no certificates. + + Invalid signature parameters. + + + This platform requires that the PSS hash algorithm ({0}) match the data digest algorithm ({1}). + + + This platform does not support the MGF hash algorithm ({0}) being different from the signature hash algorithm ({1}). + + + Mask generation function '{0}' is not supported by this platform. + + + PSS salt size {0} is not supported by this platform with hash algorithm {1}. + The EncryptedPrivateKeyInfo structure was decoded but was not successfully interpreted, the password may be incorrect. diff --git a/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj b/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj index 8b7ade84fd4722..f43aea595b59d3 100644 --- a/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj +++ b/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj @@ -167,6 +167,10 @@ Common\System\Security\Cryptography\Asn1\PssParamsAsn.xml + + Common\System\Security\Cryptography\Asn1\PssParamsAsn.manual.cs + Common\System\Security\Cryptography\Asn1\PssParamsAsn.xml + Common\System\Security\Cryptography\Asn1\PssParamsAsn.xml.cs Common\System\Security\Cryptography\Asn1\PssParamsAsn.xml @@ -416,6 +420,8 @@ + + diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/CertificateRequest.Load.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/CertificateRequest.Load.cs new file mode 100644 index 00000000000000..a6f09c355504cf --- /dev/null +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/CertificateRequest.Load.cs @@ -0,0 +1,393 @@ +// 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.Diagnostics; +using System.Formats.Asn1; +using System.Security.Cryptography.Asn1; +using System.Security.Cryptography.X509Certificates.Asn1; + +namespace System.Security.Cryptography.X509Certificates +{ + public sealed partial class CertificateRequest + { + private const CertificateRequestLoadOptions AllOptions = + CertificateRequestLoadOptions.SkipSignatureValidation | + CertificateRequestLoadOptions.UnsafeLoadCertificateExtensions; + + public static unsafe CertificateRequest LoadSigningRequestPem( + string pkcs10Pem, + HashAlgorithmName signerHashAlgorithm, + CertificateRequestLoadOptions options = CertificateRequestLoadOptions.Default, + RSASignaturePadding? signerSignaturePadding = null) + { + ArgumentNullException.ThrowIfNull(pkcs10Pem); + + return LoadSigningRequestPem( + pkcs10Pem.AsSpan(), + signerHashAlgorithm, + options, + signerSignaturePadding); + } + + public static unsafe CertificateRequest LoadSigningRequestPem( + ReadOnlySpan pkcs10Pem, + HashAlgorithmName signerHashAlgorithm, + CertificateRequestLoadOptions options = CertificateRequestLoadOptions.Default, + RSASignaturePadding? signerSignaturePadding = null) + { + ArgumentException.ThrowIfNullOrEmpty(signerHashAlgorithm.Name, nameof(signerHashAlgorithm)); + + if ((options & ~AllOptions) != 0) + { + throw new ArgumentOutOfRangeException(nameof(options), options, SR.Argument_InvalidFlag); + } + + foreach ((ReadOnlySpan contents, PemFields fields) in new PemEnumerator(pkcs10Pem)) + { + if (contents[fields.Label].SequenceEqual(PemLabels.Pkcs10CertificateRequest)) + { + byte[] rented = ArrayPool.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(); + } + + try + { + return LoadSigningRequest( + rented.AsSpan(0, bytesWritten), + permitTrailingData: false, + signerHashAlgorithm, + out _, + options, + signerSignaturePadding); + } + finally + { + ArrayPool.Shared.Return(rented); + } + } + } + + throw new CryptographicException(SR.Cryptography_NoPemOfLabel, PemLabels.Pkcs10CertificateRequest); + } + + public static unsafe CertificateRequest LoadSigningRequest( + byte[] pkcs10, + HashAlgorithmName signerHashAlgorithm, + CertificateRequestLoadOptions options = CertificateRequestLoadOptions.Default, + RSASignaturePadding? signerSignaturePadding = null) + { + ArgumentNullException.ThrowIfNull(pkcs10); + + return LoadSigningRequest( + pkcs10, + permitTrailingData: false, + signerHashAlgorithm, + out _, + options, + signerSignaturePadding); + } + + public static unsafe CertificateRequest LoadSigningRequest( + ReadOnlySpan pkcs10, + HashAlgorithmName signerHashAlgorithm, + out int bytesConsumed, + CertificateRequestLoadOptions options = CertificateRequestLoadOptions.Default, + RSASignaturePadding? signerSignaturePadding = null) + { + return LoadSigningRequest( + pkcs10, + permitTrailingData: true, + signerHashAlgorithm, + out bytesConsumed, + options, + signerSignaturePadding); + } + + private static unsafe CertificateRequest LoadSigningRequest( + ReadOnlySpan pkcs10, + bool permitTrailingData, + HashAlgorithmName signerHashAlgorithm, + out int bytesConsumed, + CertificateRequestLoadOptions options, + RSASignaturePadding? signerSignaturePadding) + { + ArgumentException.ThrowIfNullOrEmpty(signerHashAlgorithm.Name, nameof(signerHashAlgorithm)); + + if ((options & ~AllOptions) != 0) + { + throw new ArgumentOutOfRangeException(nameof(options), options, SR.Argument_InvalidFlag); + } + + bool skipSignatureValidation = + (options & CertificateRequestLoadOptions.SkipSignatureValidation) != 0; + + bool unsafeLoadCertificateExtensions = + (options & CertificateRequestLoadOptions.UnsafeLoadCertificateExtensions) != 0; + + try + { + AsnValueReader outer = new AsnValueReader(pkcs10, AsnEncodingRules.DER); + int encodedLength = outer.PeekEncodedValue().Length; + + AsnValueReader pkcs10Asn = outer.ReadSequence(); + CertificateRequest req; + + if (!permitTrailingData) + { + outer.ThrowIfNotEmpty(); + } + + fixed (byte* p10ptr = pkcs10) + { + using (PointerMemoryManager manager = new PointerMemoryManager(p10ptr, encodedLength)) + { + ReadOnlyMemory rebind = manager.Memory; + ReadOnlySpan encodedRequestInfo = pkcs10Asn.PeekEncodedValue(); + CertificationRequestInfoAsn requestInfo; + AlgorithmIdentifierAsn algorithmIdentifier; + ReadOnlySpan signature; + int signatureUnusedBitCount; + + CertificationRequestInfoAsn.Decode(ref pkcs10Asn, rebind, out requestInfo); + AlgorithmIdentifierAsn.Decode(ref pkcs10Asn, rebind, out algorithmIdentifier); + + if (!pkcs10Asn.TryReadPrimitiveBitString(out signatureUnusedBitCount, out signature)) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); + } + + pkcs10Asn.ThrowIfNotEmpty(); + + if (requestInfo.Version < 0) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); + } + + // They haven't bumped from v0 to v1 as of 2022. + const int MaxSupportedVersion = 0; + + if (requestInfo.Version != MaxSupportedVersion) + { + throw new CryptographicException( + SR.Format( + SR.Cryptography_CertReq_Load_VersionTooNew, + requestInfo.Version, + MaxSupportedVersion)); + } + + PublicKey publicKey = PublicKey.DecodeSubjectPublicKeyInfo(ref requestInfo.SubjectPublicKeyInfo); + + if (!skipSignatureValidation) + { + // None of the supported signature algorithms support signatures that are not full bytes. + // So, shortcut the verification on the bit length + if (signatureUnusedBitCount != 0 || + !VerifyX509Signature(encodedRequestInfo, signature, publicKey, algorithmIdentifier)) + { + throw new CryptographicException(SR.Cryptography_CertReq_SignatureVerificationFailed); + } + } + + X500DistinguishedName subject = new X500DistinguishedName(requestInfo.Subject.Span); + + req = new CertificateRequest( + subject, + publicKey, + signerHashAlgorithm, + signerSignaturePadding); + + if (requestInfo.Attributes is not null) + { + bool foundCertExt = false; + + foreach (AttributeAsn attr in requestInfo.Attributes) + { + if (attr.AttrType == Oids.Pkcs9ExtensionRequest) + { + if (foundCertExt) + { + throw new CryptographicException( + SR.Cryptography_CertReq_Load_DuplicateExtensionRequests); + } + + foundCertExt = true; + + if (attr.AttrValues.Length != 1) + { + throw new CryptographicException( + SR.Cryptography_CertReq_Load_DuplicateExtensionRequests); + } + + AsnValueReader extsReader = new AsnValueReader( + attr.AttrValues[0].Span, + AsnEncodingRules.DER); + + AsnValueReader exts = extsReader.ReadSequence(); + extsReader.ThrowIfNotEmpty(); + + // Minimum length is 1, so do..while + do + { + X509ExtensionAsn.Decode(ref exts, rebind, out X509ExtensionAsn extAsn); + + if (unsafeLoadCertificateExtensions) + { + X509Extension ext = new X509Extension( + extAsn.ExtnId, + extAsn.ExtnValue.Span, + extAsn.Critical); + + X509Extension? rich = + X509Certificate2.CreateCustomExtensionIfAny(extAsn.ExtnId); + + if (rich is not null) + { + rich.CopyFrom(ext); + req.CertificateExtensions.Add(rich); + } + else + { + req.CertificateExtensions.Add(ext); + } + } + } while (exts.HasData); + } + else + { + if (attr.AttrValues.Length == 0) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); + } + + foreach (ReadOnlyMemory val in attr.AttrValues) + { + req.OtherRequestAttributes.Add( + new AsnEncodedData(attr.AttrType, val.Span)); + } + } + } + } + } + } + + bytesConsumed = encodedLength; + return req; + } + catch (AsnContentException e) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e); + } + } + + private static bool VerifyX509Signature( + ReadOnlySpan toBeSigned, + ReadOnlySpan signature, + PublicKey publicKey, + AlgorithmIdentifierAsn algorithmIdentifier) + { + RSA? rsa = publicKey.GetRSAPublicKey(); + ECDsa? ecdsa = publicKey.GetECDsaPublicKey(); + + try + { + HashAlgorithmName hashAlg; + + if (algorithmIdentifier.Algorithm == Oids.RsaPss) + { + if (rsa is null || !algorithmIdentifier.Parameters.HasValue) + { + return false; + } + + PssParamsAsn pssParams = PssParamsAsn.Decode( + algorithmIdentifier.Parameters.GetValueOrDefault(), + AsnEncodingRules.DER); + + RSASignaturePadding padding = pssParams.GetSignaturePadding(); + hashAlg = HashAlgorithmName.FromOid(pssParams.HashAlgorithm.Algorithm); + + return rsa.VerifyData( + toBeSigned, + signature, + hashAlg, + padding); + } + + switch (algorithmIdentifier.Algorithm) + { + case Oids.RsaPkcs1Sha256: + case Oids.ECDsaWithSha256: + hashAlg = HashAlgorithmName.SHA256; + break; + case Oids.RsaPkcs1Sha384: + case Oids.ECDsaWithSha384: + hashAlg = HashAlgorithmName.SHA384; + break; + case Oids.RsaPkcs1Sha512: + case Oids.ECDsaWithSha512: + hashAlg = HashAlgorithmName.SHA512; + break; + case Oids.RsaPkcs1Sha1: + case Oids.ECDsaWithSha1: + hashAlg = HashAlgorithmName.SHA1; + break; + default: + throw new NotSupportedException( + SR.Format(SR.Cryptography_UnknownKeyAlgorithm, algorithmIdentifier.Algorithm)); + } + + // All remaining supported algorithms have no defined parameters + if (!algorithmIdentifier.HasNullEquivalentParameters()) + { + return false; + } + + switch (algorithmIdentifier.Algorithm) + { + case Oids.RsaPkcs1Sha256: + case Oids.RsaPkcs1Sha384: + case Oids.RsaPkcs1Sha512: + case Oids.RsaPkcs1Sha1: + if (rsa is null) + { + return false; + } + + return rsa.VerifyData(toBeSigned, signature, hashAlg, RSASignaturePadding.Pkcs1); + case Oids.ECDsaWithSha256: + case Oids.ECDsaWithSha384: + case Oids.ECDsaWithSha512: + case Oids.ECDsaWithSha1: + if (ecdsa is null) + { + return false; + } + + return ecdsa.VerifyData(toBeSigned, signature, hashAlg, DSASignatureFormat.Rfc3279DerSequence); + default: + Debug.Fail( + $"Algorithm ID {algorithmIdentifier.Algorithm} was in the first switch, but not the second"); + return false; + } + } + catch (AsnContentException) + { + return false; + } + catch (CryptographicException) + { + return false; + } + finally + { + rsa?.Dispose(); + ecdsa?.Dispose(); + } + } + } +} diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/CertificateRequest.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/CertificateRequest.cs index ca3c1ac642511d..c3084c964d7dc2 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/CertificateRequest.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/CertificateRequest.cs @@ -18,7 +18,7 @@ namespace System.Security.Cryptography.X509Certificates /// create a certificate signing request blob to send to a Certificate Authority (CA). /// [UnsupportedOSPlatform("browser")] - public sealed class CertificateRequest + public sealed partial class CertificateRequest { private readonly AsymmetricAlgorithm? _key; private readonly X509SignatureGenerator? _generator; @@ -34,6 +34,16 @@ public sealed class CertificateRequest /// public Collection CertificateExtensions { get; } = new Collection(); + /// + /// Gets a collection representing attributes, other than the extension request attribute, to include + /// in a certificate request. + /// + /// + /// A collection representing attributes, other than the extension request attribute, to include + /// in a certificate request + /// + public Collection OtherRequestAttributes { get; } = new Collection(); + /// /// A representation of the public key for the certificate or certificate request. /// @@ -191,6 +201,38 @@ public CertificateRequest(X500DistinguishedName subjectName, PublicKey publicKey HashAlgorithm = hashAlgorithm; } + /// + /// Create a CertificateRequest for the specified subject name, encoded public key, hash algorithm, + /// and RSA signature padding. + /// + /// + /// The parsed representation of the subject name for the certificate or certificate request. + /// + /// + /// The encoded representation of the public key to include in the certificate or certificate request. + /// + /// + /// The hash algorithm to use when signing the certificate or certificate request. + /// + /// + /// The RSA signature padding to use when signing this request with an RSA certificate. + /// + public CertificateRequest( + X500DistinguishedName subjectName, + PublicKey publicKey, + HashAlgorithmName hashAlgorithm, + RSASignaturePadding? rsaSignaturePadding = null) + { + ArgumentNullException.ThrowIfNull(subjectName); + ArgumentNullException.ThrowIfNull(publicKey); + ArgumentException.ThrowIfNullOrEmpty(hashAlgorithm.Name, nameof(hashAlgorithm)); + + SubjectName = subjectName; + PublicKey = publicKey; + HashAlgorithm = hashAlgorithm; + _rsaPadding = rsaSignaturePadding; + } + /// /// Create an ASN.1 DER-encoded PKCS#10 CertificationRequest object representing the current state /// of this object. @@ -199,36 +241,40 @@ public CertificateRequest(X500DistinguishedName subjectName, PublicKey publicKey /// /// When submitting a certificate signing request via a web browser, or other graphical or textual /// interface, the input is frequently expected to be in the PEM (Privacy Enhanced Mail) format, - /// instead of the DER binary format. To convert the return value to PEM format, make a string - /// consisting of -----BEGIN CERTIFICATE REQUEST-----, a newline, the Base-64-encoded - /// representation of the request (by convention, linewrapped at 64 characters), a newline, - /// and -----END CERTIFICATE REQUEST-----. - /// - /// + /// instead of the DER binary format. /// + /// + /// + /// contains a value. + /// + /// - or - + /// + /// contains an entry with a + /// value. + /// + /// - or - + /// + /// contains an entry representing the PKCS#9 + /// Extension Request Attribute (1.2.840.113549.1.9.14). + /// + /// - or - + /// + /// contains a value. + /// + /// - or - + /// + /// contains an entry with a + /// value. + /// + /// - or - + /// + /// This object was created with a constructor which did not accept a signing key. + /// + /// + /// + /// A cryptographic error occurs while creating the signing request. + /// + /// public byte[] CreateSigningRequest() { if (_generator == null) @@ -244,21 +290,185 @@ public byte[] CreateSigningRequest() /// /// A with which to sign the request. /// + /// + /// is . + /// + /// + /// + /// contains a value. + /// + /// - or - + /// + /// contains an entry with a + /// value. + /// + /// - or - + /// + /// contains an entry representing the PKCS#9 + /// Extension Request Attribute (1.2.840.113549.1.9.14). + /// + /// - or - + /// + /// contains a value. + /// + /// - or - + /// + /// contains an entry with a + /// value. + /// + /// - or - + /// + /// This object was created with a constructor which did not accept a signing key. + /// + /// + /// + /// A cryptographic error occurs while creating the signing request. + /// + /// + /// When submitting a certificate signing request via a web browser, or other graphical or textual + /// interface, the input is frequently expected to be in the PEM (Privacy Enhanced Mail) format, + /// instead of the DER binary format. + /// + /// public byte[] CreateSigningRequest(X509SignatureGenerator signatureGenerator) { ArgumentNullException.ThrowIfNull(signatureGenerator); X501Attribute[] attributes = Array.Empty(); + bool hasExtensions = CertificateExtensions.Count > 0; - if (CertificateExtensions.Count > 0) + if (OtherRequestAttributes.Count > 0 || hasExtensions) { - attributes = new X501Attribute[] { new Pkcs9ExtensionRequest(CertificateExtensions) }; + attributes = new X501Attribute[OtherRequestAttributes.Count + (hasExtensions ? 1 : 0)]; + } + + int attrCount = 0; + + foreach (AsnEncodedData attr in OtherRequestAttributes) + { + if (attr is null) + { + throw new InvalidOperationException( + SR.Format(SR.Cryptography_CertReq_NullValueInCollection, nameof(OtherRequestAttributes))); + } + + if (attr.Oid is null || attr.Oid.Value is null) + { + throw new InvalidOperationException( + SR.Format(SR.Cryptography_CertReq_MissingOidInCollection, nameof(OtherRequestAttributes))); + } + + if (attr.Oid.Value == Oids.Pkcs9ExtensionRequest) + { + throw new InvalidOperationException(SR.Cryptography_CertReq_ExtensionRequestInOtherAttributes); + } + + Helpers.ValidateDer(attr.RawData); + attributes[attrCount] = new X501Attribute(attr.Oid.Value, attr.RawData); + attrCount++; + } + + if (hasExtensions) + { + attributes[attrCount] = new Pkcs9ExtensionRequest(CertificateExtensions); } var requestInfo = new Pkcs10CertificationRequestInfo(SubjectName, PublicKey, attributes); return requestInfo.ToPkcs10Request(signatureGenerator, HashAlgorithm); } + /// + /// Create a PEM-encoded PKCS#10 CertificationRequest representing the current state + /// of this object using the provided signature generator. + /// + /// + /// + /// contains a value. + /// + /// - or - + /// + /// contains an entry with a + /// value. + /// + /// - or - + /// + /// contains an entry representing the PKCS#9 + /// Extension Request Attribute (1.2.840.113549.1.9.14). + /// + /// - or - + /// + /// contains a value. + /// + /// - or - + /// + /// contains an entry with a + /// value. + /// + /// - or - + /// + /// This object was created with a constructor which did not accept a signing key. + /// + /// + /// + /// A cryptographic error occurs while creating the signing request. + /// + /// + public string CreateSigningRequestPem() + { + byte[] der = CreateSigningRequest(); + return PemEncoding.WriteString(PemLabels.Pkcs10CertificateRequest, der); + } + + /// + /// Create a PEM-encoded PKCS#10 CertificationRequest representing the current state + /// of this object using the provided signature generator. + /// + /// + /// A with which to sign the request. + /// + /// + /// is . + /// + /// + /// + /// contains a value. + /// + /// - or - + /// + /// contains an entry with a + /// value. + /// + /// - or - + /// + /// contains an entry representing the PKCS#9 + /// Extension Request Attribute (1.2.840.113549.1.9.14). + /// + /// - or - + /// + /// contains a value. + /// + /// - or - + /// + /// contains an entry with a + /// value. + /// + /// - or - + /// + /// This object was created with a constructor which did not accept a signing key. + /// + /// + /// + /// A cryptographic error occurs while creating the signing request. + /// + /// + public string CreateSigningRequestPem(X509SignatureGenerator signatureGenerator) + { + ArgumentNullException.ThrowIfNull(signatureGenerator); + + byte[] der = CreateSigningRequest(signatureGenerator); + return PemEncoding.WriteString(PemLabels.Pkcs10CertificateRequest, der); + } + /// /// Create a self-signed certificate using the established subject, key, and optional /// extensions. @@ -358,8 +568,8 @@ public X509Certificate2 CreateSelfSigned(DateTimeOffset notBefore, DateTimeOffse /// has a different key algorithm than the requested certificate. /// /// - /// is an RSA certificate and this object was created via a constructor - /// which does not accept a value. + /// is an RSA certificate and this object was created without + /// specifying an value in the constructor. /// public X509Certificate2 Create( X509Certificate2 issuerCertificate, diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/CertificateRequestLoadOptions.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/CertificateRequestLoadOptions.cs new file mode 100644 index 00000000000000..9ccfb70cc0cc63 --- /dev/null +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/CertificateRequestLoadOptions.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Security.Cryptography.X509Certificates +{ + /// + /// Specifies options when loading a . + /// + [Flags] + public enum CertificateRequestLoadOptions + { + /// + /// Load the certificate request with default options. + /// + Default = 0, + + /// + /// When loading the request, do not check if the embedded public key validates the request + /// signature. + /// + SkipSignatureValidation = 0x01, + + /// + /// When loading the request, populate the + /// collection based on the PKCS#9 + /// Extension Request attribute (1.2.840.113549.1.9.14). + /// + UnsafeLoadCertificateExtensions = 0x02, + } +} diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/PublicKey.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/PublicKey.cs index b6612d0798a1a7..e53f36d403e4b4 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/PublicKey.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/PublicKey.cs @@ -299,11 +299,32 @@ private static unsafe int DecodeSubjectPublicKeyInfo( throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e); } - oid = new Oid(spki.Algorithm.Algorithm, null); - parameters = new AsnEncodedData(spki.Algorithm.Parameters.GetValueOrDefault().Span); - keyValue = new AsnEncodedData(spki.SubjectPublicKey.Span); + DecodeSubjectPublicKeyInfo(ref spki, out oid, out parameters, out keyValue); return read; } } + + internal static PublicKey DecodeSubjectPublicKeyInfo(ref SubjectPublicKeyInfoAsn spki) + { + DecodeSubjectPublicKeyInfo( + ref spki, + out Oid oid, + out AsnEncodedData parameters, + out AsnEncodedData keyValue); + + return new PublicKey(oid, parameters, keyValue); + } + + private static void DecodeSubjectPublicKeyInfo( + ref SubjectPublicKeyInfoAsn spki, + out Oid oid, + out AsnEncodedData parameters, + out AsnEncodedData keyValue) + { + oid = new Oid(spki.Algorithm.Algorithm, null); + parameters = new AsnEncodedData(spki.Algorithm.Parameters.GetValueOrDefault().Span); + keyValue = new AsnEncodedData(spki.SubjectPublicKey.Span); + } + } } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/X509Certificate2.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/X509Certificate2.cs index 0b86f6573845c0..422c29c10e8f67 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/X509Certificate2.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/X509Certificate2.cs @@ -1482,7 +1482,10 @@ private static X509Certificate2 ExtractKeyFromEncryptedPem( } private static X509Extension? CreateCustomExtensionIfAny(Oid oid) => - oid.Value switch + CreateCustomExtensionIfAny(oid.Value); + + internal static X509Extension? CreateCustomExtensionIfAny(string? oidValue) => + oidValue switch { Oids.BasicConstraints => X509Pal.Instance.SupportsLegacyBasicConstraintsExtension ? new X509BasicConstraintsExtension() : null, Oids.BasicConstraints2 => new X509BasicConstraintsExtension(), From deab99ab11c57b29f90aa9e1eb6dc71af6e9ff60 Mon Sep 17 00:00:00 2001 From: Jeremy Barton Date: Fri, 29 Jul 2022 08:20:51 -0700 Subject: [PATCH 2/4] Ratchet back the DSA tests --- .../CertificateCreation/CertificateRequestLoadTests.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/tests/CertificateCreation/CertificateRequestLoadTests.cs b/src/libraries/System.Security.Cryptography.X509Certificates/tests/CertificateCreation/CertificateRequestLoadTests.cs index 70ddd99a24193f..d9a1a704774222 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/tests/CertificateCreation/CertificateRequestLoadTests.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/tests/CertificateCreation/CertificateRequestLoadTests.cs @@ -403,12 +403,12 @@ public static void VerifySignature_RSA_PSS(string hashAlgorithm) } } - [Theory] - [InlineData("SHA256")] - [InlineData("SHA1")] - public static void VerifySignature_DSA(string hashAlgorithm) + [Fact] + [SkipOnPlatform(PlatformSupport.MobileAppleCrypto, "DSA is not available")] + public static void VerifySignature_DSA() { - HashAlgorithmName hashAlgorithmName = new HashAlgorithmName(hashAlgorithm); + // macOS is limited to FIPS 186-2 DSA, so SHA-1 is the only valid algorithm. + HashAlgorithmName hashAlgorithmName = HashAlgorithmName.SHA1; using (DSA key = DSA.Create(TestData.GetDSA1024Params())) { From 33c5633fa50837ddd75294d4f9abec146a621cad Mon Sep 17 00:00:00 2001 From: Jeremy Barton Date: Fri, 29 Jul 2022 11:41:07 -0700 Subject: [PATCH 3/4] Apply feedback --- .../src/Resources/Strings.resx | 2 +- .../X509Certificates/CertificateRequest.Load.cs | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/libraries/System.Security.Cryptography/src/Resources/Strings.resx b/src/libraries/System.Security.Cryptography/src/Resources/Strings.resx index d20ccd721f9787..f81818a7e794b1 100644 --- a/src/libraries/System.Security.Cryptography/src/Resources/Strings.resx +++ b/src/libraries/System.Security.Cryptography/src/Resources/Strings.resx @@ -229,7 +229,7 @@ An X509Extension with OID '{0}' has already been specified. - The OtherRequestAttributes collection contains a PKCS#9 Extension Request Attribute. This attribute cannot be manually specified, it is generated via the CertificateExtensions property. + The OtherRequestAttributes collection contains a PKCS#9 Extension Request Attribute. This attribute cannot be manually specified, it is generated via the CertificateExtensions property. The issuer certificate does not have an appropriate value for the Basic Constraints extension. diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/CertificateRequest.Load.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/CertificateRequest.Load.cs index a6f09c355504cf..180f291f592461 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/CertificateRequest.Load.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/CertificateRequest.Load.cs @@ -15,7 +15,7 @@ public sealed partial class CertificateRequest CertificateRequestLoadOptions.SkipSignatureValidation | CertificateRequestLoadOptions.UnsafeLoadCertificateExtensions; - public static unsafe CertificateRequest LoadSigningRequestPem( + public static CertificateRequest LoadSigningRequestPem( string pkcs10Pem, HashAlgorithmName signerHashAlgorithm, CertificateRequestLoadOptions options = CertificateRequestLoadOptions.Default, @@ -30,7 +30,7 @@ public static unsafe CertificateRequest LoadSigningRequestPem( signerSignaturePadding); } - public static unsafe CertificateRequest LoadSigningRequestPem( + public static CertificateRequest LoadSigningRequestPem( ReadOnlySpan pkcs10Pem, HashAlgorithmName signerHashAlgorithm, CertificateRequestLoadOptions options = CertificateRequestLoadOptions.Default, @@ -49,7 +49,8 @@ public static unsafe CertificateRequest LoadSigningRequestPem( { byte[] rented = ArrayPool.Shared.Rent(fields.DecodedDataLength); - if (!Convert.TryFromBase64Chars(contents[fields.Base64Data], rented, out int bytesWritten)) + if (!Convert.TryFromBase64Chars(contents[fields.Base64Data], rented, out int bytesWritten) || + bytesWritten != fields.DecodedDataLength) { Debug.Fail("Base64Decode failed, but PemEncoding said it was legal"); throw new UnreachableException(); @@ -72,10 +73,11 @@ public static unsafe CertificateRequest LoadSigningRequestPem( } } - throw new CryptographicException(SR.Cryptography_NoPemOfLabel, PemLabels.Pkcs10CertificateRequest); + throw new CryptographicException( + SR.Format(SR.Cryptography_NoPemOfLabel, PemLabels.Pkcs10CertificateRequest)); } - public static unsafe CertificateRequest LoadSigningRequest( + public static CertificateRequest LoadSigningRequest( byte[] pkcs10, HashAlgorithmName signerHashAlgorithm, CertificateRequestLoadOptions options = CertificateRequestLoadOptions.Default, @@ -92,7 +94,7 @@ public static unsafe CertificateRequest LoadSigningRequest( signerSignaturePadding); } - public static unsafe CertificateRequest LoadSigningRequest( + public static CertificateRequest LoadSigningRequest( ReadOnlySpan pkcs10, HashAlgorithmName signerHashAlgorithm, out int bytesConsumed, From bc9606a3a87a5d58b160a309775daec5b8f69370 Mon Sep 17 00:00:00 2001 From: Jeremy Barton Date: Wed, 3 Aug 2022 09:42:46 -0700 Subject: [PATCH 4/4] Disable tests that are failing on Android --- .../CertificateCreation/CertificateRequestLoadTests.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/tests/CertificateCreation/CertificateRequestLoadTests.cs b/src/libraries/System.Security.Cryptography.X509Certificates/tests/CertificateCreation/CertificateRequestLoadTests.cs index d9a1a704774222..4f597ddeacd835 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/tests/CertificateCreation/CertificateRequestLoadTests.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/tests/CertificateCreation/CertificateRequestLoadTests.cs @@ -14,6 +14,7 @@ public static class CertificateRequestLoadTests [InlineData(CertificateRequestLoadOptions.UnsafeLoadCertificateExtensions, false)] [InlineData(CertificateRequestLoadOptions.Default, true)] [InlineData(CertificateRequestLoadOptions.UnsafeLoadCertificateExtensions, true)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/72906", TestPlatforms.Android)] public static void LoadBigExponentRequest_Span(CertificateRequestLoadOptions options, bool oversized) { byte[] pkcs10 = TestData.BigExponentPkcs10Bytes; @@ -37,6 +38,7 @@ public static void LoadBigExponentRequest_Span(CertificateRequestLoadOptions opt [Theory] [InlineData(CertificateRequestLoadOptions.Default)] [InlineData(CertificateRequestLoadOptions.UnsafeLoadCertificateExtensions)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/72906", TestPlatforms.Android)] public static void LoadBigExponentRequest_Bytes(CertificateRequestLoadOptions options) { CertificateRequest req = CertificateRequest.LoadSigningRequest( @@ -68,6 +70,7 @@ public static void LoadBigExponentRequest_Bytes_Oversized(CertificateRequestLoad [InlineData(CertificateRequestLoadOptions.UnsafeLoadCertificateExtensions, false)] [InlineData(CertificateRequestLoadOptions.Default, true)] [InlineData(CertificateRequestLoadOptions.UnsafeLoadCertificateExtensions, true)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/72906", TestPlatforms.Android)] public static void LoadBigExponentRequest_PemString(CertificateRequestLoadOptions options, bool multiPem) { string pem = TestData.BigExponentPkcs10Pem; @@ -104,6 +107,7 @@ public static void LoadBigExponentRequest_PemString(CertificateRequestLoadOption [InlineData(CertificateRequestLoadOptions.UnsafeLoadCertificateExtensions, false)] [InlineData(CertificateRequestLoadOptions.Default, true)] [InlineData(CertificateRequestLoadOptions.UnsafeLoadCertificateExtensions, true)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/72906", TestPlatforms.Android)] public static void LoadBigExponentRequest_PemSpam(CertificateRequestLoadOptions options, bool multiPem) { string pem = TestData.BigExponentPkcs10Pem; @@ -138,6 +142,7 @@ More Text. } [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/72906", TestPlatforms.Android)] public static void HashAlgorithmLaxInLoad() { HashAlgorithmName hashAlgorithm = new HashAlgorithmName("I promise to be a hash algorithm"); @@ -176,6 +181,7 @@ public static void LoadPem_NoMatch() } [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/72906", TestPlatforms.Android)] public static void LoadWithAttributes() { // Generated by `openssl req -new -keyin bigexponent.pem` where bigexponent.pem @@ -435,6 +441,7 @@ public static void VerifySignature_DSA() } [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/72906", TestPlatforms.Android)] public static void LoadAndSignRequest_NoRSAPadding() { DateTimeOffset now = DateTimeOffset.UtcNow; @@ -485,6 +492,7 @@ public static void LoadAndSignRequest_NoRSAPadding() } [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/72906", TestPlatforms.Android)] public static void LoadAndSignRequest_WithRSAPadding() { DateTimeOffset now = DateTimeOffset.UtcNow; @@ -598,6 +606,7 @@ public static void LoadAndSignRequest_ECDsaIgnoresRSAPadding(bool specifyAnyways } [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/72906", TestPlatforms.Android)] public static void LoadRequestWithDuplicateAttributes() { // The output from CertificateRequestUsageTests.CreateSigningRequestWithDuplicateAttributes