pgp/http/
mod.rs

1//! # HTTP key discovery
2//!
3//! Module dedicated to HTTP public key discovery. The main purpose of
4//! this module is to get public keys belonging to given emails by
5//! contacting key servers.
6
7pub mod hkp;
8pub mod wkd;
9
10use std::{
11    io::{Cursor, Read},
12    sync::Arc,
13};
14
15use futures::{stream::FuturesUnordered, StreamExt};
16use http::ureq::http::Uri;
17use tracing::{debug, warn};
18
19use crate::{
20    native::{Deserializable, SignedPublicKey},
21    utils::spawn,
22    Error, Result,
23};
24
25/// Calls the given key server in order to get the public key
26/// belonging to the given email address.
27async fn fetch(client: &http::Client, email: &str, key_server: &str) -> Result<SignedPublicKey> {
28    let uri: Uri = key_server
29        .replace("<email>", email)
30        .parse()
31        .map_err(http::Error::from)?;
32
33    let uri = match uri.scheme_str() {
34        Some("hkp") | Some("hkps") => hkp::format_key_server_uri(uri, email).unwrap(),
35        // TODO: manage file scheme
36        _ => uri,
37    };
38
39    let uri_clone = uri.clone();
40    let res = client
41        .send(move |agent| agent.get(uri_clone).call())
42        .await?;
43
44    let status = res.status();
45    let mut body = res.into_body();
46    let mut body = body.as_reader();
47
48    if !status.is_success() {
49        let mut err = String::new();
50        body.read_to_string(&mut err)
51            .map_err(|err| Error::ReadHttpError(err, uri.clone(), status))?;
52        return Err(Error::GetPublicKeyError(err, uri, status));
53    }
54
55    let mut bytes = Vec::new();
56    body.read_to_end(&mut bytes)
57        .map_err(|err| Error::ReadPublicKeyError(err, uri.clone()))?;
58    let cursor = Cursor::new(bytes);
59    let (pkey, _) = SignedPublicKey::from_armor_single(cursor)
60        .map_err(|err| Error::ParsePublicKeyError(err, uri))?;
61
62    Ok(pkey)
63}
64
65/// Calls the given key servers synchronously and stops when a public
66/// key belonging to the given email address is found.
67///
68/// A better algorithm would be to contact asynchronously all key
69/// servers and to abort pending futures when a public key is found.
70async fn get(
71    client: &http::Client,
72    email: &String,
73    key_servers: &[String],
74) -> Result<SignedPublicKey> {
75    for key_server in key_servers {
76        match fetch(client, email, key_server).await {
77            Ok(pkey) => {
78                debug!("found pgp public key for {email} at {key_server}");
79                return Ok(pkey);
80            }
81            Err(err) => {
82                let msg = format!("cannot get pgp public key for {email} at {key_server}");
83                warn!("{msg}: {err}");
84                debug!("{msg}: {err:?}");
85                continue;
86            }
87        }
88    }
89
90    Err(Error::FindPublicKeyError(email.to_owned()))
91}
92
93/// Gets public key associated to the given email.
94pub async fn get_one(email: String, key_servers: Vec<String>) -> Result<SignedPublicKey> {
95    let client = http::Client::new();
96    self::get(&client, &email, &key_servers).await
97}
98
99/// Gets public keys associated to the given emails.
100pub async fn get_all(
101    emails: Vec<String>,
102    key_servers: Vec<String>,
103) -> Vec<(String, Result<SignedPublicKey>)> {
104    let key_servers = Arc::new(key_servers);
105    let client = http::Client::new();
106
107    FuturesUnordered::from_iter(emails.into_iter().map(|email| {
108        let key_servers = key_servers.clone();
109        let client = client.clone();
110        spawn(async move {
111            (
112                email.clone(),
113                self::get(&client, &email, &key_servers).await,
114            )
115        })
116    }))
117    .filter_map(|res| async {
118        match res {
119            Ok(res) => {
120                return Some(res);
121            }
122            Err(err) => {
123                debug!(?err, "skipping failed task");
124                None
125            }
126        }
127    })
128    .collect()
129    .await
130}