diff --git a/include/evse_security.hpp b/include/evse_security.hpp index 9c0ee26..835cf80 100644 --- a/include/evse_security.hpp +++ b/include/evse_security.hpp @@ -122,6 +122,14 @@ class EvseSecurity { /// @return day count until the leaf certificate expires int get_leaf_expiry_days_count(LeafCertificateType certificate_type); + /// @brief Verifies the file at the given \p path using the provided \p signing_certificate and \p signature + /// @param path + /// @param signing_certificate + /// @param signature + /// @return true if the verification was successful, false if not + static bool verify_file_signature(const std::filesystem::path& path, const std::string& signing_certificate, + const std::string signature); + private: // why not reusing the FilePaths here directly (storage duplication) std::map ca_bundle_path_map; diff --git a/include/sec_types.hpp b/include/sec_types.hpp index 71e8b0e..6bf4837 100644 --- a/include/sec_types.hpp +++ b/include/sec_types.hpp @@ -41,6 +41,13 @@ template <> class std::default_delete { } }; +template <> class std::default_delete { +public: + void operator()(EVP_PKEY_CTX* ptr) const { + ::EVP_PKEY_CTX_free(ptr); + } +}; + template <> class std::default_delete { public: void operator()(BIO* ptr) const { @@ -48,6 +55,27 @@ template <> class std::default_delete { } }; +template <> class std::default_delete { +public: + void operator()(FILE* ptr) const { + ::fclose(ptr); + } +}; + +template <> class std::default_delete { +public: + void operator()(EVP_MD_CTX* ptr) const { + ::EVP_MD_CTX_destroy(ptr); + } +}; + +template <> class std::default_delete { +public: + void operator()(EVP_ENCODE_CTX* ptr) const { + ::EVP_ENCODE_CTX_free(ptr); + } +}; + namespace evse_security { using X509_ptr = std::unique_ptr; @@ -55,8 +83,12 @@ using X509_STORE_ptr = std::unique_ptr; using X509_STORE_CTX_ptr = std::unique_ptr; using X509_REQ_ptr = std::unique_ptr; using EVP_PKEY_ptr = std::unique_ptr; +using EVP_PKEY_CTX_ptr = std::unique_ptr; using BIO_ptr = std::unique_ptr; +using FILE_ptr = std::unique_ptr; +using EVP_MD_CTX_ptr = std::unique_ptr; +using EVP_ENCODE_CTX_ptr = std::unique_ptr; } // namespace evse_security -#endif \ No newline at end of file +#endif diff --git a/lib/evse_security.cpp b/lib/evse_security.cpp index e231e43..ac2320d 100644 --- a/lib/evse_security.cpp +++ b/lib/evse_security.cpp @@ -8,8 +8,11 @@ #include #include #include +#include #include +#include #include +#include #include #include @@ -622,6 +625,113 @@ int EvseSecurity::get_leaf_expiry_days_count(LeafCertificateType certificate_typ return 0; } +bool EvseSecurity::verify_file_signature(const std::filesystem::path& path, const std::string& signing_certificate, + const std::string signature) { + EVLOG_info << "Verifying file signature for " << path.string(); + + // calculate sha256 of file + FILE_ptr file_ptr(fopen(path.string().c_str(), "rb")); + if (!file_ptr.get()) { + EVLOG_error << "Could not open file at: " << path.string(); + return false; + } + EVP_MD_CTX_ptr md_context_ptr(EVP_MD_CTX_create()); + if (!md_context_ptr.get()) { + EVLOG_error << "Could not create EVP_MD_CTX"; + return false; + } + const EVP_MD* md = EVP_get_digestbyname("SHA256"); + if (EVP_DigestInit_ex(md_context_ptr.get(), md, nullptr) == 0) { + EVLOG_error << "Error during EVP_DigestInit_ex"; + return false; + } + size_t in_length; + unsigned char file_buffer[BUFSIZ]; + do { + in_length = fread(file_buffer, 1, BUFSIZ, file_ptr.get()); + if (EVP_DigestUpdate(md_context_ptr.get(), file_buffer, in_length) == 0) { + EVLOG_error << "Error during EVP_DigestUpdate"; + return false; + } + } while (in_length == BUFSIZ); + unsigned int sha256_out_length; + unsigned char sha256_out[EVP_MAX_MD_SIZE]; + if (EVP_DigestFinal_ex(md_context_ptr.get(), sha256_out, &sha256_out_length) == 0) { + EVLOG_error << "Error during EVP_DigestFinal_ex"; + return false; + } + + // extract public key + BIO_ptr bio_signing_certificate_ptr( + BIO_new_mem_buf(signing_certificate.data(), static_cast(signing_certificate.size()))); + if (!bio_signing_certificate_ptr.get()) { + EVLOG_error << "Error during BIO_new_mem_buf"; + return false; + } + X509_ptr x509_signing_cerificate_ptr(PEM_read_bio_X509(bio_signing_certificate_ptr.get(), nullptr, 0, nullptr)); + if (!x509_signing_cerificate_ptr.get()) { + EVLOG_error << "Error during PEM_read_bio_X509"; + return false; + } + EVP_PKEY_ptr public_key_ptr(X509_get_pubkey(x509_signing_cerificate_ptr.get())); + if (!public_key_ptr.get()) { + EVLOG_error << "Error during X509_get_pubkey"; + return false; + } + + // decode base64 encoded signature + EVP_ENCODE_CTX_ptr base64_decode_context_ptr(EVP_ENCODE_CTX_new()); + if (!base64_decode_context_ptr.get()) { + EVLOG_error << "Error during EVP_ENCODE_CTX_new"; + return false; + } + EVP_DecodeInit(base64_decode_context_ptr.get()); + if (!base64_decode_context_ptr.get()) { + EVLOG_error << "Error during EVP_DecodeInit"; + return false; + } + const unsigned char* signature_str = reinterpret_cast(signature.data()); + int base64_length = signature.size(); + unsigned char signature_out[base64_length]; + int signature_out_length; + if (EVP_DecodeUpdate(base64_decode_context_ptr.get(), signature_out, &signature_out_length, signature_str, + base64_length) < 0) { + EVLOG_error << "Error during DecodeUpdate"; + return false; + } + int decode_final_out; + if (EVP_DecodeFinal(base64_decode_context_ptr.get(), signature_out, &decode_final_out) < 0) { + EVLOG_error << "Error during EVP_DecodeFinal"; + return false; + } + + // verify firmware signature + EVP_PKEY_CTX_ptr public_key_context_ptr(EVP_PKEY_CTX_new(public_key_ptr.get(), nullptr)); + if (!public_key_context_ptr.get()) { + EVLOG_error << "Error setting up public key context"; + } + if (EVP_PKEY_verify_init(public_key_context_ptr.get()) <= 0) { + EVLOG_error << "Error during EVP_PKEY_verify_init"; + } + if (EVP_PKEY_CTX_set_signature_md(public_key_context_ptr.get(), EVP_sha256()) <= 0) { + EVLOG_error << "Error during EVP_PKEY_CTX_set_signature_md"; + }; + int result = EVP_PKEY_verify(public_key_context_ptr.get(), reinterpret_cast(signature_out), + signature_out_length, sha256_out, sha256_out_length); + + EVP_cleanup(); + + if (result != 1) { + EVLOG_error << "Failure to verify: " << result; + return false; + } else { + EVLOG_error << "Succesful verification"; + return true; + } + + return false; +} + InstallCertificateResult EvseSecurity::verify_certificate(const std::string& certificate_chain, LeafCertificateType certificate_type) { try {