libcerts/
fetch.rs

1use std::io::Write;
2use std::net::TcpStream;
3
4use native_tls::TlsConnector;
5use url::Url;
6
7use crate::certificate::Certificate;
8use crate::error::{Error, Result};
9
10const SSL_PORT: u16 = 443;
11const HTTP_GET_REQUEST: &[u8] = b"GET / HTTP/1.0\r\n\r\n";
12
13pub struct Fetch {
14    domain: String,
15}
16
17impl Fetch {
18    pub fn new(url: &Url) -> Result<Self> {
19        if url.scheme() != "https" {
20            return Err(Error::HttpsOnly(url.to_string()));
21        }
22
23        let Some(domain) = url.domain() else {
24            return Err(Error::InvalidUrl(url.to_string()));
25        };
26
27        Ok(Self {
28            domain: domain.to_string(),
29        })
30    }
31
32    pub fn certificate_pem(&self) -> Result<Certificate> {
33        let connector = TlsConnector::builder()
34            .build()
35            .map_err(Error::ConnectorConfiguration)?;
36        let stream =
37            TcpStream::connect(self.addr()).map_err(|_e| Error::FetchFailed(self.addr()))?;
38        let mut stream = connector
39            .connect(&self.domain, stream)
40            .map_err(|_e| Error::FetchFailed(self.addr()))?;
41
42        stream
43            .write_all(HTTP_GET_REQUEST)
44            .map_err(|_e| Error::StreamWrite(self.addr()))?;
45
46        let Some(cert_bytes) = stream
47            .peer_certificate()
48            .map_err(|err| {
49                eprint!("{err:?}");
50                Error::RetrievePeerCertificate
51            })? else {
52                return Err(Error::CertificateNotFound(self.addr()));
53            };
54        let der_encoded = cert_bytes.to_der().map_err(Error::DerEncodedRetrieval)?;
55        let certificate = Certificate::from_der(&der_encoded);
56
57        Ok(certificate)
58    }
59
60    pub fn addr(&self) -> String {
61        format!("{}:{}", self.domain, SSL_PORT)
62    }
63}