crtin 0.1.0

Certificate introspection tool
use std::io::{Read, Write};
use std::net::TcpStream;
use std::sync::Arc;
use rustls::pki_types::ServerName;
use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier};
use rustls::crypto::{verify_tls12_signature, verify_tls13_signature, WebPkiSupportedAlgorithms};
use rustls::{ClientConfig, DigitallySignedStruct, SignatureScheme};

use crate::cert_parser::{self, CertInfo, CertFormat};

#[derive(Debug)]
struct NoVerification(WebPkiSupportedAlgorithms);

impl NoVerification {
    fn new() -> Self {
        let algos = rustls::crypto::CryptoProvider::get_default()
            .unwrap()
            .signature_verification_algorithms
            .clone();
        NoVerification(algos)
    }
}

impl ServerCertVerifier for NoVerification {
    fn verify_server_cert(
        &self,
        _end_entity: &rustls::pki_types::CertificateDer<'_>,
        _intermediates: &[rustls::pki_types::CertificateDer<'_>],
        _server_name: &ServerName<'_>,
        _ocsp_response: &[u8],
        _now: rustls::pki_types::UnixTime,
    ) -> Result<ServerCertVerified, rustls::Error> {
        Ok(ServerCertVerified::assertion())
    }

    fn verify_tls12_signature(
        &self,
        message: &[u8],
        cert: &rustls::pki_types::CertificateDer<'_>,
        dss: &DigitallySignedStruct,
    ) -> Result<HandshakeSignatureValid, rustls::Error> {
        verify_tls12_signature(message, cert, dss, &self.0)
    }

    fn verify_tls13_signature(
        &self,
        message: &[u8],
        cert: &rustls::pki_types::CertificateDer<'_>,
        dss: &DigitallySignedStruct,
    ) -> Result<HandshakeSignatureValid, rustls::Error> {
        verify_tls13_signature(message, cert, dss, &self.0)
    }

    fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
        self.0.supported_schemes()
    }
}

pub fn extract_cert_info(domain: &str, port: u16) -> Result<CertInfo, Box<dyn std::error::Error>> {
    let config = ClientConfig::builder()
        .dangerous()
        .with_custom_certificate_verifier(Arc::new(NoVerification::new()))
        .with_no_client_auth();
    let server_name = ServerName::try_from(domain.to_string())
        .map_err(|_| "Invalid domain name")?;
    let mut conn = rustls::ClientConnection::new(Arc::new(config), server_name)?;
    let resolved = dns_lookup::lookup_host(domain)?;
    let addr = resolved
        .iter()
        .next()
        .ok_or("DNS resolution returned no addresses")?;
    let sock_addr = std::net::SocketAddr::new(*addr, port);
    let mut sock = TcpStream::connect_timeout(
        &sock_addr,
        std::time::Duration::from_secs(10),
    )?;
    sock.set_read_timeout(Some(std::time::Duration::from_secs(10)))?;
    sock.set_write_timeout(Some(std::time::Duration::from_secs(10)))?;
    let mut stream = rustls::Stream::new(&mut conn, &mut sock);
    stream.write_all(b"GET / HTTP/1.1\r\nHost: ")?;
    stream.write_all(domain.as_bytes())?;
    stream.write_all(b"\r\nConnection: close\r\n\r\n")?;
    stream.flush()?;
    let mut buf = [0u8; 1];
    let _ = stream.read(&mut buf);
    let certs = conn.peer_certificates().ok_or("No certificates presented")?;
    let leaf = certs.first().ok_or("Empty certificate chain")?;
    let der = leaf.to_vec();
    let pem_str = cert_parser::der_to_pem(&der);
    let mut info = cert_parser::parse_x509(&der, &pem_str)?;
    info.source_type = format!("TLS ({})", domain);
    info.format = CertFormat::Pem;
    info.der = der;
    Ok(info)
}