papaleguas 0.0.9

ACME client
Documentation
use std::sync::Arc;

use futures::future::try_join_all;
use serde_json::json;
use time::OffsetDateTime;

use crate::{
    account::{Account, AccountInner},
    api,
    authorization::Authorization,
    error::AcmeResult,
    key,
    utils::{add_field, base64},
    AcmeClientInner, AcmeRequest,
};

pub use api::OrderStatus;

#[derive(Debug, Clone)]
pub struct Order {
    pub(crate) acme: Arc<AcmeClientInner>,
    pub(crate) account: Arc<AccountInner>,
    pub(crate) url: String,
    pub(crate) order: api::Order,
}

impl Order {
    pub fn url(&self) -> &str {
        &self.url
    }

    pub fn status(&self) -> &OrderStatus {
        &self.order.status
    }

    pub fn expires(&self) -> Option<OffsetDateTime> {
        self.order.expires
    }

    pub fn not_before(&self) -> Option<OffsetDateTime> {
        self.order.not_before
    }

    pub fn not_after(&self) -> Option<OffsetDateTime> {
        self.order.not_after
    }

    pub fn error(&self) -> Option<&api::ServerError> {
        self.order.error.as_ref()
    }

    pub async fn authorizations(&self) -> AcmeResult<Vec<Authorization>> {
        let auths = self
            .order
            .authorizations
            .iter()
            .map(|auth| AcmeRequest {
                url: auth,
                kid: Some(&self.account.kid),
                private_key: &self.account.key,
                payload: None,
            })
            .map(|req| async {
                let res = self.acme.send_request(req).await?;
                Ok(Authorization {
                    acme: self.acme.clone(),
                    account: self.account.clone(),
                    authorization: serde_json::from_slice(&res.into_body())?,
                })
            });

        try_join_all(auths).await
    }

    pub async fn finalize(self, private_key: &key::PrivateKey) -> AcmeResult<Self> {
        let domains = self
            .order
            .identifiers
            .iter()
            .map(|i| i.value.clone())
            .collect::<Vec<_>>();

        let csr = private_key
            .csr(domains)
            .map_err(|_| "Failed to certificate signing request")?;

        let res = self
            .acme
            .send_request(AcmeRequest {
                url: &self.order.finalize,
                kid: Some(&self.account.kid),
                private_key: &self.account.key,
                payload: Some(json!({ "csr": base64(&csr) })),
            })
            .await?;

        let order = serde_json::from_slice(&res.into_body())?;

        Ok(Self {
            acme: self.acme,
            account: self.account,
            url: self.url,
            order,
        })
    }

    pub async fn certificate(&self) -> AcmeResult<String> {
        let res = self
            .acme
            .send_request(AcmeRequest {
                url: self
                    .order
                    .certificate
                    .as_ref()
                    .ok_or("Order is not in a valid state to fetch the certificate")?,
                kid: Some(&self.account.kid),
                private_key: &self.account.key,
                payload: None,
            })
            .await?;

        Ok(std::str::from_utf8(&res.into_body())?.to_owned())
    }
}

pub enum OrderIdentifier {
    Dns(String),
    Other(String, String),
}

impl<T: Into<String>> From<T> for OrderIdentifier {
    fn from(dns: T) -> Self {
        OrderIdentifier::Dns(dns.into())
    }
}

pub struct NewOrderRequest<'a> {
    pub(crate) account: &'a Account,
    pub(crate) identifiers: Vec<OrderIdentifier>,
}

impl<'a> NewOrderRequest<'a> {
    pub(crate) fn new(account: &'a Account) -> Self {
        NewOrderRequest {
            account,
            identifiers: Default::default(),
        }
    }

    add_field!(identifiers, Vec<OrderIdentifier>);

    pub fn dns(self, dns: impl Into<String>) -> Self {
        let mut identifiers = self.identifiers;
        identifiers.push(dns.into().into());
        Self {
            identifiers,
            ..self
        }
    }

    pub fn identifier(self, identifier: impl Into<OrderIdentifier>) -> Self {
        let mut identifiers = self.identifiers;
        identifiers.push(identifier.into());
        Self {
            identifiers,
            ..self
        }
    }

    pub async fn send(self) -> AcmeResult<Order> {
        let acme = self.account.inner.acme.clone();
        let account = self.account.inner.clone();

        let identifiers = self
            .identifiers
            .into_iter()
            .map(|identifier| match identifier {
                OrderIdentifier::Dns(dns) => json!({ "type": "dns", "value": dns }),
                OrderIdentifier::Other(r#type, value) => {
                    json!({ "type": r#type, "value": value })
                }
            })
            .collect::<Vec<_>>();

        let res = acme
            .send_request(AcmeRequest {
                url: &acme.directory.new_order,
                kid: Some(self.account.kid()),
                private_key: &self.account.inner.key,
                payload: Some(json!({ "identifiers": identifiers })),
            })
            .await?;

        let url = res
            .headers()
            .get(http::header::LOCATION)
            .and_then(|kid| kid.to_str().map(|kid| kid.to_owned()).ok())
            .ok_or("Failed to get order url")?;

        Ok(Order {
            acme,
            account,
            url,
            order: serde_json::from_slice(&res.into_body())?,
        })
    }
}