rust: initial implementation of an ACME client
This commit is contained in:
commit
0060778f15
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "acme"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
base64 = "0.13.0"
|
||||
reqwest = { version = "0.11", default-features = false, features = ["json", "rustls-tls-native-roots"] }
|
||||
ring = { version = "0.16.20", features = ["std"] }
|
||||
serde = { version = "1.0.104", features = ["derive"] }
|
||||
serde_json = "1.0.78"
|
||||
thiserror = "1.0.30"
|
|
@ -0,0 +1,369 @@
|
|||
#![warn(unreachable_pub)]
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use base64::URL_SAFE_NO_PAD;
|
||||
use reqwest::header::{CONTENT_TYPE, LOCATION};
|
||||
use reqwest::redirect::Policy;
|
||||
use reqwest::{Body, Response};
|
||||
use ring::rand::SystemRandom;
|
||||
use ring::signature::{EcdsaKeyPair, ECDSA_P256_SHA256_FIXED_SIGNING};
|
||||
use serde::de::{DeserializeOwned, Error as _, Unexpected};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
mod types;
|
||||
use types::{
|
||||
AccountCredentials, Challenge, DirectoryUrls, Empty, FinalizeRequest, Header, JoseJson, Jwk,
|
||||
KeyAuthorization, KeyOrKeyId, OrderState, Problem, SigningAlgorithm,
|
||||
};
|
||||
pub use types::{
|
||||
Authorization, ChallengeType, Error, Identifier, LetsEncrypt, NewAccount, NewOrder, OrderStatus,
|
||||
};
|
||||
|
||||
pub struct Order {
|
||||
account: Arc<AccountInner>,
|
||||
nonce: Option<String>,
|
||||
#[allow(dead_code)]
|
||||
order_url: String,
|
||||
}
|
||||
|
||||
impl Order {
|
||||
pub async fn authorizations(
|
||||
&mut self,
|
||||
authz_urls: &[String],
|
||||
) -> Result<Vec<Authorization>, Error> {
|
||||
let mut authorizations = Vec::with_capacity(authz_urls.len());
|
||||
for url in authz_urls {
|
||||
authorizations.push(self.account.get(&mut self.nonce, url).await?);
|
||||
}
|
||||
Ok(authorizations)
|
||||
}
|
||||
|
||||
pub fn key_authorization(&self, challenge: &Challenge) -> KeyAuthorization {
|
||||
KeyAuthorization(format!("{}.{}", challenge.token, &self.account.key.thumb))
|
||||
}
|
||||
|
||||
pub async fn finalize(
|
||||
&mut self,
|
||||
csr_der: &[u8],
|
||||
finalize_url: &str,
|
||||
) -> Result<OrderState, Error> {
|
||||
let rsp = self
|
||||
.account
|
||||
.post(
|
||||
Some(&FinalizeRequest::new(csr_der)),
|
||||
self.nonce.take(),
|
||||
finalize_url,
|
||||
)
|
||||
.await?;
|
||||
|
||||
self.nonce = nonce_from_response(&rsp);
|
||||
Problem::check(rsp).await
|
||||
}
|
||||
|
||||
pub async fn set_challenge_ready(&mut self, challenge_url: &str) -> Result<(), Error> {
|
||||
let rsp = self
|
||||
.account
|
||||
.post(Some(&Empty {}), self.nonce.take(), challenge_url)
|
||||
.await?;
|
||||
|
||||
self.nonce = nonce_from_response(&rsp);
|
||||
let _ = Problem::check::<Challenge>(rsp).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn certificate_chain(&mut self, cert_url: &str) -> Result<String, Error> {
|
||||
let rsp = self
|
||||
.account
|
||||
.post(None::<&Empty>, self.nonce.take(), cert_url)
|
||||
.await?;
|
||||
|
||||
self.nonce = nonce_from_response(&rsp);
|
||||
let status = rsp.status();
|
||||
match status.is_client_error() || status.is_server_error() {
|
||||
false => Ok(rsp.text().await?),
|
||||
true => Err(rsp.json::<Problem>().await?.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn challenge(&mut self, challenge_url: &str) -> Result<Challenge, Error> {
|
||||
self.account.get(&mut self.nonce, challenge_url).await
|
||||
}
|
||||
|
||||
pub async fn state(&mut self) -> Result<OrderState, Error> {
|
||||
self.account.get(&mut self.nonce, &self.order_url).await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Account {
|
||||
inner: Arc<AccountInner>,
|
||||
}
|
||||
|
||||
impl Account {
|
||||
pub async fn create(account: &NewAccount<'_>, server_url: &str) -> Result<Account, Error> {
|
||||
let client = client()?;
|
||||
let urls = client.get(server_url).send().await?;
|
||||
let client = Client {
|
||||
client,
|
||||
urls: urls.json().await?,
|
||||
};
|
||||
|
||||
let key = Key::generate()?;
|
||||
let nonce = client.nonce().await?;
|
||||
let header = key.key_header(&nonce, &client.urls.new_account);
|
||||
let body = key.signed_json(Some(account), header)?;
|
||||
|
||||
let rsp = client
|
||||
.client
|
||||
.post(&client.urls.new_account)
|
||||
.header(CONTENT_TYPE, JOSE_JSON)
|
||||
.body(body)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
let account_url = rsp
|
||||
.headers()
|
||||
.get(LOCATION)
|
||||
.and_then(|hv| hv.to_str().ok())
|
||||
.map(|s| s.to_owned());
|
||||
|
||||
let status = rsp.status();
|
||||
if status.is_client_error() || status.is_server_error() {
|
||||
return Err(rsp.json::<Problem>().await?.into());
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
inner: Arc::new(AccountInner {
|
||||
client,
|
||||
key,
|
||||
id: account_url.ok_or("failed to get account URL")?,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn new_order<'a>(
|
||||
&'a self,
|
||||
order: &NewOrder<'_>,
|
||||
) -> Result<(Order, OrderState), Error> {
|
||||
let rsp = self
|
||||
.inner
|
||||
.post(Some(order), None, &self.inner.client.urls.new_order)
|
||||
.await?;
|
||||
|
||||
let nonce = nonce_from_response(&rsp);
|
||||
let order_url = rsp
|
||||
.headers()
|
||||
.get(LOCATION)
|
||||
.and_then(|hv| hv.to_str().ok())
|
||||
.map(|s| s.to_owned());
|
||||
|
||||
let status = Problem::check(rsp).await?;
|
||||
Ok((
|
||||
Order {
|
||||
account: self.inner.clone(),
|
||||
nonce,
|
||||
order_url: order_url.ok_or("no order URL found")?,
|
||||
},
|
||||
status,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Account {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let creds = AccountCredentials::deserialize(deserializer)?;
|
||||
let pkcs8_der = base64::decode_config(&creds.key_pkcs8, URL_SAFE_NO_PAD).map_err(|_| {
|
||||
D::Error::invalid_value(
|
||||
Unexpected::Str(&creds.key_pkcs8),
|
||||
&"unable to base64-decode key",
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(Self {
|
||||
inner: Arc::new(AccountInner {
|
||||
key: Key::from_pkcs8_der(pkcs8_der).map_err(|_| {
|
||||
D::Error::invalid_value(
|
||||
Unexpected::Str(&creds.key_pkcs8),
|
||||
&"unable to parse key",
|
||||
)
|
||||
})?,
|
||||
client: Client {
|
||||
client: client().map_err(D::Error::custom)?,
|
||||
urls: creds.urls.clone(),
|
||||
},
|
||||
id: creds.id.clone(),
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Account {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
AccountCredentials {
|
||||
id: self.inner.id.clone(),
|
||||
key_pkcs8: base64::encode_config(&self.inner.key.pkcs8_der, URL_SAFE_NO_PAD),
|
||||
urls: self.inner.client.urls.clone(),
|
||||
}
|
||||
.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
struct AccountInner {
|
||||
client: Client,
|
||||
key: Key,
|
||||
id: String,
|
||||
}
|
||||
|
||||
impl AccountInner {
|
||||
async fn get<T: DeserializeOwned>(
|
||||
&self,
|
||||
nonce: &mut Option<String>,
|
||||
url: &str,
|
||||
) -> Result<T, Error> {
|
||||
let rsp = self.post(None::<&Empty>, nonce.take(), url).await?;
|
||||
*nonce = nonce_from_response(&rsp);
|
||||
Ok(Problem::check(rsp).await?)
|
||||
}
|
||||
|
||||
async fn post(
|
||||
&self,
|
||||
payload: Option<&impl Serialize>,
|
||||
nonce: Option<String>,
|
||||
url: &str,
|
||||
) -> Result<Response, Error> {
|
||||
let nonce = match nonce {
|
||||
Some(nonce) => nonce,
|
||||
None => self.client.nonce().await?,
|
||||
};
|
||||
|
||||
let header = self.key_id_header(&nonce, url);
|
||||
let body = self.key.signed_json(payload, header)?;
|
||||
Ok(self
|
||||
.client
|
||||
.client
|
||||
.post(url)
|
||||
.header(CONTENT_TYPE, JOSE_JSON)
|
||||
.body(body)
|
||||
.send()
|
||||
.await?)
|
||||
}
|
||||
|
||||
fn key_id_header<'n, 'u: 'n, 'a: 'u>(&'a self, nonce: &'n str, url: &'u str) -> Header<'n> {
|
||||
Header {
|
||||
alg: self.key.signing_algorithm,
|
||||
key: KeyOrKeyId::KeyId(&self.id),
|
||||
nonce,
|
||||
url,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Client {
|
||||
client: reqwest::Client,
|
||||
urls: DirectoryUrls,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
async fn nonce(&self) -> Result<String, Error> {
|
||||
let future = self.client.head(&self.urls.new_nonce).send();
|
||||
match nonce_from_response(&future.await?) {
|
||||
Some(nonce) => Ok(nonce),
|
||||
None => Err("no nonce found".into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Key {
|
||||
rng: SystemRandom,
|
||||
signing_algorithm: SigningAlgorithm,
|
||||
inner: EcdsaKeyPair,
|
||||
pkcs8_der: Vec<u8>,
|
||||
thumb: String,
|
||||
}
|
||||
|
||||
impl Key {
|
||||
fn generate() -> Result<Self, Error> {
|
||||
let rng = SystemRandom::new();
|
||||
let pkcs8 = EcdsaKeyPair::generate_pkcs8(&ECDSA_P256_SHA256_FIXED_SIGNING, &rng)?;
|
||||
let key = EcdsaKeyPair::from_pkcs8(&ECDSA_P256_SHA256_FIXED_SIGNING, pkcs8.as_ref())?;
|
||||
let thumb = base64::encode_config(Jwk::thumb_sha256(&key)?, URL_SAFE_NO_PAD);
|
||||
|
||||
Ok(Self {
|
||||
rng,
|
||||
signing_algorithm: SigningAlgorithm::Es256,
|
||||
inner: key,
|
||||
pkcs8_der: pkcs8.as_ref().to_vec(),
|
||||
thumb,
|
||||
})
|
||||
}
|
||||
|
||||
fn from_pkcs8_der(pkcs8_der: Vec<u8>) -> Result<Self, Error> {
|
||||
let key = EcdsaKeyPair::from_pkcs8(&ECDSA_P256_SHA256_FIXED_SIGNING, &pkcs8_der)?;
|
||||
let thumb = base64::encode_config(Jwk::thumb_sha256(&key)?, URL_SAFE_NO_PAD);
|
||||
|
||||
Ok(Self {
|
||||
rng: SystemRandom::new(),
|
||||
signing_algorithm: SigningAlgorithm::Es256,
|
||||
inner: key,
|
||||
pkcs8_der,
|
||||
thumb,
|
||||
})
|
||||
}
|
||||
|
||||
fn signed_json(
|
||||
&self,
|
||||
payload: Option<&impl Serialize>,
|
||||
protected: Header<'_>,
|
||||
) -> Result<Body, Error> {
|
||||
let protected = base64(&protected)?;
|
||||
let payload = match payload {
|
||||
Some(data) => base64(&data)?,
|
||||
None => String::new(),
|
||||
};
|
||||
|
||||
let combined = format!("{}.{}", protected, payload);
|
||||
let signature = self.inner.sign(&self.rng, combined.as_bytes())?;
|
||||
Ok(Body::from(serde_json::to_vec(&JoseJson {
|
||||
protected,
|
||||
payload,
|
||||
signature: base64::encode_config(signature.as_ref(), URL_SAFE_NO_PAD),
|
||||
})?))
|
||||
}
|
||||
|
||||
fn key_header<'n, 'u: 'n, 'k: 'u>(&'k self, nonce: &'n str, url: &'u str) -> Header<'n> {
|
||||
Header {
|
||||
alg: self.signing_algorithm,
|
||||
key: KeyOrKeyId::from_key(&self.inner),
|
||||
nonce,
|
||||
url,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn nonce_from_response(rsp: &Response) -> Option<String> {
|
||||
rsp.headers()
|
||||
.get(REPLAY_NONCE)
|
||||
.and_then(|hv| String::from_utf8(hv.as_ref().to_vec()).ok())
|
||||
}
|
||||
|
||||
fn base64(data: &impl Serialize) -> Result<String, serde_json::Error> {
|
||||
Ok(base64::encode_config(
|
||||
serde_json::to_vec(data)?,
|
||||
URL_SAFE_NO_PAD,
|
||||
))
|
||||
}
|
||||
|
||||
fn client() -> Result<reqwest::Client, reqwest::Error> {
|
||||
reqwest::Client::builder().redirect(Policy::none()).build()
|
||||
}
|
||||
|
||||
const JOSE_JSON: &str = "application/jose+json";
|
||||
const REPLAY_NONCE: &str = "Replay-Nonce";
|
|
@ -0,0 +1,286 @@
|
|||
use std::fmt;
|
||||
|
||||
use base64::URL_SAFE_NO_PAD;
|
||||
use reqwest::Response;
|
||||
use ring::digest::{digest, Digest, SHA256};
|
||||
use ring::signature::{EcdsaKeyPair, KeyPair};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
#[error("API error")]
|
||||
Api(#[from] Problem),
|
||||
#[error("base64 decoding failed")]
|
||||
Base64(#[from] base64::DecodeError),
|
||||
#[error("cryptographic operation failed")]
|
||||
Crypto(#[from] ring::error::Unspecified),
|
||||
#[error("invalid key bytes")]
|
||||
CryptoKey(#[from] ring::error::KeyRejected),
|
||||
#[error("HTTP request failure")]
|
||||
Http(#[from] reqwest::Error),
|
||||
#[error("failed to (de)serialize JSON")]
|
||||
Json(#[from] serde_json::Error),
|
||||
#[error("missing data: {0}")]
|
||||
Str(&'static str),
|
||||
}
|
||||
|
||||
impl From<&'static str> for Error {
|
||||
fn from(s: &'static str) -> Self {
|
||||
Error::Str(s)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub(crate) struct AccountCredentials {
|
||||
pub(crate) id: String,
|
||||
pub(crate) key_pkcs8: String,
|
||||
pub(crate) urls: DirectoryUrls,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Problem {
|
||||
pub r#type: String,
|
||||
pub detail: String,
|
||||
pub status: u16,
|
||||
}
|
||||
|
||||
impl Problem {
|
||||
pub(crate) async fn check<T: DeserializeOwned>(rsp: Response) -> Result<T, Error> {
|
||||
let status = rsp.status();
|
||||
match status.is_client_error() || status.is_server_error() {
|
||||
false => Ok(rsp.json().await?),
|
||||
true => Err(rsp.json::<Self>().await?.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Problem {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "API error: {} ({})", self.detail, self.r#type)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Problem {}
|
||||
|
||||
pub struct KeyAuthorization(pub(crate) String);
|
||||
|
||||
impl KeyAuthorization {
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
|
||||
pub fn dns_value(&self) -> String {
|
||||
base64::encode_config(digest(&SHA256, self.0.as_bytes()), URL_SAFE_NO_PAD)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for KeyAuthorization {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_tuple("KeyAuthorization").finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub(crate) struct FinalizeRequest {
|
||||
csr: String,
|
||||
}
|
||||
|
||||
impl FinalizeRequest {
|
||||
pub(crate) fn new(csr_der: &[u8]) -> Self {
|
||||
Self {
|
||||
csr: base64::encode_config(csr_der, URL_SAFE_NO_PAD),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub(crate) struct Header<'a> {
|
||||
pub(crate) alg: SigningAlgorithm,
|
||||
#[serde(flatten)]
|
||||
pub(crate) key: KeyOrKeyId<'a>,
|
||||
pub(crate) nonce: &'a str,
|
||||
pub(crate) url: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub(crate) enum KeyOrKeyId<'a> {
|
||||
#[serde(rename = "jwk")]
|
||||
Key(Jwk),
|
||||
#[serde(rename = "kid")]
|
||||
KeyId(&'a str),
|
||||
}
|
||||
|
||||
impl<'a> KeyOrKeyId<'a> {
|
||||
pub(crate) fn from_key(key: &EcdsaKeyPair) -> KeyOrKeyId<'static> {
|
||||
KeyOrKeyId::Key(Jwk::new(key))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub(crate) struct Jwk {
|
||||
alg: SigningAlgorithm,
|
||||
crv: &'static str,
|
||||
kty: &'static str,
|
||||
r#use: &'static str,
|
||||
x: String,
|
||||
y: String,
|
||||
}
|
||||
|
||||
impl Jwk {
|
||||
pub(crate) fn new(key: &EcdsaKeyPair) -> Self {
|
||||
let (x, y) = key.public_key().as_ref()[1..].split_at(32);
|
||||
Self {
|
||||
alg: SigningAlgorithm::Es256,
|
||||
crv: "P-256",
|
||||
kty: "EC",
|
||||
r#use: "sig",
|
||||
x: base64::encode_config(x, URL_SAFE_NO_PAD),
|
||||
y: base64::encode_config(y, URL_SAFE_NO_PAD),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn thumb_sha256(key: &EcdsaKeyPair) -> Result<Digest, serde_json::Error> {
|
||||
let jwk = Self::new(key);
|
||||
Ok(digest(
|
||||
&SHA256,
|
||||
&serde_json::to_vec(&JwkThumb {
|
||||
crv: jwk.crv,
|
||||
kty: jwk.kty,
|
||||
x: &jwk.x,
|
||||
y: &jwk.y,
|
||||
})?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct JwkThumb<'a> {
|
||||
crv: &'a str,
|
||||
kty: &'a str,
|
||||
x: &'a str,
|
||||
y: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Challenge {
|
||||
pub r#type: ChallengeType,
|
||||
pub url: String,
|
||||
pub token: String,
|
||||
pub status: ChallengeStatus,
|
||||
pub error: Option<Problem>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct OrderState {
|
||||
pub status: OrderStatus,
|
||||
pub authorizations: Vec<String>,
|
||||
pub error: Option<Problem>,
|
||||
pub finalize: String,
|
||||
pub certificate: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct NewOrder<'a> {
|
||||
pub identifiers: &'a [Identifier],
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct NewAccount<'a> {
|
||||
pub contact: &'a [&'a str],
|
||||
pub terms_of_service_agreed: bool,
|
||||
pub only_return_existing: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct DirectoryUrls {
|
||||
pub(crate) new_nonce: String,
|
||||
pub(crate) new_account: String,
|
||||
pub(crate) new_order: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub(crate) struct JoseJson {
|
||||
pub(crate) protected: String,
|
||||
pub(crate) payload: String,
|
||||
pub(crate) signature: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(tag = "status", rename_all = "camelCase")]
|
||||
pub enum Authorization {
|
||||
Pending {
|
||||
identifier: Identifier,
|
||||
challenges: Vec<Challenge>,
|
||||
},
|
||||
Valid,
|
||||
Invalid,
|
||||
Revoked,
|
||||
Expired,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[serde(tag = "type", content = "value", rename_all = "camelCase")]
|
||||
pub enum Identifier {
|
||||
Dns(String),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
|
||||
pub enum ChallengeType {
|
||||
#[serde(rename = "http-01")]
|
||||
Http01,
|
||||
#[serde(rename = "dns-01")]
|
||||
Dns01,
|
||||
#[serde(rename = "tls-alpn-01")]
|
||||
TlsAlpn01,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum ChallengeStatus {
|
||||
Pending,
|
||||
Processing,
|
||||
Valid,
|
||||
Invalid,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum OrderStatus {
|
||||
Pending,
|
||||
Ready,
|
||||
Processing,
|
||||
Valid,
|
||||
Invalid,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum LetsEncrypt {
|
||||
Production,
|
||||
Staging,
|
||||
}
|
||||
|
||||
impl LetsEncrypt {
|
||||
pub fn url(&self) -> &'static str {
|
||||
match self {
|
||||
LetsEncrypt::Production => "https://acme-v02.api.letsencrypt.org/directory",
|
||||
LetsEncrypt::Staging => "https://acme-staging-v02.api.letsencrypt.org/directory",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize)]
|
||||
#[serde(rename_all = "UPPERCASE")]
|
||||
pub(crate) enum SigningAlgorithm {
|
||||
/// ECDSA using P-256 and SHA-256
|
||||
Es256,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub(crate) struct Empty {}
|
Loading…
Reference in New Issue