Skip to content

Commit 65e00df

Browse files
committed
simplified the tls.rs and moved the tests, also rebased
1 parent 1d73c11 commit 65e00df

6 files changed

Lines changed: 119 additions & 156 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

daemon/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,5 +35,4 @@ async-trait = "0.1.88"
3535
axum-server = { version = "0.7", default-features = false, features = ["tls-rustls-no-provider"] }
3636
rustls = { version = "0.23", default-features = false, features = ["ring", "std", "tls12"] }
3737
rcgen = "0.13"
38-
if-addrs = "0.13"
3938
rustls-pemfile = "2.2"

daemon/src/main.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,10 @@ async fn setup_http_redirect_server(
215215
.with_graceful_shutdown(shutdown_token.cancelled_owned())
216216
.await
217217
{
218-
error!("HTTP redirect server on {} stopped unexpectedly: {}", addr, e);
218+
error!(
219+
"HTTP redirect server on {} stopped unexpectedly: {}",
220+
addr, e
221+
);
219222
}
220223
});
221224

daemon/src/tls.rs

Lines changed: 87 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,4 @@
11
//! TLS certificate generation and management for HTTPS support.
2-
//!
3-
//! This module handles:
4-
//! - Detecting device IP addresses for certificate SANs
5-
//! - Generating self-signed certificates using rcgen
6-
//! - Loading existing certificates or generating new ones
7-
//! - Validating certificate expiration and integrity
8-
//! - Boot loop prevention for certificate regeneration
92
103
use std::net::IpAddr;
114
use std::path::{Path, PathBuf};
@@ -45,40 +38,29 @@ pub fn get_device_default_ip(device: &Device) -> IpAddr {
4538
}
4639
}
4740

