Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions florestad/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,11 +160,19 @@ pub struct Cli {
pub assume_utreexo: bool,

#[arg(long, value_name = "PATH")]
/// Path to the SSL certificate file
/// Path to the SSL certificate file (defaults to <data-dir>/ssl/cert.pem).
///
/// The user should create a PKCS#8 based one with openssl. For example:
///
/// openssl req -x509 -new -key key.pem -out cert.pem -days 365 -subj "/CN=localhost"
pub ssl_cert_path: Option<String>,

#[arg(long, value_name = "PATH")]
/// Path to the SSL private key file
/// Path to the SSL private key file (defaults to <data-dir>/ssl/key.pem).
///
/// The user should create a PKCS#8 based one with openssl. For example:
///
/// openssl genpkey -algorithm RSA -out key.pem -pkeyopt rsa_keygen_bits:2048
pub ssl_key_path: Option<String>,

#[arg(long, default_value_t = false)]
Expand Down
20 changes: 20 additions & 0 deletions florestad/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ pub enum Error {
WalletInput(slip132::Error),
AddressParsing(bitcoin::address::ParseError),
Miniscript(miniscript::Error),
EmptyPrivKeySet(String),
InvalidPrivKey(String),
InvalidCert(String),
CouldNotOpenPrivKeyFile(String, std::io::Error),
CouldNotOpenCertFile(String, std::io::Error),
CouldNotConfigureTLS(tokio_rustls::rustls::TLSError),
}

impl std::fmt::Display for Error {
Expand All @@ -36,6 +42,20 @@ impl std::fmt::Display for Error {
Error::AddressParsing(err) => write!(f, "Invalid address {err}"),
Error::Miniscript(err) => write!(f, "Miniscript error: {err}"),
Error::BlockValidation(err) => write!(f, "Error while validating block: {err:?}"),
Error::EmptyPrivKeySet(path) => write!(f, "No private keys found in '{path:?}'"),
Error::CouldNotConfigureTLS(err) => write!(f, "Error while configuring TLS: {err:?}"),
Error::InvalidPrivKey(path) => {
write!(f, "Error while reading PKCS#8 private key {path:?}")
}
Error::InvalidCert(path) => {
write!(f, "Error while reading PKCS#8 certificate {path}")
}
Error::CouldNotOpenPrivKeyFile(path, err) => {
write!(f, "Error while opening PKCS#8 private key {path}: {err}")
}
Error::CouldNotOpenCertFile(path, err) => {
write!(f, "Error while opening PKCS#8 certificate {path}: {err}")
}
}
}
}
Expand Down
103 changes: 78 additions & 25 deletions florestad/src/florestad.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use core::panic;
use std::fmt::Arguments;
use std::fs::File;
use std::io;
use std::io::BufReader;
#[cfg(feature = "metrics")]
use std::net::Ipv4Addr;
use std::net::SocketAddr;
use std::path::Path;
use std::path::PathBuf;
use std::process::exit;
use std::sync::Arc;
Expand Down Expand Up @@ -60,6 +60,7 @@ use tokio_rustls::rustls::ServerConfig;
use tokio_rustls::TlsAcceptor;

