use super::{cert::Cert, certstore::QueryTerm};
#[derive(serde::Serialize, Debug)]
struct UploadRequest<S> {
keytext: S,
}
#[derive(serde::Serialize, Debug)]
struct VerifyRequest<'a> {
token: &'a str,
addresses: Vec<&'a str>,
}
#[derive(serde::Deserialize, Debug)]
pub struct KeyserverResponse {
pub status: std::collections::BTreeMap<String, KeyserverEmailStatus>,
pub token: String,
}
#[derive(serde::Deserialize, Clone, Debug)]
#[serde(rename_all = "lowercase")]
pub enum KeyserverEmailStatus {
Unpublished,
Published,
Revoked,
Pending,
}
#[derive(Debug)]
pub struct Keyserver {
client: reqwest::Client,
}
impl Keyserver {
pub fn new() -> Result<Self, reqwest::Error> {
Ok(Self {
client: reqwest::Client::builder().use_rustls_tls().build()?,
})
}
pub async fn verify_cert(
&self,
token: &str,
email: &str,
) -> Result<KeyserverResponse, reqwest::Error> {
const VERIFY_URL: &str = "https://keys.openpgp.org/vks/v1/request-verify";
let response = self
.client
.post(VERIFY_URL)
.json(&VerifyRequest {
token,
addresses: vec![email],
})
.send()
.await?;
response.json().await
}
pub async fn upload_cert(&self, cert: &Cert) -> Result<KeyserverResponse, reqwest::Error> {
const UPLOAD_URL: &str = "https://keys.openpgp.org/vks/v1/upload";
let response = self
.client
.post(UPLOAD_URL)
.json(&UploadRequest {
keytext: cert.public(),
})
.send()
.await?;
response.json().await
}
pub async fn get_cert(&self, query: &QueryTerm<'_>) -> Result<Cert, GetCertError> {
let url = match query {
QueryTerm::Fingerprint(fingerprint) => {
format!("https://keys.openpgp.org/vks/v1/by-fingerprint/{fingerprint}")
}
QueryTerm::Email(email) => {
format!("https://keys.openpgp.org/vks/v1/by-email/{email}")
}
};
let response = self.client.get(url).send().await?;
if let reqwest::StatusCode::NOT_FOUND = response.status() {
return Err(GetCertError::NotFound);
}
Ok(Cert::from_bytes(
response.error_for_status()?.bytes().await?,
)?)
}
}
#[derive(Debug)]
pub enum GetCertError {
NotFound,
Request(reqwest::Error),
Pgp(super::error::PgpError),
}
impl std::fmt::Display for GetCertError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Error while trying to get a certificate from the keyserver"
)
}
}
impl core::error::Error for GetCertError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::NotFound => None,
Self::Request(source) => Some(source),
Self::Pgp(source) => Some(source),
}
}
}
impl From<reqwest::Error> for GetCertError {
fn from(value: reqwest::Error) -> Self {
Self::Request(value)
}
}
impl From<super::error::PgpError> for GetCertError {
fn from(value: super::error::PgpError) -> Self {
Self::Pgp(value)
}
}