use crate::{
acc::AccountInner,
api::{ApiAuth, ApiEmptyString, ApiFinalize, ApiOrder},
cert::{create_csr, Certificate},
error::*,
util::{base64url, read_json},
};
use openssl::pkey::{self, PKey};
use std::{sync::Arc, thread, time::Duration};
mod auth;
pub use self::auth::{Auth, Challenge};
pub(crate) struct Order {
inner: Arc<AccountInner>,
api_order: ApiOrder,
url: String,
}
impl Order {
pub(crate) fn new(inner: &Arc<AccountInner>, api_order: ApiOrder, url: String) -> Self {
Order {
inner: inner.clone(),
api_order,
url,
}
}
}
pub(crate) fn refresh_order(
inner: &Arc<AccountInner>,
url: String,
want_status: &'static str,
) -> Result<Order> {
let res = inner.transport.call(&url, &ApiEmptyString)?;
let api_order = api_order_of(res, want_status)?;
Ok(Order {
inner: inner.clone(),
api_order,
url,
})
}
#[cfg(not(test))]
fn api_order_of(res: ureq::Response, _want_status: &str) -> Result<ApiOrder> {
read_json(res)
}
#[cfg(test)]
fn api_order_of(res: ureq::Response, want_status: &str) -> Result<ApiOrder> {
let s = res.into_string()?;
#[allow(clippy::trivial_regex)]
let re = regex::Regex::new("<STATUS>").unwrap();
let b = re.replace_all(&s, want_status).to_string();
let api_order: ApiOrder = serde_json::from_str(&b)?;
Ok(api_order)
}
pub struct NewOrder {
pub(crate) order: Order,
}
impl NewOrder {
pub fn is_validated(&self) -> bool {
self.order.api_order.is_status_ready() || self.order.api_order.is_status_valid()
}
pub fn confirm_validations(&self) -> Option<CsrOrder> {
if self.is_validated() {
Some(CsrOrder {
order: Order::new(
&self.order.inner,
self.order.api_order.clone(),
self.order.url.clone(),
),
})
} else {
None
}
}
pub fn refresh(&mut self) -> Result<()> {
let order = refresh_order(&self.order.inner, self.order.url.clone(), "ready")?;
self.order = order;
Ok(())
}
pub fn authorizations(&self) -> Result<Vec<Auth>> {
let mut result = vec![];
if let Some(authorizations) = &self.order.api_order.authorizations {
for auth_url in authorizations {
let res = self.order.inner.transport.call(auth_url, &ApiEmptyString)?;
let api_auth: ApiAuth = read_json(res)?;
result.push(Auth::new(&self.order.inner, api_auth, auth_url));
}
}
Ok(result)
}
pub fn api_order(&self) -> &ApiOrder {
&self.order.api_order
}
}
pub struct CsrOrder {
pub(crate) order: Order,
}
impl CsrOrder {
pub fn finalize(self, private_key_pem: &str, delay: Duration) -> Result<CertOrder> {
let pkey_pri = PKey::private_key_from_pem(private_key_pem.as_bytes())
.context("Error reading private key PEM")?;
self.finalize_pkey(pkey_pri, delay)
}
pub fn finalize_pkey(
self,
private_key: PKey<pkey::Private>,
delay: Duration,
) -> Result<CertOrder> {
let domains = self.order.api_order.domains();
let csr = create_csr(&private_key, &domains)?;
let csr_der = csr.to_der()?;
let csr_enc = base64url(&csr_der);
let finalize = ApiFinalize { csr: csr_enc };
let inner = self.order.inner;
let order_url = self.order.url;
let finalize_url = &self.order.api_order.finalize;
inner.transport.call(finalize_url, &finalize)?;
let order = wait_for_order_status(&inner, &order_url, delay)?;
if !order.api_order.is_status_valid() {
bail!("Order is in status: {:?}", order.api_order.status);
}
Ok(CertOrder { private_key, order })
}
pub fn api_order(&self) -> &ApiOrder {
&self.order.api_order
}
}
fn wait_for_order_status(inner: &Arc<AccountInner>, url: &str, delay: Duration) -> Result<Order> {
loop {
let order = refresh_order(inner, url.to_string(), "valid")?;
if !order.api_order.is_status_processing() {
return Ok(order);
}
thread::sleep(delay);
}
}
pub struct CertOrder {
private_key: PKey<pkey::Private>,
order: Order,
}
impl CertOrder {
pub fn download_cert(self) -> Result<Certificate> {
let url = self
.order
.api_order
.certificate
.ok_or_else(|| anyhow::anyhow!("certificate url"))?;
let inner = self.order.inner;
let res = inner.transport.call(&url, &ApiEmptyString)?;
let pkey_pem_bytes = self.private_key.private_key_to_pem_pkcs8()?;
let pkey_pem = String::from_utf8_lossy(&pkey_pem_bytes);
let cert = res.into_string()?;
Ok(Certificate::new(pkey_pem.to_string(), cert))
}
pub fn api_order(&self) -> &ApiOrder {
&self.order.api_order
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::*;
#[test]
fn test_get_authorizations() -> Result<()> {
let server = crate::test::with_directory_server();
let url = DirectoryUrl::Other(&server.dir_url);
let dir = Directory::from_url(url)?;
let acc = dir.register_account(vec!["mailto:foo@bar.com".to_string()])?;
let ord = acc.new_order("acmetest.example.com", &[])?;
let _ = ord.authorizations()?;
Ok(())
}
#[test]
fn test_finalize() -> Result<()> {
let server = crate::test::with_directory_server();
let url = DirectoryUrl::Other(&server.dir_url);
let dir = Directory::from_url(url)?;
let acc = dir.register_account(vec!["mailto:foo@bar.com".to_string()])?;
let ord = acc.new_order("acmetest.example.com", &[])?;
let ord = CsrOrder { order: ord.order };
let pkey = cert::create_p256_key()?;
let _ord = ord.finalize_pkey(pkey, Duration::from_millis(1))?;
Ok(())
}
#[test]
fn test_download_and_save_cert() -> Result<()> {
let server = crate::test::with_directory_server();
let url = DirectoryUrl::Other(&server.dir_url);
let dir = Directory::from_url(url)?;
let acc = dir.register_account(vec!["mailto:foo@bar.com".to_string()])?;
let ord = acc.new_order("acmetest.example.com", &[])?;
let ord = CsrOrder { order: ord.order };
let pkey = cert::create_p256_key()?;
let ord = ord.finalize_pkey(pkey, Duration::from_millis(1))?;
let cert = ord.download_cert()?;
assert_eq!("CERT HERE", cert.certificate());
assert!(!cert.private_key().is_empty());
assert_eq!(cert.valid_days_left()?, 89);
Ok(())
}
}