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, 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) async fn refresh_order(
inner: &Arc<AccountInner>,
url: String,
want_status: &'static str,
) -> Result<Order, error::Error> {
let res = inner.transport.call(&url, &ApiEmptyString).await?;
let api_order = api_order_of(res, want_status).await?;
Ok(Order {
inner: inner.clone(),
api_order,
url,
})
}
#[cfg(not(test))]
async fn api_order_of(
res: crate::req::ReqResult,
_want_status: &str,
) -> Result<ApiOrder, error::Error> {
read_json(res).await
}
#[cfg(test)]
async fn api_order_of(
res: crate::req::ReqResult,
want_status: &str,
) -> Result<ApiOrder, error::Error> {
#[allow(clippy::trivial_regex)]
let re = regex::Regex::new("<STATUS>").unwrap();
let b = re.replace_all(&res.body, 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 async fn is_validated(&self) -> bool {
self.order.api_order.is_status_ready() || self.order.api_order.is_status_valid()
}
pub async fn confirm_validations(&self) -> Option<CsrOrder> {
if self.is_validated().await {
Some(CsrOrder {
order: Order::new(
&self.order.inner,
self.order.api_order.clone(),
self.order.url.clone(),
),
})
} else {
None
}
}
pub async fn refresh(&mut self) -> Result<(), error::Error> {
let order = refresh_order(&self.order.inner, self.order.url.clone(), "ready").await?;
self.order = order;
Ok(())
}
pub async fn authorizations(&self) -> Result<Vec<Auth>, error::Error> {
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)
.await?;
let api_auth: ApiAuth = read_json(res).await?;
result.push(Auth::new(&self.order.inner, api_auth, auth_url).await);
}
}
Ok(result)
}
pub async fn api_order(&self) -> &ApiOrder {
&self.order.api_order
}
}
pub struct CsrOrder {
pub(crate) order: Order,
}
impl CsrOrder {
pub async fn finalize(
self,
private_key_pem: &str,
delay: Duration,
) -> Result<CertOrder, error::Error> {
let pkey_pri = PKey::private_key_from_pem(private_key_pem.as_bytes())?;
self.finalize_pkey(pkey_pri, delay).await
}
pub async fn finalize_pkey(
self,
private_key: PKey<pkey::Private>,
delay: Duration,
) -> Result<CertOrder, error::Error> {
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).await?;
let order = wait_for_order_status(&inner, &order_url, delay).await?;
if !order.api_order.is_status_valid() {
return Err(error::Error::LetsEncryptError(format!(
"Order is in status: {:?}",
order.api_order.status
)));
}
Ok(CertOrder { private_key, order })
}
pub fn api_order(&self) -> &ApiOrder {
&self.order.api_order
}
}
async fn wait_for_order_status(
inner: &Arc<AccountInner>,
url: &str,
delay: Duration,
) -> Result<Order, error::Error> {
loop {
let order = refresh_order(inner, url.to_string(), "valid").await?;
if !order.api_order.is_status_processing() {
return Ok(order);
}
tokio::time::sleep(delay).await;
}
}
pub struct CertOrder {
private_key: PKey<pkey::Private>,
order: Order,
}
impl CertOrder {
pub async fn download_cert(self) -> Result<Certificate, error::Error> {
let url = self
.order
.api_order
.certificate
.ok_or_else(|| error::Error::LetsEncryptError("certificate url".to_string()))?;
let inner = self.order.inner;
let res = inner.transport.call(&url, &ApiEmptyString).await?;
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.body;
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::*;
#[tokio::test]
async fn test_get_authorizations() -> Result<(), error::Error> {
let server = crate::test::with_directory_server();
let url = DirectoryUrl::Other(&server.dir_url);
let dir = Directory::from_url(url).await?;
let acc = dir
.register_account(vec!["mailto:foo@bar.com".to_string()])
.await?;
let ord = acc.new_order("acmetest.example.com", &[]).await?;
let _ = ord.authorizations().await?;
Ok(())
}
#[tokio::test]
async fn test_finalize() -> Result<(), error::Error> {
let server = crate::test::with_directory_server();
let url = DirectoryUrl::Other(&server.dir_url);
let dir = Directory::from_url(url).await?;
let acc = dir
.register_account(vec!["mailto:foo@bar.com".to_string()])
.await?;
let ord = acc.new_order("acmetest.example.com", &[]).await?;
let ord = CsrOrder { order: ord.order };
let pkey = cert::create_p256_key()?;
let _ord = ord.finalize_pkey(pkey, Duration::from_millis(1)).await?;
Ok(())
}
#[tokio::test]
async fn test_download_and_save_cert() -> Result<(), error::Error> {
let server = crate::test::with_directory_server();
let url = DirectoryUrl::Other(&server.dir_url);
let dir = Directory::from_url(url).await?;
let acc = dir
.register_account(vec!["mailto:foo@bar.com".to_string()])
.await?;
let ord = acc.new_order("acmetest.example.com", &[]).await?;
let ord = CsrOrder { order: ord.order };
let pkey = cert::create_p256_key()?;
let ord = ord.finalize_pkey(pkey, Duration::from_millis(1)).await?;
let cert = ord.download_cert().await?;
assert_eq!("CERT HERE", cert.certificate());
assert!(!cert.private_key().is_empty());
let test_expiry = chrono::DateTime::<chrono::Utc>::from_utc(chrono::NaiveDateTime::parse_from_str("May 15 11:11:11 2015 GMT", "%h %e %H:%M:%S %Y GMT")?, chrono::Utc);
assert_eq!(
cert.expiry()?,
test_expiry
);
Ok(())
}
}