diff --git a/ldk-server-cli/src/main.rs b/ldk-server-cli/src/main.rs index 224e1d42..f0cc8482 100644 --- a/ldk-server-cli/src/main.rs +++ b/ldk-server-cli/src/main.rs @@ -15,8 +15,8 @@ use clap_complete::{generate, Shell}; use hex_conservative::{DisplayHex, FromHex}; use ldk_server_client::client::LdkServerClient; use ldk_server_client::config::{ - get_default_config_path, load_config, resolve_api_key, resolve_base_url, resolve_cert_path, - DEFAULT_GRPC_SERVICE_ADDRESS, + get_default_config_path, load_config, read_tls_certificate, resolve_api_key, resolve_base_url, + resolve_cert_path, DEFAULT_GRPC_SERVICE_ADDRESS, }; use ldk_server_client::error::LdkServerError; use ldk_server_client::error::LdkServerErrorCode::{ @@ -617,10 +617,15 @@ async fn main() { }, }; - let api_key = resolve_api_key(cli.api_key, config.as_ref()).unwrap_or_else(|| { - eprintln!("API key not provided. Use --api-key or ensure the api_key file exists at {DEFAULT_DIR}/[network]/api_key"); - std::process::exit(1); - }); + let api_key = resolve_api_key(cli.api_key, config.as_ref()) + .unwrap_or_else(|e| { + eprintln!("Failed to resolve API key: {e}"); + std::process::exit(1); + }) + .unwrap_or_else(|| { + eprintln!("API key not provided. Use --api-key or ensure the api_key file exists at {DEFAULT_DIR}/[network]/api_key"); + std::process::exit(1); + }); let base_url = resolve_base_url(cli.base_url, config.as_ref()); @@ -630,8 +635,8 @@ async fn main() { std::process::exit(1); }); - let server_cert_pem = std::fs::read(&tls_cert_path).unwrap_or_else(|e| { - eprintln!("Failed to read server certificate file '{}': {}", tls_cert_path.display(), e); + let server_cert_pem = read_tls_certificate(&tls_cert_path).unwrap_or_else(|e| { + eprintln!("{e}"); std::process::exit(1); }); diff --git a/ldk-server-client/src/config.rs b/ldk-server-client/src/config.rs index cbe9a38c..243ab18a 100644 --- a/ldk-server-client/src/config.rs +++ b/ldk-server-client/src/config.rs @@ -13,7 +13,8 @@ //! locating the server's TLS certificate and API key on disk, so multiple clients (CLI, MCP //! bridge, etc.) can resolve connection credentials in a consistent way. -use std::path::PathBuf; +use std::io::{self, ErrorKind, Read}; +use std::path::{Path, PathBuf}; use hex_conservative::DisplayHex; use serde::{Deserialize, Serialize}; @@ -21,6 +22,9 @@ use serde::{Deserialize, Serialize}; const DEFAULT_CONFIG_FILE: &str = "config.toml"; const DEFAULT_CERT_FILE: &str = "tls.crt"; const API_KEY_FILE: &str = "api_key"; +const API_KEY_LEN: usize = 32; +const CONFIG_FILE_SIZE_LIMIT: usize = 1024 * 1024; +const TLS_CERT_FILE_SIZE_LIMIT: usize = 1024 * 1024; /// Default address of the `ldk-server` gRPC endpoint when no explicit value is configured. pub const DEFAULT_GRPC_SERVICE_ADDRESS: &str = "127.0.0.1:3536"; @@ -124,13 +128,21 @@ impl Config { } /// Reads and parses the `ldk-server` configuration file at `path`. -pub fn load_config(path: &PathBuf) -> Result { - let contents = std::fs::read_to_string(path) +pub fn load_config(path: &Path) -> Result { + let contents = read_to_string_with_limit(path, CONFIG_FILE_SIZE_LIMIT) .map_err(|e| format!("Failed to read config file '{}': {}", path.display(), e))?; toml::from_str(&contents) .map_err(|e| format!("Failed to parse config file '{}': {}", path.display(), e)) } +/// Reads the server TLS certificate at `path`. +/// +/// Returns an error if the file exceeds 1 MiB. +pub fn read_tls_certificate(path: &Path) -> Result, String> { + read_with_limit(path, TLS_CERT_FILE_SIZE_LIMIT) + .map_err(|e| format!("Failed to read server certificate file '{}': {e}", path.display())) +} + /// Resolves the base URL of the `ldk-server` gRPC endpoint. /// /// Prefers `override_url`, falls back to the configuration file, and finally to @@ -146,18 +158,65 @@ pub fn resolve_base_url(override_url: Option, config: Option<&Config>) - /// Prefers `override_key`, falls back to reading the API key file from the configured storage /// directory, and finally from the OS-specific default data directory. The raw bytes read from /// disk are lower-hex encoded before being returned. -pub fn resolve_api_key(override_key: Option, config: Option<&Config>) -> Option { - override_key.or_else(|| { - let network = - config.and_then(|c| c.network().ok()).unwrap_or_else(|| "bitcoin".to_string()); - storage_dir(config) - .map(|dir| api_key_path_for_storage_dir(dir, &network)) - .and_then(|path| std::fs::read(&path).ok()) - .or_else(|| { - get_default_api_key_path(&network).and_then(|path| std::fs::read(&path).ok()) - }) - .map(|bytes| bytes.to_lower_hex_string()) - }) +/// +/// Returns an error if a candidate API key file exists but cannot be read or does not contain +/// exactly 32 bytes. +pub fn resolve_api_key( + override_key: Option, config: Option<&Config>, +) -> Result, String> { + if override_key.is_some() { + return Ok(override_key); + } + + let network = config.and_then(|c| c.network().ok()).unwrap_or_else(|| "bitcoin".to_string()); + if let Some(dir) = storage_dir(config) { + let path = api_key_path_for_storage_dir(dir, &network); + if let Some(api_key) = read_api_key(&path)? { + return Ok(Some(api_key)); + } + } + + match get_default_api_key_path(&network) { + Some(path) => read_api_key(&path), + None => Ok(None), + } +} + +fn read_api_key(path: &Path) -> Result, String> { + let file = match std::fs::File::open(path) { + Ok(file) => file, + Err(e) if e.kind() == ErrorKind::NotFound => return Ok(None), + Err(e) => return Err(format!("Failed to read API key file '{}': {e}", path.display())), + }; + let mut bytes = Vec::with_capacity(API_KEY_LEN + 1); + file.take((API_KEY_LEN + 1) as u64) + .read_to_end(&mut bytes) + .map_err(|e| format!("Failed to read API key file '{}': {e}", path.display()))?; + if bytes.len() != API_KEY_LEN { + return Err(format!( + "API key file '{}' must contain exactly {API_KEY_LEN} bytes", + path.display() + )); + } + Ok(Some(bytes.to_lower_hex_string())) +} + +fn read_with_limit(path: &Path, limit: usize) -> io::Result> { + let file = std::fs::File::open(path)?; + let mut contents = Vec::new(); + file.take(limit.saturating_add(1) as u64).read_to_end(&mut contents)?; + if contents.len() > limit { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + format!("File '{}' exceeds the {limit} byte limit", path.display()), + )); + } + Ok(contents) +} + +fn read_to_string_with_limit(path: &Path, limit: usize) -> io::Result { + String::from_utf8(read_with_limit(path, limit)?) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)) } /// Resolves the path to the server's TLS certificate (PEM). @@ -187,7 +246,10 @@ fn default_grpc_service_address() -> String { #[cfg(test)] mod tests { - use super::{resolve_base_url, Config, DEFAULT_GRPC_SERVICE_ADDRESS}; + use super::{ + load_config, read_tls_certificate, resolve_base_url, Config, CONFIG_FILE_SIZE_LIMIT, + DEFAULT_GRPC_SERVICE_ADDRESS, TLS_CERT_FILE_SIZE_LIMIT, + }; #[test] fn config_defaults_grpc_service_address() { @@ -282,4 +344,28 @@ mod tests { fn resolve_base_url_falls_back_to_default() { assert_eq!(resolve_base_url(None, None), DEFAULT_GRPC_SERVICE_ADDRESS); } + + #[test] + fn read_tls_certificate_rejects_oversized_file() { + let path = std::env::temp_dir() + .join(format!("ldk-server-client-oversized-cert-{}", std::process::id())); + std::fs::write(&path, vec![0; TLS_CERT_FILE_SIZE_LIMIT + 1]).unwrap(); + + let error = read_tls_certificate(&path).unwrap_err(); + assert!(error.contains("exceeds")); + + std::fs::remove_file(path).unwrap(); + } + + #[test] + fn load_config_rejects_oversized_file() { + let path = std::env::temp_dir() + .join(format!("ldk-server-client-oversized-config-{}", std::process::id())); + std::fs::write(&path, vec![b'a'; CONFIG_FILE_SIZE_LIMIT + 1]).unwrap(); + + let error = load_config(&path).unwrap_err(); + assert!(error.contains("exceeds")); + + std::fs::remove_file(path).unwrap(); + } } diff --git a/ldk-server-mcp/src/config.rs b/ldk-server-mcp/src/config.rs index 6f54f60b..f8c066d9 100644 --- a/ldk-server-mcp/src/config.rs +++ b/ldk-server-mcp/src/config.rs @@ -10,7 +10,8 @@ use std::path::PathBuf; use ldk_server_client::config::{ - get_default_config_path, load_config, resolve_api_key, resolve_base_url, resolve_cert_path, + get_default_config_path, load_config, read_tls_certificate, resolve_api_key, resolve_base_url, + resolve_cert_path, }; pub struct ResolvedConfig { @@ -39,7 +40,7 @@ pub fn resolve_config(config_path: Option) -> Result) -> Result {}, + Err(e) if e.kind() == io::ErrorKind::AlreadyExists => {}, + Err(e) => { + let msg = + format!("Failed to create database file {}: {}", db_file_path.display(), e); + return Err(io::Error::other(msg)); + }, + } let connection = Connection::open(db_file_path.clone()).map_err(|e| { let msg = diff --git a/ldk-server/src/main.rs b/ldk-server/src/main.rs index 28ea10de..7b0f7159 100644 --- a/ldk-server/src/main.rs +++ b/ldk-server/src/main.rs @@ -14,6 +14,7 @@ mod util; use std::collections::HashSet; use std::fs; +use std::io::Read; use std::path::{Path, PathBuf}; use std::sync::Arc; use std::time::{Duration, SystemTime, UNIX_EPOCH}; @@ -54,9 +55,10 @@ use crate::util::logger::{LogConfig, ServerLogger}; use crate::util::metrics::Metrics; use crate::util::proto_adapter::{forwarded_payment_to_proto, payment_to_proto}; use crate::util::tls::get_or_generate_tls_config; -use crate::util::{systemd, write_new}; +use crate::util::{create_dir_all_private, systemd, write_new}; const API_KEY_FILE: &str = "api_key"; +const API_KEY_LEN: usize = 32; const FULL_VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), " (", env!("GIT_HASH"), ")"); pub fn get_default_data_dir() -> Option { @@ -893,15 +895,31 @@ fn upsert_payment_details( fn load_or_generate_api_key(storage_dir: &Path) -> std::io::Result { let api_key_path = storage_dir.join(API_KEY_FILE); - if api_key_path.exists() { - let key_bytes = fs::read(&api_key_path)?; + let file = match fs::File::open(&api_key_path) { + Ok(file) => Some(file), + Err(e) if e.kind() == std::io::ErrorKind::NotFound => None, + Err(e) => return Err(e), + }; + + if let Some(file) = file { + let mut key_bytes = Vec::with_capacity(API_KEY_LEN + 1); + file.take((API_KEY_LEN + 1) as u64).read_to_end(&mut key_bytes)?; + if key_bytes.len() != API_KEY_LEN { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + format!( + "API key file '{}' must contain exactly {API_KEY_LEN} bytes", + api_key_path.display() + ), + )); + } Ok(key_bytes.to_lower_hex_string()) } else { // Ensure the storage directory exists - fs::create_dir_all(storage_dir)?; + create_dir_all_private(storage_dir)?; // Generate a 32-byte random API key - let mut key_bytes = [0u8; 32]; + let mut key_bytes = [0u8; API_KEY_LEN]; getrandom::getrandom(&mut key_bytes).map_err(std::io::Error::other)?; write_new(&api_key_path, &key_bytes, 0o400)?; @@ -929,6 +947,23 @@ mod tests { use super::*; + #[test] + fn load_api_key_rejects_invalid_lengths() { + let nonce = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos(); + let dir = std::env::temp_dir() + .join(format!("ldk-server-api-key-length-{}-{nonce}", std::process::id())); + fs::create_dir_all(&dir).unwrap(); + let path = dir.join(API_KEY_FILE); + + for len in [0, 1, API_KEY_LEN - 1, API_KEY_LEN + 1] { + fs::write(&path, vec![0x42; len]).unwrap(); + let error = load_or_generate_api_key(&dir).unwrap_err(); + assert_eq!(error.kind(), std::io::ErrorKind::InvalidData); + } + + fs::remove_dir_all(dir).unwrap(); + } + #[test] fn test_is_channel_open_failure_classification() { assert!(is_channel_open_failure(Some(&ClosureReason::FundingTimedOut))); diff --git a/ldk-server/src/util/config.rs b/ldk-server/src/util/config.rs index b8028ef6..48b8de6f 100644 --- a/ldk-server/src/util/config.rs +++ b/ldk-server/src/util/config.rs @@ -7,11 +7,11 @@ // You may not use this file except in accordance with one or both of these // licenses. +use std::io; use std::net::SocketAddr; use std::path::PathBuf; use std::str::FromStr; use std::time::Duration; -use std::{fs, io}; use clap::Parser; use ldk_node::bitcoin::secp256k1::PublicKey; @@ -24,6 +24,9 @@ use ldk_node::probing::{ProbingConfig, ProbingConfigBuilder}; use log::LevelFilter; use serde::{Deserialize, Serialize}; +use crate::util::read_to_string_with_limit; + +const CONFIG_FILE_SIZE_LIMIT: usize = 1024 * 1024; const DEFAULT_GRPC_SERVICE_ADDRESS: &str = "127.0.0.1:3536"; const DEFAULT_PATHFINDING_SCORES_SOURCE_URL: &str = "https://rapidsync.lightningdevkit.org/scoring/scorer.bin"; @@ -1235,7 +1238,7 @@ pub fn load_config(args: &ArgsConfig) -> io::Result { }; if let Some(path) = config_file { - let content = fs::read_to_string(&path).map_err(|e| { + let content = read_to_string_with_limit(&path, CONFIG_FILE_SIZE_LIMIT).map_err(|e| { io::Error::new(e.kind(), format!("Failed to read config file '{:?}': {}", path, e)) })?; let toml_config: TomlConfig = toml::from_str(&content).map_err(|e| { @@ -1288,7 +1291,7 @@ fn parse_host_port(addr: &str) -> io::Result<(String, u16)> { #[cfg(test)] mod tests { - use std::str::FromStr; + use std::{fs, str::FromStr}; use clap::Parser; use ldk_node::bitcoin::secp256k1::PublicKey; @@ -1719,6 +1722,20 @@ mod tests { assert_eq!(error.to_string(), "Must set a single chain source, multiple were configured"); } + #[test] + fn test_rejects_oversized_config_file() { + let path = std::env::temp_dir() + .join(format!("ldk-server-oversized-config-{}", std::process::id())); + fs::write(&path, vec![b'a'; CONFIG_FILE_SIZE_LIMIT + 1]).unwrap(); + let mut args_config = empty_args_config(); + args_config.config_file = Some(path.to_string_lossy().to_string()); + + let error = load_config(&args_config).unwrap_err(); + assert_eq!(error.kind(), io::ErrorKind::InvalidData); + + fs::remove_file(path).unwrap(); + } + #[test] fn test_config_optional_values() { let storage_path = std::env::temp_dir(); diff --git a/ldk-server/src/util/entropy.rs b/ldk-server/src/util/entropy.rs index 4b73d6ee..d3299f7a 100644 --- a/ldk-server/src/util/entropy.rs +++ b/ldk-server/src/util/entropy.rs @@ -7,40 +7,42 @@ // You may not use this file except in accordance with one or both of these // licenses. +use std::io; use std::path::Path; use std::str::FromStr; -use std::{fs, io}; use ldk_node::bip39::Mnemonic; use ldk_node::entropy::{generate_entropy_mnemonic, NodeEntropy}; use log::info; -use crate::util::write_new; +use crate::util::{create_dir_all_private, read_to_string_with_limit, write_new}; const DEFAULT_MNEMONIC_FILE: &str = "keys_mnemonic"; +const MNEMONIC_FILE_SIZE_LIMIT: usize = 1024; pub(crate) fn load_or_generate_node_entropy(storage_dir: &Path) -> io::Result { let mnemonic_path = storage_dir.join(DEFAULT_MNEMONIC_FILE); - let mnemonic = if mnemonic_path.exists() { - let raw = fs::read_to_string(&mnemonic_path)?; - Mnemonic::from_str(raw.trim()).map_err(|e| { + let mnemonic = match read_to_string_with_limit(&mnemonic_path, MNEMONIC_FILE_SIZE_LIMIT) { + Ok(raw) => Mnemonic::from_str(raw.trim()).map_err(|e| { io::Error::new( io::ErrorKind::InvalidData, format!("Invalid BIP39 mnemonic in {}: {}", mnemonic_path.display(), e), ) - })? - } else { - if let Some(parent) = mnemonic_path.parent() { - fs::create_dir_all(parent)?; - } - let mnemonic = generate_entropy_mnemonic(None); - write_new(&mnemonic_path, format!("{}\n", mnemonic).as_bytes(), 0o600)?; - info!( - "Generated new BIP39 mnemonic at {}. Back up this file securely — it is required to recover on-chain funds.", - mnemonic_path.display() - ); - mnemonic + })?, + Err(e) if e.kind() == io::ErrorKind::NotFound => { + if let Some(parent) = mnemonic_path.parent() { + create_dir_all_private(parent)?; + } + let mnemonic = generate_entropy_mnemonic(None); + write_new(&mnemonic_path, format!("{}\n", mnemonic).as_bytes(), 0o600)?; + info!( + "Generated new BIP39 mnemonic at {}. Back up this file securely — it is required to recover on-chain funds.", + mnemonic_path.display() + ); + mnemonic + }, + Err(e) => return Err(e), }; Ok(NodeEntropy::from_bip39_mnemonic(mnemonic, None)) @@ -48,6 +50,7 @@ pub(crate) fn load_or_generate_node_entropy(storage_dir: &Path) -> io::Result, bytes_written: usize, @@ -67,7 +70,7 @@ impl ServerLogger { let state = if let Some(path) = &log_file_path { // Create parent directories if they don't exist if let Some(parent) = path.parent() { - fs::create_dir_all(parent)?; + create_dir_all_private(parent)?; } let file = open_log_file(path)?; @@ -259,7 +262,7 @@ fn format_level(level: Level) -> &'static str { } fn open_log_file(log_file_path: &Path) -> Result { - OpenOptions::new().create(true).append(true).open(log_file_path) + OpenOptions::new().create(true).append(true).mode(0o600).open(log_file_path) } fn cleanup_old_logs(log_file_path: &Path, max_files: usize) -> io::Result<()> { @@ -319,3 +322,26 @@ impl Log for LoggerWrapper { self.0.flush() } } + +#[cfg(test)] +mod tests { + use std::os::unix::fs::PermissionsExt; + use std::time::{SystemTime, UNIX_EPOCH}; + + use super::*; + + #[test] + fn open_log_file_creates_private_file() { + let nonce = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos(); + let dir = std::env::temp_dir() + .join(format!("ldk-server-log-mode-{}-{nonce}", std::process::id())); + fs::create_dir_all(&dir).unwrap(); + let path = dir.join("ldk-server.log"); + + drop(open_log_file(&path).unwrap()); + + let mode = fs::metadata(&path).unwrap().permissions().mode(); + assert_eq!(mode & 0o077, 0); + fs::remove_dir_all(dir).unwrap(); + } +} diff --git a/ldk-server/src/util/mod.rs b/ldk-server/src/util/mod.rs index 7e900eb7..010ffc68 100644 --- a/ldk-server/src/util/mod.rs +++ b/ldk-server/src/util/mod.rs @@ -15,11 +15,29 @@ pub(crate) mod proto_adapter; pub(crate) mod systemd; pub(crate) mod tls; -use std::fs::{self, OpenOptions}; -use std::io::{self, Write}; -use std::os::unix::fs::{OpenOptionsExt, PermissionsExt}; +use std::fs::{self, DirBuilder, OpenOptions}; +use std::io::{self, Read, Write}; +use std::os::unix::fs::{DirBuilderExt, OpenOptionsExt, PermissionsExt}; use std::path::Path; +pub(crate) fn create_dir_all_private(path: &Path) -> io::Result<()> { + let mut builder = DirBuilder::new(); + builder.recursive(true).mode(0o700).create(path) +} + +pub(crate) fn read_to_string_with_limit(path: &Path, limit: usize) -> io::Result { + let file = fs::File::open(path)?; + let mut contents = String::new(); + file.take(limit.saturating_add(1) as u64).read_to_string(&mut contents)?; + if contents.len() > limit { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + format!("File '{}' exceeds the {limit} byte limit", path.display()), + )); + } + Ok(contents) +} + pub(crate) fn write_new(path: &Path, contents: &[u8], mode: u32) -> io::Result<()> { let mut file = OpenOptions::new().create_new(true).write(true).mode(mode).open(path)?; file.write_all(contents)?; diff --git a/ldk-server/src/util/tls.rs b/ldk-server/src/util/tls.rs index 3196dea6..9bed12c9 100644 --- a/ldk-server/src/util/tls.rs +++ b/ldk-server/src/util/tls.rs @@ -19,10 +19,11 @@ use tokio_rustls::rustls::pki_types::{CertificateDer, PrivateKeyDer}; use tokio_rustls::rustls::ServerConfig; use crate::util::config::TlsConfig; -use crate::util::write_new; +use crate::util::{read_to_string_with_limit, write_new}; // Issuer and Subject common name const ISSUER_NAME: &str = "localhost"; +const TLS_FILE_SIZE_LIMIT: usize = 1024 * 1024; // PEM markers const PEM_CERT_BEGIN: &str = "-----BEGIN CERTIFICATE-----"; @@ -397,9 +398,9 @@ fn der_context_implicit(tag_num: u8, content: &[u8]) -> Vec { /// Loads TLS configuration from provided paths. fn load_tls_config(cert_path: &str, key_path: &str) -> Result { - let cert_pem = fs::read_to_string(cert_path) + let cert_pem = read_to_string_with_limit(Path::new(cert_path), TLS_FILE_SIZE_LIMIT) .map_err(|e| format!("Failed to read TLS certificate file '{cert_path}': {e}"))?; - let key_pem = fs::read_to_string(key_path) + let key_pem = read_to_string_with_limit(Path::new(key_path), TLS_FILE_SIZE_LIMIT) .map_err(|e| format!("Failed to read TLS key file '{key_path}': {e}"))?; let certs = parse_pem_certs(&cert_pem)?; @@ -496,4 +497,33 @@ mod tests { let _ = fs::remove_file(&cert_path); let _ = fs::remove_file(&key_path); } + + #[test] + fn test_load_rejects_oversized_tls_files() { + let temp_dir = std::env::temp_dir(); + let mut suffix_bytes = [0u8; 8]; + getrandom::getrandom(&mut suffix_bytes).unwrap(); + let suffix = u64::from_ne_bytes(suffix_bytes); + let cert_path = temp_dir.join(format!("oversized_tls_cert_{suffix}.pem")); + let key_path = temp_dir.join(format!("oversized_tls_key_{suffix}.pem")); + + generate_self_signed_cert(cert_path.to_str().unwrap(), key_path.to_str().unwrap(), &[]) + .unwrap(); + let valid_cert = fs::read(&cert_path).unwrap(); + + fs::write(&cert_path, vec![b'a'; TLS_FILE_SIZE_LIMIT + 1]).unwrap(); + let error = + load_tls_config(cert_path.to_str().unwrap(), key_path.to_str().unwrap()).unwrap_err(); + assert!(error.contains("exceeds")); + + fs::write(&cert_path, valid_cert).unwrap(); + fs::remove_file(&key_path).unwrap(); + fs::write(&key_path, vec![b'a'; TLS_FILE_SIZE_LIMIT + 1]).unwrap(); + let error = + load_tls_config(cert_path.to_str().unwrap(), key_path.to_str().unwrap()).unwrap_err(); + assert!(error.contains("exceeds")); + + let _ = fs::remove_file(&cert_path); + let _ = fs::remove_file(&key_path); + } }