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//!
4//! Deprecated since 0.3.1 and removed at 1.0: an HTTP client is the application's choice
5//! (its TLS roots, proxies, timeouts, async runtime), so the crate keeps only the part that is
6//! protocol, [`keys_from_response`], which turns any client's response into keys.
7
8use std::time::Duration;
9
10use crate::error::{err, ErrorCode, Result};
11use crate::keys::{keys_from_response, KeyDoc};
12use crate::lexical;
13
14/// The largest key document read: 64 keys fit in far less.
15const MAX_BODY: u64 = 256 * 1024;
16
17/// Fetches `https://<host>/.well-known/datumesh.json` and checks it (9.2, 9.3).
18///
19/// Certificates are validated (Web PKI roots), no redirect is followed, and the request
20/// times out after 10 seconds. A 200 with a valid document gives the keys; a 200 with an
21/// invalid document is `E_KEY_DOC`; every other status (redirects included) and every
22/// network, DNS, TLS or timeout failure is `E_KEY_UNAVAILABLE`. A host that is not a DNS
23/// name is never fetched: it has no keys (`E_KEY_UNAVAILABLE`, 9.3).
24///
25/// Deprecated since 0.3.1; the `fetch` feature and this function are removed at 1.0. Fetch
26/// `https://<host>/.well-known/datumesh.json` with your own HTTP client (no redirects,
27/// validated certificates, a bounded body) and pass the status and body to
28/// [`keys_from_response`], or record a network failure with
29/// [`Keys::insert_unavailable`](crate::Keys::insert_unavailable).
30#[deprecated(
31 since = "0.3.1",
32 note = "removed at 1.0: fetch the key document with your own HTTP client and pass the response to `keys_from_response`"
33)]
34pub fn fetch_keys(host: &str) -> Result<KeyDoc> {
35 if lexical::check_dns_name(host, "host").is_err() {
36 return err(ErrorCode::KeyUnavailable, "$", format!("{host:?} is not a DNS name: no keys are resolved for it"));
37 }
38 let agent: ureq::Agent = ureq::Agent::config_builder()
39 .max_redirects(0)
40 .http_status_as_error(false)
41 .https_only(true)
42 .timeout_global(Some(Duration::from_secs(10)))
43 .build()
44 .into();
45 let url = format!("https://{host}/.well-known/datumesh.json");
46 let mut resp = match agent.get(&url).call() {
47 Ok(r) => r,
48 Err(e) => return err(ErrorCode::KeyUnavailable, "$", format!("{url}: {e}")),
49 };
50 let status = resp.status().as_u16();
51 if status != 200 {
52 return keys_from_response(host, status, None);
53 }
54 match resp.body_mut().with_config().limit(MAX_BODY).read_to_vec() {
55 Ok(body) => keys_from_response(host, status, Some(&body)),
56 Err(e) => err(ErrorCode::KeyUnavailable, "$", format!("{url}: {e}")),
57 }
58}