diff --git a/Cargo.toml b/Cargo.toml index 0220a45..c9b1534 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ base64 = "0.21.0" hyper = { version = "0.14.18", features = ["client", "http1", "http2"] } hyper-rustls = { version = "0.24", default-features = false, features = ["http1", "http2", "native-tokio", "tls12"], optional = true } ring = { version = "0.17", features = ["std"] } +rustls-pki-types = "1.1.0" serde = { version = "1.0.104", features = ["derive"] } serde_json = "1.0.78" thiserror = "1.0.30" diff --git a/src/lib.rs b/src/lib.rs index 4910066..36e9ac2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,7 +24,8 @@ use serde::Serialize; mod types; pub use types::{ AccountCredentials, Authorization, AuthorizationStatus, Challenge, ChallengeType, Error, - Identifier, LetsEncrypt, NewAccount, NewOrder, OrderState, OrderStatus, Problem, ZeroSsl, + Identifier, LetsEncrypt, NewAccount, NewOrder, OrderState, OrderStatus, Problem, + RevocationReason, RevocationRequest, ZeroSsl, }; use types::{ DirectoryUrls, Empty, FinalizeRequest, Header, JoseJson, Jwk, KeyOrKeyId, NewAccountPayload, @@ -362,6 +363,17 @@ impl Account { url: order_url.ok_or("no order URL found")?, }) } + + /// Revokes a previously issued certificate + pub async fn revoke<'a>(&'a self, payload: &RevocationRequest<'a>) -> Result<(), Error> { + let rsp = self + .inner + .post(Some(payload), None, &self.inner.client.urls.revoke_cert) + .await?; + // The body is empty if the request was successful + let _ = Problem::from_response(rsp).await?; + Ok(()) + } } struct AccountInner { @@ -697,7 +709,7 @@ mod tests { #[tokio::test] async fn deserialize_old_credentials() -> Result<(), Error> { - const CREDENTIALS: &str = r#"{"id":"id","key_pkcs8":"MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgJVWC_QzOTCS5vtsJp2IG-UDc8cdDfeoKtxSZxaznM-mhRANCAAQenCPoGgPFTdPJ7VLLKt56RxPlYT1wNXnHc54PEyBg3LxKaH0-sJkX0mL8LyPEdsfL_Oz4TxHkWLJGrXVtNhfH","urls":{"newNonce":"new-nonce","newAccount":"new-acct","newOrder":"new-order"}}"#; + const CREDENTIALS: &str = r#"{"id":"id","key_pkcs8":"MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgJVWC_QzOTCS5vtsJp2IG-UDc8cdDfeoKtxSZxaznM-mhRANCAAQenCPoGgPFTdPJ7VLLKt56RxPlYT1wNXnHc54PEyBg3LxKaH0-sJkX0mL8LyPEdsfL_Oz4TxHkWLJGrXVtNhfH","urls":{"newNonce":"new-nonce","newAccount":"new-acct","newOrder":"new-order", "revokeCert": "revoke-cert"}}"#; Account::from_credentials(serde_json::from_str::(CREDENTIALS)?).await?; Ok(()) } diff --git a/src/types.rs b/src/types.rs index faa6d90..541d4b4 100644 --- a/src/types.rs +++ b/src/types.rs @@ -4,7 +4,9 @@ use base64::prelude::{Engine, BASE64_URL_SAFE_NO_PAD}; use hyper::{Body, Response}; use ring::digest::{digest, Digest, SHA256}; use ring::signature::{EcdsaKeyPair, KeyPair}; +use rustls_pki_types::CertificateDer; use serde::de::DeserializeOwned; +use serde::ser::SerializeMap; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -271,6 +273,52 @@ pub struct NewOrder<'a> { pub identifiers: &'a [Identifier], } +/// Payload for a certificate revocation request +/// Defined in +#[derive(Debug)] +pub struct RevocationRequest<'a> { + /// The certificate to revoke + pub certificate: &'a CertificateDer<'a>, + /// Reason for revocation + pub reason: Option, +} + +impl<'a> Serialize for RevocationRequest<'a> { + fn serialize(&self, serializer: S) -> Result { + let base64 = BASE64_URL_SAFE_NO_PAD.encode(self.certificate); + let mut map = serializer.serialize_map(Some(2))?; + map.serialize_entry("certificate", &base64)?; + if let Some(reason) = &self.reason { + map.serialize_entry("reason", reason)?; + } + map.end() + } +} + +/// The reason for a certificate revocation +/// Defined in +#[allow(missing_docs)] +#[derive(Debug, Clone)] +#[repr(u8)] +pub enum RevocationReason { + Unspecified = 0, + KeyCompromise = 1, + CaCompromise = 2, + AffiliationChanged = 3, + Superseded = 4, + CessationOfOperation = 5, + CertificateHold = 6, + RemoveFromCrl = 8, + PrivilegeWithdrawn = 9, + AaCompromise = 10, +} + +impl Serialize for RevocationReason { + fn serialize(&self, serializer: S) -> Result { + serializer.serialize_u8(self.clone() as u8) + } +} + #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub(crate) struct NewAccountPayload<'a> { @@ -302,6 +350,7 @@ pub(crate) struct DirectoryUrls { pub(crate) new_nonce: String, pub(crate) new_account: String, pub(crate) new_order: String, + pub(crate) revoke_cert: String, } #[derive(Serialize)]