diff --git a/src/order.rs b/src/order.rs index 07df0cd..dc9e716 100644 --- a/src/order.rs +++ b/src/order.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use std::ops::{ControlFlow, Deref}; use std::sync::Arc; use std::time::{Duration, Instant, SystemTime}; @@ -12,7 +13,7 @@ use tokio::time::sleep; use crate::account::AccountInner; use crate::types::{ Authorization, AuthorizationState, AuthorizationStatus, AuthorizedIdentifier, Challenge, - ChallengeType, Empty, FinalizeRequest, OrderState, OrderStatus, Problem, + ChallengeType, DeviceAttestation, Empty, FinalizeRequest, OrderState, OrderStatus, Problem, }; use crate::{Error, Key, crypto, nonce_from_response, retry_after}; @@ -453,6 +454,40 @@ impl ChallengeHandle<'_> { } } + /// Notify the server that the challenge is ready by sending a device attestation + /// + /// This function is for the ACME challenge device-attest-01. It should not be used + /// with other challenge types. + /// See for details. + /// + /// `payload` is the device attestation object as defined in link. Provide the attestation + /// object as a raw blob. Base64 encoding of the attestation object `payload.att_obj` + /// is done by this function. + pub async fn send_device_attestation( + &mut self, + payload: &DeviceAttestation<'_>, + ) -> Result<(), Error> { + if self.challenge.r#type != ChallengeType::DeviceAttest01 { + return Err(Error::Str("challenge type should be device-attest-01")); + } + + let payload = DeviceAttestation { + att_obj: Cow::Owned(BASE64_URL_SAFE_NO_PAD.encode(&payload.att_obj).into()), + }; + + let rsp = self + .account + .post(Some(&payload), self.nonce.take(), &self.challenge.url) + .await?; + + *self.nonce = nonce_from_response(&rsp); + let response = Problem::check::(rsp).await?; + match response.error { + Some(details) => Err(Error::Api(details)), + None => Ok(()), + } + } + /// Create a [`KeyAuthorization`] for this challenge /// /// Combines a challenge's token with the thumbprint of the account's public key to compute diff --git a/src/types.rs b/src/types.rs index 0d72d73..c85d7c3 100644 --- a/src/types.rs +++ b/src/types.rs @@ -648,7 +648,7 @@ pub enum AuthorizationStatus { #[allow(missing_docs)] #[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] #[non_exhaustive] -#[serde(tag = "type", content = "value", rename_all = "camelCase")] +#[serde(tag = "type", content = "value", rename_all = "kebab-case")] pub enum Identifier { Dns(String), @@ -656,6 +656,16 @@ pub enum Identifier { /// /// Note that not all ACME servers will accept an order with an IP address identifier. Ip(IpAddr), + + /// Permanent Identifier + /// + /// Note that this identifier is only used for attestation. + PermanentIdentifier(String), + + /// Hardware Module identifier + /// + /// Note that this identifier is only used for attestation. + HardwareModule(String), } impl Identifier { @@ -690,6 +700,10 @@ impl fmt::Display for AuthorizedIdentifier<'_> { (true, Identifier::Dns(dns)) => f.write_fmt(format_args!("*.{dns}")), (false, Identifier::Dns(dns)) => f.write_str(dns), (_, Identifier::Ip(addr)) => write!(f, "{addr}"), + (_, Identifier::PermanentIdentifier(permanent_identifier)) => { + f.write_str(permanent_identifier) + } + (_, Identifier::HardwareModule(hardware_module)) => f.write_str(hardware_module), } } } @@ -704,6 +718,8 @@ pub enum ChallengeType { Dns01, #[serde(rename = "tls-alpn-01")] TlsAlpn01, + #[serde(rename = "device-attest-01")] + DeviceAttest01, #[serde(untagged)] Unknown(String), } @@ -930,6 +946,16 @@ pub(crate) enum SigningAlgorithm { Hs256, } +/// Attestation payload used for device-attest-01 +/// +/// See for details. +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct DeviceAttestation<'a> { + /// attestation payload + pub att_obj: Cow<'a, [u8]>, +} + #[derive(Debug, Serialize)] pub(crate) struct Empty {}