139 lines
4.7 KiB
Rust
139 lines
4.7 KiB
Rust
use rcgen::{generate_simple_self_signed, CertifiedKey};
|
|
use rustls::{
|
|
client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
|
|
crypto::{verify_tls12_signature, verify_tls13_signature, CryptoProvider},
|
|
pki_types::{CertificateDer, ServerName, UnixTime},
|
|
ClientConfig, DigitallySignedStruct, Error as TlsError, SignatureScheme,
|
|
};
|
|
use sha2::{Digest, Sha256};
|
|
use std::{
|
|
collections::HashMap,
|
|
sync::{Arc, RwLock},
|
|
time::Duration,
|
|
};
|
|
|
|
#[derive(Debug)]
|
|
pub struct PinnedCertVerifier {
|
|
trusted_fingerprints: Arc<RwLock<HashMap<String, String>>>,
|
|
provider: Arc<CryptoProvider>,
|
|
}
|
|
|
|
impl PinnedCertVerifier {
|
|
pub fn new(trusted_fingerprints: Arc<RwLock<HashMap<String, String>>>) -> Self {
|
|
Self {
|
|
trusted_fingerprints,
|
|
provider: Arc::new(rustls::crypto::ring::default_provider()),
|
|
}
|
|
}
|
|
|
|
fn cert_fingerprint(cert: &CertificateDer<'_>) -> String {
|
|
let mut hasher = Sha256::new();
|
|
hasher.update(cert.as_ref());
|
|
hex::encode(hasher.finalize())
|
|
}
|
|
}
|
|
|
|
impl ServerCertVerifier for PinnedCertVerifier {
|
|
fn verify_server_cert(
|
|
&self,
|
|
end_entity: &CertificateDer<'_>,
|
|
_intermediates: &[CertificateDer<'_>],
|
|
server_name: &ServerName<'_>,
|
|
_ocsp_response: &[u8],
|
|
_now: UnixTime,
|
|
) -> Result<ServerCertVerified, TlsError> {
|
|
let actual_fp = Self::cert_fingerprint(end_entity);
|
|
let host = match server_name {
|
|
ServerName::DnsName(name) => name.as_ref().to_string(),
|
|
ServerName::IpAddress(ip) => std::net::IpAddr::from(*ip).to_string(),
|
|
_ => return Err(TlsError::General("Неподдерживаемое имя узла".into())),
|
|
};
|
|
|
|
let trusted = self.trusted_fingerprints.read().unwrap();
|
|
match trusted.get(&host) {
|
|
Some(expected_fp) if expected_fp.eq_ignore_ascii_case(&actual_fp) => {
|
|
Ok(ServerCertVerified::assertion())
|
|
}
|
|
Some(_) => Err(TlsError::General(format!(
|
|
"ВНИМАНИЕ: Сертификат узла {} не совпадает с заверенным!",
|
|
host
|
|
))),
|
|
None => Err(TlsError::General(format!(
|
|
"Узел {} не заверен! Нажмите «Заверить» в списке устройств.",
|
|
host
|
|
))),
|
|
}
|
|
}
|
|
|
|
fn verify_tls12_signature(
|
|
&self,
|
|
message: &[u8],
|
|
cert: &CertificateDer<'_>,
|
|
dss: &DigitallySignedStruct,
|
|
) -> Result<HandshakeSignatureValid, TlsError> {
|
|
verify_tls12_signature(
|
|
message,
|
|
cert,
|
|
dss,
|
|
&self.provider.signature_verification_algorithms,
|
|
)
|
|
}
|
|
|
|
fn verify_tls13_signature(
|
|
&self,
|
|
message: &[u8],
|
|
cert: &CertificateDer<'_>,
|
|
dss: &DigitallySignedStruct,
|
|
) -> Result<HandshakeSignatureValid, TlsError> {
|
|
verify_tls13_signature(
|
|
message,
|
|
cert,
|
|
dss,
|
|
&self.provider.signature_verification_algorithms,
|
|
)
|
|
}
|
|
|
|
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
|
|
self.provider
|
|
.signature_verification_algorithms
|
|
.supported_schemes()
|
|
}
|
|
}
|
|
|
|
/// Генерирует самоподписанный сертификат и приватный ключ в ОЗУ
|
|
pub fn generate_in_memory_cert(ip: &str) -> (Vec<u8>, Vec<u8>, String) {
|
|
let san = vec!["localhost".to_string(), ip.to_string()];
|
|
let CertifiedKey { cert, key_pair } =
|
|
generate_simple_self_signed(san).expect("Ошибка генерации сертификата");
|
|
|
|
let cert_der = cert.der().to_vec();
|
|
let key_der = key_pair.serialize_der();
|
|
|
|
let mut hasher = Sha256::new();
|
|
hasher.update(&cert_der);
|
|
let fingerprint = hex::encode(hasher.finalize());
|
|
|
|
(cert_der, key_der, fingerprint)
|
|
}
|
|
|
|
/// Создает преднастроенный HTTPS-клиент с кастомным верификатором
|
|
pub fn build_pinned_http_client(
|
|
trusted_fingerprints: Arc<RwLock<HashMap<String, String>>>,
|
|
) -> reqwest::Client {
|
|
let verifier = Arc::new(PinnedCertVerifier::new(trusted_fingerprints));
|
|
let rustls_client_config = ClientConfig::builder_with_provider(Arc::new(
|
|
rustls::crypto::ring::default_provider(),
|
|
))
|
|
.with_safe_default_protocol_versions()
|
|
.unwrap()
|
|
.dangerous()
|
|
.with_custom_certificate_verifier(verifier)
|
|
.with_no_client_auth();
|
|
|
|
reqwest::Client::builder()
|
|
.use_preconfigured_tls(rustls_client_config)
|
|
.timeout(Duration::from_secs(30))
|
|
.build()
|
|
.expect("Ошибка создания HTTPS-клиента")
|
|
}
|