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}