Add certificate revocation (#38)

This commit is contained in:
Deftware 2024-01-29 16:18:25 +01:00 committed by GitHub
parent a800b1bff7
commit 8290a95649
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 64 additions and 2 deletions

View File

@ -19,6 +19,7 @@ base64 = "0.21.0"
hyper = { version = "0.14.18", features = ["client", "http1", "http2"] } 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 } hyper-rustls = { version = "0.24", default-features = false, features = ["http1", "http2", "native-tokio", "tls12"], optional = true }
ring = { version = "0.17", features = ["std"] } ring = { version = "0.17", features = ["std"] }
rustls-pki-types = "1.1.0"
serde = { version = "1.0.104", features = ["derive"] } serde = { version = "1.0.104", features = ["derive"] }
serde_json = "1.0.78" serde_json = "1.0.78"
thiserror = "1.0.30" thiserror = "1.0.30"

View File

@ -24,7 +24,8 @@ use serde::Serialize;
mod types; mod types;
pub use types::{ pub use types::{
AccountCredentials, Authorization, AuthorizationStatus, Challenge, ChallengeType, Error, 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::{ use types::{
DirectoryUrls, Empty, FinalizeRequest, Header, JoseJson, Jwk, KeyOrKeyId, NewAccountPayload, DirectoryUrls, Empty, FinalizeRequest, Header, JoseJson, Jwk, KeyOrKeyId, NewAccountPayload,
@ -362,6 +363,17 @@ impl Account {
url: order_url.ok_or("no order URL found")?, 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 { struct AccountInner {
@ -697,7 +709,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn deserialize_old_credentials() -> Result<(), Error> { 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::<AccountCredentials>(CREDENTIALS)?).await?; Account::from_credentials(serde_json::from_str::<AccountCredentials>(CREDENTIALS)?).await?;
Ok(()) Ok(())
} }

View File

@ -4,7 +4,9 @@ use base64::prelude::{Engine, BASE64_URL_SAFE_NO_PAD};
use hyper::{Body, Response}; use hyper::{Body, Response};
use ring::digest::{digest, Digest, SHA256}; use ring::digest::{digest, Digest, SHA256};
use ring::signature::{EcdsaKeyPair, KeyPair}; use ring::signature::{EcdsaKeyPair, KeyPair};
use rustls_pki_types::CertificateDer;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use serde::ser::SerializeMap;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use thiserror::Error; use thiserror::Error;
@ -271,6 +273,52 @@ pub struct NewOrder<'a> {
pub identifiers: &'a [Identifier], pub identifiers: &'a [Identifier],
} }
/// Payload for a certificate revocation request
/// Defined in <https://datatracker.ietf.org/doc/html/rfc8555#section-7.6>
#[derive(Debug)]
pub struct RevocationRequest<'a> {
/// The certificate to revoke
pub certificate: &'a CertificateDer<'a>,
/// Reason for revocation
pub reason: Option<RevocationReason>,
}
impl<'a> Serialize for RevocationRequest<'a> {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
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 <https://datatracker.ietf.org/doc/html/rfc5280#section-5.3.1>
#[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<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_u8(self.clone() as u8)
}
}
#[derive(Serialize)] #[derive(Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub(crate) struct NewAccountPayload<'a> { pub(crate) struct NewAccountPayload<'a> {
@ -302,6 +350,7 @@ pub(crate) struct DirectoryUrls {
pub(crate) new_nonce: String, pub(crate) new_nonce: String,
pub(crate) new_account: String, pub(crate) new_account: String,
pub(crate) new_order: String, pub(crate) new_order: String,
pub(crate) revoke_cert: String,
} }
#[derive(Serialize)] #[derive(Serialize)]