use super::CertificateRefusal;
use std::net::IpAddr;
use std::num::NonZeroUsize;
use std::time::Duration;
#[derive(serde::Deserialize)]
#[serde(deny_unknown_fields)]
struct ServiceKey {
ed25519: String,
}
pub fn endpoint(base: &str) -> Result<reqwest::Url, CertificateRefusal> {
let mut url = reqwest::Url::parse(base).map_err(|_| CertificateRefusal::ServiceKey)?;
let host = url.host_str().ok_or(CertificateRefusal::ServiceKey)?;
let loopback = host == "localhost"
|| host
.trim_matches(['[', ']'])
.parse::<IpAddr>()
.is_ok_and(|address| address.is_loopback());
if !matches!(url.scheme(), "https" | "http")
|| (url.scheme() == "http" && !loopback)
|| !url.username().is_empty()
|| url.password().is_some()
|| url.query().is_some()
|| url.fragment().is_some()
{
return Err(CertificateRefusal::ServiceKey);
}
if !url.path().ends_with('/') {
url.set_path(&format!("{}/", url.path()));
}
url.join("service-key")
.map_err(|_| CertificateRefusal::ServiceKey)
}
pub fn fetch(
base: &str,
bound: NonZeroUsize,
deadline: Duration,
handle: &tokio::runtime::Handle,
) -> Result<[u8; 32], CertificateRefusal> {
if deadline.is_zero() {
return Err(CertificateRefusal::ServiceKey);
}
let endpoint = endpoint(base)?;
let client = reqwest::Client::builder()
.redirect(reqwest::redirect::Policy::none())
.timeout(deadline)
.build()
.map_err(|_| CertificateRefusal::ServiceKey)?;
std::thread::scope(|scope| {
scope
.spawn(|| {
handle.block_on(async {
let mut response = client
.get(endpoint)
.send()
.await
.map_err(|_| CertificateRefusal::ServiceKey)?;
if !response.status().is_success() {
return Err(CertificateRefusal::ServiceKey);
}
let mut bytes = Vec::new();
while let Some(chunk) = response
.chunk()
.await
.map_err(|_| CertificateRefusal::ServiceKey)?
{
if bytes
.len()
.checked_add(chunk.len())
.is_none_or(|length| length > bound.get())
{
return Err(CertificateRefusal::Bound);
}
bytes.extend_from_slice(&chunk);
}
decode(&bytes)
})
})
.join()
})
.map_err(|_| CertificateRefusal::ServiceKey)?
}
pub fn decode(bytes: &[u8]) -> Result<[u8; 32], CertificateRefusal> {
let response: ServiceKey =
serde_json::from_slice(bytes).map_err(|_| CertificateRefusal::ServiceKey)?;
let mut key = [0; 32];
hex::decode_to_slice(&response.ed25519, &mut key)
.map_err(|_| CertificateRefusal::ServiceKey)?;
let verifying = ed25519_dalek::VerifyingKey::from_bytes(&key)
.map_err(|_| CertificateRefusal::ServiceKey)?;
if verifying.is_weak() {
return Err(CertificateRefusal::ServiceKey);
}
Ok(key)
}