Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion src/order.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::ops::{ControlFlow, Deref};
use std::sync::Arc;
use std::time::{Duration, Instant, SystemTime};
Expand All @@ -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};

Expand Down Expand Up @@ -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 <https://datatracker.ietf.org/doc/draft-acme-device-attest/> 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::<Challenge>(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
Expand Down
28 changes: 27 additions & 1 deletion src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -648,14 +648,24 @@ 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),

/// An IP address (IPv4 or IPv6) 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),
Comment on lines +660 to +668
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went down a bit of a rabbit hole thinking about what the value of these identifier types should be. I think String is the right choice for now, but I also filed some issues upstream about the spec because I think as written it leaves some important details up in the air w.r.t how CSRs and order identifiers interact:

brandonweeks/draft-bweeks-acme-device-attest#9
brandonweeks/draft-bweeks-acme-device-attest#10

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thats great! If it turns out, that there is something better than String then I'll provide a pr.

}

impl Identifier {
Expand Down Expand Up @@ -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),
}
}
}
Expand All @@ -704,6 +718,8 @@ pub enum ChallengeType {
Dns01,
#[serde(rename = "tls-alpn-01")]
TlsAlpn01,
#[serde(rename = "device-attest-01")]
DeviceAttest01,
#[serde(untagged)]
Unknown(String),
}
Expand Down Expand Up @@ -930,6 +946,16 @@ pub(crate) enum SigningAlgorithm {
Hs256,
}

/// Attestation payload used for device-attest-01
///
/// See <https://datatracker.ietf.org/doc/draft-acme-device-attest/> 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 {}

Expand Down