diff --git a/Cargo.lock b/Cargo.lock index a11bc2f..ea01734 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3390,6 +3390,7 @@ dependencies = [ "log", "p256", "p384", + "pem", "portable-atomic", "rand", "rand_core", diff --git a/Cargo.toml b/Cargo.toml index 2d835f0..f89e63d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,7 +54,7 @@ slab = "0.4.9" thiserror = "2.0.0" tokio-util = "0.7.11" local-channel = "0.1.5" -webrtc-dtls = "0.10.0" +webrtc-dtls = { version = "0.10.0", features = ["pem"] } webrtc-util = "0.9.0" rustls = { version = "0.23.12", default-features = false, features = [ "std", diff --git a/src/config.rs b/src/config.rs index f672d46..9bf98e8 100644 --- a/src/config.rs +++ b/src/config.rs @@ -4,6 +4,7 @@ use std::env::{self, VarError}; use std::fmt::Display; use std::fs; use std::net::IpAddr; +use std::path::{Path, PathBuf}; use std::{collections::HashSet, io}; use thiserror::Error; use toml; @@ -22,6 +23,8 @@ pub struct ConfigToml { pub port: Option, pub frontend: Option, pub release_bind: Option>, + pub pk_path: Option, + pub sk_path: Option, pub left: Option, pub right: Option, pub top: Option, @@ -40,9 +43,9 @@ pub struct TomlClient { } impl ConfigToml { - pub fn new(path: &str) -> Result { + pub fn new(path: &Path) -> Result { let config = fs::read_to_string(path)?; - log::info!("using config: \"{path}\""); + log::info!("using config: \"{path:?}\""); Ok(toml::from_str::<_>(&config)?) } } @@ -81,6 +84,10 @@ struct CliArgs { /// emulation backend override #[arg(long)] emulation_backend: Option, + + /// path to non-default certificate location + #[arg(long)] + cert_path: Option, } #[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, ValueEnum)] @@ -233,6 +240,7 @@ pub struct Config { pub release_bind: Vec, pub test_capture: bool, pub test_emulation: bool, + pub cert_path: PathBuf, } pub struct ConfigClient { @@ -260,12 +268,14 @@ const DEFAULT_RELEASE_KEYS: [scancode::Linux; 4] = impl Config { pub fn new() -> Result { let args = CliArgs::parse(); - let config_file = "config.toml"; + const CONFIG_FILE_NAME: &str = "config.toml"; + const CERT_FILE_NAME: &str = "lan-mouse.pem"; + #[cfg(unix)] let config_path = { let xdg_config_home = env::var("XDG_CONFIG_HOME").unwrap_or(format!("{}/.config", env::var("HOME")?)); - format!("{xdg_config_home}/lan-mouse/{config_file}") + format!("{xdg_config_home}/lan-mouse/") }; #[cfg(not(unix))] @@ -275,12 +285,15 @@ impl Config { format!("{app_data}\\lan-mouse\\{config_file}") }; - // --config overrules default location - let config_path = args.config.unwrap_or(config_path); + let config_path = PathBuf::from(config_path); + let config_file = config_path.join(CONFIG_FILE_NAME); - let config_toml = match ConfigToml::new(config_path.as_str()) { + // --config overrules default location + let config_file = args.config.map(PathBuf::from).unwrap_or(config_file); + + let config_toml = match ConfigToml::new(&config_file) { Err(e) => { - log::warn!("{config_path}: {e}"); + log::warn!("{config_file:?}: {e}"); log::warn!("Continuing without config file ..."); None } @@ -310,6 +323,11 @@ impl Config { .emulation_backend .or(config_toml.as_ref().and_then(|c| c.emulation_backend)); + let cert_path = args + .cert_path + .or(config_toml.as_ref().and_then(|c| c.pk_path.clone())) + .unwrap_or(config_path.join(CERT_FILE_NAME)); + let mut clients: Vec<(TomlClient, Position)> = vec![]; if let Some(config_toml) = config_toml { @@ -341,6 +359,7 @@ impl Config { release_bind, test_capture, test_emulation, + cert_path, }) } diff --git a/src/crypto.rs b/src/crypto.rs index b135e25..4030940 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -1,12 +1,10 @@ -use std::io::{self, Read}; -use std::path::PathBuf; +use std::io::{self, BufWriter, Read, Write}; +use std::path::Path; use std::{fs::File, io::BufReader}; -use rcgen::KeyPair; -use rustls::pki_types::CertificateDer; use sha2::{Digest, Sha256}; use thiserror::Error; -use webrtc_dtls::crypto::{Certificate, CryptoPrivateKey}; +use webrtc_dtls::crypto::Certificate; #[derive(Debug, Error)] pub enum Error { @@ -27,6 +25,10 @@ pub enum Error { Dtls(#[from] webrtc_dtls::Error), #[error("{0}")] Other(String), + #[error("a file containing the public key is missing")] + PkMissing, + #[error("a file containing the private key is missing")] + SkMissing, } pub fn generate_fingerprint(cert: &[u8]) -> String { @@ -41,42 +43,34 @@ pub fn generate_fingerprint(cert: &[u8]) -> String { fingerprint } -/// load_key_and_certificate reads certificates or key from file -pub fn load_key_and_certificate( - key_path: PathBuf, - certificate_path: PathBuf, -) -> Result { - let private_key = load_key(key_path)?; - - let certificate = load_certificate(certificate_path)?; - - Ok(Certificate { - certificate, - private_key, - }) +pub fn certificate_fingerprint(cert: &Certificate) -> String { + let certificate = cert.certificate.get(0).expect("certificate missing"); + generate_fingerprint(certificate) } -/// load_key Load/read key from file -pub fn load_key(path: PathBuf) -> Result { - let f = File::open(path)?; - let mut reader = BufReader::new(f); - let mut buf = vec![]; - reader.read_to_end(&mut buf)?; - - let s = String::from_utf8(buf).expect("utf8 of file"); - - let key_pair = KeyPair::from_pem(s.as_str()).expect("key pair in file"); - - Ok(CryptoPrivateKey::from_key_pair(&key_pair).expect("crypto key pair")) -} - -/// load_certificate Load/read certificate(s) from file -pub fn load_certificate(path: PathBuf) -> Result>, Error> { +/// load certificate from file +pub fn load_certificate(path: &Path) -> Result { let f = File::open(path)?; let mut reader = BufReader::new(f); - match rustls_pemfile::certs(&mut reader).collect::, _>>() { - Ok(certs) => Ok(certs.into_iter().map(CertificateDer::from).collect()), - Err(_) => Err(Error::ErrNoCertificateFound), + let mut pem = String::new(); + reader.read_to_string(&mut pem)?; + Ok(Certificate::from_pem(pem.as_str())?) +} + +pub(crate) fn load_or_generate_key_and_cert(path: &Path) -> Result { + if path.exists() && path.is_file() { + return Ok(load_certificate(path)?); + } else { + return Ok(generate_key_and_cert(path)?); } } + +pub(crate) fn generate_key_and_cert(path: &Path) -> Result { + let cert = Certificate::generate_self_signed(["ignored".to_owned()])?; + let serialized = cert.serialize_pem(); + let f = File::create(path)?; + let mut writer = BufWriter::new(f); + writer.write(serialized.as_bytes())?; + Ok(cert) +} diff --git a/src/listen.rs b/src/listen.rs index e8b18b8..e799f12 100644 --- a/src/listen.rs +++ b/src/listen.rs @@ -47,12 +47,12 @@ type VerifyPeerCertificateFn = Arc< impl LanMouseListener { pub(crate) async fn new( port: u16, + cert: Certificate, authorized_keys: Arc>>, ) -> Result { let (listen_tx, listen_rx) = channel(); let listen_addr = SocketAddr::new("0.0.0.0".parse().expect("invalid ip"), port); - let certificate = Certificate::generate_self_signed(["localhost".to_owned()])?; let verify_peer_certificate: Option = Some(Arc::new( move |certs: &[Vec], _chains: &[CertificateDer<'static>]| { log::error!("verifying device fingerprint!"); @@ -74,7 +74,7 @@ impl LanMouseListener { }, )); let cfg = Config { - certificates: vec![certificate], + certificates: vec![cert], extended_master_secret: ExtendedMasterSecretType::Require, client_auth: RequireAnyClientCert, verify_peer_certificate, diff --git a/src/server.rs b/src/server.rs index 7bb02e8..61ff27a 100644 --- a/src/server.rs +++ b/src/server.rs @@ -3,6 +3,7 @@ use crate::{ client::ClientManager, config::Config, connect::LanMouseConnection, + crypto, dns::DnsResolver, emulation::Emulation, listen::{LanMouseListener, ListenerCreationError}, @@ -36,6 +37,8 @@ pub enum ServiceError { Io(#[from] io::Error), #[error(transparent)] ListenError(#[from] ListenerCreationError), + #[error("failed to load certificate: `{0}`")] + Certificate(#[from] crypto::Error), } pub struct ReleaseToken; @@ -44,10 +47,10 @@ pub struct ReleaseToken; pub struct Server { active: Rc>>, authorized_keys: Arc>>, - known_hosts: Rc>>, + _known_hosts: Rc>>, pub(crate) client_manager: ClientManager, port: Rc>, - public_key_fingerprint: String, + public_key_fingerprint: Option, notifies: Rc, pub(crate) config: Rc, pending_frontend_events: Rc>>, @@ -96,8 +99,8 @@ impl Server { Self { active: Rc::new(Cell::new(None)), authorized_keys: Default::default(), - known_hosts: Default::default(), - public_key_fingerprint: "87:f9:d9:a6:c4:a1:14:d2:c8:25:4f:72:b7:01:86:65:73:cc:bc:a1:37:cc:96:69:f8:f4:72:8a:60:9a:3b:4d".to_owned(), + _known_hosts: Default::default(), + public_key_fingerprint: None, config, client_manager, port, @@ -121,11 +124,14 @@ impl Server { e => e?, }; - // let cert = crypto::load_key_and_certificate(config.sk_path, config.pk_path)?; + // load certificate + let cert = crypto::load_or_generate_key_and_cert(&self.config.cert_path)?; + let public_key_fingerprint = crypto::certificate_fingerprint(&cert); + self.public_key_fingerprint.replace(public_key_fingerprint); // listener + connection let listener = - LanMouseListener::new(self.config.port, self.authorized_keys.clone()).await?; + LanMouseListener::new(self.config.port, cert, self.authorized_keys.clone()).await?; let conn = LanMouseConnection::new(self.clone()); // input capture + emulation @@ -264,7 +270,10 @@ impl Server { self.notify_frontend(FrontendEvent::CaptureStatus(self.capture_status.get())); self.notify_frontend(FrontendEvent::PortChanged(self.port.get(), None)); self.notify_frontend(FrontendEvent::PublicKeyFingerprint( - self.public_key_fingerprint.clone(), + self.public_key_fingerprint + .as_ref() + .expect("fingerprint") + .clone(), )); self.notify_frontend(FrontendEvent::AuthorizedUpdated( self.authorized_keys.read().expect("lock").clone(),