/* * (C) 2006,2011,2012,2014,2015 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ #include "tests.h" #if defined(BOTAN_HAS_X509_CERTIFICATES) #include #include #endif #include #include #include #include #include #include #include namespace Botan_Tests { namespace { #if defined(BOTAN_HAS_X509_CERTIFICATES) class X509test_Path_Validation_Tests : public Test { public: std::vector run() override { std::vector results; // Test certs generated by https://github.com/yymax/x509test const std::string test_dir = "src/tests/data/x509test"; std::map expected = read_results(test_dir + "/expected.txt"); const Botan::Path_Validation_Restrictions default_restrictions; Botan::X509_Certificate root(test_dir + "/root.pem"); Botan::Certificate_Store_In_Memory trusted; trusted.add_certificate(root); for(auto i = expected.begin(); i != expected.end(); ++i) { Test::Result result("X509test path validation"); const std::string fsname = i->first; const std::string expected = i->second; std::vector certs = load_cert_file(test_dir + "/" + fsname); if(certs.empty()) throw std::runtime_error("Failed to read certs from " + fsname); Botan::Path_Validation_Result path_result = Botan::x509_path_validate( certs, default_restrictions, trusted, "www.tls.test", Botan::Usage_Type::TLS_SERVER_AUTH); if(path_result.successful_validation() && path_result.trust_root() != root) path_result = Botan::Path_Validation_Result(Botan::Certificate_Status_Code::CANNOT_ESTABLISH_TRUST); result.test_eq("validation result", path_result.result_string(), expected); results.push_back(result); } return results; } private: std::vector load_cert_file(const std::string& filename) { Botan::DataSource_Stream in(filename); std::vector certs; while(!in.end_of_data()) { try { certs.emplace_back(in); } catch(Botan::Decoding_Error&) {} } return certs; } std::map read_results(const std::string& results_file) { std::ifstream in(results_file); if(!in.good()) throw std::runtime_error("Failed reading " + results_file); std::map m; std::string line; while(in.good()) { std::getline(in, line); if(line == "") continue; if(line[0] == '#') continue; std::vector parts = Botan::split_on(line, ':'); if(parts.size() != 2) throw std::runtime_error("Invalid line " + line); m[parts[0]] = parts[1]; } return m; } }; BOTAN_REGISTER_TEST("x509_path_x509test", X509test_Path_Validation_Tests); class NIST_Path_Validation_Tests : public Test { public: std::vector run() override; private: std::map get_expected(); }; /* The expected results are essentially the error codes that best coorespond to the problem described in the testing documentation. There are a few cases where the tests say there should or should not be an error, and I disagree. A few of the tests have test results different from what they "should" be: these changes are marked as such, and have comments explaining the problem at hand. */ std::map NIST_Path_Validation_Tests::get_expected() { using namespace Botan; std::map expected_results; // TODO read from a file expected_results[1] = Certificate_Status_Code::VERIFIED; expected_results[2] = Certificate_Status_Code::SIGNATURE_ERROR; expected_results[3] = Certificate_Status_Code::SIGNATURE_ERROR; expected_results[4] = Certificate_Status_Code::VERIFIED; expected_results[5] = Certificate_Status_Code::CERT_NOT_YET_VALID; expected_results[6] = Certificate_Status_Code::CERT_NOT_YET_VALID; expected_results[7] = Certificate_Status_Code::VERIFIED; expected_results[8] = Certificate_Status_Code::CERT_NOT_YET_VALID; expected_results[9] = Certificate_Status_Code::CERT_HAS_EXPIRED; expected_results[10] = Certificate_Status_Code::CERT_HAS_EXPIRED; expected_results[11] = Certificate_Status_Code::CERT_HAS_EXPIRED; expected_results[12] = Certificate_Status_Code::VERIFIED; expected_results[13] = Certificate_Status_Code::CERT_ISSUER_NOT_FOUND; expected_results[14] = Certificate_Status_Code::CERT_ISSUER_NOT_FOUND; expected_results[15] = Certificate_Status_Code::VERIFIED; expected_results[16] = Certificate_Status_Code::VERIFIED; expected_results[17] = Certificate_Status_Code::VERIFIED; expected_results[18] = Certificate_Status_Code::VERIFIED; expected_results[19] = Certificate_Status_Code::NO_REVOCATION_DATA; expected_results[20] = Certificate_Status_Code::CERT_IS_REVOKED; expected_results[21] = Certificate_Status_Code::CERT_IS_REVOKED; expected_results[22] = Certificate_Status_Code::CA_CERT_NOT_FOR_CERT_ISSUER; expected_results[23] = Certificate_Status_Code::CA_CERT_NOT_FOR_CERT_ISSUER; expected_results[24] = Certificate_Status_Code::VERIFIED; expected_results[25] = Certificate_Status_Code::CA_CERT_NOT_FOR_CERT_ISSUER; expected_results[26] = Certificate_Status_Code::VERIFIED; expected_results[27] = Certificate_Status_Code::VERIFIED; expected_results[28] = Certificate_Status_Code::CA_CERT_NOT_FOR_CERT_ISSUER; expected_results[29] = Certificate_Status_Code::CA_CERT_NOT_FOR_CERT_ISSUER; expected_results[30] = Certificate_Status_Code::VERIFIED; expected_results[31] = Certificate_Status_Code::CA_CERT_NOT_FOR_CRL_ISSUER; expected_results[32] = Certificate_Status_Code::CA_CERT_NOT_FOR_CRL_ISSUER; expected_results[33] = Certificate_Status_Code::VERIFIED; /* Policy tests: a little trickier because there are other inputs which affect the result. In the case of the tests currently in the suite, the default method (with acceptable policy being "any-policy" and with no explicit policy required), will almost always result in a verified status. This is not particularly helpful. So, we should do several different tests for each test set: 1) With the user policy as any-policy and no explicit policy 2) With the user policy as any-policy and an explicit policy required 3) With the user policy as test-policy-1 (2.16.840.1.101.3.1.48.1) and an explicit policy required 4) With the user policy as either test-policy-1 or test-policy-2 and an explicit policy required This provides reasonably good coverage of the possible outcomes. */ expected_results[34] = Certificate_Status_Code::VERIFIED; expected_results[35] = Certificate_Status_Code::VERIFIED; expected_results[36] = Certificate_Status_Code::VERIFIED; expected_results[37] = Certificate_Status_Code::VERIFIED; expected_results[38] = Certificate_Status_Code::VERIFIED; expected_results[39] = Certificate_Status_Code::VERIFIED; expected_results[40] = Certificate_Status_Code::VERIFIED; expected_results[41] = Certificate_Status_Code::VERIFIED; expected_results[42] = Certificate_Status_Code::VERIFIED; expected_results[43] = Certificate_Status_Code::VERIFIED; expected_results[44] = Certificate_Status_Code::VERIFIED; //expected_results[45] = Certificate_Status_Code::EXPLICIT_POLICY_REQUIRED; //expected_results[46] = Certificate_Status_Code::ACCEPT; //expected_results[47] = Certificate_Status_Code::EXPLICIT_POLICY_REQUIRED; expected_results[48] = Certificate_Status_Code::VERIFIED; expected_results[49] = Certificate_Status_Code::VERIFIED; expected_results[50] = Certificate_Status_Code::VERIFIED; expected_results[51] = Certificate_Status_Code::VERIFIED; expected_results[52] = Certificate_Status_Code::VERIFIED; expected_results[53] = Certificate_Status_Code::VERIFIED; expected_results[54] = Certificate_Status_Code::CERT_CHAIN_TOO_LONG; expected_results[55] = Certificate_Status_Code::CERT_CHAIN_TOO_LONG; expected_results[56] = Certificate_Status_Code::VERIFIED; expected_results[57] = Certificate_Status_Code::VERIFIED; expected_results[58] = Certificate_Status_Code::CERT_CHAIN_TOO_LONG; expected_results[59] = Certificate_Status_Code::CERT_CHAIN_TOO_LONG; expected_results[60] = Certificate_Status_Code::CERT_CHAIN_TOO_LONG; expected_results[61] = Certificate_Status_Code::CERT_CHAIN_TOO_LONG; expected_results[62] = Certificate_Status_Code::VERIFIED; expected_results[63] = Certificate_Status_Code::VERIFIED; expected_results[64] = Certificate_Status_Code::CRL_BAD_SIGNATURE; expected_results[65] = Certificate_Status_Code::NO_REVOCATION_DATA; expected_results[66] = Certificate_Status_Code::NO_REVOCATION_DATA; expected_results[67] = Certificate_Status_Code::VERIFIED; expected_results[68] = Certificate_Status_Code::CERT_IS_REVOKED; expected_results[69] = Certificate_Status_Code::CERT_IS_REVOKED; expected_results[70] = Certificate_Status_Code::CERT_IS_REVOKED; expected_results[71] = Certificate_Status_Code::CERT_IS_REVOKED; expected_results[72] = Certificate_Status_Code::CRL_HAS_EXPIRED; expected_results[73] = Certificate_Status_Code::CRL_HAS_EXPIRED; expected_results[74] = Certificate_Status_Code::VERIFIED; /* These tests use weird CRL extensions which aren't supported yet */ //expected_results[75] = ; //expected_results[76] = ; return expected_results; } std::vector NIST_Path_Validation_Tests::run() { std::vector results; /** * Code to run the X.509v3 processing tests described in "Conformance * Testing of Relying Party Client Certificate Path Proccessing Logic", * which is available on NIST's web site. * * Known Failures/Problems: * - Policy extensions are not implemented, so we skip tests #34-#53. * - Tests #75 and #76 are skipped as they make use of relatively * obscure CRL extensions which are not supported. */ const std::string root_test_dir = "src/tests/data/nist_x509/"; try { // Do nothing, just test filesystem access Botan::get_files_recursive(root_test_dir); } catch(Botan::No_Filesystem_Access) { Test::Result result("NIST path validation"); result.test_note("Skipping due to missing filesystem access"); results.push_back(result); return results; } const size_t total_tests = 76; std::map expected_results = get_expected(); for(size_t test_no = 1; test_no <= total_tests; ++test_no) { try { Test::Result result("NIST path validation"); const std::string test_dir = root_test_dir + "/test" + (test_no <= 9 ? "0" : "") + std::to_string(test_no); const std::vector all_files = Botan::get_files_recursive(test_dir); if (all_files.empty()) { result.test_failure("No test files found in " + test_dir); continue; } std::vector certs, crls; std::string root_cert, to_verify; for(const auto ¤t : all_files) { if(current.find("int") != std::string::npos && current.find(".crt") != std::string::npos) certs.push_back(current); else if(current.find("root.crt") != std::string::npos) root_cert = current; else if(current.find("end.crt") != std::string::npos) to_verify = current; else if(current.find(".crl") != std::string::npos) crls.push_back(current); } if(expected_results.find(test_no) == expected_results.end()) { result.test_note("Skipping test"); continue; } Botan::Certificate_Store_In_Memory store; store.add_certificate(Botan::X509_Certificate(root_cert)); Botan::X509_Certificate end_user(to_verify); for(size_t i = 0; i != certs.size(); i++) store.add_certificate(Botan::X509_Certificate(certs[i])); for(size_t i = 0; i != crls.size(); i++) { Botan::DataSource_Stream in(crls[i], true); Botan::X509_CRL crl(in); store.add_crl(crl); } Botan::Path_Validation_Restrictions restrictions(true); Botan::Path_Validation_Result validation_result = Botan::x509_path_validate(end_user, restrictions, store); auto expected = expected_results[test_no]; result.test_eq("path validation result", validation_result.result_string(), Botan::Path_Validation_Result::status_string(expected)); results.push_back(result); } catch(std::exception& e) { results.push_back(Test::Result::Failure("NIST X509 " + std::to_string(test_no), e.what())); } } return results; } BOTAN_REGISTER_TEST("x509_path_nist", NIST_Path_Validation_Tests); #endif } }