Skip to content

Commit 70bbd25

Browse files
Jeffrey Ryanjeffro256
authored andcommitted
core_rpc_server: new file: rpc_ssl.fingerprint
1 parent 94e67bf commit 70bbd25

4 files changed

Lines changed: 129 additions & 25 deletions

File tree

contrib/epee/include/net/net_ssl.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,33 @@ namespace net_utils
151151
bool create_ec_ssl_certificate(EVP_PKEY *&pkey, X509 *&cert);
152152
bool create_rsa_ssl_certificate(EVP_PKEY *&pkey, X509 *&cert);
153153

154+
/**
155+
* @brief Create a human-readable X509 certificate fingerprint
156+
*
157+
* 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"
158+
*
159+
* @param[in] cert The certificate which will be used to create the fingerprint
160+
* @param[in] fdig The digest algorithm to use, defaults to SHA-256 b/c that is what ssl_options_t uses
161+
* @return The human-readable fingerprint string
162+
*
163+
* @throw boost::system_error if there is an OpenSSL error
164+
*/
165+
std::string get_hr_ssl_fingerprint(const X509 *cert, const EVP_MD *fdig = EVP_sha256());
166+
167+
/**
168+
* @brief Create a human-readable fingerprint from the contents of an X509 certificate
169+
*
170+
* Should be equivalent to the command `openssl x509 -in <cert file> -fingerprint -sha256 -noout`
171+
* 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"
172+
*
173+
* @param[in] cert_path The path to an X509 certificate which will be used to create the fingerprint
174+
* @param[in] fdig The digest algorithm to use, defaults to SHA-256 b/c that is what ssl_options_t uses
175+
* @return The human-readable fingerprint string
176+
*
177+
* @throw boost::system_error if there is an OpenSSL error or file I/O error
178+
*/
179+
std::string get_hr_ssl_fingerprint_from_file(const std::string& cert_path, const EVP_MD *fdig = EVP_sha256());
180+
154181
//! Store private key for `ssl` at `base + ".key"` unencrypted and certificate for `ssl` at `base + ".crt"`.
155182
boost::system::error_code store_ssl_keys(boost::asio::ssl::context& ssl, const boost::filesystem::path& base);
156183
}

contrib/epee/src/net_ssl.cpp

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -641,6 +641,56 @@ bool ssl_options_t::handshake(
641641
return true;
642642
}
643643