48-
/// Detect IP addresses for certificate SANs.
41+
/// Build Subject Alternative Names for the certificate.
4942
///
50-
/// Combines device-specific default IP with any detected network interface IPs.
51-
/// The device default IP is always included first to ensure it's in the certificate
52-
/// even when there's no SIM card or network connection.
53-
pub fn detect_device_ips(device: &Device) -> Vec<IpAddr> {
54-
let mut ips = Vec::new();
55-
56-
// Always include the device-specific default IP first
57-
let device_ip = get_device_default_ip(device);
58-
ips.push(device_ip);
59-
info!("Using device default IP: {}", device_ip);
60-
61-
// Try to detect additional IPs from network interfaces
62-
match if_addrs::get_if_addrs() {
63-
Ok(interfaces) => {
64-
let detected: Vec<IpAddr> = interfaces
65-
.into_iter()
66-
.filter(|iface| !iface.is_loopback())
67-
.map(|iface| iface.ip())
68-
.filter(|ip| ip.is_ipv4() && !ips.contains(ip))
69-
.collect();
70-
71-
if !detected.is_empty() {
72-
info!("Also detected network IPs: {:?}", detected);
73-
ips.extend(detected);
74-
}
43+
/// Uses device-specific default gateway IP plus localhost, with optional custom hosts.
44+
pub fn get_certificate_sans(device: &Device, custom_hosts: &[String]) -> Vec<SanType> {
45+
let mut sans = vec![
46+
SanType::IpAddress(get_device_default_ip(device)),
47+
SanType::IpAddress("127.0.0.1".parse().unwrap()),
48+
];
49+
50+
// Add user-provided custom hosts if any
51+
for host in custom_hosts {
52+
let host = host.trim();
53+
if host.is_empty() {
54+
continue;
7555
}
76-
Err(e) => {
77-
warn!("Failed to detect network interfaces: {}", e);
56+
if let Some(san) = parse_san_entry(host) {
57+
if !sans.contains(&san) {
58+
sans.push(san);
59+
}
7860
}
7961
}
8062

81-
ips
63+
sans
8264
}
8365

8466
/// Parse a host string into a SanType (either IP or DNS name).
@@ -94,21 +76,11 @@ fn parse_san_entry(host: &str) -> Option<SanType> {
9476
}
9577
}
9678

97-
/// Generate a self-signed certificate with the given IP addresses as SANs.
79+
/// Generate a self-signed certificate with the given SANs.
9880
///
9981
/// Returns (certificate_pem, private_key_pem) as strings.
100-
#[allow(dead_code)] // Used by tests and kept as simpler public API
101-
pub fn generate_self_signed_cert(ips: &[IpAddr]) -> Result<(String, String), RayhunterError> {
102-
generate_self_signed_cert_with_hosts(ips, &[])
103-
}
104-
105-
/// Generate a self-signed certificate with IPs and custom hosts as SANs.
106-
///
107-
/// Custom hosts can be IP addresses or DNS names (hostnames).
108-
/// Returns (certificate_pem, private_key_pem) as strings.
109-
pub fn generate_self_signed_cert_with_hosts(
110-
ips: &[IpAddr],
111-
custom_hosts: &[String],
82+
fn generate_self_signed_cert_with_sans(
83+
sans: Vec<SanType>,
11284
) -> Result<(String, String), RayhunterError> {
11385
let mut params = CertificateParams::default();
11486

@@ -120,30 +92,6 @@ pub fn generate_self_signed_cert_with_hosts(
12092
.distinguished_name
12193
.push(DnType::OrganizationName, "Electronic Frontier Foundation");
12294

123-
// Add Subject Alternative Names (SANs)
124-
let mut sans: Vec<SanType> = ips.iter().map(|ip| SanType::IpAddress(*ip)).collect();
125-
126-
// Add custom hosts (can be IPs or DNS names)
127-
for host in custom_hosts {
128-
let host = host.trim();
129-
if host.is_empty() {
130-
continue;
131-
}
132-
if let Some(san) = parse_san_entry(host) {
133-
// Avoid duplicates
134-
if !sans.contains(&san) {
135-
info!("Adding custom SAN: {}", host);
136-
sans.push(san);
137-
}
138-
}
139-
}
140-
141-
// Always include localhost
142-
let localhost_san = SanType::IpAddress("127.0.0.1".parse().unwrap());
143-
if !sans.contains(&localhost_san) {
144-
sans.push(localhost_san);
145-
}
146-
14795
let san_count = sans.len();
14896
params.subject_alt_names = sans;
14997

@@ -262,7 +210,10 @@ async fn validate_certificate(cert_path: &Path) -> Result<bool, RayhunterError>
262210
// Full expiration checking would require x509-parser crate
263211
// Instead, we'll rely on rustls failing to load expired certs
264212

265-
info!("Certificate validation passed (parseable PEM with {} cert(s))", certs.len());
213+
info!(
214+
"Certificate validation passed (parseable PEM with {} cert(s))",
215+
certs.len()
216+
);
266217
Ok(true)
267218
}
268219

@@ -272,13 +223,15 @@ async fn set_key_permissions(key_path: &Path) -> Result<(), RayhunterError> {
272223
use std::os::unix::fs::PermissionsExt;
273224

274225
let permissions = std::fs::Permissions::from_mode(0o600);
275-
fs::set_permissions(key_path, permissions).await.map_err(|e| {
276-
RayhunterError::TlsError(format!(
277-
"Failed to set permissions on {}: {}",
278-
key_path.display(),
279-
e
280-
))
281-
})?;
226+
fs::set_permissions(key_path, permissions)
227+
.await
228+
.map_err(|e| {
229+
RayhunterError::TlsError(format!(
230+
"Failed to set permissions on {}: {}",
231+
key_path.display(),
232+
e
233+
))
234+
})?;
282235

283236
info!("Set private key permissions to 0600");
284237
Ok(())
@@ -310,15 +263,13 @@ pub async fn load_or_generate_certs(
310263
let key_path = tls_dir.join("key.pem");
311264

312265
// Ensure TLS directory exists
313-
fs::create_dir_all(&tls_dir)
314-
.await
315-
.map_err(|e| {
316-
RayhunterError::TlsError(format!(
317-
"Failed to create TLS directory {}: {}",
318-
tls_dir.display(),
319-
e
320-
))
321-
})?;
266+
fs::create_dir_all(&tls_dir).await.map_err(|e| {
267+
RayhunterError::TlsError(format!(
268+
"Failed to create TLS directory {}: {}",
269+
tls_dir.display(),
270+
e
271+
))
272+
})?;
322273

323274
// Check boot loop prevention - if we've tried too many times, fail immediately
324275
let regen_attempts = read_regen_attempts(&tls_dir).await;
@@ -351,7 +302,10 @@ pub async fn load_or_generate_certs(
351302
true
352303
}
353304
Err(e) => {
354-
warn!("Failed to validate existing certificate: {}, will regenerate", e);
305+
warn!(
306+
"Failed to validate existing certificate: {}, will regenerate",
307+
e
308+
);
355309
true
356310
}
357311
}
@@ -374,31 +328,27 @@ pub async fn load_or_generate_certs(
374328
let _ = fs::remove_file(&cert_path).await;
375329
let _ = fs::remove_file(&key_path).await;
376330

377-
// Detect IPs (using device-specific defaults) and generate cert with custom hosts
378-
let ips = detect_device_ips(device);
379-
let (cert_pem, key_pem) = generate_self_signed_cert_with_hosts(&ips, custom_hosts)?;
331+
// Build SANs and generate certificate
332+
let sans = get_certificate_sans(device, custom_hosts);
333+
let (cert_pem, key_pem) = generate_self_signed_cert_with_sans(sans)?;
380334

381335
// Write certificate
382-
fs::write(&cert_path, &cert_pem)
383-
.await
384-
.map_err(|e| {
385-
RayhunterError::TlsError(format!(
386-
"Failed to write certificate to {}: {}",
387-
cert_path.display(),
388-
e
389-
))
390-
})?;
336+
fs::write(&cert_path, &cert_pem).await.map_err(|e| {
337+
RayhunterError::TlsError(format!(
338+
"Failed to write certificate to {}: {}",
339+
cert_path.display(),
340+
e
341+
))
342+
})?;
391343

392344
// Write private key
393-
fs::write(&key_path, &key_pem)
394-
.await
395-
.map_err(|e| {
396-
RayhunterError::TlsError(format!(
397-
"Failed to write private key to {}: {}",
398-
key_path.display(),
399-
e
400-
))
401-
})?;
345+
fs::write(&key_path, &key_pem).await.map_err(|e| {
346+
RayhunterError::TlsError(format!(
347+
"Failed to write private key to {}: {}",
348+
key_path.display(),
349+
e
350+
))
351+
})?;
402352

403353
// Set restrictive permissions on private key
404354
set_key_permissions(&key_path).await?;
@@ -441,29 +391,31 @@ mod tests {
441391
}
442392

443393
#[test]
444-
fn test_detect_device_ips_orbic() {
445-
let ips = detect_device_ips(&Device::Orbic);
446-
// Should always include the device default IP first
447-
assert!(!ips.is_empty());
448-
assert_eq!(ips[0], "192.168.1.1".parse::<IpAddr>().unwrap());
449-
// All should be valid IPv4 addresses
450-
for ip in &ips {
451-
assert!(ip.is_ipv4());
452-
}
394+
fn test_get_certificate_sans_basic() {
395+
let sans = get_certificate_sans(&Device::Orbic, &[]);
396+
// Should include device default IP and localhost
397+
assert!(sans.len() >= 2);
453398
}
454399

455400
#[test]
456-
fn test_detect_device_ips_tplink() {
457-
let ips = detect_device_ips(&Device::Tplink);
458-
assert!(!ips.is_empty());
459-
assert_eq!(ips[0], "192.168.0.1".parse::<IpAddr>().unwrap());
401+
fn test_get_certificate_sans_with_custom_hosts() {
402+
let custom_hosts = vec![
403+
"rayhunter.local".to_string(),
404+
"10.0.0.5".to_string(), // IP as string
405+
];
406+
let sans = get_certificate_sans(&Device::Orbic, &custom_hosts);
407+
// Should include device IP, localhost, and custom hosts
408+
assert!(sans.len() >= 4);
460409
}
461410

462411
#[test]
463412
fn test_generate_self_signed_cert() {
464-
let ips: Vec<IpAddr> = vec!["192.168.1.1".parse().unwrap(), "10.0.0.1".parse().unwrap()];
413+
let sans = vec![
414+
SanType::IpAddress("192.168.1.1".parse().unwrap()),
415+
SanType::IpAddress("10.0.0.1".parse().unwrap()),
416+
];
465417

466-
let result = generate_self_signed_cert(&ips);
418+
let result = generate_self_signed_cert_with_sans(sans);
467419
assert!(result.is_ok());
468420

469421
let (cert_pem, key_pem) = result.unwrap();
@@ -475,13 +427,6 @@ mod tests {
475427
assert!(key_pem.ends_with("-----END PRIVATE KEY-----\n"));
476428
}
477429

478-
#[test]
479-
fn test_generate_self_signed_cert_empty_ips() {
480-
// Should still work with empty IPs (will add localhost)
481-
let result = generate_self_signed_cert(&[]);
482-
assert!(result.is_ok());
483-
}
484-
485430
#[test]
486431
fn test_get_tls_dir() {
487432
let tls_dir = get_tls_dir("/data/rayhunter/qmdl");
@@ -492,14 +437,14 @@ mod tests {
492437
}
493438

494439
#[test]
495-
fn test_generate_self_signed_cert_with_custom_hosts() {
496-
let ips: Vec<IpAddr> = vec!["192.168.1.1".parse().unwrap()];
497-
let custom_hosts = vec![
498-
"rayhunter.local".to_string(),
499-
"10.0.0.5".to_string(), // IP as string
440+
fn test_generate_self_signed_cert_with_custom_sans() {
441+
let sans = vec![
442+
SanType::IpAddress("192.168.1.1".parse().unwrap()),
443+
SanType::DnsName(Ia5String::try_from("rayhunter.local").unwrap()),
444+
SanType::IpAddress("10.0.0.5".parse().unwrap()),
500445
];
501446

502-
let result = generate_self_signed_cert_with_hosts(&ips, &custom_hosts);
447+
let result = generate_self_signed_cert_with_sans(sans);
503448
assert!(result.is_ok());
504449

505450
let (cert_pem, _) = result.unwrap();
@@ -566,8 +511,8 @@ mod tests {
566511
std::fs::create_dir_all(&tls_path).unwrap();
567512

568513
// Create valid cert files (validation now checks PEM format)
569-
let ips: Vec<IpAddr> = vec!["192.168.1.1".parse().unwrap()];
570-
let (cert_pem, key_pem) = generate_self_signed_cert(&ips).unwrap();
514+
let sans = vec![SanType::IpAddress("192.168.1.1".parse().unwrap())];
515+
let (cert_pem, key_pem) = generate_self_signed_cert_with_sans(sans).unwrap();
571516

572517
let cert_path = tls_path.join("cert.pem");
573518
let key_path = tls_path.join("key.pem");

0 commit comments

Comments
 (0)