use openssl::pkey::{self, PKey};
use std::sync::Arc;
use std::thread;
use std::time::Duration;
use crate::acc::AccountInner;
use crate::api::{ApiAuth, ApiEmptyString, ApiFinalize, ApiOrder};
use crate::cert::{create_csr, Certificate};
use crate::persist::{Persist, PersistKey, PersistKind};
use crate::util::{base64url, read_json};
use crate::Result;
mod auth;
pub use self::auth::{Auth, Challenge};
pub(crate) struct Order<P: Persist> {
inner: Arc<AccountInner<P>>,
api_order: ApiOrder,
url: String,
}
impl<P: Persist> Order<P> {
pub(crate) fn new(inner: &Arc<AccountInner<P>>, api_order: ApiOrder, url: String) -> Self {
Order {
inner: inner.clone(),
api_order,
url,
}
}
}
pub(crate) fn refresh_order<P: Persist>(
inner: &Arc<AccountInner<P>>,
url: String,
want_status: &'static str,
) -> Result<Order<P>> {
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<P: Persist> {
pub(crate) order: Order<P>,
}
impl<P: Persist> NewOrder<P> {
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<P>> {
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<P>>> {
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<P: Persist> {
pub(crate) order: Order<P>,
}
impl<P: Persist> CsrOrder<P> {
pub fn finalize(self, private_key_pem: &str, delay_millis: u64) -> Result<CertOrder<P>> {
let pkey_pri = PKey::private_key_from_pem(private_key_pem.as_bytes())
.map_err(|e| format!("Error reading private key PEM: {}", e))?;
self.finalize_pkey(pkey_pri, delay_millis)
}
pub fn finalize_pkey(
self,
private_key: PKey<pkey::Private>,
delay_millis: u64,
) -> Result<CertOrder<P>> {
let domains = self.order.api_order.domains();
let csr = create_csr(&private_key, &domains)?;
let csr_der = csr.to_der().expect("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_millis)?;
if !order.api_order.is_status_valid() {
return Err(format!("Order is in status: {:?}", order.api_order.status).into());
}
Ok(CertOrder { private_key, order })
}
pub fn api_order(&self) -> &ApiOrder {
&self.order.api_order
}
}
fn wait_for_order_status<P: Persist>(
inner: &Arc<AccountInner<P>>,
url: &str,
delay_millis: u64,
) -> Result<Order<P>> {
loop {
let order = refresh_order(inner, url.to_string(), "valid")?;
if !order.api_order.is_status_processing() {
return Ok(order);
}
thread::sleep(Duration::from_millis(delay_millis));
}
}
pub struct CertOrder<P: Persist> {
private_key: PKey<pkey::Private>,
order: Order<P>,
}
impl<P: Persist> CertOrder<P> {
pub fn download_and_save_cert(self) -> Result<Certificate> {
let primary_name = self.order.api_order.domains()[0].to_string();
let url = self.order.api_order.certificate.expect("certificate url");
let inner = self.order.inner;
let realm = &inner.realm[..];
let res = inner.transport.call(&url, &ApiEmptyString)?;
let persist = &inner.persist;
let pk_key = PersistKey::new(realm, PersistKind::PrivateKey, &primary_name);
let pkey_pem_bytes = self.private_key.private_key_to_pem_pkcs8().expect("to_pem");
let pkey_pem = String::from_utf8_lossy(&pkey_pem_bytes);
debug!("Save private key: {}", pk_key);
persist.put(&pk_key, &pkey_pem_bytes)?;
let cert = res.into_string()?;
let pk_crt = PersistKey::new(realm, PersistKind::Certificate, &primary_name);
debug!("Save certificate: {}", pk_crt);
persist.put(&pk_crt, cert.as_bytes())?;
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::persist::*;
use crate::*;
#[test]
fn test_get_authorizations() -> Result<()> {
let server = crate::test::with_directory_server();
let url = DirectoryUrl::Other(&server.dir_url);
let persist = MemoryPersist::new();
let dir = Directory::from_url(persist, url)?;
let acc = dir.account("foo@bar.com")?;
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 persist = MemoryPersist::new();
let dir = Directory::from_url(persist, url)?;
let acc = dir.account("foo@bar.com")?;
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, 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 persist = MemoryPersist::new();
let dir = Directory::from_url(persist, url)?;
let acc = dir.account("foo@bar.com")?;
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, 1)?;
let cert = ord.download_and_save_cert()?;
assert_eq!("CERT HERE", cert.certificate());
assert!(!cert.private_key().is_empty());
let cert2 = acc.certificate("acmetest.example.com")?.unwrap();
assert_eq!(cert.private_key(), cert2.private_key());
assert_eq!(cert.certificate(), cert2.certificate());
assert_eq!(cert.valid_days_left(), 89);
Ok(())
}
}