Repository: randombit/botan
Pull request #1890 (2019-04-12 01:05:09)

Support verifying from a v1 root
Discovered in #1885, the "Verisign Class 3 Public Primary Certification Authority - G3" is a v1 certificate and is still included in distro trust stores.


Patch:
diff --git a/src/lib/x509/x509cert.cpp b/src/lib/x509/x509cert.cpp
index 5afbcc815..dda15835e 100644
--- a/src/lib/x509/x509cert.cpp
+++ b/src/lib/x509/x509cert.cpp
@@ -201,10 +201,13 @@ std::unique_ptr<X509_Certificate_Data> parse_x509_cert_body(const X509_Object& o
 
    if(v3_exts_data.is_a(3, ASN1_Tag(CONSTRUCTED | CONTEXT_SPECIFIC)))
       {
+      // Path validation will reject a v1/v2 cert with v3 extensions
       BER_Decoder(v3_exts_data).decode(data->m_v3_extensions).verify_end();
       }
    else if(v3_exts_data.is_set())
+      {
       throw BER_Bad_Tag("Unknown tag in X.509 cert", v3_exts_data.tagging());
+      }
 
    // Now cache some fields from the extensions
    if(auto ext = data->m_v3_extensions.get_extension_object_as<Cert_Extension::Key_Usage>())
@@ -442,11 +445,17 @@ const std::vector<uint8_t>& X509_Certificate::raw_subject_dn() const
 
 bool X509_Certificate::is_CA_cert() const
    {
+   if(data().m_version < 3 && data().m_self_signed)
+      return true;
+
    return data().m_is_ca_certificate;
    }
 
 uint32_t X509_Certificate::path_limit() const
    {
+   if(data().m_version < 3 && data().m_self_signed)
+      return 32; // in theory infinite, but this is more than enough
+
    return static_cast<uint32_t>(data().m_path_len_constraint);
    }
 


Test patch:
diff --git a/src/tests/data/x509/misc/v1ca/ee.pem b/src/tests/data/x509/misc/v1ca/ee.pem
new file mode 100644
index 000000000..5c31e9a03
--- /dev/null
+++ b/src/tests/data/x509/misc/v1ca/ee.pem
@@ -0,0 +1,39 @@
+-----BEGIN CERTIFICATE-----
+MIIG4jCCBcqgAwIBAgIQYy9GA8UA5qlx5vrqipbPVzANBgkqhkiG9w0BAQsFADB+
+MQswCQYDVQQGEwJVUzEdMBsGA1UEChMUU3ltYW50ZWMgQ29ycG9yYXRpb24xHzAd
+BgNVBAsTFlN5bWFudGVjIFRydXN0IE5ldHdvcmsxLzAtBgNVBAMTJlN5bWFudGVj
+IENsYXNzIDMgU2VjdXJlIFNlcnZlciBDQSAtIEc0MB4XDTE3MDIwMTAwMDAwMFoX
+DTIwMDIwMTIzNTk1OVowdzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3Ju
+aWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxHTAbBgNVBAoMFFN5bWFudGVjIENv
+cnBvcmF0aW9uMRwwGgYDVQQDDBNzc2x0ZXN0My5iYnRlc3QubmV0MIIBIjANBgkq
+hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAscaHBWE2Nf8BRzp6eNgK+Ta661anmssg
+kopOdX+ZtrKhPXWVqdrNos+Ceq0Wzbjb/TE9VJ552smUS1I0ciOhyQtXOkT4c+KT
+/ZYSZpMssgTHnXnjPxvZuX/A973R3WbITnZkQiPxncJCgDs45WnJd2k8zmd4kNUR
+5LQHfBlY/+XuWG6vbhOn7I1pw0wdeoe04bCW2iKIp23275+uiLXiDTdQzD9WySjM
+vDNi7Qm1vPmHPSugXouJZKsQxzyu8vAxB5OGc22i6sxtQHksZril6xPnsplXnRQt
+cCvMZFwBXcyj9u2ySkeGYyPdfe7NWW9hIBCS3yZcyL/TG02Qk2ltVwIDAQABo4ID
+YTCCA10wHgYDVR0RBBcwFYITc3NsdGVzdDMuYmJ0ZXN0Lm5ldDAJBgNVHRMEAjAA
+MA4GA1UdDwEB/wQEAwIFoDArBgNVHR8EJDAiMCCgHqAchhpodHRwOi8vc3Muc3lt
+Y2IuY29tL3NzLmNybDBhBgNVHSAEWjBYMFYGBmeBDAECAjBMMCMGCCsGAQUFBwIB
+FhdodHRwczovL2Quc3ltY2IuY29tL2NwczAlBggrBgEFBQcCAjAZDBdodHRwczov
+L2Quc3ltY2IuY29tL3JwYTAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw
+HwYDVR0jBBgwFoAUX2DPYZBV34RDFIpgKrL1evRDGO8wVwYIKwYBBQUHAQEESzBJ
+MB8GCCsGAQUFBzABhhNodHRwOi8vc3Muc3ltY2QuY29tMCYGCCsGAQUFBzAChhpo
+dHRwOi8vc3Muc3ltY2IuY29tL3NzLmNydDCCAfUGCisGAQQB1nkCBAIEggHlBIIB
+4QHfAHcA3esdK3oNT6Ygi4GtgWhwfi6OnQHVXIiNPRHEzbbsvswAAAFZ+9pGfAAA
+BAMASDBGAiEAq3g0UzvqQxdOfvWIbZoLKDW5WEwEV88zMbp8leiGSAsCIQC3cA6n
+AgvExoW76LEWD3CtuwoIIi35GliCFgpx2FZkXwB1AKS5CZC0GFgUh7sTosxncAo8
+NZgE+RvfuON3zQ7IDdwQAAABWfvaRrcAAAQDAEYwRAIgK1Y5HoYVBb/8lbkwSpbv
+d6Fg90+onLC49D1XljNav4gCIERSqXkpjibnfqL/xqv3BRkjpSjk1dhtq+F3F0Ip
++WQDAHUA7ku9t3XOYLrhQmkfq+GeZqMPfl+wctiDAMR7iXqo/csAAAFZ+9pIawAA
+BAMARjBEAiASOkJKxLMhxlntlK4YVse3c87mt9soN924flewVLiy9wIgbtKZM+Ub
+yoyRKHQHJ/qlzN7ynv1AqYjOFGYOsDgyIIkAdgC8eOHfxfY8aEZJM02hD6FfCXlp
+IAnAgbTz9pF/Ptm4pQAAAVn72kdyAAAEAwBHMEUCIDsqjiwtIIBy03n+vUplSyEd
+r8rt/ZIQy1HgGRgpGB/9AiEAn9v1ZDa27SSBS9yShGuZYV+zZPM835ePfzwlQKSK
+XFEwDQYJKoZIhvcNAQELBQADggEBAAjzN4oQGQuocJm45D3b34dhAZ/VoMe3MOAO
+saz/5ExO2/2HkULoOv8Y6poVxCdMYVbeNn3Bw1KE+LKmQUuVRgdTaq1RntkE3yGq
+FlsrRfxQyx3JLTJLBYOS2iuY8xhWHhFVRBoOCgsqWc2jjaftWmShIbWMuER//Sx9
+t+10b8HJ++KLD2g4WwZ8j15HEI1m+pAREmB/Kem/Ntfp39JDP8lW0hZkYH7iBvli
+MrA6bHwrkdZgQ96yS5Xe6At+FeyKKn3h1sATdByfA3EWxEnMM3MPe6mC5HTEtUDb
+u0cIInAO4BrmIdxFWWFmJntBbilmVpXfF+MVOzEwuxl51OAjNmI=
+-----END CERTIFICATE-----
diff --git a/src/tests/data/x509/misc/v1ca/int.pem b/src/tests/data/x509/misc/v1ca/int.pem
new file mode 100644
index 000000000..b5a83a706
--- /dev/null
+++ b/src/tests/data/x509/misc/v1ca/int.pem
@@ -0,0 +1,30 @@
+-----BEGIN CERTIFICATE-----
+MIIFFTCCA/2gAwIBAgIQKC4nkXkzkuQo8iGnTsk3rjANBgkqhkiG9w0BAQsFADCB
+yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
+ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMTk5OSBWZXJp
+U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW
+ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0
+aG9yaXR5IC0gRzMwHhcNMTMxMDMxMDAwMDAwWhcNMjMxMDMwMjM1OTU5WjB+MQsw
+CQYDVQQGEwJVUzEdMBsGA1UEChMUU3ltYW50ZWMgQ29ycG9yYXRpb24xHzAdBgNV
+BAsTFlN5bWFudGVjIFRydXN0IE5ldHdvcmsxLzAtBgNVBAMTJlN5bWFudGVjIENs
+YXNzIDMgU2VjdXJlIFNlcnZlciBDQSAtIEc0MIIBIjANBgkqhkiG9w0BAQEFAAOC
+AQ8AMIIBCgKCAQEAstgFyhx0LbUXVjnFSlIJluhL2AzxaJ+aQihiw6UwU35VEYJb
+A3oNL+F5BMm0lncZgQGUWfm893qZJ4Itt4PdWid/sgN6nFMl6UgfRk/InSn4vnlW
+9vf92Tpo2otLgjNBEsPIPMzWlnqEIRoiBAMnF4scaGGTDw5RgDMdtLXO637QYqzu
+s3sBdO9pNevK1T2p7peYyo2qRA4lmUoVlqTObQJUHypqJuIGOmNIrLRM0XWTUP8T
+L9ba4cYY9Z/JJV3zADreJk20KQnNDz0jbxZKgRb78oMQw7jW2FUyPfG9D72MUpVK
+Fpd6UiFjdS8W+cRmvvW1Cdj/JwDNRHxvSz+w9wIDAQABo4IBQDCCATwwHQYDVR0O
+BBYEFF9gz2GQVd+EQxSKYCqy9Xr0QxjvMBIGA1UdEwEB/wQIMAYBAf8CAQAwawYD
+VR0gBGQwYjBgBgpghkgBhvhFAQc2MFIwJgYIKwYBBQUHAgEWGmh0dHA6Ly93d3cu
+c3ltYXV0aC5jb20vY3BzMCgGCCsGAQUFBwICMBwaGmh0dHA6Ly93d3cuc3ltYXV0
+aC5jb20vcnBhMC8GA1UdHwQoMCYwJKAioCCGHmh0dHA6Ly9zLnN5bWNiLmNvbS9w
+Y2EzLWczLmNybDAOBgNVHQ8BAf8EBAMCAQYwKQYDVR0RBCIwIKQeMBwxGjAYBgNV
+BAMTEVN5bWFudGVjUEtJLTEtNTM0MC4GCCsGAQUFBwEBBCIwIDAeBggrBgEFBQcw
+AYYSaHR0cDovL3Muc3ltY2QuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQBbF1K+1lZ7
+9Pc0CUuWysf2IdBpgO/nmhnoJOJ/2S9h3RPrWmXk4WqQy04q6YoW51KN9kMbRwUN
+gKOomv4p07wdKNWlStRxPA91xQtzPwBIZXkNq2oeJQzAAt5mrL1LBmuaV4oqgX5n
+m7pSYHPEFfe7wVDJCKW6V0o6GxBzHOF7tpQDS65RsIJAOloknO4NWF2uuil6yjOe
+soHCL47BJ89A8AShP/U3wsr8rFNtqVNpT+F2ZAwlgak3A/I5czTSwXx4GByoaxbn
+5+CdKa/Y5Gk5eZVpuXtcXQGc1PfzSEUTZJXXCm5y2kMiJG8+WnDcwJLgLeVX+OQr
+J+71/xuzAYN6
+-----END CERTIFICATE-----
diff --git a/src/tests/data/x509/misc/v1ca/root.pem b/src/tests/data/x509/misc/v1ca/root.pem
new file mode 100644
index 000000000..688036446
--- /dev/null
+++ b/src/tests/data/x509/misc/v1ca/root.pem
@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQsw
+CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl
+cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu
+LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT
+aWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp
+dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD
+VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT
+aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ
+bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu
+IENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg
+LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMu6nFL8eB8aHm8b
+N3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1EUGO+i2t
+KmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGu
+kxUccLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBm
+CC+Vk7+qRy+oRpfwEuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJ
+Xwzw3sJ2zq/3avL6QaaiMxTJ5Xpj055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWu
+imi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAERSWwauSCPc/L8my/uRan2Te
+2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5fj267Cz3qWhMe
+DGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC
+/Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565p
+F4ErWjfJXir0xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGt
+TxzhT5yvDwyd93gN2PQ1VoDat20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ==
+-----END CERTIFICATE-----
diff --git a/src/tests/test_x509_path.cpp b/src/tests/test_x509_path.cpp
index 0e9f8eba0..71118ab2f 100644
--- a/src/tests/test_x509_path.cpp
+++ b/src/tests/test_x509_path.cpp
@@ -482,7 +482,65 @@ std::vector<Test::Result> PSS_Path_Validation_Tests::run()
 
 BOTAN_REGISTER_TEST("x509_path_rsa_pss", PSS_Path_Validation_Tests);
 
+class Validate_V1Cert_Test final : public Test
+   {
+   public:
+      std::vector<Test::Result> run() override;
+   };
+
+std::vector<Test::Result> Validate_V1Cert_Test::run()
+   {
+   if(Botan::has_filesystem_impl() == false)
+      {
+      return {Test::Result::Note("BSI path validation",
+                                 "Skipping due to missing filesystem access")};
+      }
+
+   std::vector<Test::Result> results;
+
+   const std::string root_crt = Test::data_file("/x509/misc/v1ca/root.pem");
+   const std::string int_crt  = Test::data_file("/x509/misc/v1ca/int.pem");
+   const std::string ee_crt   = Test::data_file("/x509/misc/v1ca/ee.pem");
+
+   auto validation_time =
+      Botan::calendar_point(2019, 4, 19, 23, 0, 0).to_std_timepoint();
+
+   Botan::X509_Certificate root(root_crt);
+   Botan::X509_Certificate intermediate(int_crt);
+   Botan::X509_Certificate ee_cert(ee_crt);
+
+   Botan::Certificate_Store_In_Memory trusted;
+   trusted.add_certificate(root);
+
+   std::vector<Botan::X509_Certificate> chain = { ee_cert, intermediate };
+
+   Botan::Path_Validation_Restrictions restrictions;
+   Botan::Path_Validation_Result validation_result =
+      Botan::x509_path_validate(chain, restrictions, trusted, "",
+                                Botan::Usage_Type::UNSPECIFIED, validation_time);
+
+   Test::Result result("Verifying using v1 certificate");
+   result.test_eq("Path validation result",
+                  validation_result.result_string(), "Verified");
+
+   Botan::Certificate_Store_In_Memory empty;
+
+   std::vector<Botan::X509_Certificate> new_chain = { ee_cert, intermediate, root };
+
+   Botan::Path_Validation_Result validation_result2 =
+      Botan::x509_path_validate(new_chain, restrictions, empty, "",
+                                Botan::Usage_Type::UNSPECIFIED, validation_time);
+
+   result.test_eq("Path validation result",
+                  validation_result2.result_string(), "Cannot establish trust");
+
+   return {result};
+   }
+
+BOTAN_REGISTER_TEST("x509_v1_ca", Validate_V1Cert_Test);
+
 class BSI_Path_Validation_Tests final : public Test
+
    {
    public:
       std::vector<Test::Result> run() override;
@@ -532,7 +590,7 @@ std::vector<Test::Result> BSI_Path_Validation_Tests::run()
       std::vector<Botan::X509_Certificate> certs;
 
       auto validation_time =
-            Botan::calendar_point(2017, 8, 19, 12, 0, 0).to_std_timepoint();
+         Botan::calendar_point(2017, 8, 19, 12, 0, 0).to_std_timepoint();
 
       // By convention: if CRL is a substring if the directory name,
       // we need to check the CRLs