From dc85761ef02c2ae5d5b676696d7de20c15d571c7 Mon Sep 17 00:00:00 2001 From: Jack Lloyd Date: Wed, 8 Aug 2018 18:48:56 -0400 Subject: Add path validation to FFI --- src/lib/asn1/asn1_time.cpp | 6 ++++ src/lib/asn1/asn1_time.h | 3 ++ src/lib/ffi/ffi.h | 34 ++++++++++++++++++- src/lib/ffi/ffi_cert.cpp | 81 +++++++++++++++++++++++++++++++++++++++++++++- src/lib/x509/cert_status.h | 1 + src/tests/test_ffi.cpp | 60 +++++++++++++++++++++++++++++++++- 6 files changed, 182 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/lib/asn1/asn1_time.cpp b/src/lib/asn1/asn1_time.cpp index 43f94af6a..8de42dfde 100644 --- a/src/lib/asn1/asn1_time.cpp +++ b/src/lib/asn1/asn1_time.cpp @@ -262,6 +262,12 @@ std::chrono::system_clock::time_point X509_Time::to_std_timepoint() const return calendar_point(m_year, m_month, m_day, m_hour, m_minute, m_second).to_std_timepoint(); } +uint64_t X509_Time::time_since_epoch() const + { + auto tp = this->to_std_timepoint(); + return std::chrono::duration_cast(tp.time_since_epoch()).count(); + } + /* * Compare two X509_Times for in various ways */ diff --git a/src/lib/asn1/asn1_time.h b/src/lib/asn1/asn1_time.h index 717c58a7d..83567e780 100644 --- a/src/lib/asn1/asn1_time.h +++ b/src/lib/asn1/asn1_time.h @@ -49,6 +49,9 @@ class BOTAN_PUBLIC_API(2,0) X509_Time final : public ASN1_Object /// Returns a STL timepoint object std::chrono::system_clock::time_point to_std_timepoint() const; + /// Return time since epoch + uint64_t time_since_epoch() const; + private: void set_to(const std::string& t_spec, ASN1_Tag); bool passes_sanity_check() const; diff --git a/src/lib/ffi/ffi.h b/src/lib/ffi/ffi.h index 8ddfcabe2..0b61693ce 100644 --- a/src/lib/ffi/ffi.h +++ b/src/lib/ffi/ffi.h @@ -1235,7 +1235,12 @@ int botan_mceies_decrypt(botan_privkey_t mce_key, const uint8_t ad[], size_t ad_len, uint8_t pt[], size_t* pt_len); +/* +* X.509 certificates +**************************/ + typedef struct botan_x509_cert_struct* botan_x509_cert_t; + BOTAN_PUBLIC_API(2,0) int botan_x509_cert_load(botan_x509_cert_t* cert_obj, const uint8_t cert[], size_t cert_len); BOTAN_PUBLIC_API(2,0) int botan_x509_cert_load_file(botan_x509_cert_t* cert_obj, const char* filename); BOTAN_PUBLIC_API(2,0) int botan_x509_cert_destroy(botan_x509_cert_t cert); @@ -1247,10 +1252,13 @@ int botan_x509_cert_gen_selfsigned(botan_x509_cert_t* cert, const char* common_name, const char* org_name); -// TODO: return botan_time_struct instead +// Prefer botan_x509_cert_not_before and botan_x509_cert_not_after BOTAN_PUBLIC_API(2,0) int botan_x509_cert_get_time_starts(botan_x509_cert_t cert, char out[], size_t* out_len); BOTAN_PUBLIC_API(2,0) int botan_x509_cert_get_time_expires(botan_x509_cert_t cert, char out[], size_t* out_len); +BOTAN_PUBLIC_API(2,8) int botan_x509_cert_not_before(botan_x509_cert_t cert, uint64_t* time_since_epoch); +BOTAN_PUBLIC_API(2,8) int botan_x509_cert_not_after(botan_x509_cert_t cert, uint64_t* time_since_epoch); + BOTAN_PUBLIC_API(2,0) int botan_x509_cert_get_fingerprint(botan_x509_cert_t cert, const char* hash, uint8_t out[], size_t* out_len); BOTAN_PUBLIC_API(2,0) int botan_x509_cert_get_serial_number(botan_x509_cert_t cert, uint8_t out[], size_t* out_len); @@ -1296,6 +1304,30 @@ BOTAN_PUBLIC_API(2,0) int botan_x509_cert_allowed_usage(botan_x509_cert_t cert, */ BOTAN_PUBLIC_API(2,5) int botan_x509_cert_hostname_match(botan_x509_cert_t cert, const char* hostname); +/** +* Returns 0 if the validation was successful, 1 if validation failed, +* and negative on error. A status code with details is written to +* *validation_result +* +* Intermediates or trusted lists can be null +* Trusted path can be null +*/ +BOTAN_PUBLIC_API(2,8) int botan_x509_cert_verify( + int* validation_result, + botan_x509_cert_t cert, + botan_x509_cert_t* intermediates, + size_t intermediates_len, + botan_x509_cert_t* trusted, + size_t trusted_len, + const char* trusted_path, + size_t required_strength); + +/** +* Returns a pointer to a static character string explaining the status code, +* or else NULL if unknown. +*/ +BOTAN_PUBLIC_API(2,8) const char* botan_x509_cert_validation_status(int code); + /** * Key wrapping as per RFC 3394 */ diff --git a/src/lib/ffi/ffi_cert.cpp b/src/lib/ffi/ffi_cert.cpp index af3aa6195..20a7ed567 100644 --- a/src/lib/ffi/ffi_cert.cpp +++ b/src/lib/ffi/ffi_cert.cpp @@ -1,5 +1,5 @@ /* -* (C) 2015,2017 Jack Lloyd +* (C) 2015,2017,2018 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -10,6 +10,7 @@ #if defined(BOTAN_HAS_X509_CERTIFICATES) #include + #include #include #endif @@ -157,6 +158,20 @@ int botan_x509_cert_get_time_expires(botan_x509_cert_t cert, char out[], size_t* #endif } +int botan_x509_cert_not_before(botan_x509_cert_t cert, uint64_t* time_since_epoch) + { + return BOTAN_FFI_DO(Botan::X509_Certificate, cert, c, { + *time_since_epoch = c.not_before().time_since_epoch(); + }); + } + +int botan_x509_cert_not_after(botan_x509_cert_t cert, uint64_t* time_since_epoch) + { + return BOTAN_FFI_DO(Botan::X509_Certificate, cert, c, { + *time_since_epoch = c.not_after().time_since_epoch(); + }); + } + int botan_x509_cert_get_serial_number(botan_x509_cert_t cert, uint8_t out[], size_t* out_len) { #if defined(BOTAN_HAS_X509_CERTIFICATES) @@ -221,4 +236,68 @@ int botan_x509_cert_hostname_match(botan_x509_cert_t cert, const char* hostname) #endif } +int botan_x509_cert_verify(int* result_code, + botan_x509_cert_t cert, + botan_x509_cert_t* intermediates, + size_t intermediates_len, + botan_x509_cert_t* trusted, + size_t trusted_len, + const char* trusted_path, + size_t required_strength) + { + if(required_strength == 0) + required_strength = 110; + + return ffi_guard_thunk(BOTAN_CURRENT_FUNCTION, [=]() -> int { + std::vector end_certs; + + end_certs.push_back(safe_get(cert)); + for(size_t i = 0; i != intermediates_len; ++i) + end_certs.push_back(safe_get(intermediates[i])); + + std::unique_ptr trusted_from_path; + std::unique_ptr trusted_extra; + std::vector trusted_roots; + + if(trusted_path) + { + trusted_from_path.reset(new Botan::Certificate_Store_In_Memory(trusted_path)); + trusted_roots.push_back(trusted_from_path.get()); + } + + if(trusted_len > 0) + { + trusted_extra.reset(new Botan::Certificate_Store_In_Memory); + for(size_t i = 0; i != trusted_len; ++i) + { + trusted_extra->add_certificate(safe_get(trusted[i])); + } + trusted_roots.push_back(trusted_extra.get()); + } + + Botan::Path_Validation_Restrictions restrictions(false, required_strength); + + auto validation_result = Botan::x509_path_validate(end_certs, + restrictions, + trusted_roots); + + if(result_code) + *result_code = static_cast(validation_result.result()); + + if(validation_result.successful_validation()) + return 0; + else + return 1; + }); + } + +const char* botan_x509_cert_validation_status(int code) + { + if(code < 0) + return nullptr; + + Botan::Certificate_Status_Code sc = static_cast(code); + return Botan::to_string(sc); + } + } diff --git a/src/lib/x509/cert_status.h b/src/lib/x509/cert_status.h index 1c3a5de89..fc1174df3 100644 --- a/src/lib/x509/cert_status.h +++ b/src/lib/x509/cert_status.h @@ -14,6 +14,7 @@ namespace Botan { /** * Certificate validation status code +* Warning: reflect any changes to this in botan_cert_status_code in ffi.h */ enum class Certificate_Status_Code { OK = 0, diff --git a/src/tests/test_ffi.cpp b/src/tests/test_ffi.cpp index d6824afd1..d5cda29a7 100644 --- a/src/tests/test_ffi.cpp +++ b/src/tests/test_ffi.cpp @@ -92,6 +92,7 @@ class FFI_Unit_Tests final : public Test results.push_back(ffi_test_scrypt()); results.push_back(ffi_test_mp(rng)); results.push_back(ffi_test_pkcs_hash_id()); + results.push_back(ffi_test_cert_validation()); #if defined(BOTAN_HAS_AES) results.push_back(ffi_test_block_ciphers()); @@ -111,6 +112,7 @@ class FFI_Unit_Tests final : public Test #endif + #if defined(BOTAN_HAS_FPE_FE1) results.push_back(ffi_test_fpe()); #endif @@ -213,7 +215,7 @@ class FFI_Unit_Tests final : public Test { Test::Result result("FFI RSA cert"); -#if defined(BOTAN_HAS_ECDSA) && defined(BOTAN_HAS_X509_CERTIFICATES) +#if defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_X509_CERTIFICATES) botan_x509_cert_t cert; if(TEST_FFI_OK(botan_x509_cert_load_file, (&cert, Test::data_file("x509/ocsp/randombit.pem").c_str()))) { @@ -229,6 +231,54 @@ class FFI_Unit_Tests final : public Test return result; } + Test::Result ffi_test_cert_validation() + { + Test::Result result("FFI Cert validation"); +#if defined(BOTAN_HAS_X509_CERTIFICATES) + + botan_x509_cert_t root; + int rc; + + REQUIRE_FFI_OK(botan_x509_cert_load_file, (&root, Test::data_file("x509/nist/root.crt").c_str())); + + botan_x509_cert_t end2; + botan_x509_cert_t sub2; + REQUIRE_FFI_OK(botan_x509_cert_load_file, (&end2, Test::data_file("x509/nist/test02/end.crt").c_str())); + REQUIRE_FFI_OK(botan_x509_cert_load_file, (&sub2, Test::data_file("x509/nist/test02/int.crt").c_str())); + + TEST_FFI_RC(1, botan_x509_cert_verify, (&rc, end2, &sub2, 1, &root, 1, NULL, 0)); + result.confirm("Validation failed", rc == 5002); + result.test_eq("Validation status string", botan_x509_cert_validation_status(rc), "Signature error"); + + TEST_FFI_RC(1, botan_x509_cert_verify, (&rc, end2, nullptr, 0, &root, 1, NULL, 0)); + result.confirm("Validation failed", rc == 3000); + result.test_eq("Validation status string", botan_x509_cert_validation_status(rc), "Certificate issuer not found"); + + botan_x509_cert_t end7; + botan_x509_cert_t sub7; + REQUIRE_FFI_OK(botan_x509_cert_load_file, (&end7, Test::data_file("x509/nist/test07/end.crt").c_str())); + REQUIRE_FFI_OK(botan_x509_cert_load_file, (&sub7, Test::data_file("x509/nist/test07/int.crt").c_str())); + + botan_x509_cert_t subs[2] = {sub2, sub7}; + TEST_FFI_RC(1, botan_x509_cert_verify, (&rc, end7, subs, 2, &root, 1, NULL, 0)); + result.confirm("Validation failed", rc == 1001); + result.test_eq("Validation status string", botan_x509_cert_validation_status(rc), + "Hash function used is considered too weak for security"); + + TEST_FFI_RC(0, botan_x509_cert_verify, (&rc, end7, subs, 2, &root, 1, NULL, 80)); + result.confirm("Validation passed", rc == 0); + result.test_eq("Validation status string", botan_x509_cert_validation_status(rc), "Verified"); + + TEST_FFI_OK(botan_x509_cert_destroy, (end2)); + TEST_FFI_OK(botan_x509_cert_destroy, (sub2)); + TEST_FFI_OK(botan_x509_cert_destroy, (end7)); + TEST_FFI_OK(botan_x509_cert_destroy, (sub7)); + TEST_FFI_OK(botan_x509_cert_destroy, (root)); + +#endif + return result; + } + Test::Result ffi_test_ecdsa_cert() { Test::Result result("FFI ECDSA cert"); @@ -251,6 +301,14 @@ class FFI_Unit_Tests final : public Test TEST_FFI_OK(botan_x509_cert_get_time_expires, (cert, &date[0], &date_len)); result.test_eq("cert valid until", date, "280119151800Z"); + uint64_t not_before = 0; + TEST_FFI_OK(botan_x509_cert_not_before, (cert, ¬_before)); + result.confirm("cert not before", not_before == 1184858838); + + uint64_t not_after = 0; + TEST_FFI_OK(botan_x509_cert_not_after, (cert, ¬_after)); + result.confirm("cert not after", not_after == 1831907880); + size_t serial_len = 0; TEST_FFI_RC(BOTAN_FFI_ERROR_INSUFFICIENT_BUFFER_SPACE, botan_x509_cert_get_serial_number, (cert, nullptr, &serial_len)); -- cgit v1.2.3