Skip to main content

datumesh_core/
fetch.rs

1//! Key retrieval over HTTPS (9.3), only with the `fetch` feature. [`crate::verify`] never
2//! calls this: retrieval is a separate, explicit operation (9.4).
3
4use std::time::Duration;
5
6use crate::error::{err, ErrorCode, Result};
7use crate::keys::{keys_from_response, KeyDoc};
8use crate::lexical;
9
10/// The largest key document read: 64 keys fit in far less.
11const MAX_BODY: u64 = 256 * 1024;
12
13/// Fetches `https://<host>/.well-known/datumesh.json` and checks it (9.2, 9.3).
14///
15/// Certificates are validated (Web PKI roots), no redirect is followed, and the request
16/// times out after 10 seconds. A 200 with a valid document gives the keys; a 200 with an
17/// invalid document is `E_KEY_DOC`; every other status (redirects included) and every
18/// network, DNS, TLS or timeout failure is `E_KEY_UNAVAILABLE`. A host that is not a DNS
19/// name is never fetched: it has no keys (`E_KEY_UNAVAILABLE`, 9.3).
20pub fn fetch_keys(host: &str) -> Result<KeyDoc> {
21    if lexical::check_dns_name(host, "host").is_err() {
22        return err(ErrorCode::KeyUnavailable, "$", format!("{host:?} is not a DNS name: no keys are resolved for it"));
23    }
24    let agent: ureq::Agent = ureq::Agent::config_builder()
25        .max_redirects(0)
26        .http_status_as_error(false)
27        .https_only(true)
28        .timeout_global(Some(Duration::from_secs(10)))
29        .build()
30        .into();
31    let url = format!("https://{host}/.well-known/datumesh.json");
32    let mut resp = match agent.get(&url).call() {
33        Ok(r) => r,
34        Err(e) => return err(ErrorCode::KeyUnavailable, "$", format!("{url}: {e}")),
35    };
36    let status = resp.status().as_u16();
37    if status != 200 {
38        return keys_from_response(host, status, None);
39    }
40    match resp.body_mut().with_config().limit(MAX_BODY).read_to_vec() {
41        Ok(body) => keys_from_response(host, status, Some(&body)),
42        Err(e) => err(ErrorCode::KeyUnavailable, "$", format!("{url}: {e}")),
43    }
44}