use crate::account::Account;
use crate::error::*;
use crate::helpers::*;
use openssl::hash::MessageDigest;
use openssl::pkey::PKey;
use openssl::pkey::Private;
use openssl::stack::Stack;
use openssl::x509::extension::SubjectAlternativeName;
use openssl::x509::X509Name;
use openssl::x509::X509Req;
use openssl::x509::X509;
use serde::Deserialize;
use serde_json::json;
use std::sync::Arc;
use std::time::Duration;
use tracing::debug;
use tracing::field;
use tracing::instrument;
use tracing::Level;
use tracing::Span;
#[derive(Deserialize, Debug, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub enum OrderStatus {
Pending,
Ready,
Processing,
Valid,
Invalid,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Order {
#[serde(skip)]
pub(crate) account: Option<Arc<Account>>,
#[serde(skip)]
pub(crate) url: String,
pub status: OrderStatus,
pub expires: Option<String>,
pub identifiers: Vec<Identifier>,
pub not_before: Option<String>,
pub not_after: Option<String>,
pub error: Option<ServerError>,
#[serde(rename = "authorizations")]
pub(crate) authorization_urls: Vec<String>,
#[serde(rename = "finalize")]
pub(crate) finalize_url: String,
#[serde(rename = "certificate")]
pub(crate) certificate_url: Option<String>,
}
#[derive(Debug)]
pub struct OrderBuilder {
account: Arc<Account>,
identifiers: Vec<Identifier>,
}
impl OrderBuilder {
pub fn new(account: Arc<Account>) -> Self {
OrderBuilder {
account,
identifiers: vec![],
}
}
pub fn set_identifiers(&mut self, identifiers: Vec<Identifier>) -> &mut Self {
self.identifiers = identifiers;
self
}
pub fn add_dns_identifier(&mut self, fqdn: String) -> &mut Self {
self.identifiers.push(Identifier {
r#type: "dns".to_string(),
value: fqdn,
});
self
}
#[instrument(level = Level::INFO, name = "acme2::OrderBuilder::build", err, skip(self), fields(identifiers = ?self.identifiers, order_url = field::Empty))]
pub async fn build(&mut self) -> Result<Order, Error> {
let dir = self.account.directory.clone().unwrap();
let (res, headers) = dir
.authenticated_request::<_, Order>(
&dir.new_order_url,
json!({
"identifiers": self.identifiers,
}),
self.account.private_key.clone().unwrap(),
Some(self.account.private_key_id.clone()),
)
.await?;
let res: Result<Order, Error> = res.into();
let mut order = res?;
let order_url = map_transport_err(
headers
.get(reqwest::header::LOCATION)
.ok_or_else(|| {
transport_err(
"mandatory location header in newOrder response not present",
)
})?
.to_str(),
)?
.to_string();
Span::current().record("order_url", &field::display(&order_url));
order.account = Some(self.account.clone());
order.url = order_url;
Ok(order)
}
}
pub enum Csr {
Automatic(PKey<Private>),
Custom(X509Req),
}
fn gen_csr(
pkey: &PKey<openssl::pkey::Private>,
domains: Vec<String>,
) -> Result<X509Req, Error> {
if domains.is_empty() {
return Err(Error::Validation(
"at least one domain name needs to be supplied",
));
}
let mut builder = X509Req::builder()?;
let name = {
let mut name = X509Name::builder()?;
name.append_entry_by_text("CN", &domains[0])?;
name.build()
};
builder.set_subject_name(&name)?;
let san_extension = {
let mut san = SubjectAlternativeName::new();
for domain in domains.iter() {
san.dns(domain);
}
san.build(&builder.x509v3_context(None))?
};
let mut stack = Stack::new()?;
stack.push(san_extension)?;
builder.add_extensions(&stack)?;
builder.set_pubkey(&pkey)?;
builder.sign(pkey, MessageDigest::sha256())?;
Ok(builder.build())
}
impl Order {
#[instrument(level = Level::INFO, name = "acme2::Order::finalize", err, skip(self, csr), fields(order_url = %self.url, status = field::Empty))]
pub async fn finalize(&self, csr: Csr) -> Result<Order, Error> {
let csr = match csr {
Csr::Automatic(pkey) => gen_csr(
&pkey,
self
.identifiers
.iter()
.map(|f| f.value.clone())
.collect::<Vec<_>>(),
)?,
Csr::Custom(csr) => csr,
};
let csr_b64 = b64(&csr.to_der()?);
let account = self.account.clone().unwrap();
let directory = account.directory.clone().unwrap();
let (res, _) = directory
.authenticated_request::<_, Order>(
&self.finalize_url,
json!({ "csr": csr_b64 }),
account.private_key.clone().unwrap(),
Some(account.private_key_id.clone()),
)
.await?;
let res: Result<Order, Error> = res.into();
let mut order = res?;
Span::current().record("status", &field::debug(&order.status));
order.account = Some(account.clone());
order.url = self.url.clone();
Ok(order)
}
#[instrument(level = Level::INFO, name = "acme2::Order::certificate", err, skip(self), fields(order_url = %self.url, has_certificate = field::Empty))]
pub async fn certificate(&self) -> Result<Option<Vec<X509>>, Error> {
Span::current().record("has_certificate", &self.certificate_url.is_some());
let certificate_url = match self.certificate_url.clone() {
Some(certificate_url) => certificate_url,
None => return Ok(None),
};
let account = self.account.clone().unwrap();
let directory = account.directory.clone().unwrap();
let bytes = directory
.authenticated_request_bytes(
&certificate_url,
"",
&account.private_key.clone().unwrap(),
&Some(account.private_key_id.clone()),
)
.await?
.0?;
Ok(Some(X509::stack_from_pem(&bytes)?))
}
#[instrument(level = Level::DEBUG, name = "acme2::Order::poll", err, skip(self), fields(order_url = %self.url, status = field::Empty))]
pub async fn poll(&self) -> Result<Order, Error> {
let account = self.account.clone().unwrap();
let directory = account.directory.clone().unwrap();
let (res, _) = directory
.authenticated_request::<_, Order>(
&self.url,
json!(""),
account.private_key.clone().unwrap(),
Some(account.private_key_id.clone()),
)
.await?;
let res: Result<Order, Error> = res.into();
let mut order = res?;
Span::current().record("status", &field::debug(&order.status));
order.account = Some(account.clone());
order.url = self.url.clone();
Ok(order)
}
#[instrument(level = Level::INFO, name = "acme2::Order::wait_ready", err, skip(self), fields(order_url = %self.url))]
pub async fn wait_ready(
self,
poll_interval: Duration,
attempts: usize,
) -> Result<Order, Error> {
let mut order = self;
let mut i: usize = 0;
while order.status == OrderStatus::Pending {
if i >= attempts {
return Err(Error::MaxAttemptsExceeded);
}
debug!(
{ delay = ?poll_interval },
"Order still pending. Waiting to poll."
);
tokio::time::sleep(poll_interval).await;
order = order.poll().await?;
i += 1;
}
Ok(order)
}
#[instrument(level = Level::INFO, name = "acme2::Order::wait_ready", err, skip(self), fields(order_url = %self.url))]
pub async fn wait_done(
self,
poll_interval: Duration,
attempts: usize,
) -> Result<Order, Error> {
let mut order = self;
let mut i: usize = 0;
while order.status == OrderStatus::Pending
|| order.status == OrderStatus::Ready
|| order.status == OrderStatus::Processing
{
if i >= attempts {
return Err(Error::MaxAttemptsExceeded);
}
debug!(
{ delay = ?poll_interval, status = ?order.status },
"Order not done. Waiting to poll."
);
tokio::time::sleep(poll_interval).await;
order = order.poll().await?;
i += 1;
}
Ok(order)
}
}