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..4f597ddeacd835 --- /dev/null +++ b/src/libraries/System.Security.Cryptography.X509Certificates/tests/CertificateCreation/CertificateRequestLoadTests.cs @@ -0,0 +1,754 @@ +// 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)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/72906", TestPlatforms.Android)] + 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)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/72906", TestPlatforms.Android)] + 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)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/72906", TestPlatforms.Android)] + 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)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/72906", TestPlatforms.Android)] + 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] + [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"); + + 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] + [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 + // 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); + } + } + + [Fact] + [SkipOnPlatform(PlatformSupport.MobileAppleCrypto, "DSA is not available")] + public static void VerifySignature_DSA() + { + // 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())) + { + 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] + [ActiveIssue("https://github.com/dotnet/runtime/issues/72906", TestPlatforms.Android)] + 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] + [ActiveIssue("https://github.com/dotnet/runtime/issues/72906", TestPlatforms.Android)] + 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] + [ActiveIssue("https://github.com/dotnet/runtime/issues/72906", TestPlatforms.Android)] + 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 1e63f64d6c7f80..6738bae8a83199 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 1c1b430e55c21b..3cc428cfbeed11 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 438f9a746ae5b6..986ea2c977f87d 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..180f291f592461 --- /dev/null +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/CertificateRequest.Load.cs @@ -0,0 +1,395 @@ +// 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 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 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) || + bytesWritten != fields.DecodedDataLength) + { + 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.Format(SR.Cryptography_NoPemOfLabel, PemLabels.Pkcs10CertificateRequest)); + } + + public static 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 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(),