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 {}