Postpone certificate retrieval until order is valid

This commit is contained in:
Dirkjan Ochtman 2023-03-15 16:27:40 +01:00
parent a2d4129201
commit 0cc77ff2ca
3 changed files with 47 additions and 13 deletions

View File

@ -130,8 +130,15 @@ async fn main() -> anyhow::Result<()> {
// Finalize the order and print certificate chain, private key and account credentials.
let cert_chain_pem = order.finalize(&csr).await.unwrap();
info!("certficate chain:\n\n{}", cert_chain_pem,);
order.finalize(&csr).await.unwrap();
let cert_chain_pem = loop {
match order.certificate().await.unwrap() {
Some(cert_chain_pem) => break cert_chain_pem,
None => sleep(Duration::from_secs(1)).await,
}
};
info!("certficate chain:\n\n{}", cert_chain_pem);
info!("private key:\n\n{}", cert.serialize_private_key_pem());
info!(
"account credentials:\n\n{}",

View File

@ -75,9 +75,9 @@ impl Order {
/// Request a certificate from the given Certificate Signing Request (CSR)
///
/// Creating a CSR is outside of the scope of instant-acme. Make sure you pass in a
/// DER representation of the CSR in `csr_der`. The resulting `String` will contain the
/// PEM-encoded certificate chain.
pub async fn finalize(&mut self, csr_der: &[u8]) -> Result<String, Error> {
/// DER representation of the CSR in `csr_der`. Call `certificate()` to retrieve the
/// certificate chain once the order is in the appropriate state.
pub async fn finalize(&mut self, csr_der: &[u8]) -> Result<(), Error> {
let rsp = self
.account
.post(
@ -88,24 +88,51 @@ impl Order {
.await?;
self.nonce = nonce_from_response(&rsp);
let state = Problem::check::<OrderState>(rsp).await?;
self.state = Problem::check::<OrderState>(rsp).await?;
Ok(())
}
let cert_url = match state.certificate {
Some(url) => url,
None => return Err(Error::Str("no certificate URL")),
/// Get the certificate for this order
///
/// If the cached order state is in `ready` or `processing` state, this will poll the server
/// for the latest state. If the order is still in `processing` state after that, this will
/// return `Ok(None)`. If the order is in `valid` state, this will attempt to retrieve
/// the certificate from the server and return it as a `String`. If the order contains
/// an error or ends up in any state other than `valid` or `processing`, return an error.
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 cert_url = match &self.state.certificate {
Some(cert_url) => cert_url,
None => return Err(Error::Str("no certificate URL found")),
};
let rsp = self
.account
.post(None::<&Empty>, self.nonce.take(), &cert_url)
.post(None::<&Empty>, self.nonce.take(), cert_url)
.await?;
self.nonce = nonce_from_response(&rsp);
let body = hyper::body::to_bytes(Problem::from_response(rsp).await?).await?;
Ok(
Ok(Some(
String::from_utf8(body.to_vec())
.map_err(|_| "unable to decode certificate as UTF-8")?,
)
))
}
/// Notify the server that the given challenge is ready to be completed

View File

@ -60,7 +60,7 @@ pub struct AccountCredentials<'a> {
}
/// An RFC 7807 problem document as returned by the ACME server
#[derive(Debug, Deserialize)]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Problem {
/// One of an enumerated list of problem types