/* * (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 #include #include #include #include #include #include #include #include using namespace Botan; std::map get_expected(); namespace { std::vector load_cert_file(const std::string& filename) { DataSource_Stream in(filename); std::vector certs; while(!in.end_of_data()) { try { certs.emplace_back(in); } catch(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 = split_on(line, ':'); if(parts.size() != 2) throw std::runtime_error("Invalid line " + line); m[parts[0]] = parts[1]; } return m; } } size_t test_x509_x509test() { // Test certs generated by https://github.com/yymax/x509test const std::string test_dir = "src/tests/data/x509test"; std::map results = read_results(test_dir + "/expected.txt"); const Path_Validation_Restrictions default_restrictions; size_t fail = 0; X509_Certificate root(test_dir + "/root.pem"); Certificate_Store_In_Memory trusted; trusted.add_certificate(root); for(auto i = results.begin(); i != results.end(); ++i) { 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); Path_Validation_Result result = x509_path_validate(certs, default_restrictions, trusted, "www.tls.test", Usage_Type::TLS_SERVER_AUTH); if(result.successful_validation() && result.trust_root() != root) result = Path_Validation_Result(Certificate_Status_Code::CANNOT_ESTABLISH_TRUST); if(result.result_string() != expected) { std::cout << "FAIL " << fsname << " expected '" << expected << "' got '" << result.result_string() << "'\n"; ++fail; } } test_report("X.509 (x509test)", results.size(), fail); return fail; } size_t test_nist_x509() { /** * 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/"; const size_t total_tests = 76; try { // Do nothing, just test filesystem access get_files_recursive(root_test_dir); } catch(No_Filesystem_Access) { std::cout << "Warning: No filesystem access, skipping NIST X.509 validation tests" << std::endl; return 0; } size_t unexp_failure = 0; size_t unexp_success = 0; size_t wrong_error = 0; size_t skipped = 0; size_t ran = 0; auto expected_results = get_expected(); try { for(size_t test_no = 1; test_no <= total_tests; ++test_no) { const std::string test_dir = root_test_dir + "/test" + (test_no <= 9 ? "0" : "") + std::to_string(test_no); const std::vector all_files = get_files_recursive(test_dir); if (all_files.empty()) std::cout << "Warning: No test files found in '" << test_dir << "'" << std::endl; 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()) { //printf("Skipping %d\n", test_no); skipped++; continue; } ++ran; Certificate_Store_In_Memory store; store.add_certificate(X509_Certificate(root_cert)); X509_Certificate end_user(to_verify); for(size_t i = 0; i != certs.size(); i++) store.add_certificate(X509_Certificate(certs[i])); for(size_t i = 0; i != crls.size(); i++) { DataSource_Stream in(crls[i], true); X509_CRL crl(in); store.add_crl(crl); } Path_Validation_Restrictions restrictions(true); Path_Validation_Result validation_result = x509_path_validate(end_user, restrictions, store); auto expected = expected_results[test_no]; Path_Validation_Result::Code result = validation_result.result(); if(result != expected) { std::cout << "NIST X.509 test #" << test_no << ": "; const std::string result_str = Path_Validation_Result::status_string(result); const std::string exp_str = Path_Validation_Result::status_string(expected); if(expected == Certificate_Status_Code::VERIFIED) { std::cout << "unexpected failure: " << result_str << std::endl; unexp_failure++; } else if(result == Certificate_Status_Code::VERIFIED) { std::cout << "unexpected success, expected " << exp_str << std::endl; unexp_success++; } else { std::cout << "wrong error, got '" << result_str << "' expected '" << exp_str << "'" << std::endl; wrong_error++; } } } } catch(std::exception& e) { std::cout << e.what() << std::endl; ++unexp_failure; } const size_t all_failures = unexp_failure + unexp_success + wrong_error; test_report("NIST X.509 path validation", ran, all_failures); return all_failures; } /* 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 get_expected() { std::map expected_results; /* OK, not a super great way of doing this... */ 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; } #else size_t test_x509_x509test() { return 0; } size_t test_nist_x509() { return 0; } #endif