use crate::config_file::ConfigFile;
use crate::error;
#[cfg(feature = "json-rpc")]
use crate::json_rpc;
use crate::wallet_input::InitialWalletSetup;
Expand Down Expand Up @@ -158,9 +159,17 @@ pub struct Config {
pub user_agent: String,
/// The value to use for assumeutreexo
pub assumeutreexo_value: Option<AssumeUtreexoValue>,
/// Path to the SSL certificate file
/// Path to the SSL certificate file (defaults to <data-dir>/ssl/cert.pem).
///
/// The user should create a PKCS#8 based one with openssl. For example:
///
/// openssl req -x509 -new -key key.pem -out cert.pem -days 365 -subj "/CN=localhost"
pub ssl_cert_path: Option<String>,
/// Path to the SSL private key file
/// Path to the SSL private key file (defaults to <data-dir>/ssl/key.pem).
///
/// The user should create a PKCS#8 based one with openssl. For example:
///
/// openssl genpkey -algorithm RSA -out key.pem -pkeyopt rsa_keygen_bits:2048
pub ssl_key_path: Option<String>,
/// Whether to disable SSL for the Electrum server
pub no_ssl: bool,
Expand Down Expand Up @@ -478,8 +487,8 @@ impl Florestad {
let tls_config = if !self.config.no_ssl {
match self.create_tls_config(&data_dir) {
Ok(config) => Some(config),
Err(_) => {
warn!("Failed to load SSL certificates, ignoring SSL");
Err(e) => {
warn!("Failed to load SSL certificates: {}", e);
None
}
}
Expand Down Expand Up @@ -525,6 +534,8 @@ impl Florestad {
std::process::exit(1);
}
};

info!("TLS server running on: {ssl_e_addr}");
task::spawn(client_accept_loop(
tls_listener,
electrum_server.message_transmitter.clone(),
Expand All @@ -536,10 +547,6 @@ impl Florestad {
task::spawn(electrum_server.main_loop());
info!("Server running on: {}", e_addr);

if !self.config.no_ssl {
info!("TLS server running on: {ssl_e_addr}");
}

// Chain provider
let kill_signal = self.stop_signal.clone();
let (sender, receiver) = oneshot::channel();
Expand Down Expand Up @@ -761,24 +768,70 @@ impl Florestad {
result
}

fn create_tls_config(&self, data_dir: &str) -> io::Result<Arc<ServerConfig>> {
let cert_path = self
.config
.ssl_cert_path
.clone()
.unwrap_or_else(|| data_dir.to_owned() + "ssl/cert.pem");
let key_path = self
.config
.ssl_cert_path
.clone()
.unwrap_or_else(|| data_dir.to_owned() + "ssl/key.pem");
/// Create tls configuration with a PKCS#8 formatted key and certificates and
/// defaults to `<data-dir>/ssl/cert.pem` and `<data-dir>/ssl/key.pem`.
///
/// It will check if those files are well formated to PKCS#8 structure
/// and if it was in wrong structure, will exit with logs.
///
/// If pass the check process, it will try to open those files and, if not exist,
/// florestad will skip the SSL configuration.
fn create_tls_config(&self, data_dir: &str) -> Result<Arc<ServerConfig>, error::Error> {
// Use an agnostic way to build paths for platforms and fix the differences
// in how Unix and Windows represent strings, maybe a user could use a weird
// string on his/her path.
//
// See more at https://doc.rust-lang.org/std/ffi/struct.OsStr.html#method.to_string_lossy
let cert_path = self.config.ssl_cert_path.clone().unwrap_or_else(|| {
PathBuf::from(&data_dir)
.join("ssl")
.join("cert.pem")
.to_string_lossy()
.into_owned()
});

let key_path = self.config.ssl_key_path.clone().unwrap_or_else(|| {
PathBuf::from(&data_dir)
.join("ssl")
.join("key.pem")
.to_string_lossy()
.into_owned()
});

// Convert paths to Path for system-agnostic handling
let cert_path = Path::new(&cert_path);
let key_path = Path::new(&key_path);

// Check if certificate really exists and handle error if not exists
let cert_file = File::open(cert_path)
.map_err(|e| error::Error::CouldNotOpenCertFile(cert_path.display().to_string(), e))?;

let cert_file = File::open(cert_path)?;
let key_file = File::open(key_path)?;
let cert_chain = certs(&mut BufReader::new(cert_file)).unwrap();
let mut keys = pkcs8_private_keys(&mut BufReader::new(key_file)).unwrap();
// Check if private key really exists and handle error if not exists
let key_file = File::open(key_path).map_err(|e| {
error::Error::CouldNotOpenPrivKeyFile(cert_path.display().to_string(), e)
})?;

// Parse certificate chain and handle error if exist any
let cert_chain = certs(&mut BufReader::new(cert_file))
.map_err(|_e| error::Error::InvalidCert(cert_path.display().to_string()))?;

// Create private key vector and handle error if exist any
let mut keys = pkcs8_private_keys(&mut BufReader::new(key_file))
.map_err(|_e| error::Error::InvalidPrivKey(key_path.display().to_string()))?;

// Check if the key's vector are empty
if keys.is_empty() {
return Err(error::Error::EmptyPrivKeySet(
key_path.display().to_string(),
));
}

// Check if nothing goes wrong
let mut config = ServerConfig::new(Arc::new(NoClientAuth));
config.set_single_cert(cert_chain, keys.remove(0)).unwrap();
config
.set_single_cert(cert_chain, keys.remove(0))
.map_err(error::Error::CouldNotConfigureTLS)?;

Ok(Arc::new(config))
}
}
Expand Down