644+
std::string get_hr_ssl_fingerprint(const X509 *cert, const EVP_MD *fdig)
645+
{
646+
unsigned int j;
647+
unsigned int n;
648+
unsigned char md[EVP_MAX_MD_SIZE];
649+
std::string fingerprint;
650+
651+
CHECK_AND_ASSERT_THROW_MES(cert && fdig, "Pointer args to get_hr_ssl_fingerprint cannot be null");
652+
653+
if (!X509_digest(cert, fdig, md, &n))
654+
{
655+
const unsigned long ssl_err_val = static_cast<int>(ERR_get_error());
656+
const boost::system::error_code ssl_err_code = boost::asio::error::ssl_errors(static_cast<int>(ssl_err_val));
657+
MERROR("Failed to create SSL fingerprint: " << ERR_reason_error_string(ssl_err_val));
658+
throw boost::system::system_error(ssl_err_code, ERR_reason_error_string(ssl_err_val));
659+
}
660+
fingerprint.resize(n * 3 - 1);
661+
char *out = &fingerprint[0];
662+
for (j = 0; j < n; ++j)
663+
{
664+
snprintf(out, 3 + (j + 1 < n), "%02X%s", md[j], (j + 1 == n) ? "" : ":");
665+
out += 3;
666+
}
667+
return fingerprint;
668+
}
669+
670+
std::string get_hr_ssl_fingerprint_from_file(const std::string& cert_path, const EVP_MD *fdig) {
671+
// Open file for reading
672+
FILE* fp = fopen(cert_path.c_str(), "r");
673+
if (!fp)
674+
{
675+
const boost::system::error_code err_code(errno, boost::system::system_category());
676+
throw boost::system::system_error(err_code, "Failed to open certificate file '" + cert_path + "'");
677+
}
678+
std::unique_ptr<FILE, decltype(&fclose)> file(fp, &fclose);
679+
680+
// Extract certificate structure from file
681+
X509* ssl_cert_handle = PEM_read_X509(file.get(), NULL, NULL, NULL);
682+
if (!ssl_cert_handle) {
683+
const unsigned long ssl_err_val = static_cast<int>(ERR_get_error());
684+
const boost::system::error_code ssl_err_code = boost::asio::error::ssl_errors(static_cast<int>(ssl_err_val));
685+
MERROR("OpenSSL error occurred while loading certificate at '" + cert_path + "'");
686+
throw boost::system::system_error(ssl_err_code, ERR_reason_error_string(ssl_err_val));
687+
}
688+
std::unique_ptr<X509, decltype(&X509_free)> ssl_cert(ssl_cert_handle, &X509_free);
689+
690+
// Get the fingerprint from X509 structure
691+
return get_hr_ssl_fingerprint(ssl_cert.get(), fdig);
692+
}
693+
644694
bool ssl_support_from_string(ssl_support_t &ssl, boost::string_ref s)
645695
{
646696
if (s == "enabled")
@@ -705,6 +755,29 @@ boost::system::error_code store_ssl_keys(boost::asio::ssl::context& ssl, const b
705755
return boost::asio::error::ssl_errors(ERR_get_error());
706756
if (std::fclose(file.release()) != 0)
707757
return {errno, boost::system::system_category()};
758+
759+
// write SHA-256 fingerprint file
760+
const boost::filesystem::path fp_file{base.string() + ".fingerprint"};
761+
file.reset(std::fopen(fp_file.string().c_str(), "w"));
762+
if (!file)
763+
return {errno, boost::system::system_category()};
764+
const auto fp_perms = (boost::filesystem::owner_read | boost::filesystem::group_read | boost::filesystem::others_read);
765+
boost::filesystem::permissions(fp_file, fp_perms, error);
766+
if (error)
767+
return error;
768+
try
769+
{
770+
const std::string fingerprint = get_hr_ssl_fingerprint(ssl_cert);
771+
if (fingerprint.length() != fwrite(fingerprint.c_str(), sizeof(char), fingerprint.length(), file.get()))
772+
return {errno, boost::system::system_category()};
773+
}
774+
catch (const boost::system::system_error& fperr)
775+
{
776+
return fperr.code();
777+
}
778+
if (std::fclose(file.release()) != 0)
779+
return {errno, boost::system::system_category()};
780+
708781
return error;
709782
}
710783

src/gen_ssl_cert/gen_ssl_cert.cpp

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -65,29 +65,6 @@ namespace
6565
const command_line::arg_descriptor<bool> arg_prompt_for_passphrase = {"prompt-for-passphrase", gencert::tr("Prompt for a passphrase with which to encrypt the private key"), false};
6666
}
6767

