aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJack Lloyd <[email protected]>2018-08-14 12:15:09 -0400
committerJack Lloyd <[email protected]>2018-08-14 12:15:09 -0400
commitb1fed4525295b0cf64fd9209eeb1fcf35269a72d (patch)
tree620a03c1b0c9b336fc1553738552dadddfc35752 /src
parent36b279bd09a274fee8f93e1fd397638586432bda (diff)
parentdc85761ef02c2ae5d5b676696d7de20c15d571c7 (diff)
Merge GH #1647 Add X.509 path validation to FFI
Diffstat (limited to 'src')
-rw-r--r--src/lib/asn1/asn1_time.cpp6
-rw-r--r--src/lib/asn1/asn1_time.h3
-rw-r--r--src/lib/ffi/ffi.h34
-rw-r--r--src/lib/ffi/ffi_cert.cpp81
-rw-r--r--src/lib/x509/cert_status.h1
-rw-r--r--src/tests/test_ffi.cpp60
6 files changed, 182 insertions, 3 deletions
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<std::chrono::seconds>(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);
@@ -1297,6 +1305,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
*/
BOTAN_PUBLIC_API(2,2)
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 <botan/x509cert.h>
+ #include <botan/x509path.h>
#include <botan/data_src.h>
#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<Botan::X509_Certificate> 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<Botan::Certificate_Store> trusted_from_path;
+ std::unique_ptr<Botan::Certificate_Store_In_Memory> trusted_extra;
+ std::vector<Botan::Certificate_Store*> 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<int>(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<Botan::Certificate_Status_Code>(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 ee16267a1..3cf41bcc6 100644
--- a/src/tests/test_ffi.cpp
+++ b/src/tests/test_ffi.cpp
@@ -55,6 +55,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());
@@ -74,6 +75,7 @@ class FFI_Unit_Tests final : public Test
#endif
+
#if defined(BOTAN_HAS_FPE_FE1)
results.push_back(ffi_test_fpe());
#endif
@@ -230,7 +232,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())))
{
@@ -246,6 +248,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");
@@ -268,6 +318,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, &not_before));
+ result.confirm("cert not before", not_before == 1184858838);
+
+ uint64_t not_after = 0;
+ TEST_FFI_OK(botan_x509_cert_not_after, (cert, &not_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));