sett 0.4.0

Rust port of sett (data compression, encryption and transfer tool).
Documentation
//! Interface to an OpenPGP keyserver.

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)]
/// Standard response from an OpenPGP keyserver.
pub struct KeyserverResponse {
    /// Status of the email verification process for this key.
    pub status: std::collections::BTreeMap<String, KeyserverEmailStatus>,
    /// Opaque token, to be used to initiate the verification process after
    /// uploading the key.
    pub token: String,
}

#[derive(serde::Deserialize, Clone, Debug)]
#[serde(rename_all = "lowercase")]
/// Email verification process statuses.
pub enum KeyserverEmailStatus {
    /// No verification process has been triggered for this email.
    Unpublished,
    /// The key associated to this email has been already verified.
    Published,
    /// The key associated to this email has been revoked and cannot be used.
    Revoked,
    /// The verification process has been triggered but is still pending.
    Pending,
}

/// A client for interacting with the Keyserver API.
#[derive(Debug)]
pub struct Keyserver {
    client: reqwest::Client,
}

impl Keyserver {
    /// Return a new Keyserver client.
    pub fn new() -> Result<Self, reqwest::Error> {
        Ok(Self {
            client: reqwest::Client::builder().use_rustls_tls().build()?,
        })
    }

    /// Request key verification (email ownership verification) from keyserver.
    ///
    /// Send a request to keyserver to initiate the key verification process
    /// for the certificate associated with the given `token` and `email`.
    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
    }

    /// Upload a certificate to this keyserver.
    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
    }

    /// Get a certificate from this keyserver.
    ///
    /// Only exact matches are supported.
    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?,
        )?)
    }
}

/// Error occurring when trying to download from a keyserver
#[derive(Debug)]
pub enum GetCertError {
    /// Certificate not found
    NotFound,
    /// Problem with the connection to the server
    Request(reqwest::Error),
    /// Problem with pgp
    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)
    }
}