use std::borrow::Cow;
use std::ops::{ControlFlow, Deref};
use std::sync::Arc;
use std::time::{Duration, Instant, SystemTime};
use std::{fmt, slice};
use base64::prelude::{BASE64_URL_SAFE_NO_PAD, Engine};
#[cfg(feature = "rcgen")]
use rcgen::{CertificateParams, DistinguishedName, KeyPair};
use serde::Serialize;
use tokio::time::sleep;
use crate::account::AccountInner;
use crate::types::{
Authorization, AuthorizationState, AuthorizationStatus, AuthorizedIdentifier, Challenge,
ChallengeType, DeviceAttestation, Empty, FinalizeRequest, OrderState, OrderStatus, Problem,
};
use crate::{ChallengeStatus, Error, Key, crypto, nonce_from_response, retry_after};
pub struct Order {
pub(crate) account: Arc<AccountInner>,
pub(crate) nonce: Option<String>,
pub(crate) retry_after: Option<SystemTime>,
pub(crate) url: String,
pub(crate) state: OrderState,
}
impl Order {
pub fn authorizations(&mut self) -> Authorizations<'_> {
Authorizations {
inner: AuthStream {
iter: self.state.authorizations.iter_mut(),
nonce: &mut self.nonce,
account: &self.account,
},
}
}
#[cfg(feature = "rcgen")]
pub async fn finalize(&mut self) -> Result<String, Error> {
let mut names = Vec::with_capacity(self.state.authorizations.len());
let mut identifiers = self.identifiers();
while let Some(result) = identifiers.next().await {
names.push(result?.to_string());
}
let mut params = CertificateParams::new(names).map_err(Error::from_rcgen)?;
params.distinguished_name = DistinguishedName::new();
let private_key = KeyPair::generate().map_err(Error::from_rcgen)?;
let csr = params
.serialize_request(&private_key)
.map_err(Error::from_rcgen)?;
self.finalize_csr(csr.der()).await?;
Ok(private_key.serialize_pem())
}
pub async fn finalize_csr(&mut self, csr_der: &[u8]) -> Result<(), Error> {
let rsp = self
.account
.post(
Some(&FinalizeRequest::new(csr_der)),
self.nonce.take(),
&self.state.finalize,
)
.await?;
self.nonce = nonce_from_response(&rsp);
self.state = Problem::check::<OrderState>(rsp).await?;
Ok(())
}
pub async fn certificate(&mut self) -> Result<Option<String>, Error> {
if matches!(self.state.status, OrderStatus::Processing) {
let rsp = self
.account
.post(None::<&Empty>, self.nonce.take(), &self.url)
.await?;
self.nonce = nonce_from_response(&rsp);
self.state = Problem::check::<OrderState>(rsp).await?;
}
if let Some(error) = &self.state.error {
return Err(Error::Api(error.clone()));
} else if self.state.status == OrderStatus::Processing {
return Ok(None);
} else if self.state.status != OrderStatus::Valid {
return Err(Error::Str("invalid order state"));
}
let Some(cert_url) = &self.state.certificate else {
return Err(Error::Str("no certificate URL found"));
};
let rsp = self
.account
.post(None::<&Empty>, self.nonce.take(), cert_url)
.await?;
self.nonce = nonce_from_response(&rsp);
let body = Problem::from_response(rsp).await?;
Ok(Some(
String::from_utf8(body.to_vec())
.map_err(|_| "unable to decode certificate as UTF-8")?,
))
}
pub fn identifiers(&mut self) -> Identifiers<'_> {
Identifiers {
inner: AuthStream {
iter: self.state.authorizations.iter_mut(),
nonce: &mut self.nonce,
account: &self.account,
},
}
}
pub async fn poll_ready(&mut self, retries: &RetryPolicy) -> Result<OrderStatus, Error> {
let mut retrying = retries.state();
self.retry_after = None;
loop {
if let ControlFlow::Break(err) = retrying.wait(self.retry_after.take()).await {
return Err(err);
}
let state = self.refresh().await?;
if let Some(error) = &state.error {
return Err(Error::Api(error.clone()));
} else if let OrderStatus::Ready | OrderStatus::Invalid = state.status {
return Ok(state.status);
}
}
}
pub async fn poll_certificate(&mut self, retries: &RetryPolicy) -> Result<String, Error> {
let mut retrying = retries.state();
self.retry_after = None;
loop {
if let ControlFlow::Break(err) = retrying.wait(self.retry_after.take()).await {
return Err(err);
}
let state = self.refresh().await?;
if let Some(error) = &state.error {
return Err(Error::Api(error.clone()));
} else if let OrderStatus::Valid | OrderStatus::Invalid = state.status {
return self
.certificate()
.await?
.ok_or(Error::Str("no certificates received from ACME CA"));
}
}
}
pub async fn refresh(&mut self) -> Result<&OrderState, Error> {
let rsp = self
.account
.post(None::<&Empty>, self.nonce.take(), &self.url)
.await?;
self.nonce = nonce_from_response(&rsp);
self.retry_after = retry_after(&rsp);
self.state = Problem::check::<OrderState>(rsp).await?;
Ok(&self.state)
}
pub fn into_parts(self) -> (String, OrderState) {
(self.url, self.state)
}
pub fn state(&mut self) -> &OrderState {
&self.state
}
pub fn url(&self) -> &str {
&self.url
}
}
pub struct Authorizations<'a> {
inner: AuthStream<'a>,
}
impl Authorizations<'_> {
pub async fn next(&mut self) -> Option<Result<AuthorizationHandle<'_>, Error>> {
let (url, state) = match self.inner.next().await? {
Ok((url, state)) => (url, state),
Err(err) => return Some(Err(err)),
};
Some(Ok(AuthorizationHandle {
state,
url,
nonce: self.inner.nonce,
account: self.inner.account,
}))
}
}
pub struct Identifiers<'a> {
inner: AuthStream<'a>,
}
impl<'a> Identifiers<'a> {
pub async fn next(&mut self) -> Option<Result<AuthorizedIdentifier<'a>, Error>> {
Some(match self.inner.next().await? {
Ok((_, state)) => Ok(state.identifier()),
Err(err) => Err(err),
})
}
}
struct AuthStream<'a> {
iter: slice::IterMut<'a, Authorization>,
nonce: &'a mut Option<String>,
account: &'a AccountInner,
}
impl<'a> AuthStream<'a> {
async fn next(&mut self) -> Option<Result<(&'a str, &'a mut AuthorizationState), Error>> {
let authz = self.iter.next()?;
if authz.state.is_none() {
match self.account.get(self.nonce, &authz.url).await {
Ok(state) => authz.state = Some(state),
Err(e) => return Some(Err(e)),
}
}
let state = authz.state.as_mut().unwrap();
Some(Ok((&authz.url, state)))
}
}
pub struct AuthorizationHandle<'a> {
state: &'a mut AuthorizationState,
url: &'a str,
nonce: &'a mut Option<String>,
account: &'a AccountInner,
}
impl<'a> AuthorizationHandle<'a> {
pub async fn refresh(&mut self) -> Result<&AuthorizationState, Error> {
let rsp = self
.account
.post(None::<&Empty>, self.nonce.take(), self.url)
.await?;
*self.nonce = nonce_from_response(&rsp);
*self.state = Problem::check::<AuthorizationState>(rsp).await?;
Ok(self.state)
}
pub async fn deactivate(&mut self) -> Result<&AuthorizationState, Error> {
if !matches!(
self.state.status,
AuthorizationStatus::Pending | AuthorizationStatus::Valid
) {
return Err(Error::Other("authorization not pending or valid".into()));
}
#[derive(Serialize)]
struct DeactivateRequest {
status: AuthorizationStatus,
}
let rsp = self
.account
.post(
Some(&DeactivateRequest {
status: AuthorizationStatus::Deactivated,
}),
self.nonce.take(),
self.url,
)
.await?;
*self.nonce = nonce_from_response(&rsp);
*self.state = Problem::check::<AuthorizationState>(rsp).await?;
match self.state.status {
AuthorizationStatus::Deactivated => Ok(self.state),
_ => Err(Error::Other(
"authorization was not deactivated by ACME server".into(),
)),
}
}
pub fn challenge(&'a mut self, r#type: ChallengeType) -> Option<ChallengeHandle<'a>> {
let challenge = self.state.challenges.iter().find(|c| c.r#type == r#type)?;
Some(ChallengeHandle {
identifier: self.state.identifier(),
challenge,
nonce: self.nonce,
account: self.account,
})
}
pub fn url(&self) -> &str {
self.url
}
}
impl Deref for AuthorizationHandle<'_> {
type Target = AuthorizationState;
fn deref(&self) -> &Self::Target {
self.state
}
}
pub struct ChallengeHandle<'a> {
identifier: AuthorizedIdentifier<'a>,
challenge: &'a Challenge,
nonce: &'a mut Option<String>,
account: &'a AccountInner,
}
impl ChallengeHandle<'_> {
pub async fn set_ready(&mut self) -> Result<(), Error> {
let rsp = self
.account
.post(Some(&Empty {}), self.nonce.take(), &self.challenge.url)
.await?;
*self.nonce = nonce_from_response(&rsp);
let response = Problem::check::<Challenge>(rsp).await?;
match response.error {
Some(details) => Err(Error::Api(details)),
None => Ok(()),
}
}
pub async fn send_device_attestation(
&mut self,
payload: &DeviceAttestation<'_>,
) -> Result<ChallengeStatus, Error> {
if self.challenge.r#type != ChallengeType::DeviceAttest01 {
return Err(Error::Str("challenge type should be device-attest-01"));
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct DeviceAttestationBase64<'a> {
att_obj: Cow<'a, str>,
}
let payload = DeviceAttestationBase64 {
att_obj: Cow::Owned(BASE64_URL_SAFE_NO_PAD.encode(&payload.att_obj)),
};
let rsp = self
.account
.post(Some(&payload), self.nonce.take(), &self.challenge.url)
.await?;
*self.nonce = nonce_from_response(&rsp);
let response = Problem::check::<Challenge>(rsp).await?;
match response.error {
Some(details) => Err(Error::Api(details)),
None => Ok(response.status),
}
}
pub fn key_authorization(&self) -> KeyAuthorization {
KeyAuthorization::new(self.challenge, &self.account.key)
}
pub fn identifier(&self) -> &AuthorizedIdentifier<'_> {
&self.identifier
}
}
impl Deref for ChallengeHandle<'_> {
type Target = Challenge;
fn deref(&self) -> &Self::Target {
self.challenge
}
}
pub struct KeyAuthorization(String);
impl KeyAuthorization {
fn new(challenge: &Challenge, key: &Key) -> Self {
Self(format!("{}.{}", challenge.token, &key.thumb))
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn digest(&self) -> impl AsRef<[u8]> {
crypto::digest(&crypto::SHA256, self.0.as_bytes())
}
pub fn dns_value(&self) -> String {
BASE64_URL_SAFE_NO_PAD.encode(self.digest())
}
}
impl fmt::Debug for KeyAuthorization {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("KeyAuthorization").finish()
}
}
#[derive(Debug, Clone, Copy)]
pub struct RetryPolicy {
delay: Duration,
backoff: f32,
timeout: Duration,
}
impl RetryPolicy {
pub const fn new() -> Self {
Self {
delay: Duration::from_millis(250),
backoff: 2.0,
timeout: Duration::from_secs(30),
}
}
pub const fn initial_delay(mut self, delay: Duration) -> Self {
self.delay = delay;
self
}
pub const fn backoff(mut self, backoff: f32) -> Self {
self.backoff = backoff;
self
}
pub const fn timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
}
fn state(&self) -> RetryState {
RetryState {
delay: self.delay,
backoff: self.backoff,
deadline: Instant::now() + self.timeout,
}
}
}
impl Default for RetryPolicy {
fn default() -> Self {
Self::new()
}
}
struct RetryState {
delay: Duration,
backoff: f32,
deadline: Instant,
}
impl RetryState {
async fn wait(&mut self, after: Option<SystemTime>) -> ControlFlow<Error, ()> {
if let Some(after) = after {
let now = SystemTime::now();
if let Ok(delay) = after.duration_since(now) {
let next = Instant::now() + delay;
if next > self.deadline {
return ControlFlow::Break(Error::Timeout(Some(next)));
} else {
sleep(delay).await;
return ControlFlow::Continue(());
}
}
}
sleep(self.delay).await;
self.delay = self.delay.mul_f32(self.backoff);
match Instant::now() + self.delay > self.deadline {
true => ControlFlow::Break(Error::Timeout(None)),
false => ControlFlow::Continue(()),
}
}
}