From fb6c14a351a75b9ff7b4faa95413339c2a5e55ea Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Thu, 23 Apr 2020 11:39:43 -0400 Subject: [PATCH 1/6] Handle more basic constraints chain statuses. macOS can return additional chain status strings for a certificate that was issued by a certificate that violated its basic constraints. If a leaf certificate is issued from a certificate with CA:FALSE, the strings BasicConstraintsCA and BasicConstraintsPathLen are reported. We map these the same for BasicConstraints. --- .../System.Security.Cryptography.Native.Apple/pal_x509chain.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_x509chain.c b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_x509chain.c index fa3055efb3d224..76dfb3e088efdf 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_x509chain.c +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/pal_x509chain.c @@ -166,7 +166,8 @@ static void MergeStatusCodes(CFTypeRef key, CFTypeRef value, void* context) *pStatus |= PAL_X509ChainNotValidForUsage; else if (CFEqual(keyString, CFSTR("AnchorTrusted"))) *pStatus |= PAL_X509ChainUntrustedRoot; - else if (CFEqual(keyString, CFSTR("BasicConstraints"))) + else if (CFEqual(keyString, CFSTR("BasicConstraints")) || CFEqual(keyString, CFSTR("BasicConstraintsCA")) || + CFEqual(keyString, CFSTR("BasicConstraintsPathLen"))) *pStatus |= PAL_X509ChainInvalidBasicConstraints; else if (CFEqual(keyString, CFSTR("UsageConstraints"))) *pStatus |= PAL_X509ChainExplicitDistrust; From 2e90a06a6d43e8169c8e52a3c4dce69ecde9b000 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Thu, 23 Apr 2020 13:03:09 -0400 Subject: [PATCH 2/6] Add test for basic constraints. --- .../tests/DynamicChainTests.cs | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/tests/DynamicChainTests.cs b/src/libraries/System.Security.Cryptography.X509Certificates/tests/DynamicChainTests.cs index d6acb4c035691f..6bb981bb71aee5 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/tests/DynamicChainTests.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/tests/DynamicChainTests.cs @@ -201,6 +201,97 @@ void CheckChain() } } + [Fact] + public static void TestBasicConstraintsViolation() + { + byte[] serialNumber = new byte[9]; + RandomNumberGenerator.Fill(serialNumber); + serialNumber[0] &= 0b01111111; + using (RSA key = RSA.Create()) + { + CertificateRequest rootReq = new CertificateRequest( + "CN=Root", + key, + HashAlgorithmName.SHA256, + RSASignaturePadding.Pkcs1); + + rootReq.CertificateExtensions.Add( + new X509BasicConstraintsExtension( + certificateAuthority: true, + hasPathLengthConstraint: true, + pathLengthConstraint: 3, + critical: true)); + + // Create two intermediates, one that can sign and one that cannot + // with the same subject and serial number. This will let us create + // a certificate with the CA:TRUE constraint, but build a chain with + // the CA:FALSE certificate. + CertificateRequest signingIntermediateReq = new CertificateRequest( + "CN=Intermediate", + key, + HashAlgorithmName.SHA256, + RSASignaturePadding.Pkcs1); + + signingIntermediateReq.CertificateExtensions.Add( + new X509BasicConstraintsExtension( + certificateAuthority: true, + hasPathLengthConstraint: true, + pathLengthConstraint: 2, + critical: true)); + + CertificateRequest noSigningIntermediateReq = new CertificateRequest( + "CN=Intermediate", + key, + HashAlgorithmName.SHA256, + RSASignaturePadding.Pkcs1); + + noSigningIntermediateReq.CertificateExtensions.Add( + new X509BasicConstraintsExtension( + certificateAuthority: false, + hasPathLengthConstraint: true, + pathLengthConstraint: 0, + critical: true)); + + CertificateRequest leafReq = new CertificateRequest( + "CN=Leaf", + key, + HashAlgorithmName.SHA256, + RSASignaturePadding.Pkcs1); + + DateTimeOffset notBefore = DateTimeOffset.UtcNow.AddDays(-1); + DateTimeOffset notAfter = notBefore.AddDays(30); + + using (X509Certificate2 root = rootReq.CreateSelfSigned(notBefore, notAfter)) + using (X509Certificate2 signingIntermediate = signingIntermediateReq.Create(root, notBefore, notAfter, serialNumber)) + using (X509Certificate2 noSigningIntermediate = noSigningIntermediateReq.Create(root, notBefore, notAfter, serialNumber)) + using (X509Certificate2 signingIntermediateWithKey = signingIntermediate.CopyWithPrivateKey(key)) + using (X509Certificate2 leaf = leafReq.Create(signingIntermediateWithKey, notBefore, notAfter, serialNumber)) + { + X509Chain chain = new X509Chain(); + chain.ChainPolicy.ExtraStore.Add(root); + chain.ChainPolicy.ExtraStore.Add(noSigningIntermediate); + chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; + Assert.False(chain.Build(leaf)); + + bool verifiedIntermediate = false; + + foreach (X509ChainElement element in chain.ChainElements) + { + if (element.Certificate.Subject == "CN=Intermediate") + { + const X509ChainStatusFlags expectedFlag = X509ChainStatusFlags.InvalidBasicConstraints; + X509ChainStatusFlags intermediateStatus = element.AllStatusFlags(); + Assert.True((intermediateStatus & expectedFlag) == expectedFlag, $"Has {expectedFlag} flag"); + verifiedIntermediate = true; + break; + } + } + + Assert.True(verifiedIntermediate, "verify leaf certificate"); + } + } + } + [Fact] public static void TestInvalidAia() { From 1b818dca8b0b54f2e470efb90a41c300ddc5a63d Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Thu, 23 Apr 2020 20:18:04 -0400 Subject: [PATCH 3/6] Improve tests. Test CA:FALSE and path length violations independently. --- .../tests/DynamicChainTests.cs | 119 ++++++++++++++---- 1 file changed, 95 insertions(+), 24 deletions(-) diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/tests/DynamicChainTests.cs b/src/libraries/System.Security.Cryptography.X509Certificates/tests/DynamicChainTests.cs index 6bb981bb71aee5..22e180cd204a37 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/tests/DynamicChainTests.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/tests/DynamicChainTests.cs @@ -202,11 +202,8 @@ void CheckChain() } [Fact] - public static void TestBasicConstraintsViolation() + public static void BasicConstraints_ExceedMaximumPathLength() { - byte[] serialNumber = new byte[9]; - RandomNumberGenerator.Fill(serialNumber); - serialNumber[0] &= 0b01111111; using (RSA key = RSA.Create()) { CertificateRequest rootReq = new CertificateRequest( @@ -219,60 +216,134 @@ public static void TestBasicConstraintsViolation() new X509BasicConstraintsExtension( certificateAuthority: true, hasPathLengthConstraint: true, - pathLengthConstraint: 3, + pathLengthConstraint: 0, critical: true)); - // Create two intermediates, one that can sign and one that cannot - // with the same subject and serial number. This will let us create - // a certificate with the CA:TRUE constraint, but build a chain with - // the CA:FALSE certificate. - CertificateRequest signingIntermediateReq = new CertificateRequest( + CertificateRequest intermediateReq = new CertificateRequest( "CN=Intermediate", key, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); - signingIntermediateReq.CertificateExtensions.Add( + intermediateReq.CertificateExtensions.Add( new X509BasicConstraintsExtension( certificateAuthority: true, hasPathLengthConstraint: true, - pathLengthConstraint: 2, + pathLengthConstraint: 0, + critical: true)); + + CertificateRequest leafReq = new CertificateRequest( + "CN=Leaf", + key, + HashAlgorithmName.SHA256, + RSASignaturePadding.Pkcs1); + + leafReq.CertificateExtensions.Add( + new X509BasicConstraintsExtension( + certificateAuthority: false, + hasPathLengthConstraint: true, + pathLengthConstraint: 0, critical: true)); - CertificateRequest noSigningIntermediateReq = new CertificateRequest( + DateTimeOffset notBefore = DateTimeOffset.UtcNow.AddDays(-1); + DateTimeOffset notAfter = notBefore.AddDays(30); + + X509SignatureGenerator generator = X509SignatureGenerator.CreateForRSA(key, RSASignaturePadding.Pkcs1); + + byte[] serial = new byte[8]; + RandomNumberGenerator.Fill(serial); + + using (X509Certificate2 root = rootReq.Create(rootReq.SubjectName, generator, notBefore, notAfter, serial)) + using (X509Certificate2 intermediate = intermediateReq.Create(root.SubjectName, generator, notBefore, notAfter, serial)) + using (X509Certificate2 leaf = leafReq.Create(intermediate.SubjectName, generator, notBefore, notAfter, serial)) + using (X509Chain chain = new X509Chain()) + { + chain.ChainPolicy.ExtraStore.Add(root); + chain.ChainPolicy.ExtraStore.Add(intermediate); + chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; + Assert.False(chain.Build(leaf)); + + bool verifiedIntermediate = false; + + foreach (X509ChainElement element in chain.ChainElements) + { + if (element.Certificate.Subject == "CN=Intermediate") + { + const X509ChainStatusFlags expectedFlag = X509ChainStatusFlags.InvalidBasicConstraints; + X509ChainStatusFlags intermediateStatus = element.AllStatusFlags(); + Assert.True((intermediateStatus & expectedFlag) == expectedFlag, $"Has {expectedFlag} flag"); + verifiedIntermediate = true; + break; + } + } + + Assert.True(verifiedIntermediate, "Verified intermediate certificate"); + } + } + } + + [Fact] + public static void BasicConstraints_ViolatesCaFalse() + { + using (RSA key = RSA.Create()) + { + CertificateRequest rootReq = new CertificateRequest( + "CN=Root", + key, + HashAlgorithmName.SHA256, + RSASignaturePadding.Pkcs1); + + rootReq.CertificateExtensions.Add( + new X509BasicConstraintsExtension( + certificateAuthority: true, + hasPathLengthConstraint: true, + pathLengthConstraint: 1, + critical: true)); + + CertificateRequest intermediateReq = new CertificateRequest( "CN=Intermediate", key, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); - noSigningIntermediateReq.CertificateExtensions.Add( + intermediateReq.CertificateExtensions.Add( new X509BasicConstraintsExtension( certificateAuthority: false, hasPathLengthConstraint: true, pathLengthConstraint: 0, critical: true)); - + CertificateRequest leafReq = new CertificateRequest( "CN=Leaf", key, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + leafReq.CertificateExtensions.Add( + new X509BasicConstraintsExtension( + certificateAuthority: false, + hasPathLengthConstraint: true, + pathLengthConstraint: 0, + critical: true)); + DateTimeOffset notBefore = DateTimeOffset.UtcNow.AddDays(-1); DateTimeOffset notAfter = notBefore.AddDays(30); - using (X509Certificate2 root = rootReq.CreateSelfSigned(notBefore, notAfter)) - using (X509Certificate2 signingIntermediate = signingIntermediateReq.Create(root, notBefore, notAfter, serialNumber)) - using (X509Certificate2 noSigningIntermediate = noSigningIntermediateReq.Create(root, notBefore, notAfter, serialNumber)) - using (X509Certificate2 signingIntermediateWithKey = signingIntermediate.CopyWithPrivateKey(key)) - using (X509Certificate2 leaf = leafReq.Create(signingIntermediateWithKey, notBefore, notAfter, serialNumber)) + X509SignatureGenerator generator = X509SignatureGenerator.CreateForRSA(key, RSASignaturePadding.Pkcs1); + + byte[] serial = new byte[8]; + RandomNumberGenerator.Fill(serial); + + using (X509Certificate2 root = rootReq.Create(rootReq.SubjectName, generator, notBefore, notAfter, serial)) + using (X509Certificate2 intermediate = intermediateReq.Create(root.SubjectName, generator, notBefore, notAfter, serial)) + using (X509Certificate2 leaf = leafReq.Create(intermediate.SubjectName, generator, notBefore, notAfter, serial)) + using (X509Chain chain = new X509Chain()) { - X509Chain chain = new X509Chain(); chain.ChainPolicy.ExtraStore.Add(root); - chain.ChainPolicy.ExtraStore.Add(noSigningIntermediate); + chain.ChainPolicy.ExtraStore.Add(intermediate); chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; Assert.False(chain.Build(leaf)); - + bool verifiedIntermediate = false; foreach (X509ChainElement element in chain.ChainElements) @@ -287,7 +358,7 @@ public static void TestBasicConstraintsViolation() } } - Assert.True(verifiedIntermediate, "verify leaf certificate"); + Assert.True(verifiedIntermediate, "Verified intermediate certificate"); } } } From 07ed58c0a09efbed09a9dca9fcd73aa693162ab7 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Fri, 24 Apr 2020 12:02:47 -0400 Subject: [PATCH 4/6] Try to make everyone happy --- .../tests/DynamicChainTests.cs | 204 ++++++------------ .../tests/TestDataGenerator.cs | 148 +++++++++---- 2 files changed, 167 insertions(+), 185 deletions(-) diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/tests/DynamicChainTests.cs b/src/libraries/System.Security.Cryptography.X509Certificates/tests/DynamicChainTests.cs index 22e180cd204a37..e813c03621fa02 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/tests/DynamicChainTests.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/tests/DynamicChainTests.cs @@ -204,162 +204,80 @@ void CheckChain() [Fact] public static void BasicConstraints_ExceedMaximumPathLength() { - using (RSA key = RSA.Create()) - { - CertificateRequest rootReq = new CertificateRequest( - "CN=Root", - key, - HashAlgorithmName.SHA256, - RSASignaturePadding.Pkcs1); - - rootReq.CertificateExtensions.Add( - new X509BasicConstraintsExtension( - certificateAuthority: true, - hasPathLengthConstraint: true, - pathLengthConstraint: 0, - critical: true)); - - CertificateRequest intermediateReq = new CertificateRequest( - "CN=Intermediate", - key, - HashAlgorithmName.SHA256, - RSASignaturePadding.Pkcs1); - - intermediateReq.CertificateExtensions.Add( - new X509BasicConstraintsExtension( - certificateAuthority: true, - hasPathLengthConstraint: true, - pathLengthConstraint: 0, - critical: true)); - - CertificateRequest leafReq = new CertificateRequest( - "CN=Leaf", - key, - HashAlgorithmName.SHA256, - RSASignaturePadding.Pkcs1); - - leafReq.CertificateExtensions.Add( - new X509BasicConstraintsExtension( - certificateAuthority: false, - hasPathLengthConstraint: true, - pathLengthConstraint: 0, - critical: true)); - - DateTimeOffset notBefore = DateTimeOffset.UtcNow.AddDays(-1); - DateTimeOffset notAfter = notBefore.AddDays(30); - - X509SignatureGenerator generator = X509SignatureGenerator.CreateForRSA(key, RSASignaturePadding.Pkcs1); - - byte[] serial = new byte[8]; - RandomNumberGenerator.Fill(serial); + X509Extension[] rootExtensions = new [] { + new X509BasicConstraintsExtension( + certificateAuthority: true, + hasPathLengthConstraint: true, + pathLengthConstraint: 1, + critical: true) + }; - using (X509Certificate2 root = rootReq.Create(rootReq.SubjectName, generator, notBefore, notAfter, serial)) - using (X509Certificate2 intermediate = intermediateReq.Create(root.SubjectName, generator, notBefore, notAfter, serial)) - using (X509Certificate2 leaf = leafReq.Create(intermediate.SubjectName, generator, notBefore, notAfter, serial)) - using (X509Chain chain = new X509Chain()) - { - chain.ChainPolicy.ExtraStore.Add(root); - chain.ChainPolicy.ExtraStore.Add(intermediate); - chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; - Assert.False(chain.Build(leaf)); + X509Extension[] intermediateExtensions = new [] { + new X509BasicConstraintsExtension( + certificateAuthority: true, + hasPathLengthConstraint: true, + pathLengthConstraint: 0, + critical: true) + }; - bool verifiedIntermediate = false; + TestDataGenerator.MakeTestChain4( + out X509Certificate2 endEntityCert, + out X509Certificate2 intermediateCert1, + out X509Certificate2 intermediateCert2, + out X509Certificate2 rootCert, + rootExtensions: rootExtensions, + intermediateExtensions: intermediateExtensions); - foreach (X509ChainElement element in chain.ChainElements) - { - if (element.Certificate.Subject == "CN=Intermediate") - { - const X509ChainStatusFlags expectedFlag = X509ChainStatusFlags.InvalidBasicConstraints; - X509ChainStatusFlags intermediateStatus = element.AllStatusFlags(); - Assert.True((intermediateStatus & expectedFlag) == expectedFlag, $"Has {expectedFlag} flag"); - verifiedIntermediate = true; - break; - } - } + using (endEntityCert) + using (intermediateCert1) + using (intermediateCert2) + using (rootCert) + using (ChainHolder chainHolder = new ChainHolder()) + { + X509Chain chain = chainHolder.Chain; + chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; + chain.ChainPolicy.VerificationTime = endEntityCert.NotBefore.AddSeconds(1); + chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust; + chain.ChainPolicy.CustomTrustStore.Add(intermediateCert1); + chain.ChainPolicy.CustomTrustStore.Add(intermediateCert2); + chain.ChainPolicy.CustomTrustStore.Add(rootCert); - Assert.True(verifiedIntermediate, "Verified intermediate certificate"); - } + Assert.False(chain.Build(endEntityCert)); + Assert.Equal(X509ChainStatusFlags.InvalidBasicConstraints, chain.AllStatusFlags()); } } [Fact] public static void BasicConstraints_ViolatesCaFalse() { - using (RSA key = RSA.Create()) - { - CertificateRequest rootReq = new CertificateRequest( - "CN=Root", - key, - HashAlgorithmName.SHA256, - RSASignaturePadding.Pkcs1); - - rootReq.CertificateExtensions.Add( - new X509BasicConstraintsExtension( - certificateAuthority: true, - hasPathLengthConstraint: true, - pathLengthConstraint: 1, - critical: true)); - - CertificateRequest intermediateReq = new CertificateRequest( - "CN=Intermediate", - key, - HashAlgorithmName.SHA256, - RSASignaturePadding.Pkcs1); - - intermediateReq.CertificateExtensions.Add( - new X509BasicConstraintsExtension( - certificateAuthority: false, - hasPathLengthConstraint: true, - pathLengthConstraint: 0, - critical: true)); - - CertificateRequest leafReq = new CertificateRequest( - "CN=Leaf", - key, - HashAlgorithmName.SHA256, - RSASignaturePadding.Pkcs1); - - leafReq.CertificateExtensions.Add( - new X509BasicConstraintsExtension( - certificateAuthority: false, - hasPathLengthConstraint: true, - pathLengthConstraint: 0, - critical: true)); - - DateTimeOffset notBefore = DateTimeOffset.UtcNow.AddDays(-1); - DateTimeOffset notAfter = notBefore.AddDays(30); - - X509SignatureGenerator generator = X509SignatureGenerator.CreateForRSA(key, RSASignaturePadding.Pkcs1); - - byte[] serial = new byte[8]; - RandomNumberGenerator.Fill(serial); - - using (X509Certificate2 root = rootReq.Create(rootReq.SubjectName, generator, notBefore, notAfter, serial)) - using (X509Certificate2 intermediate = intermediateReq.Create(root.SubjectName, generator, notBefore, notAfter, serial)) - using (X509Certificate2 leaf = leafReq.Create(intermediate.SubjectName, generator, notBefore, notAfter, serial)) - using (X509Chain chain = new X509Chain()) - { - chain.ChainPolicy.ExtraStore.Add(root); - chain.ChainPolicy.ExtraStore.Add(intermediate); - chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; - Assert.False(chain.Build(leaf)); + X509Extension[] intermediateExtensions = new [] { + new X509BasicConstraintsExtension( + certificateAuthority: false, + hasPathLengthConstraint: false, + pathLengthConstraint: 0, + critical: true) + }; - bool verifiedIntermediate = false; + TestDataGenerator.MakeTestChain3( + out X509Certificate2 endEntityCert, + out X509Certificate2 intermediateCert, + out X509Certificate2 rootCert, + intermediateExtensions: intermediateExtensions); - foreach (X509ChainElement element in chain.ChainElements) - { - if (element.Certificate.Subject == "CN=Intermediate") - { - const X509ChainStatusFlags expectedFlag = X509ChainStatusFlags.InvalidBasicConstraints; - X509ChainStatusFlags intermediateStatus = element.AllStatusFlags(); - Assert.True((intermediateStatus & expectedFlag) == expectedFlag, $"Has {expectedFlag} flag"); - verifiedIntermediate = true; - break; - } - } + using (endEntityCert) + using (intermediateCert) + using (rootCert) + using (ChainHolder chainHolder = new ChainHolder()) + { + X509Chain chain = chainHolder.Chain; + chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; + chain.ChainPolicy.VerificationTime = endEntityCert.NotBefore.AddSeconds(1); + chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust; + chain.ChainPolicy.CustomTrustStore.Add(intermediateCert); + chain.ChainPolicy.CustomTrustStore.Add(rootCert); - Assert.True(verifiedIntermediate, "Verified intermediate certificate"); - } + Assert.False(chain.Build(endEntityCert)); + Assert.Equal(X509ChainStatusFlags.InvalidBasicConstraints, chain.AllStatusFlags()); } } diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/tests/TestDataGenerator.cs b/src/libraries/System.Security.Cryptography.X509Certificates/tests/TestDataGenerator.cs index 40412515dc7bc7..4e972ae3ea5b8f 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/tests/TestDataGenerator.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/tests/TestDataGenerator.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Collections.Generic; + namespace System.Security.Cryptography.X509Certificates.Tests { internal static class TestDataGenerator @@ -9,7 +11,10 @@ internal static class TestDataGenerator internal static void MakeTestChain3( out X509Certificate2 endEntityCert, out X509Certificate2 intermediateCert, - out X509Certificate2 rootCert) + out X509Certificate2 rootCert, + IEnumerable endEntityExtensions = null, + IEnumerable intermediateExtensions = null, + IEnumerable rootExtensions = null) { using (RSA rootKey = RSA.Create()) using (RSA intermediateKey = RSA.Create()) @@ -23,7 +28,12 @@ internal static void MakeTestChain3( }; Span certs = new X509Certificate2[keys.Length]; - MakeTestChain(keys, certs); + MakeTestChain( + keys, + certs, + endEntityExtensions, + intermediateExtensions, + rootExtensions); endEntityCert = certs[0]; intermediateCert = certs[1]; @@ -31,33 +41,81 @@ internal static void MakeTestChain3( } } + + internal static void MakeTestChain4( + out X509Certificate2 endEntityCert, + out X509Certificate2 intermediateCert1, + out X509Certificate2 intermediateCert2, + out X509Certificate2 rootCert, + IEnumerable endEntityExtensions = null, + IEnumerable intermediateExtensions = null, + IEnumerable rootExtensions = null) + { + using (RSA rootKey = RSA.Create()) + using (RSA intermediateKey = RSA.Create()) + using (RSA endEntityKey = RSA.Create()) + { + ReadOnlySpan keys = new[] + { + rootKey, + intermediateKey, + intermediateKey, + endEntityKey, + }; + + Span certs = new X509Certificate2[keys.Length]; + MakeTestChain( + keys, + certs, + endEntityExtensions, + intermediateExtensions, + rootExtensions); + + endEntityCert = certs[0]; + intermediateCert1 = certs[1]; + intermediateCert2 = certs[2]; + rootCert = certs[3]; + } + } + internal static void MakeTestChain( ReadOnlySpan keys, Span certs, - string eeName = "CN=End-Entity", - OidCollection eeEku = null) + IEnumerable endEntityExtensions, + IEnumerable intermediateExtensions, + IEnumerable rootExtensions) { if (keys.Length < 2) throw new ArgumentException(nameof(keys)); if (keys.Length != certs.Length) throw new ArgumentException(nameof(certs)); - if (string.IsNullOrEmpty(eeName)) - throw new ArgumentOutOfRangeException(nameof(eeName)); - - var caUnlimited = new X509BasicConstraintsExtension(true, false, 0, true); - var eeConstraint = new X509BasicConstraintsExtension(false, false, 0, true); - var caUsage = new X509KeyUsageExtension( - X509KeyUsageFlags.CrlSign | - X509KeyUsageFlags.KeyCertSign | - X509KeyUsageFlags.DigitalSignature, - false); - - var eeUsage = new X509KeyUsageExtension( + rootExtensions ??= new X509Extension[] { + new X509BasicConstraintsExtension(true, false, 0, true), + new X509KeyUsageExtension( + X509KeyUsageFlags.CrlSign | + X509KeyUsageFlags.KeyCertSign | + X509KeyUsageFlags.DigitalSignature, + false) + }; + + intermediateExtensions ??= new X509Extension[] { + new X509BasicConstraintsExtension(true, false, 0, true), + new X509KeyUsageExtension( + X509KeyUsageFlags.CrlSign | + X509KeyUsageFlags.KeyCertSign | + X509KeyUsageFlags.DigitalSignature, + false) + }; + + endEntityExtensions ??= new X509Extension[] { + new X509BasicConstraintsExtension(false, false, 0, true), + new X509KeyUsageExtension( X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.NonRepudiation | X509KeyUsageFlags.KeyEncipherment, - false); + false) + }; TimeSpan notBeforeInterval = TimeSpan.FromDays(30); TimeSpan notAfterInterval = TimeSpan.FromDays(90); @@ -76,14 +134,20 @@ internal static void MakeTestChain( hashAlgorithm, signaturePadding); - rootReq.CertificateExtensions.Add(caUnlimited); - rootReq.CertificateExtensions.Add(caUsage); + foreach (X509Extension extension in rootExtensions) + { + rootReq.CertificateExtensions.Add(extension); + } + + X509SignatureGenerator lastGenerator = X509SignatureGenerator.CreateForRSA(keys[rootIndex], RSASignaturePadding.Pkcs1); + X500DistinguishedName lastSubject = rootReq.SubjectName; - X509Certificate2 lastWithKey = rootReq.CreateSelfSigned( + certs[rootIndex] = rootReq.Create( + lastSubject, + lastGenerator, eeStart - (notBeforeInterval * rootIndex), - eeEnd + (notAfterInterval * rootIndex)); - - certs[rootIndex] = new X509Certificate2(lastWithKey.RawData); + eeEnd + (notAfterInterval * rootIndex), + CreateSerial()); int presentationNumber = 0; @@ -97,41 +161,41 @@ internal static void MakeTestChain( hashAlgorithm, signaturePadding); - intermediateReq.CertificateExtensions.Add(caUnlimited); - intermediateReq.CertificateExtensions.Add(caUsage); - - // Leave serialBuf[0] as 0 to avoid a realloc in the signer - RandomNumberGenerator.Fill(serialBuf.AsSpan(1)); + foreach (X509Extension extension in intermediateExtensions) + { + intermediateReq.CertificateExtensions.Add(extension); + } certs[i] = intermediateReq.Create( - lastWithKey, + lastSubject, + lastGenerator, eeStart - (notBeforeInterval * i), eeEnd + (notAfterInterval * i), - serialBuf); + CreateSerial()); - lastWithKey.Dispose(); - lastWithKey = certs[i].CopyWithPrivateKey(keys[i]); + lastSubject = intermediateReq.SubjectName; + lastGenerator = X509SignatureGenerator.CreateForRSA(keys[i], RSASignaturePadding.Pkcs1); } CertificateRequest eeReq = new CertificateRequest( - eeName, + "CN=End-Entity", keys[0], hashAlgorithm, signaturePadding); - eeReq.CertificateExtensions.Add(eeConstraint); - eeReq.CertificateExtensions.Add(eeUsage); - - if (eeEku != null) + foreach (X509Extension extension in endEntityExtensions) { - eeReq.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(eeEku, false)); + eeReq.CertificateExtensions.Add(extension); } - // Leave serialBuf[0] as 0 to avoid a realloc in the signer - RandomNumberGenerator.Fill(serialBuf.AsSpan(1)); + certs[0] = eeReq.Create(lastSubject, lastGenerator, eeStart, eeEnd, CreateSerial()); + } - certs[0] = eeReq.Create(lastWithKey, eeStart, eeEnd, serialBuf); - lastWithKey.Dispose(); + private static byte[] CreateSerial() + { + byte[] bytes = new byte[8]; + RandomNumberGenerator.Fill(bytes); + return bytes; } } } From aa220859a86675a6f393afe8f18552f5c76a7d54 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Fri, 24 Apr 2020 13:15:12 -0400 Subject: [PATCH 5/6] Add intermediates to extra store --- .../tests/DynamicChainTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/tests/DynamicChainTests.cs b/src/libraries/System.Security.Cryptography.X509Certificates/tests/DynamicChainTests.cs index e813c03621fa02..1cba0c095674e2 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/tests/DynamicChainTests.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/tests/DynamicChainTests.cs @@ -238,9 +238,9 @@ public static void BasicConstraints_ExceedMaximumPathLength() chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; chain.ChainPolicy.VerificationTime = endEntityCert.NotBefore.AddSeconds(1); chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust; - chain.ChainPolicy.CustomTrustStore.Add(intermediateCert1); - chain.ChainPolicy.CustomTrustStore.Add(intermediateCert2); chain.ChainPolicy.CustomTrustStore.Add(rootCert); + chain.ChainPolicy.ExtraStore.Add(intermediateCert1); + chain.ChainPolicy.ExtraStore.Add(intermediateCert2); Assert.False(chain.Build(endEntityCert)); Assert.Equal(X509ChainStatusFlags.InvalidBasicConstraints, chain.AllStatusFlags()); @@ -273,8 +273,8 @@ public static void BasicConstraints_ViolatesCaFalse() chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; chain.ChainPolicy.VerificationTime = endEntityCert.NotBefore.AddSeconds(1); chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust; - chain.ChainPolicy.CustomTrustStore.Add(intermediateCert); chain.ChainPolicy.CustomTrustStore.Add(rootCert); + chain.ChainPolicy.ExtraStore.Add(intermediateCert); Assert.False(chain.Build(endEntityCert)); Assert.Equal(X509ChainStatusFlags.InvalidBasicConstraints, chain.AllStatusFlags()); From 133b1428fed07865fcbd81539501eb62d562d731 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Sat, 25 Apr 2020 13:21:04 -0400 Subject: [PATCH 6/6] Windows should be happy, too --- .../tests/DynamicChainTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/tests/DynamicChainTests.cs b/src/libraries/System.Security.Cryptography.X509Certificates/tests/DynamicChainTests.cs index 1cba0c095674e2..2fb3882674524b 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/tests/DynamicChainTests.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/tests/DynamicChainTests.cs @@ -208,7 +208,7 @@ public static void BasicConstraints_ExceedMaximumPathLength() new X509BasicConstraintsExtension( certificateAuthority: true, hasPathLengthConstraint: true, - pathLengthConstraint: 1, + pathLengthConstraint: 0, critical: true) };