68-
// adapted from openssl's apps/x509.c
69-
static std::string get_fingerprint(X509 *cert, const EVP_MD *fdig)
70-
{
71-
unsigned int j;
72-
unsigned int n;
73-
unsigned char md[EVP_MAX_MD_SIZE];
74-
std::string fingerprint;
75-
76-
if (!X509_digest(cert, fdig, md, &n))
77-
{
78-
tools::fail_msg_writer() << tr("Failed to create fingerprint: ") << ERR_reason_error_string(ERR_get_error());
79-
return fingerprint;
80-
}
81-
fingerprint.resize(n * 3 - 1);
82-
char *out = &fingerprint[0];
83-
for (j = 0; j < n; ++j)
84-
{
85-
snprintf(out, 3 + (j + 1 < n), "%02X%s", md[j], (j + 1 == n) ? "" : ":");
86-
out += 3;
87-
}
88-
return fingerprint;
89-
}
90-
9168
int main(int argc, char* argv[])
9269
{
9370
TRY_ENTRY();
@@ -246,7 +223,7 @@ int main(int argc, char* argv[])
246223

247224
tools::success_msg_writer() << tr("New certificate created:");
248225
tools::success_msg_writer() << tr("Certificate: ") << certificate_filename;
249-
tools::success_msg_writer() << tr("SHA-256 Fingerprint: ") << get_fingerprint(cert, EVP_sha256());
226+
tools::success_msg_writer() << tr("SHA-256 Fingerprint: ") << epee::net_utils::get_hr_ssl_fingerprint(cert);
250227
tools::success_msg_writer() << tr("Private key: ") << private_key_filename << " (" << (private_key_passphrase.empty() ? "unencrypted" : "encrypted") << ")";
251228

252229
return 0;

src/rpc/core_rpc_server.cpp

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,7 @@ namespace cryptonote
352352
const auto ssl_base_path = (boost::filesystem::path{data_dir} / "rpc_ssl").string();
353353
const bool ssl_cert_file_exists = boost::filesystem::exists(ssl_base_path + ".crt");
354354
const bool ssl_pkey_file_exists = boost::filesystem::exists(ssl_base_path + ".key");
355+
const bool ssl_fp_file_exists = boost::filesystem::exists(ssl_base_path + ".fingerprint");
355356
if (store_ssl_key)
356357
{
357358
// .key files are often given different read permissions as their corresponding .crt files.
@@ -361,13 +362,39 @@ namespace cryptonote
361362
MFATAL("Certificate (.crt) and private key (.key) files must both exist or both not exist at path: " << ssl_base_path);
362363
return false;
363364
}
365+
else if (!ssl_cert_file_exists && ssl_fp_file_exists) // only fingerprint file is present
366+
{
367+
MFATAL("Fingerprint file is present while certificate (.crt) and private key (.key) files are not at path: " << ssl_base_path);
368+
return false;
369+
}
364370
else if (ssl_cert_file_exists) { // and ssl_pkey_file_exists
365371
// load key from previous run, password prompted by OpenSSL
366372
store_ssl_key = false;
367373
rpc_config->ssl_options.auth =
368374
epee::net_utils::ssl_authentication_t{ssl_base_path + ".key", ssl_base_path + ".crt"};
375+
376+
// Since the .fingerprint file was added afterwards, sometimes the other 2 are present, and .fingerprint isn't
377+
// In that case, generate the .fingerprint file from the existing .crt file
378+
if (!ssl_fp_file_exists)
379+
{
380+
try
381+
{
382+
std::string fingerprint = epee::net_utils::get_hr_ssl_fingerprint_from_file(ssl_base_path + ".crt");
383+
if (!epee::file_io_utils::save_string_to_file(ssl_base_path + ".fingerprint", fingerprint))
384+
{
385+
MWARNING("Could not save SSL fingerprint to file '" << ssl_base_path << ".fingerprint'");
386+
}
387+
const auto fp_perms = boost::filesystem::owner_read | boost::filesystem::group_read | boost::filesystem::others_read;
388+
boost::filesystem::permissions(ssl_base_path + ".fingerprint", fp_perms);
389+
}
390+
catch (const std::exception& e)
391+
{
392+
// Do nothing. The fingerprint file is helpful, but not at all necessary.
393+
MWARNING("While trying to store SSL fingerprint file, got error (ignoring): " << e.what());
394+
}
395+
}
369396
}
370-
}
397+
} // if (store_ssl_key)
371398

372399
auto rng = [](size_t len, uint8_t *ptr){ return crypto::rand(len, ptr); };
373400
const bool inited = epee::http_server_impl_base<core_rpc_server, connection_context>::init(

0 commit comments

Comments
 (0)