Fix Linux chain.ChainStatus when multiple layers have errors
authorJeremy Barton <jbarton@microsoft.com>
Tue, 5 Mar 2019 17:21:27 +0000 (09:21 -0800)
committerGitHub <noreply@github.com>
Tue, 5 Mar 2019 17:21:27 +0000 (09:21 -0800)
The recent chain rewrite caused only the last layer with errors to be reported.

Commit migrated from https://github.com/dotnet/corefx/commit/fc9629cae7bfcda2196b24ba5127265efce1716a

src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Unix/OpenSslX509ChainProcessor.cs
src/libraries/System.Security.Cryptography.X509Certificates/tests/ChainTests.cs

index ea7ca81..d7f4f8b 100644 (file)
@@ -570,7 +570,7 @@ namespace Internal.Cryptography.Pal
                     if (elementErrors.HasValue && elementErrors.Value.HasErrors)
                     {
                         List<X509ChainStatus> statusBuilder = new List<X509ChainStatus>();
-                        overallStatus = new List<X509ChainStatus>();
+                        overallStatus = overallStatus ?? new List<X509ChainStatus>();
 
                         AddElementStatus(elementErrors.Value, statusBuilder, overallStatus);
                         status = statusBuilder.ToArray();
index b480b50..11ac21b 100644 (file)
@@ -559,9 +559,7 @@ namespace System.Security.Cryptography.X509Certificates.Tests
 
                     for (int j = 0; j < onlineChain.ChainElements.Count; j++)
                     {
-                        X509ChainStatusFlags chainFlags = onlineChain.ChainStatus.Aggregate(
-                            X509ChainStatusFlags.NoError,
-                            (cur, status) => cur | status.Status);
+                        X509ChainStatusFlags chainFlags = onlineChain.AllStatusFlags();
 
                         const X509ChainStatusFlags WontCheck =
                             X509ChainStatusFlags.RevocationStatusUnknown | X509ChainStatusFlags.UntrustedRoot;
@@ -577,9 +575,7 @@ namespace System.Security.Cryptography.X509Certificates.Tests
                         // Since `NoError` gets mapped as the empty array, just look for non-empty arrays
                         if (chainElement.ChainElementStatus.Length > 0)
                         {
-                            X509ChainStatusFlags allFlags = chainElement.ChainElementStatus.Aggregate(
-                                X509ChainStatusFlags.NoError,
-                                (cur, status) => cur | status.Status);
+                            X509ChainStatusFlags allFlags = chainElement.AllStatusFlags();
 
                             Console.WriteLine(
                                 $"{nameof(VerifyWithRevocation)}: online attempt {i} - errors at depth {j}: {allFlags}");
@@ -696,10 +692,7 @@ namespace System.Security.Cryptography.X509Certificates.Tests
 
                 chain.Build(cert);
 
-                X509ChainStatusFlags allFlags =
-                    chain.ChainStatus.Select(cs => cs.Status).Aggregate(
-                        X509ChainStatusFlags.NoError,
-                        (a, b) => a | b);
+                X509ChainStatusFlags allFlags = chain.AllStatusFlags();
 
                 if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
                 {
@@ -718,6 +711,90 @@ namespace System.Security.Cryptography.X509Certificates.Tests
         }
 
         [Fact]
+        public static void ChainErrorsAtMultipleLayers()
+        {
+            // These certificates were generated for this test using CertificateRequest
+            // but the netstandard(2.0) version of this test library doesn't have
+            // CertificateRequest available.
+            //
+            // These certificates have been hard-coded to enable the scenario on
+            // netstandard.
+            byte[] endEntityBytes = Encoding.ASCII.GetBytes(@"
+-----BEGIN CERTIFICATE-----
+MIIC6DCCAdCgAwIBAgIQAKjmD7+TWUwQN2ucajn9kTANBgkqhkiG9w0BAQsFADAXMRUwEwYDVQQD
+EwxJbnRlcm1lZGlhdGUwHhcNMTkwMzAzMjM1NzA3WhcNMTkwNjAzMjM1NzA3WjAVMRMwEQYDVQQD
+EwpFbmQtRW50aXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxTybBkpMdQ8IeL1C
+jG755+ifQfqjNt4+Xhm3pbMi+nCRD68tym1xviUka1hQmx+I1mptswW0Laq1owur0r2KanKoIP2F
+i2h6orOOdslMFPMWqCuNTU4C7cUxokaWah0R7FihwW+aBeWgxG948Cvt+ByQeR1ns9yo7wa8f8kT
+IwzOUu0v1Yj5oW5bOn/cmIBE1C5CD5RivPMGUXX8mZ/myNh16dLQqJW5yQt/uvfr7lkNWC0qq+v7
+Ely4+X27acwMTdtk4chcr5/bTS5FXV7HVqwhajOmm6WrzagPZBELWKRk2EaJkha/MLrBqNfHExs4
+sx2ks+TTclrOrRzG+AUBuQIDAQABozIwMDAMBgNVHRMBAf8EAjAAMAsGA1UdDwQEAwIF4DATBgNV
+HSUEDDAKBggrBgEFBQcDAjANBgkqhkiG9w0BAQsFAAOCAQEAbrEbiw4gpgWi3SJ+sGrfcWCAldpx
+0735hkkYz94OsJjIwWfgQ03pYZwjcnIE4Ln0PU2E52D2ldsJlAE376hpNxdO0X4RLpZVZPEjKGTF
+v2Rf+d0cpqha5J//mqcTTm7F58JRKyfEQn0pqfxx4VyXeLfEsqYbT3kY7ufK0km3Jst0DGw2AGue
+MPmiZicaNlXPVO9vyW4s6J23+kol6X8K2rnVht9jagfnOQ990Ux2xXGyDGM4I0pvW1Zo4vid/eli
+psHHsU9xg0o7L2WXD5qYhD2JCQIVWNRmRZCf1luWlKqUaqWWONMJ44hk8Md+ohxpyCRmbtLRZPzd
+wlkQzPsc9A==
+-----END CERTIFICATE-----");
+            byte[] intermediateBytes = Encoding.ASCII.GetBytes(@"
+-----BEGIN CERTIFICATE-----
+MIIC1DCCAbygAwIBAgIPRoY1rB2tMVJeYB4GILkNMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNVBAMT
+CVRlc3QgUm9vdDAeFw0xOTAyMTgyMzU3MDdaFw0yMDAyMTgyMzU3MDdaMBcxFTATBgNVBAMTDElu
+dGVybWVkaWF0ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALxYzEN6nYvQ0TOg/jOF
+wdBGRUYhTiJpYGFBh9826X5vKlbCS1UAcjFRXmKtJ4WZ8v3peCBPxvVe/1KR38+MWNVtO4B1GBvr
+qR2T9k1ewgn0lO3i6krnIAhJQ+F94xGcsRAfZjXBh7lOmTE9ZlDhDJWkehBIs5TteiBOfbGDml2S
+v7x81cmm2o/sDoP1oVGhezOkFtI2/NdZYKxRthnjDywN3W4KFataJFATVv/yq+QjcLEWrXFRpzDE
+rpVdYmj66kaAnu9D4sHhFqOk1SX3JvcB361stVuUPp2ri75MaaXakweH6X/Yb4nPNV6m1ENwMoDy
+HqrZrHSK8SpzfhY9aB0CAwEAAaMgMB4wDwYDVR0TAQH/BAUwAwEB/zALBgNVHQ8EBAMCAYYwDQYJ
+KoZIhvcNAQELBQADggEBAC4oJ2SH+Ov4QIMXo7mwGSrwONkdMuKyyM9shZiGEH+zIO9SVuPuvtQG
+cePR2bijSz2DtjySi+ST8y3Ql7A3isfbXYPDFmnkzKP6hGvLkctc8eO8U1x7ny+QW1max0gm3UA8
+CY0IMP8pCHUZH9OX/K0N9L+GItqlBK8G4grJ4o43da2x9L0hIrdauPadaGcJalf8k1ymhJ4VDj7t
+ueuTl2qTtbBh015GuEld61EBXSBLIUqwOAeFYrNJbC4J2mXgnLTWC380cBf5KWeSdjLYgk2sZ1V4
+FKKQecZIhxdlDGzMAbbmEV+2EqS+As2C7+y4dkpG4nnbQe/4AFr8vekHdrI=
+-----END CERTIFICATE-----");
+            byte[] rootBytes = Encoding.ASCII.GetBytes(@"
+-----BEGIN CERTIFICATE-----
+MIICyjCCAbKgAwIBAgIIKKt3K3rRbvQwDQYJKoZIhvcNAQELBQAwFDESMBAGA1UEAxMJVGVzdCBS
+b290MB4XDTE5MDIwNDIzNTcwN1oXDTIxMDIwNDIzNTcwN1owFDESMBAGA1UEAxMJVGVzdCBSb290
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAiM7tv4YvqmWYGF1vbeM2cQWV1NVBxKU4
+ZK5XEJHZirzE2HCiA0+hI/UD7xnfBrzGQRLsHnp9vfhBi/0wenSIKTckxcGGpuM+JzNoVF97uFSd
+bKvfIwQZzbdRGyTF1eoQWCylsZsnZOXg8c/yoFhG2TJB38l09RYn+HkMkapQERFKSXPZ7taNVJNb
+Sedp3l9jO0aVmh9rmJ7taBXBfWDmSWqhkxjkEcbiRxB7z5K8YxZBlHQCLqf43JiCbKIMBHdzTg+N
+lEBkBGp6T2hoJ4/A1uwvhesjmyqagZrC2NnzOWOxUQ/WujIUfS62ii/yDkP4Jo3745lJ9XXoPbIw
+AwvWYQIDAQABoyAwHjAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBhjANBgkqhkiG9w0BAQsF
+AAOCAQEAA/pfswrUzcLP5UfmHgQDc1slJjh0btnkN+4dxCCTLcnteJCTumYw+/82qL+O4t1KlzlS
+2Eqgyx0u48YmwDp/5jWAvT8RX8pvV3Prd7T8/dp/ucES7R9r3zF2Rmw5Me9iq1yaLAypGyBGqV1J
+HAwJjH/eKZ5iuOMhFljs2R5Gh5rRsQjNVUCRsolCds4d1f+76fi2SGaKqkAA4gzg1c71SPTAaUPR
+ythjxnoCBDVFmwV5opXZj9qIZoUdH92gCVFgMWkxWCYWzyH78uIUzV1oo+KNwK1SCTnfVHcfWRIL
+tHP28fj0LUop/QFojSZPsaPAW6JvoQ0t4hd6WoyX6z7FsA==
+-----END CERTIFICATE-----");
+
+            using (X509Certificate2 endEntityCert = new X509Certificate2(endEntityBytes))
+            using (X509Certificate2 intermediateCert = new X509Certificate2(intermediateBytes))
+            using (X509Certificate2 rootCert = new X509Certificate2(rootBytes))
+            using (ChainHolder chainHolder = new ChainHolder())
+            {
+                X509Chain chain = chainHolder.Chain;
+                chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
+                chain.ChainPolicy.VerificationFlags |= X509VerificationFlags.AllowUnknownCertificateAuthority;
+                chain.ChainPolicy.ExtraStore.Add(intermediateCert);
+                chain.ChainPolicy.ExtraStore.Add(rootCert);
+                chain.ChainPolicy.VerificationTime = endEntityCert.NotAfter.AddDays(1);
+
+                Assert.Equal(false, chain.Build(endEntityCert));
+
+                Assert.Equal(3, chain.ChainElements.Count);
+                Assert.Equal(X509ChainStatusFlags.NotTimeValid, chain.ChainElements[0].AllStatusFlags());
+                Assert.Equal(X509ChainStatusFlags.NoError, chain.ChainElements[1].AllStatusFlags());
+                Assert.Equal(X509ChainStatusFlags.UntrustedRoot, chain.ChainElements[2].AllStatusFlags());
+
+                Assert.Equal(
+                    X509ChainStatusFlags.NotTimeValid | X509ChainStatusFlags.UntrustedRoot,
+                    chain.AllStatusFlags());
+            }
+        }
+
+        [Fact]
         public static void ChainWithEmptySubject()
         {
             using (var cert = new X509Certificate2(TestData.EmptySubjectCertificate))
@@ -736,5 +813,19 @@ namespace System.Security.Cryptography.X509Certificates.Tests
                 Assert.Equal(issuer.RawData, chain.ChainElements[1].Certificate.RawData);
             }
         }
+
+        private static X509ChainStatusFlags AllStatusFlags(this X509Chain chain)
+        {
+            return chain.ChainStatus.Aggregate(
+                X509ChainStatusFlags.NoError,
+                (f, s) => f | s.Status);
+        }
+
+        private static X509ChainStatusFlags AllStatusFlags(this X509ChainElement chainElement)
+        {
+            return chainElement.ChainElementStatus.Aggregate(
+                X509ChainStatusFlags.NoError,
+                (f, s) => f | s.Status);
+        }
     }
 }