diff options
author | Jeffrey Ryan <jeffreyryan@tutanota.com> | 2022-05-19 15:27:30 -0500 |
---|---|---|
committer | jeffro256 <jeffro256@tutanota.com> | 2023-07-02 19:38:51 -0500 |
commit | 70bbd2536b0eb005458f7dee419eef503ff9e0cb (patch) | |
tree | bd821c083bc992dbb457670cff6ac3d5a93b1947 /contrib | |
parent | Merge pull request #8842 (diff) | |
download | monero-70bbd2536b0eb005458f7dee419eef503ff9e0cb.tar.xz |
core_rpc_server: new file: rpc_ssl.fingerprint
Diffstat (limited to 'contrib')
-rw-r--r-- | contrib/epee/include/net/net_ssl.h | 27 | ||||
-rw-r--r-- | contrib/epee/src/net_ssl.cpp | 73 |
2 files changed, 100 insertions, 0 deletions
diff --git a/contrib/epee/include/net/net_ssl.h b/contrib/epee/include/net/net_ssl.h index c79a3acc1..c6ef925ba 100644 --- a/contrib/epee/include/net/net_ssl.h +++ b/contrib/epee/include/net/net_ssl.h @@ -151,6 +151,33 @@ namespace net_utils bool create_ec_ssl_certificate(EVP_PKEY *&pkey, X509 *&cert); bool create_rsa_ssl_certificate(EVP_PKEY *&pkey, X509 *&cert); + /** + * @brief Create a human-readable X509 certificate fingerprint + * + * Example output: "12:A3:92:19:87:D2:A2:A5:77:94:82:29:B9:5A:91:01:AB:5F:75:16:9A:BA:CD:3D:D3:69:3D:6A:87:DC:E8:0E" + * + * @param[in] cert The certificate which will be used to create the fingerprint + * @param[in] fdig The digest algorithm to use, defaults to SHA-256 b/c that is what ssl_options_t uses + * @return The human-readable fingerprint string + * + * @throw boost::system_error if there is an OpenSSL error + */ + std::string get_hr_ssl_fingerprint(const X509 *cert, const EVP_MD *fdig = EVP_sha256()); + + /** + * @brief Create a human-readable fingerprint from the contents of an X509 certificate + * + * Should be equivalent to the command `openssl x509 -in <cert file> -fingerprint -sha256 -noout` + * Example output: "12:A3:92:19:87:D2:A2:A5:77:94:82:29:B9:5A:91:01:AB:5F:75:16:9A:BA:CD:3D:D3:69:3D:6A:87:DC:E8:0E" + * + * @param[in] cert_path The path to an X509 certificate which will be used to create the fingerprint + * @param[in] fdig The digest algorithm to use, defaults to SHA-256 b/c that is what ssl_options_t uses + * @return The human-readable fingerprint string + * + * @throw boost::system_error if there is an OpenSSL error or file I/O error + */ + std::string get_hr_ssl_fingerprint_from_file(const std::string& cert_path, const EVP_MD *fdig = EVP_sha256()); + //! Store private key for `ssl` at `base + ".key"` unencrypted and certificate for `ssl` at `base + ".crt"`. boost::system::error_code store_ssl_keys(boost::asio::ssl::context& ssl, const boost::filesystem::path& base); } diff --git a/contrib/epee/src/net_ssl.cpp b/contrib/epee/src/net_ssl.cpp index ff9c48c34..3822eb16d 100644 --- a/contrib/epee/src/net_ssl.cpp +++ b/contrib/epee/src/net_ssl.cpp @@ -641,6 +641,56 @@ bool ssl_options_t::handshake( return true; } +std::string get_hr_ssl_fingerprint(const X509 *cert, const EVP_MD *fdig) +{ + unsigned int j; + unsigned int n; + unsigned char md[EVP_MAX_MD_SIZE]; + std::string fingerprint; + + CHECK_AND_ASSERT_THROW_MES(cert && fdig, "Pointer args to get_hr_ssl_fingerprint cannot be null"); + + if (!X509_digest(cert, fdig, md, &n)) + { + const unsigned long ssl_err_val = static_cast<int>(ERR_get_error()); + const boost::system::error_code ssl_err_code = boost::asio::error::ssl_errors(static_cast<int>(ssl_err_val)); + MERROR("Failed to create SSL fingerprint: " << ERR_reason_error_string(ssl_err_val)); + throw boost::system::system_error(ssl_err_code, ERR_reason_error_string(ssl_err_val)); + } + fingerprint.resize(n * 3 - 1); + char *out = &fingerprint[0]; + for (j = 0; j < n; ++j) + { + snprintf(out, 3 + (j + 1 < n), "%02X%s", md[j], (j + 1 == n) ? "" : ":"); + out += 3; + } + return fingerprint; +} + +std::string get_hr_ssl_fingerprint_from_file(const std::string& cert_path, const EVP_MD *fdig) { + // Open file for reading + FILE* fp = fopen(cert_path.c_str(), "r"); + if (!fp) + { + const boost::system::error_code err_code(errno, boost::system::system_category()); + throw boost::system::system_error(err_code, "Failed to open certificate file '" + cert_path + "'"); + } + std::unique_ptr<FILE, decltype(&fclose)> file(fp, &fclose); + + // Extract certificate structure from file + X509* ssl_cert_handle = PEM_read_X509(file.get(), NULL, NULL, NULL); + if (!ssl_cert_handle) { + const unsigned long ssl_err_val = static_cast<int>(ERR_get_error()); + const boost::system::error_code ssl_err_code = boost::asio::error::ssl_errors(static_cast<int>(ssl_err_val)); + MERROR("OpenSSL error occurred while loading certificate at '" + cert_path + "'"); + throw boost::system::system_error(ssl_err_code, ERR_reason_error_string(ssl_err_val)); + } + std::unique_ptr<X509, decltype(&X509_free)> ssl_cert(ssl_cert_handle, &X509_free); + + // Get the fingerprint from X509 structure + return get_hr_ssl_fingerprint(ssl_cert.get(), fdig); +} + bool ssl_support_from_string(ssl_support_t &ssl, boost::string_ref s) { if (s == "enabled") @@ -705,6 +755,29 @@ boost::system::error_code store_ssl_keys(boost::asio::ssl::context& ssl, const b return boost::asio::error::ssl_errors(ERR_get_error()); if (std::fclose(file.release()) != 0) return {errno, boost::system::system_category()}; + + // write SHA-256 fingerprint file + const boost::filesystem::path fp_file{base.string() + ".fingerprint"}; + file.reset(std::fopen(fp_file.string().c_str(), "w")); + if (!file) + return {errno, boost::system::system_category()}; + const auto fp_perms = (boost::filesystem::owner_read | boost::filesystem::group_read | boost::filesystem::others_read); + boost::filesystem::permissions(fp_file, fp_perms, error); + if (error) + return error; + try + { + const std::string fingerprint = get_hr_ssl_fingerprint(ssl_cert); + if (fingerprint.length() != fwrite(fingerprint.c_str(), sizeof(char), fingerprint.length(), file.get())) + return {errno, boost::system::system_category()}; + } + catch (const boost::system::system_error& fperr) + { + return fperr.code(); + } + if (std::fclose(file.release()) != 0) + return {errno, boost::system::system_category()}; + return error; } |