toole_core/
file_certif.rs1use crate::ToolError;
2use directories::ProjectDirs;
3use rcgen::{date_time_ymd, CertificateParams, DistinguishedName, DnType, KeyPair, SanType};
4use sha2::{Digest, Sha256};
5use std::collections::HashMap;
6use std::net::IpAddr;
7use std::path::PathBuf;
8use tokio::fs;
9
10#[derive(Debug)]
16pub struct PinnedServerVerifier {
17 pub expected: Option<String>,
18}
19
20impl rustls::client::danger::ServerCertVerifier for PinnedServerVerifier {
21 fn verify_server_cert(
22 &self,
23 end_entity: &rustls::pki_types::CertificateDer<'_>,
24 _intermediates: &[rustls::pki_types::CertificateDer<'_>],
25 _server_name: &rustls::pki_types::ServerName<'_>,
26 _ocsp_response: &[u8],
27 _now: rustls::pki_types::UnixTime,
28 ) -> Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
29 let fp = fingerprint(end_entity.as_ref());
30 if let Some(expected) = &self.expected {
31 if *expected != fp {
32 return Err(rustls::Error::General(format!(
33 "empreinte du pair ({fp}) differente de celle epinguee ({expected}) : attaque MITM ?"
34 )));
35 }
36 }
37 Ok(rustls::client::danger::ServerCertVerified::assertion())
38 }
39
40 fn verify_tls12_signature(
41 &self,
42 _message: &[u8],
43 _cert: &rustls::pki_types::CertificateDer<'_>,
44 _dss: &rustls::DigitallySignedStruct,
45 ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
46 Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
47 }
48
49 fn verify_tls13_signature(
50 &self,
51 _message: &[u8],
52 _cert: &rustls::pki_types::CertificateDer<'_>,
53 _dss: &rustls::DigitallySignedStruct,
54 ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
55 Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
56 }
57
58 fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
59 rustls::crypto::ring::default_provider()
60 .signature_verification_algorithms
61 .supported_schemes()
62 }
63}
64
65pub fn fingerprint(cert_der: &[u8]) -> String {
70 let mut hasher = Sha256::new();
71 hasher.update(cert_der);
72 hasher
73 .finalize()
74 .iter()
75 .map(|b| format!("{b:02x}"))
76 .collect()
77}
78
79pub fn peer_fingerprint(connection: &quinn::Connection) -> Option<String> {
82 let identity = connection.peer_identity()?;
83 let certs = identity
84 .downcast::<Vec<rustls::pki_types::CertificateDer<'static>>>()
85 .ok()?;
86 let cert = certs.first()?;
87 Some(fingerprint(cert.as_ref()))
88}
89
90pub fn pin_for(device_id: &str) -> Option<String> {
94 let raw = std::fs::read_to_string(pins_path().ok()?).ok()?;
95 let pins: HashMap<String, String> = serde_json::from_str(&raw).ok()?;
96 pins.get(device_id).cloned()
97}
98
99pub fn save_pin(device_id: &str, fingerprint: &str) -> Result<(), ToolError> {
102 let path = pins_path()?;
103 let mut pins: HashMap<String, String> = match std::fs::read_to_string(&path) {
104 Ok(raw) => serde_json::from_str(&raw).unwrap_or_default(),
105 Err(_) => HashMap::new(),
106 };
107 pins.insert(device_id.to_string(), fingerprint.to_string());
108 let raw = serde_json::to_string_pretty(&pins)?;
109 std::fs::write(path, raw)?;
110 Ok(())
111}
112
113fn pins_path() -> Result<PathBuf, ToolError> {
114 Ok(data_dir()?.join("pins.json"))
115}
116
117pub async fn certificat() -> Result<(String, String), ToolError> {
118 let (key_file, cert_file) = data_file()?;
119
120 if key_file.exists() && cert_file.exists() {
121 let cert_pem = fs::read_to_string(&cert_file).await?;
122 let key_pem = fs::read_to_string(&key_file).await?;
123 return Ok((cert_pem, key_pem));
124 }
125
126 let my_local_ip = local_ip_address::local_ip().unwrap_or(IpAddr::from([127, 0, 0, 1]));
127
128 let mut params: CertificateParams = Default::default();
129 params.not_before = date_time_ymd(2026, 1, 1);
130 params.not_after = date_time_ymd(2036, 1, 1);
131
132 params.distinguished_name = DistinguishedName::new();
133 params
134 .distinguished_name
135 .push(DnType::OrganizationName, "Toolé");
136 params
137 .distinguished_name
138 .push(DnType::CommonName, "Toolé P2P Server");
139
140 params.subject_alt_names = vec![
141 SanType::DnsName("localhost".try_into().map_err(|_| {
142 ToolError::IoError(std::io::Error::new(
143 std::io::ErrorKind::InvalidData,
144 "invalid DNS name",
145 ))
146 })?),
147 SanType::IpAddress(my_local_ip),
148 ];
149
150 let key_pair = KeyPair::generate_for(&rcgen::PKCS_ECDSA_P256_SHA256).map_err(|e| {
151 ToolError::IoError(std::io::Error::other(format!("key generation failed: {e}")))
152 })?;
153
154 let cert = params
155 .self_signed(&key_pair)
156 .map_err(|e| ToolError::IoError(std::io::Error::other(format!("self-sign failed: {e}"))))?;
157
158 let cert_pem = cert.pem();
159 let key_pem = key_pair.serialize_pem();
160
161 fs::write(&cert_file, cert_pem.as_bytes()).await?;
162 fs::write(&key_file, key_pem.as_bytes()).await?;
163
164 #[cfg(unix)]
167 {
168 use std::os::unix::fs::PermissionsExt;
169 let _ = fs::set_permissions(&key_file, std::fs::Permissions::from_mode(0o600)).await;
170 }
171
172 Ok((cert_pem, key_pem))
173}
174
175fn data_dir() -> Result<PathBuf, ToolError> {
176 let proj_dirs =
177 ProjectDirs::from("com", "Tiligre Open Space", "Toole").ok_or(ToolError::AppDirError)?;
178 let data_dir = proj_dirs.data_dir();
179 std::fs::create_dir_all(data_dir)?;
180 Ok(data_dir.to_path_buf())
181}
182
183fn data_file() -> Result<(PathBuf, PathBuf), ToolError> {
184 let data_dir = data_dir()?;
185
186 let cert_dir = data_dir.join("certs");
188 std::fs::create_dir_all(&cert_dir)?;
189
190 Ok((cert_dir.join("key.pem"), cert_dir.join("cert.pem")))
191}