Revision control

Copy as Markdown

Other Tools

/*
* (C) 2006,2011,2012,2014,2015 Jack Lloyd
* (C) 2022 René Meusel, Rohde & Schwarz Cybersecurity
*
* Botan is released under the Simplified BSD License (see license.txt)
*/
#include "tests.h"
#if defined(BOTAN_HAS_X509_CERTIFICATES)
#include <botan/data_src.h>
#include <botan/exceptn.h>
#include <botan/pkcs10.h>
#include <botan/x509_crl.h>
#include <botan/x509_key.h>
#include <botan/x509path.h>
#include <botan/internal/calendar.h>
#include <botan/internal/filesystem.h>
#include <botan/internal/fmt.h>
#include <botan/internal/parsing.h>
#if defined(BOTAN_HAS_ECDSA)
#include <botan/ec_group.h>
#endif
#include <algorithm>
#include <fstream>
#include <limits>
#include <map>
#include <string>
#include <vector>
#endif
namespace Botan_Tests {
namespace {
#if defined(BOTAN_HAS_X509_CERTIFICATES) && defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
#if defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_EMSA_PKCS1)
std::map<std::string, std::string> read_results(const std::string& results_file, const char delim = ':') {
std::ifstream in(results_file);
if(!in.good()) {
throw Test_Error("Failed reading " + results_file);
}
std::map<std::string, std::string> m;
std::string line;
while(in.good()) {
std::getline(in, line);
if(line.empty()) {
continue;
}
if(line[0] == '#') {
continue;
}
std::vector<std::string> parts = Botan::split_on(line, delim);
if(parts.size() != 2) {
throw Test_Error("Invalid line " + line);
}
m[parts[0]] = parts[1];
}
return m;
}
std::set<Botan::Certificate_Status_Code> flatten(const Botan::CertificatePathStatusCodes& codes) {
std::set<Botan::Certificate_Status_Code> result;
for(const auto& statuses : codes) {
result.insert(statuses.begin(), statuses.end());
}
return result;
}
class X509test_Path_Validation_Tests final : public Test {
public:
std::vector<Test::Result> run() override {
std::vector<Test::Result> results;
// Test certs generated by https://github.com/yymax/x509test
// Current tests use SHA-1
const Botan::Path_Validation_Restrictions restrictions(false, 80);
Botan::X509_Certificate root(Test::data_file("x509/x509test/root.pem"));
Botan::Certificate_Store_In_Memory trusted;
trusted.add_certificate(root);
auto validation_time = Botan::calendar_point(2016, 10, 21, 4, 20, 0).to_std_timepoint();
for(const auto& [filename, expected_result] : read_results(Test::data_file("x509/x509test/expected.txt"))) {
Test::Result result("X509test path validation");
result.start_timer();
std::vector<Botan::X509_Certificate> certs = load_cert_file(Test::data_file("x509/x509test/" + filename));
if(certs.empty()) {
throw Test_Error("Failed to read certs from " + filename);
}
Botan::Path_Validation_Result path_result = Botan::x509_path_validate(
certs, restrictions, trusted, "www.tls.test", Botan::Usage_Type::TLS_SERVER_AUTH, validation_time);
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("test " + filename, path_result.result_string(), expected_result);
result.test_eq("test no warnings string", path_result.warnings_string(), "");
result.confirm("test no warnings", path_result.no_warnings());
result.end_timer();
results.push_back(result);
}
// test softfail
{
Test::Result result("X509test path validation softfail");
result.start_timer();
// this certificate must not have a OCSP URL
const std::string filename = "ValidAltName.pem";
std::vector<Botan::X509_Certificate> certs = load_cert_file(Test::data_file("x509/x509test/" + filename));
if(certs.empty()) {
throw Test_Error("Failed to read certs from " + filename);
}
Botan::Path_Validation_Result path_result =
Botan::x509_path_validate(certs,
restrictions,
trusted,
"www.tls.test",
Botan::Usage_Type::TLS_SERVER_AUTH,
validation_time,
/* activate check_ocsp_online */ std::chrono::milliseconds(1000),
{});
if(path_result.successful_validation() && path_result.trust_root() != root) {
path_result = Botan::Path_Validation_Result(Botan::Certificate_Status_Code::CANNOT_ESTABLISH_TRUST);
}
// certificate verification succeed even if no OCSP URL (softfail)
result.confirm("test success", path_result.successful_validation());
result.test_eq("test " + filename, path_result.result_string(), "Verified");
#if defined(BOTAN_TARGET_OS_HAS_THREADS) && defined(BOTAN_HAS_HTTP_UTIL)
// if softfail, there is warnings
result.confirm("test warnings", !path_result.no_warnings());
result.test_eq("test warnings string", path_result.warnings_string(), "[0] OCSP URL not available");
#endif
result.end_timer();
results.push_back(result);
}
return results;
}
private:
static std::vector<Botan::X509_Certificate> load_cert_file(const std::string& filename) {
Botan::DataSource_Stream in(filename);
std::vector<Botan::X509_Certificate> certs;
while(!in.end_of_data()) {
try {
certs.emplace_back(in);
} catch(Botan::Decoding_Error&) {}
}
return certs;
}
};
BOTAN_REGISTER_TEST("x509", "x509_path_x509test", X509test_Path_Validation_Tests);
class NIST_Path_Validation_Tests final : public Test {
public:
std::vector<Test::Result> run() override;
};
std::vector<Test::Result> NIST_Path_Validation_Tests::run() {
if(Botan::has_filesystem_impl() == false) {
return {Test::Result::Note("NIST path validation", "Skipping due to missing filesystem access")};
}
std::vector<Test::Result> 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.
*/
std::map<std::string, std::string> expected = read_results(Test::data_file("x509/nist/expected.txt"));
const Botan::X509_Certificate root_cert(Test::data_file("x509/nist/root.crt"));
const Botan::X509_CRL root_crl(Test::data_file("x509/nist/root.crl"));
const auto validation_time = Botan::calendar_point(2018, 4, 1, 9, 30, 33).to_std_timepoint();
for(auto i = expected.begin(); i != expected.end(); ++i) {
Test::Result result("NIST path validation");
result.start_timer();
const std::string test_name = i->first;
try {
const std::string expected_result = i->second;
const auto all_files = Test::files_in_data_dir("x509/nist/" + test_name);
Botan::Certificate_Store_In_Memory store;
store.add_certificate(root_cert);
store.add_crl(root_crl);
for(const auto& file : all_files) {
if(file.ends_with(".crt") && file != "end.crt") {
store.add_certificate(Botan::X509_Certificate(file));
} else if(file.ends_with(".crl")) {
Botan::DataSource_Stream in(file, true);
Botan::X509_CRL crl(in);
store.add_crl(crl);
}
}
Botan::X509_Certificate end_user(Test::data_file("x509/nist/" + test_name + "/end.crt"));
// 1024 bit root cert
Botan::Path_Validation_Restrictions restrictions(true, 80);
Botan::Path_Validation_Result validation_result = Botan::x509_path_validate(
end_user, restrictions, store, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
result.test_eq(test_name + " path validation result", validation_result.result_string(), expected_result);
} catch(std::exception& e) {
result.test_failure(test_name, e.what());
}
result.end_timer();
results.push_back(result);
}
return results;
}
BOTAN_REGISTER_TEST("x509", "x509_path_nist", NIST_Path_Validation_Tests);
class Extended_Path_Validation_Tests final : public Test {
public:
std::vector<Test::Result> run() override;
};
std::vector<Test::Result> Extended_Path_Validation_Tests::run() {
if(Botan::has_filesystem_impl() == false) {
return {Test::Result::Note("Extended x509 path validation", "Skipping due to missing filesystem access")};
}
std::vector<Test::Result> results;
auto validation_time = Botan::calendar_point(2017, 9, 1, 9, 30, 33).to_std_timepoint();
for(const auto& [test_name, expected_result] : read_results(Test::data_file("x509/extended/expected.txt"))) {
Test::Result result("Extended X509 path validation");
result.start_timer();
const auto all_files = Test::files_in_data_dir("x509/extended/" + test_name);
Botan::Certificate_Store_In_Memory store;
for(const auto& file : all_files) {
if(file.ends_with(".crt") && file != "end.crt") {
store.add_certificate(Botan::X509_Certificate(file));
}
}
Botan::X509_Certificate end_user(Test::data_file("x509/extended/" + test_name + "/end.crt"));
Botan::Path_Validation_Restrictions restrictions;
Botan::Path_Validation_Result validation_result =
Botan::x509_path_validate(end_user, restrictions, store, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
result.test_eq(test_name + " path validation result", validation_result.result_string(), expected_result);
result.end_timer();
results.push_back(result);
}
return results;
}
BOTAN_REGISTER_TEST("x509", "x509_path_extended", Extended_Path_Validation_Tests);
class PSS_Path_Validation_Tests : public Test {
public:
std::vector<Test::Result> run() override;
};
std::vector<Test::Result> PSS_Path_Validation_Tests::run() {
if(Botan::has_filesystem_impl() == false) {
return {Test::Result::Note("RSA-PSS X509 signature validation", "Skipping due to missing filesystem access")};
}
std::vector<Test::Result> results;
const auto validation_times = read_results(Test::data_file("x509/pss_certs/validation_times.txt"));
auto validation_times_iter = validation_times.begin();
for(const auto& [test_name, expected_result] : read_results(Test::data_file("x509/pss_certs/expected.txt"))) {
Test::Result result("RSA-PSS X509 signature validation");
result.start_timer();
const auto all_files = Test::files_in_data_dir("x509/pss_certs/" + test_name);
std::optional<Botan::X509_CRL> crl;
std::optional<Botan::X509_Certificate> end;
std::optional<Botan::X509_Certificate> root;
Botan::Certificate_Store_In_Memory store;
std::optional<Botan::PKCS10_Request> csr;
const auto validation_year = Botan::to_u32bit((validation_times_iter++)->second);
const auto validation_time = Botan::calendar_point(validation_year, 0, 0, 0, 0, 0).to_std_timepoint();
for(const auto& file : all_files) {
if(file.find("end.crt") != std::string::npos) {
end = Botan::X509_Certificate(file);
} else if(file.find("root.crt") != std::string::npos) {
root = Botan::X509_Certificate(file);
store.add_certificate(*root);
} else if(file.ends_with(".crl")) {
crl = Botan::X509_CRL(file);
} else if(file.ends_with(".csr")) {
csr = Botan::PKCS10_Request(file);
}
}
if(end && crl && root) // CRL tests
{
const std::vector<Botan::X509_Certificate> cert_path = {*end, *root};
const std::vector<std::optional<Botan::X509_CRL>> crls = {crl};
auto crl_status = Botan::PKIX::check_crl(
cert_path,
crls,
validation_time); // alternatively we could just call crl.check_signature( root_pubkey )
result.test_eq(test_name + " check_crl result",
Botan::Path_Validation_Result::status_string(Botan::PKIX::overall_status(crl_status)),
expected_result);
} else if(end && root) // CRT chain tests
{
// sha-1 is used
Botan::Path_Validation_Restrictions restrictions(false, 80);
Botan::Path_Validation_Result validation_result =
Botan::x509_path_validate(*end, restrictions, store, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
result.test_eq(test_name + " path validation result", validation_result.result_string(), expected_result);
} else if(end && !root) // CRT self signed tests
{
auto pubkey = end->subject_public_key();
result.test_eq(test_name + " verify signature", end->check_signature(*pubkey), !!(std::stoi(expected_result)));
} else if(csr) // PKCS#10 Request
{
auto pubkey = csr->subject_public_key();
result.test_eq(test_name + " verify signature", csr->check_signature(*pubkey), !!(std::stoi(expected_result)));
}
result.end_timer();
results.push_back(result);
}
return results;
}
BOTAN_REGISTER_TEST("x509", "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", "x509_v1_ca", Validate_V1Cert_Test);
class Validate_V2Uid_in_V1_Test final : public Test {
public:
std::vector<Test::Result> run() override;
};
std::vector<Test::Result> Validate_V2Uid_in_V1_Test::run() {
if(Botan::has_filesystem_impl() == false) {
return {Test::Result::Note("Path validation", "Skipping due to missing filesystem access")};
}
std::vector<Test::Result> results;
const std::string root_crt = Test::data_file("x509/v2-in-v1/root.pem");
const std::string int_crt = Test::data_file("x509/v2-in-v1/int.pem");
const std::string ee_crt = Test::data_file("x509/v2-in-v1/leaf.pem");
auto validation_time = Botan::calendar_point(2020, 1, 1, 1, 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 v1 certificate using v2 uid fields");
result.test_eq("Path validation failed", validation_result.successful_validation(), false);
result.test_eq(
"Path validation result", validation_result.result_string(), "Encountered v2 identifiers in v1 certificate");
return {result};
}
BOTAN_REGISTER_TEST("x509", "x509_v2uid_in_v1", Validate_V2Uid_in_V1_Test);
class Validate_Name_Constraint_SAN_Test final : public Test {
public:
std::vector<Test::Result> run() override;
};
std::vector<Test::Result> Validate_Name_Constraint_SAN_Test::run() {
if(Botan::has_filesystem_impl() == false) {
return {Test::Result::Note("Path validation", "Skipping due to missing filesystem access")};
}
std::vector<Test::Result> results;
const std::string root_crt = Test::data_file("x509/name_constraint_san/root.pem");
const std::string int_crt = Test::data_file("x509/name_constraint_san/int.pem");
const std::string ee_crt = Test::data_file("x509/name_constraint_san/leaf.pem");
auto validation_time = Botan::calendar_point(2020, 1, 1, 1, 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 certificate with alternative SAN violating name constraint");
result.test_eq("Path validation failed", validation_result.successful_validation(), false);
result.test_eq(
"Path validation result", validation_result.result_string(), "Certificate does not pass name constraint");
return {result};
}
BOTAN_REGISTER_TEST("x509", "x509_name_constraint_san", Validate_Name_Constraint_SAN_Test);
class Validate_Name_Constraint_CaseInsensitive final : public Test {
public:
std::vector<Test::Result> run() override;
};
std::vector<Test::Result> Validate_Name_Constraint_CaseInsensitive::run() {
if(Botan::has_filesystem_impl() == false) {
return {Test::Result::Note("Path validation", "Skipping due to missing filesystem access")};
}
std::vector<Test::Result> results;
const std::string root_crt = Test::data_file("x509/misc/name_constraint_ci/root.pem");
const std::string int_crt = Test::data_file("x509/misc/name_constraint_ci/int.pem");
const std::string ee_crt = Test::data_file("x509/misc/name_constraint_ci/leaf.pem");
auto validation_time = Botan::calendar_point(2021, 5, 8, 1, 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("DNS name constraints are case insensitive");
result.test_eq("Path validation succeeded", validation_result.successful_validation(), true);
return {result};
}
BOTAN_REGISTER_TEST("x509", "x509_name_constraint_ci", Validate_Name_Constraint_CaseInsensitive);
class Validate_Name_Constraint_NoCheckSelf final : public Test {
public:
std::vector<Test::Result> run() override;
};
std::vector<Test::Result> Validate_Name_Constraint_NoCheckSelf::run() {
if(Botan::has_filesystem_impl() == false) {
return {Test::Result::Note("Path validation", "Skipping due to missing filesystem access")};
}
std::vector<Test::Result> results;
const std::string root_crt = Test::data_file("x509/misc/nc_skip_self/root.pem");
const std::string int_crt = Test::data_file("x509/misc/nc_skip_self/int.pem");
const std::string ee_crt = Test::data_file("x509/misc/nc_skip_self/leaf.pem");
auto validation_time = Botan::calendar_point(2021, 5, 8, 1, 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("Name constraints do not apply to the certificate which includes them");
result.test_eq("Path validation succeeded", validation_result.successful_validation(), true);
return {result};
}
BOTAN_REGISTER_TEST("x509", "x509_name_constraint_no_check_self", Validate_Name_Constraint_NoCheckSelf);
class Root_Cert_Time_Check_Test final : public Test {
public:
std::vector<Test::Result> run() override {
if(Botan::has_filesystem_impl() == false) {
return {Test::Result::Note("Path validation", "Skipping due to missing filesystem access")};
}
const std::string trusted_root_crt = Test::data_file("x509/misc/root_cert_time_check/root.crt");
const std::string leaf_crt = Test::data_file("x509/misc/root_cert_time_check/leaf.crt");
const Botan::X509_Certificate trusted_root_cert(trusted_root_crt);
const Botan::X509_Certificate leaf_cert(leaf_crt);
Botan::Certificate_Store_In_Memory trusted;
trusted.add_certificate(trusted_root_cert);
const std::vector<Botan::X509_Certificate> chain = {leaf_cert};
Test::Result result("Root cert time check");
auto assert_path_validation_result = [&](std::string_view descr,
bool ignore_trusted_root_time_range,
uint32_t year,
Botan::Certificate_Status_Code exp_status,
std::optional<Botan::Certificate_Status_Code> exp_warning =
std::nullopt) {
const Botan::Path_Validation_Restrictions restrictions(
false,
110,
false,
std::chrono::seconds::zero(),
std::make_unique<Botan::Certificate_Store_In_Memory>(),
ignore_trusted_root_time_range);
const Botan::Path_Validation_Result validation_result =
Botan::x509_path_validate(chain,
restrictions,
trusted,
"",
Botan::Usage_Type::UNSPECIFIED,
Botan::calendar_point(year, 1, 1, 1, 0, 0).to_std_timepoint());
const std::string descr_str = Botan::fmt(
"Root cert validity range {}: {}", ignore_trusted_root_time_range ? "ignored" : "checked", descr);
result.test_is_eq(descr_str, validation_result.result(), exp_status);
const auto warnings = validation_result.warnings();
BOTAN_ASSERT_NOMSG(warnings.size() == 2);
result.confirm("No warning for leaf cert", warnings.at(0).empty());
if(exp_warning) {
result.confirm("Warning for root cert",
warnings.at(1).size() == 1 && warnings.at(1).contains(*exp_warning));
} else {
result.confirm("No warning for root cert", warnings.at(1).empty());
}
};
// (Trusted) root cert validity range: 2022-2028
// Leaf cert validity range: 2020-2030
// Trusted root time range is checked
assert_path_validation_result(
"Root and leaf certs in validity range", false, 2025, Botan::Certificate_Status_Code::OK);
assert_path_validation_result(
"Root and leaf certs are expired", false, 2031, Botan::Certificate_Status_Code::CERT_HAS_EXPIRED);
assert_path_validation_result(
"Root and leaf certs are not yet valid", false, 2019, Botan::Certificate_Status_Code::CERT_NOT_YET_VALID);
assert_path_validation_result(
"Root cert is expired, leaf cert not", false, 2029, Botan::Certificate_Status_Code::CERT_HAS_EXPIRED);
assert_path_validation_result("Root cert is not yet valid, leaf cert is",
false,
2021,
Botan::Certificate_Status_Code::CERT_NOT_YET_VALID);
// Trusted root time range is ignored
assert_path_validation_result(
"Root and leaf certs in validity range", true, 2025, Botan::Certificate_Status_Code::OK);
assert_path_validation_result("Root and leaf certs are expired",
true,
2031,
Botan::Certificate_Status_Code::CERT_HAS_EXPIRED,
Botan::Certificate_Status_Code::TRUSTED_CERT_HAS_EXPIRED);
assert_path_validation_result("Root and leaf certs are not yet valid",
true,
2019,
Botan::Certificate_Status_Code::CERT_NOT_YET_VALID,
Botan::Certificate_Status_Code::TRUSTED_CERT_NOT_YET_VALID);
assert_path_validation_result("Root cert is expired, leaf cert not",
true,
2029,
Botan::Certificate_Status_Code::OK,
Botan::Certificate_Status_Code::TRUSTED_CERT_HAS_EXPIRED);
assert_path_validation_result("Root cert is not yet valid, leaf cert is",
true,
2021,
Botan::Certificate_Status_Code::OK,
Botan::Certificate_Status_Code::TRUSTED_CERT_NOT_YET_VALID);
return {result};
}
};
BOTAN_REGISTER_TEST("x509", "x509_root_cert_time_check", Root_Cert_Time_Check_Test);
class BSI_Path_Validation_Tests final : public Test
{
public:
std::vector<Test::Result> run() override;
};
std::vector<Test::Result> BSI_Path_Validation_Tests::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;
for(const auto& [test_name, expected_result] : read_results(Test::data_file("x509/bsi/expected.txt"), '$')) {
Test::Result result("BSI path validation");
result.start_timer();
const auto all_files = Test::files_in_data_dir("x509/bsi/" + test_name);
Botan::Certificate_Store_In_Memory trusted;
std::vector<Botan::X509_Certificate> certs;
#if defined(BOTAN_HAS_MD5)
const bool has_md5 = true;
#else
const bool has_md5 = false;
#endif
auto validation_time = Botan::calendar_point(2017, 8, 19, 12, 0, 0).to_std_timepoint();
// By convention: if CRL is a substring if the test name,
// we need to check the CRLs
bool use_crl = false;
if(test_name.find("CRL") != std::string::npos) {
use_crl = true;
}
try {
for(const auto& file : all_files) {
// found a trust anchor
if(file.find("TA") != std::string::npos) {
trusted.add_certificate(Botan::X509_Certificate(file));
}
// found the target certificate. It needs to be at the front of certs
else if(file.find("TC") != std::string::npos) {
certs.insert(certs.begin(), Botan::X509_Certificate(file));
}
// found a certificate that might be part of a valid certificate chain to the trust anchor
else if(file.find(".crt") != std::string::npos) {
certs.push_back(Botan::X509_Certificate(file));
} else if(file.find(".crl") != std::string::npos) {
trusted.add_crl(Botan::X509_CRL(file));
}
}
Botan::Path_Validation_Restrictions restrictions(use_crl, 79, use_crl);
/*
* Following the test document, the test are executed 16 times with
* randomly chosen order of the available certificates. However, the target
* certificate needs to stay in front.
* For certain test, the order in which the certificates are given to
* the validation function may be relevant, i.e. if issuer DNs are
* ambiguous.
*/
class random_bit_generator {
public:
using result_type = size_t;
static constexpr result_type min() { return 0; }
static constexpr result_type max() { return std::numeric_limits<size_t>::max(); }
result_type operator()() {
size_t s;
m_rng.randomize(reinterpret_cast<uint8_t*>(&s), sizeof(s));
return s;
}
random_bit_generator(Botan::RandomNumberGenerator& rng) : m_rng(rng) {}
private:
Botan::RandomNumberGenerator& m_rng;
} rbg(this->rng());
for(size_t r = 0; r < 16; r++) {
std::shuffle(++(certs.begin()), certs.end(), rbg);
Botan::Path_Validation_Result validation_result = Botan::x509_path_validate(
certs, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);
// We expect to be warned
if(expected_result.starts_with("Warning: ")) {
std::string stripped = expected_result.substr(std::string("Warning: ").size());
bool found_warning = false;
for(const auto& warning_set : validation_result.warnings()) {
for(const auto& warning : warning_set) {
std::string warning_str(Botan::to_string(warning));
if(stripped == warning_str) {
result.test_eq(test_name + " path validation result", warning_str, stripped);
found_warning = true;
}
}
}
if(!found_warning) {
result.test_failure(test_name, "Did not receive the expected warning: " + stripped);
}
} else {
if(expected_result == "Hash function used is considered too weak for security" && has_md5 == false) {
result.test_eq(test_name + " path validation result",
validation_result.result_string(),
"Certificate signed with unknown/unavailable algorithm");
} else {
result.test_eq(
test_name + " path validation result", validation_result.result_string(), expected_result);
}
}
}
}
/* Some certificates are rejected when executing the X509_Certificate constructor
* by throwing a Decoding_Error exception.
*/
catch(const Botan::Exception& e) {
if(e.error_type() == Botan::ErrorType::DecodingFailure) {
result.test_eq(test_name + " path validation result", e.what(), expected_result);
} else {
result.test_failure(test_name, e.what());
}
}
result.end_timer();
results.push_back(result);
}
return results;
}
BOTAN_REGISTER_TEST("x509", "x509_path_bsi", BSI_Path_Validation_Tests);
class Path_Validation_With_OCSP_Tests final : public Test {
public:
static Botan::X509_Certificate load_test_X509_cert(const std::string& path) {
return Botan::X509_Certificate(Test::data_file(path));
}
static std::optional<Botan::OCSP::Response> load_test_OCSP_resp(const std::string& path) {
return Botan::OCSP::Response(Test::read_binary_data_file(path));
}
static Test::Result validate_with_ocsp_with_next_update_without_max_age() {
Test::Result result("path check with ocsp with next_update w/o max_age");
Botan::Certificate_Store_In_Memory trusted;
auto restrictions = Botan::Path_Validation_Restrictions(false, 110, false);
auto ee = load_test_X509_cert("x509/ocsp/randombit.pem");
auto ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
auto trust_root = load_test_X509_cert("x509/ocsp/identrust.pem");
trusted.add_certificate(trust_root);
const std::vector<Botan::X509_Certificate> cert_path = {ee, ca, trust_root};
std::optional<const Botan::OCSP::Response> ocsp = load_test_OCSP_resp("x509/ocsp/randombit_ocsp.der");
auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
const Botan::Certificate_Status_Code expected) {
const auto path_result = Botan::x509_path_validate(cert_path,
restrictions,
trusted,
"",
Botan::Usage_Type::UNSPECIFIED,
valid_time,
std::chrono::milliseconds(0),
{ocsp});
return result.confirm(std::string("Status: '") + Botan::to_string(expected) + "' should match '" +
Botan::to_string(path_result.result()) + "'",
path_result.result() == expected);
};
check_path(Botan::calendar_point(2016, 11, 11, 12, 30, 0).to_std_timepoint(),
Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
check_path(Botan::calendar_point(2016, 11, 18, 12, 30, 0).to_std_timepoint(),
Botan::Certificate_Status_Code::OK);
check_path(Botan::calendar_point(2016, 11, 20, 8, 30, 0).to_std_timepoint(),
Botan::Certificate_Status_Code::OK);
check_path(Botan::calendar_point(2016, 11, 28, 8, 30, 0).to_std_timepoint(),
Botan::Certificate_Status_Code::OCSP_HAS_EXPIRED);
return result;
}
static Test::Result validate_with_ocsp_with_next_update_with_max_age() {
Test::Result result("path check with ocsp with next_update with max_age");
Botan::Certificate_Store_In_Memory trusted;
auto restrictions = Botan::Path_Validation_Restrictions(false, 110, false, std::chrono::minutes(59));
auto ee = load_test_X509_cert("x509/ocsp/randombit.pem");
auto ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
auto trust_root = load_test_X509_cert("x509/ocsp/identrust.pem");
trusted.add_certificate(trust_root);
const std::vector<Botan::X509_Certificate> cert_path = {ee, ca, trust_root};
auto ocsp = load_test_OCSP_resp("x509/ocsp/randombit_ocsp.der");
auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
const Botan::Certificate_Status_Code expected) {
const auto path_result = Botan::x509_path_validate(cert_path,
restrictions,
trusted,
"",
Botan::Usage_Type::UNSPECIFIED,
valid_time,
std::chrono::milliseconds(0),
{ocsp});
return result.confirm(std::string("Status: '") + Botan::to_string(expected) + "' should match '" +
Botan::to_string(path_result.result()) + "'",
path_result.result() == expected);
};
check_path(Botan::calendar_point(2016, 11, 11, 12, 30, 0).to_std_timepoint(),
Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
check_path(Botan::calendar_point(2016, 11, 18, 12, 30, 0).to_std_timepoint(),
Botan::Certificate_Status_Code::OK);
check_path(Botan::calendar_point(2016, 11, 20, 8, 30, 0).to_std_timepoint(),
Botan::Certificate_Status_Code::OK);
check_path(Botan::calendar_point(2016, 11, 28, 8, 30, 0).to_std_timepoint(),
Botan::Certificate_Status_Code::OCSP_HAS_EXPIRED);
return result;
}
static Test::Result validate_with_ocsp_without_next_update_without_max_age() {
Test::Result result("path check with ocsp w/o next_update w/o max_age");
Botan::Certificate_Store_In_Memory trusted;
auto restrictions = Botan::Path_Validation_Restrictions(false, 110, false);
auto ee = load_test_X509_cert("x509/ocsp/patrickschmidt.pem");
auto ca = load_test_X509_cert("x509/ocsp/bdrive_encryption.pem");
auto trust_root = load_test_X509_cert("x509/ocsp/bdrive_root.pem");
trusted.add_certificate(trust_root);
const std::vector<Botan::X509_Certificate> cert_path = {ee, ca, trust_root};
auto ocsp = load_test_OCSP_resp("x509/ocsp/patrickschmidt_ocsp.der");
auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
const Botan::Certificate_Status_Code expected) {
const auto path_result = Botan::x509_path_validate(cert_path,
restrictions,
trusted,
"",
Botan::Usage_Type::UNSPECIFIED,
valid_time,
std::chrono::milliseconds(0),
{ocsp});
return result.confirm(std::string("Status: '") + Botan::to_string(expected) + "' should match '" +
Botan::to_string(path_result.result()) + "'",
path_result.result() == expected);
};
check_path(Botan::calendar_point(2019, 5, 28, 7, 0, 0).to_std_timepoint(),
Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
check_path(Botan::calendar_point(2019, 5, 28, 7, 30, 0).to_std_timepoint(),
Botan::Certificate_Status_Code::OK);
check_path(Botan::calendar_point(2019, 5, 28, 8, 0, 0).to_std_timepoint(), Botan::Certificate_Status_Code::OK);
return result;
}
static Test::Result validate_with_ocsp_without_next_update_with_max_age() {
Test::Result result("path check with ocsp w/o next_update with max_age");
Botan::Certificate_Store_In_Memory trusted;
auto restrictions = Botan::Path_Validation_Restrictions(false, 110, false, std::chrono::minutes(59));
auto ee = load_test_X509_cert("x509/ocsp/patrickschmidt.pem");
auto ca = load_test_X509_cert("x509/ocsp/bdrive_encryption.pem");
auto trust_root = load_test_X509_cert("x509/ocsp/bdrive_root.pem");
trusted.add_certificate(trust_root);
const std::vector<Botan::X509_Certificate> cert_path = {ee, ca, trust_root};
auto ocsp = load_test_OCSP_resp("x509/ocsp/patrickschmidt_ocsp.der");
auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
const Botan::Certificate_Status_Code expected) {
const auto path_result = Botan::x509_path_validate(cert_path,
restrictions,
trusted,
"",
Botan::Usage_Type::UNSPECIFIED,
valid_time,
std::chrono::milliseconds(0),
{ocsp});
return result.confirm(std::string("Status: '") + Botan::to_string(expected) + "' should match '" +
Botan::to_string(path_result.result()) + "'",
path_result.result() == expected);
};
check_path(Botan::calendar_point(2019, 5, 28, 7, 0, 0).to_std_timepoint(),
Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
check_path(Botan::calendar_point(2019, 5, 28, 7, 30, 0).to_std_timepoint(),
Botan::Certificate_Status_Code::OK);
check_path(Botan::calendar_point(2019, 5, 28, 8, 0, 0).to_std_timepoint(),
Botan::Certificate_Status_Code::OCSP_IS_TOO_OLD);
return result;
}
static Test::Result validate_with_ocsp_with_authorized_responder() {
Test::Result result("path check with ocsp response from authorized responder certificate");
Botan::Certificate_Store_In_Memory trusted;
auto restrictions = Botan::Path_Validation_Restrictions(true, // require revocation info
110, // minimum key strength
true); // OCSP for all intermediates
auto ee = load_test_X509_cert("x509/ocsp/bdr.pem");
auto ca = load_test_X509_cert("x509/ocsp/bdr-int.pem");
auto trust_root = load_test_X509_cert("x509/ocsp/bdr-root.pem");
// These OCSP responses are signed by an authorized OCSP responder
// certificate issued by `ca` and `trust_root` respectively. Note that
// the responder certificates contain the "OCSP No Check" extension,
// meaning that they themselves do not need a revocation check via OCSP.
auto ocsp_ee = load_test_OCSP_resp("x509/ocsp/bdr-ocsp-resp.der");
auto ocsp_ca = load_test_OCSP_resp("x509/ocsp/bdr-int-ocsp-resp.der");
trusted.add_certificate(trust_root);
const std::vector<Botan::X509_Certificate> cert_path = {ee, ca, trust_root};
auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
const Botan::Certificate_Status_Code expected) {
const auto path_result = Botan::x509_path_validate(cert_path,
restrictions,
trusted,
"",
Botan::Usage_Type::UNSPECIFIED,
valid_time,
std::chrono::milliseconds(0),
{ocsp_ee, ocsp_ca});
return result.confirm(std::string("Status: '") + Botan::to_string(expected) + "' should match '" +
Botan::to_string(path_result.result()) + "'",
path_result.result() == expected);
};
check_path(Botan::calendar_point(2022, 9, 18, 16, 30, 0).to_std_timepoint(),
Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
check_path(Botan::calendar_point(2022, 9, 19, 16, 30, 0).to_std_timepoint(),
Botan::Certificate_Status_Code::OK);
check_path(Botan::calendar_point(2022, 9, 20, 16, 30, 0).to_std_timepoint(),
Botan::Certificate_Status_Code::OCSP_HAS_EXPIRED);
return result;
}
static Test::Result validate_with_ocsp_with_authorized_responder_without_keyusage() {
Test::Result result(
"path check with ocsp response from authorized responder certificate (without sufficient key usage)");
Botan::Certificate_Store_In_Memory trusted;
auto restrictions = Botan::Path_Validation_Restrictions(true, // require revocation info
110, // minimum key strength
false); // OCSP for all intermediates
// See `src/scripts/mychain_creater.sh` if you need to recreate those
auto ee = load_test_X509_cert("x509/ocsp/mychain_ee.pem");
auto ca = load_test_X509_cert("x509/ocsp/mychain_int.pem");
auto trust_root = load_test_X509_cert("x509/ocsp/mychain_root.pem");
auto ocsp_ee_delegate = load_test_OCSP_resp("x509/ocsp/mychain_ocsp_for_ee_delegate_signed.der").value();
auto ocsp_ee_delegate_malformed =
load_test_OCSP_resp("x509/ocsp/mychain_ocsp_for_ee_delegate_signed_malformed.der").value();
trusted.add_certificate(trust_root);
const std::vector<Botan::X509_Certificate> cert_path = {ee, ca, trust_root};
auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
const Botan::OCSP::Response& ocsp_ee,
const Botan::Certificate_Status_Code expected,
const std::optional<Botan::Certificate_Status_Code> also_expected = std::nullopt) {
const auto path_result = Botan::x509_path_validate(cert_path,
restrictions,
trusted,
"",
Botan::Usage_Type::UNSPECIFIED,
valid_time,
std::chrono::milliseconds(0),
{ocsp_ee});
result.test_is_eq("should result in expected validation status code",
static_cast<uint32_t>(path_result.result()),
static_cast<uint32_t>(expected));
if(also_expected) {
result.confirm("Secondary error is also present",
flatten(path_result.all_statuses()).contains(also_expected.value()));
}
};
check_path(Botan::calendar_point(2022, 9, 22, 23, 30, 0).to_std_timepoint(),
ocsp_ee_delegate,
Botan::Certificate_Status_Code::VERIFIED);
check_path(Botan::calendar_point(2022, 10, 8, 23, 30, 0).to_std_timepoint(),
ocsp_ee_delegate,
Botan::Certificate_Status_Code::CERT_HAS_EXPIRED,
Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED);
check_path(Botan::calendar_point(2022, 9, 22, 23, 30, 0).to_std_timepoint(),
ocsp_ee_delegate_malformed,
Botan::Certificate_Status_Code::OCSP_RESPONSE_MISSING_KEYUSAGE,
Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED);
return result;
}
static Test::Result validate_with_forged_ocsp_using_self_signed_cert() {
Test::Result result("path check with forged ocsp using self-signed certificate");
Botan::Certificate_Store_In_Memory trusted;
auto restrictions = Botan::Path_Validation_Restrictions(true, // require revocation info
110, // minimum key strength
false); // OCSP for all intermediates
auto ee = load_test_X509_cert("x509/ocsp/randombit.pem");
auto ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
auto trust_root = load_test_X509_cert("x509/ocsp/identrust.pem");
trusted.add_certificate(trust_root);
const std::vector<Botan::X509_Certificate> cert_path = {ee, ca, trust_root};
auto check_path = [&](const std::string& forged_ocsp,
const Botan::Certificate_Status_Code expected,
const Botan::Certificate_Status_Code also_expected) {
auto ocsp = load_test_OCSP_resp(forged_ocsp);
const auto path_result =
Botan::x509_path_validate(cert_path,
restrictions,
trusted,
"",
Botan::Usage_Type::UNSPECIFIED,
Botan::calendar_point(2016, 11, 18, 12, 30, 0).to_std_timepoint(),
std::chrono::milliseconds(0),
{ocsp});
result.test_is_eq(
"Path validation with forged OCSP response should fail with", path_result.result(), expected);
result.confirm("Secondary error is also present",
flatten(path_result.all_statuses()).contains(also_expected));
result.test_note(std::string("Failed with: ") + Botan::to_string(path_result.result()));
};
// In both cases the path validation should detect the forged OCSP
// response and generate an appropriate error. By no means it should
// follow the unauthentic OCSP response.
check_path("x509/ocsp/randombit_ocsp_forged_valid.der",
Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND,
Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED);
check_path("x509/ocsp/randombit_ocsp_forged_revoked.der",
Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND,
Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED);
return result;
}
static Test::Result validate_with_ocsp_self_signed_by_intermediate_cert() {
Test::Result result(
"path check with ocsp response for intermediate that is (maliciously) self-signed by the intermediate");
Botan::Certificate_Store_In_Memory trusted;
auto restrictions = Botan::Path_Validation_Restrictions(true, // require revocation info
110, // minimum key strength
true); // OCSP for all intermediates
// See `src/scripts/mychain_creater.sh` if you need to recreate those
auto ee = load_test_X509_cert("x509/ocsp/mychain_ee.pem");
auto ca = load_test_X509_cert("x509/ocsp/mychain_int.pem");
auto trust_root = load_test_X509_cert("x509/ocsp/mychain_root.pem");
// this OCSP response for EE is valid (signed by intermediate cert)
auto ocsp_ee = load_test_OCSP_resp("x509/ocsp/mychain_ocsp_for_ee.der");
// this OCSP response for Intermediate is malicious (signed by intermediate itself)
auto ocsp_ca = load_test_OCSP_resp("x509/ocsp/mychain_ocsp_for_int_self_signed.der");
trusted.add_certificate(trust_root);
const std::vector<Botan::X509_Certificate> cert_path = {ee, ca, trust_root};
const auto path_result =
Botan::x509_path_validate(cert_path,
restrictions,
trusted,
"",
Botan::Usage_Type::UNSPECIFIED,
Botan::calendar_point(2022, 9, 22, 22, 30, 0).to_std_timepoint(),
std::chrono::milliseconds(0),
{ocsp_ee, ocsp_ca});
result.confirm("should reject intermediate OCSP response",
path_result.result() == Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_FOUND);
result.test_note(std::string("Failed with: ") + Botan::to_string(path_result.result()));
return result;
}
std::vector<Test::Result> run() override {
return {validate_with_ocsp_with_next_update_without_max_age(),
validate_with_ocsp_with_next_update_with_max_age(),
validate_with_ocsp_without_next_update_without_max_age(),
validate_with_ocsp_without_next_update_with_max_age(),
validate_with_ocsp_with_authorized_responder(),
validate_with_ocsp_with_authorized_responder_without_keyusage(),
validate_with_forged_ocsp_using_self_signed_cert(),
validate_with_ocsp_self_signed_by_intermediate_cert()};
}
};
BOTAN_REGISTER_TEST("x509", "x509_path_with_ocsp", Path_Validation_With_OCSP_Tests);
#endif
#if defined(BOTAN_HAS_ECDSA)
class CVE_2020_0601_Tests final : public Test {
public:
std::vector<Test::Result> run() override {
Test::Result result("CVE-2020-0601");
if(!Botan::EC_Group::supports_application_specific_group()) {
result.test_note("Skipping as application specific groups are not supported");
return {result};
}
if(!Botan::EC_Group::supports_named_group("secp384r1")) {
result.test_note("Skipping as secp384r1 is not supported");
return {result};
}
const auto& secp384r1 = Botan::EC_Group::from_name("secp384r1");
Botan::OID curveball_oid("1.3.6.1.4.1.25258.4.2020.0601");
Botan::EC_Group curveball(
curveball_oid,
secp384r1.get_p(),
secp384r1.get_a(),
secp384r1.get_b(),
BigInt(
"0xC711162A761D568EBEB96265D4C3CEB4F0C330EC8F6DD76E39BCC849ABABB8E34378D581065DEFC77D9FCED6B39075DE"),
BigInt(
"0x0CB090DE23BAC8D13E67E019A91B86311E5F342DEE17FD15FB7E278A32A1EAC98FC97E18CB2F3B2C487A7DA6F40107AC"),
secp384r1.get_order());
auto ca_crt = Botan::X509_Certificate(Test::data_file("x509/cve-2020-0601/ca.pem"));
auto fake_ca_crt = Botan::X509_Certificate(Test::data_file("x509/cve-2020-0601/fake_ca.pem"));
auto ee_crt = Botan::X509_Certificate(Test::data_file("x509/cve-2020-0601/ee.pem"));
Botan::Certificate_Store_In_Memory trusted;
trusted.add_certificate(ca_crt);
const auto restrictions = Botan::Path_Validation_Restrictions(false, 80, false);
const auto valid_time = Botan::calendar_point(2020, 1, 20, 0, 0, 0).to_std_timepoint();
const auto path_result1 = Botan::x509_path_validate(std::vector<Botan::X509_Certificate>{ee_crt, fake_ca_crt},
restrictions,
trusted,
"",
Botan::Usage_Type::UNSPECIFIED,
valid_time,
std::chrono::milliseconds(0),
{});
result.confirm("Validation failed", !path_result1.successful_validation());
result.confirm("Expected status",
path_result1.result() == Botan::Certificate_Status_Code::CANNOT_ESTABLISH_TRUST);
const auto path_result2 = Botan::x509_path_validate(std::vector<Botan::X509_Certificate>{ee_crt},
restrictions,
trusted,
"",
Botan::Usage_Type::UNSPECIFIED,
valid_time,
std::chrono::milliseconds(0),
{});
result.confirm("Validation failed", !path_result2.successful_validation());
result.confirm("Expected status",
path_result2.result() == Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND);
// Verify the signature from the bad CA is actually correct
Botan::Certificate_Store_In_Memory frusted;
frusted.add_certificate(fake_ca_crt);
const auto path_result3 = Botan::x509_path_validate(std::vector<Botan::X509_Certificate>{ee_crt},
restrictions,
frusted,
"",
Botan::Usage_Type::UNSPECIFIED,
valid_time,
std::chrono::milliseconds(0),
{});
result.confirm("Validation succeeded", path_result3.successful_validation());
return {result};
}
};
BOTAN_REGISTER_TEST("x509", "x509_cve_2020_0601", CVE_2020_0601_Tests);
class Path_Validation_With_Immortal_CRL final : public Test {
public:
std::vector<Test::Result> run() override {
// RFC 5280 defines the nextUpdate field as "optional" (in line with
// the original X.509 standard), but then requires all conforming CAs
// to always define it. For best compatibility we must deal with both.
Test::Result result("Using a CRL without a nextUpdate field");
if(Botan::has_filesystem_impl() == false) {
result.test_note("Skipping due to missing filesystem access");
return {result};
}
Botan::X509_Certificate root(Test::data_file("x509/misc/crl_without_nextupdate/ca.pem"));
Botan::X509_Certificate revoked_subject(Test::data_file("x509/misc/crl_without_nextupdate/01.pem"));
Botan::X509_Certificate valid_subject(Test::data_file("x509/misc/crl_without_nextupdate/42.pem"));
// Check that a CRL without nextUpdate is parsable
auto crl = Botan::X509_CRL(Test::data_file("x509/misc/crl_without_nextupdate/valid_forever.crl"));
result.confirm("this update is set", crl.this_update().time_is_set());
result.confirm("next update is not set", !crl.next_update().time_is_set());
result.confirm("CRL is not empty", !crl.get_revoked().empty());
// Ensure that we support the used sig algo, otherwish stop here
if(!Botan::EC_Group::supports_named_group("brainpool512r1")) {
result.test_note("Cannot test path validation because signature algorithm is not support in this build");
return {result};
}
Botan::Certificate_Store_In_Memory trusted;
trusted.add_certificate(root);
trusted.add_crl(crl);
// Just before the CA and subject certificates expire
// (validity from 01 March 2025 to 24 Feburary 2026)
auto valid_time = Botan::calendar_point(2026, 2, 23, 0, 0, 0).to_std_timepoint();
Botan::Path_Validation_Restrictions restrictions(true /* require revocation info */);
// Validate a certificate that is not listed in the CRL
const auto valid = Botan::x509_path_validate(
valid_subject, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, valid_time);
if(!result.confirm("Valid certificate", valid.successful_validation())) {
result.test_note(valid.result_string());
}
// Ensure that a certificate listed in the CRL is recognized as revoked
const auto revoked = Botan::x509_path_validate(
revoked_subject, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, valid_time);
if(!result.confirm("No valid certificate", !revoked.successful_validation())) {
result.test_note(revoked.result_string());
}
result.test_is_eq("Certificate is revoked", revoked.result(), Botan::Certificate_Status_Code::CERT_IS_REVOKED);
return {result};
}
};
BOTAN_REGISTER_TEST("x509", "x509_path_immortal_crl", Path_Validation_With_Immortal_CRL);
#endif
#if defined(BOTAN_HAS_XMSS_RFC8391)
class XMSS_Path_Validation_Tests final : public Test {
public:
static Test::Result validate_self_signed(const std::string& name, const std::string& file) {
Test::Result result(name);
Botan::Path_Validation_Restrictions restrictions;
auto self_signed = Botan::X509_Certificate(Test::data_file("x509/xmss/" + file));
auto cert_path = std::vector<Botan::X509_Certificate>{self_signed};
auto valid_time = Botan::calendar_point(2019, 10, 8, 4, 45, 0).to_std_timepoint();
auto status = Botan::PKIX::overall_status(
Botan::PKIX::check_chain(cert_path, valid_time, "", Botan::Usage_Type::UNSPECIFIED, restrictions));
result.test_eq("Cert validation status", Botan::to_string(status), "Verified");
return result;
}
std::vector<Test::Result> run() override {
if(Botan::has_filesystem_impl() == false) {
return {Test::Result::Note("XMSS path validation", "Skipping due to missing filesystem access")};
}
return {
validate_self_signed("XMSS path validation with certificate created by ISARA corp", "xmss_isara_root.pem"),
validate_self_signed("XMSS path validation with certificate created by BouncyCastle",
"xmss_bouncycastle_sha256_10_root.pem")};
}
};
BOTAN_REGISTER_TEST("x509", "x509_path_xmss", XMSS_Path_Validation_Tests);
#endif
#endif
} // namespace
} // namespace Botan_Tests