mirror of
https://github.com/instant-labs/instant-acme.git
synced 2025-02-02 06:22:12 +00:00
Add API documentation
This commit is contained in:
parent
5b33409f41
commit
b7dd9c4dc0
64
src/lib.rs
64
src/lib.rs
@ -1,4 +1,7 @@
|
|||||||
|
//! Async pure-Rust ACME (RFC 8555) client.
|
||||||
|
|
||||||
#![warn(unreachable_pub)]
|
#![warn(unreachable_pub)]
|
||||||
|
#![warn(missing_docs)]
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@ -14,14 +17,21 @@ use serde::Serialize;
|
|||||||
|
|
||||||
mod types;
|
mod types;
|
||||||
pub use types::{
|
pub use types::{
|
||||||
AccountCredentials, Authorization, AuthorizationStatus, ChallengeType, Error, Identifier,
|
AccountCredentials, Authorization, AuthorizationStatus, Challenge, ChallengeType, Error,
|
||||||
LetsEncrypt, NewAccount, NewOrder, OrderState, OrderStatus,
|
Identifier, KeyAuthorization, LetsEncrypt, NewAccount, NewOrder, OrderState, OrderStatus,
|
||||||
|
Problem,
|
||||||
};
|
};
|
||||||
use types::{
|
use types::{
|
||||||
Challenge, DirectoryUrls, Empty, FinalizeRequest, Header, JoseJson, Jwk, KeyAuthorization,
|
DirectoryUrls, Empty, FinalizeRequest, Header, JoseJson, Jwk, KeyOrKeyId, SigningAlgorithm,
|
||||||
KeyOrKeyId, Problem, SigningAlgorithm,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// An ACME order as described in RFC 8555 (section 7.1.3)
|
||||||
|
///
|
||||||
|
/// An order is created from an [`Account`] by calling [`Account::new_order()`]. The `Order`
|
||||||
|
/// type represents the stable identity of an order, while the [`Order::state()`] method
|
||||||
|
/// gives you access to the current state of the order according to the server.
|
||||||
|
///
|
||||||
|
/// <https://datatracker.ietf.org/doc/html/rfc8555#section-7.1.3>
|
||||||
pub struct Order {
|
pub struct Order {
|
||||||
account: Arc<AccountInner>,
|
account: Arc<AccountInner>,
|
||||||
nonce: Option<String>,
|
nonce: Option<String>,
|
||||||
@ -29,6 +39,21 @@ pub struct Order {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Order {
|
impl Order {
|
||||||
|
/// Retrieve the authorizations for this order
|
||||||
|
///
|
||||||
|
/// An order will contain one authorization to complete per identifier in the order.
|
||||||
|
/// After creating an order, you'll need to retrieve the authorizations so that
|
||||||
|
/// you can set up a challenge response for each authorization.
|
||||||
|
///
|
||||||
|
/// For each authorization, you'll need to:
|
||||||
|
///
|
||||||
|
/// * Select which [`ChallengeType`] you want to complete
|
||||||
|
/// * Create a [`KeyAuthorization`] for that [`Challenge`]
|
||||||
|
/// * Call [`Order::set_challenge_ready()`] for that challenge
|
||||||
|
///
|
||||||
|
/// After the challenges have been set up, check the [`Order::state()`] to see
|
||||||
|
/// if the order is ready to be finalized (or becomes invalid). Once it is
|
||||||
|
/// ready, call `Order::finalize()` to get the certificate.
|
||||||
pub async fn authorizations(
|
pub async fn authorizations(
|
||||||
&mut self,
|
&mut self,
|
||||||
authz_urls: &[String],
|
authz_urls: &[String],
|
||||||
@ -40,10 +65,19 @@ impl Order {
|
|||||||
Ok(authorizations)
|
Ok(authorizations)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a [`KeyAuthorization`] for the given [`Challenge`]
|
||||||
|
///
|
||||||
|
/// Signs the challenge's token with the account's private key and use the
|
||||||
|
/// value from [`KeyAuthorization::as_str()`] as the challenge response.
|
||||||
pub fn key_authorization(&self, challenge: &Challenge) -> KeyAuthorization {
|
pub fn key_authorization(&self, challenge: &Challenge) -> KeyAuthorization {
|
||||||
KeyAuthorization(format!("{}.{}", challenge.token, &self.account.key.thumb))
|
KeyAuthorization(format!("{}.{}", challenge.token, &self.account.key.thumb))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Request a certificate from the given Certificate Signing Request (CSR)
|
||||||
|
///
|
||||||
|
/// Creating a CSR is outside of the scope of instant-acme. Make sure you pass in a
|
||||||
|
/// DER representation of the CSR in `csr_der` and the [`OrderState::finalize`] URL
|
||||||
|
/// in `finalize_url`. The resulting `String` will contain the PEM-encoded certificate chain.
|
||||||
pub async fn finalize(&mut self, csr_der: &[u8], finalize_url: &str) -> Result<String, Error> {
|
pub async fn finalize(&mut self, csr_der: &[u8], finalize_url: &str) -> Result<String, Error> {
|
||||||
let rsp = self
|
let rsp = self
|
||||||
.account
|
.account
|
||||||
@ -75,6 +109,9 @@ impl Order {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Notify the server that the given challenge is ready to be completed
|
||||||
|
///
|
||||||
|
/// `challenge_url` should be the `Challenge::url` field.
|
||||||
pub async fn set_challenge_ready(&mut self, challenge_url: &str) -> Result<(), Error> {
|
pub async fn set_challenge_ready(&mut self, challenge_url: &str) -> Result<(), Error> {
|
||||||
let rsp = self
|
let rsp = self
|
||||||
.account
|
.account
|
||||||
@ -86,27 +123,41 @@ impl Order {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the current state of the given challenge
|
||||||
pub async fn challenge(&mut self, challenge_url: &str) -> Result<Challenge, Error> {
|
pub async fn challenge(&mut self, challenge_url: &str) -> Result<Challenge, Error> {
|
||||||
self.account.get(&mut self.nonce, challenge_url).await
|
self.account.get(&mut self.nonce, challenge_url).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the current state of the order
|
||||||
pub async fn state(&mut self) -> Result<OrderState, Error> {
|
pub async fn state(&mut self) -> Result<OrderState, Error> {
|
||||||
self.account.get(&mut self.nonce, &self.order_url).await
|
self.account.get(&mut self.nonce, &self.order_url).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An ACME account as described in RFC 8555 (section 7.1.2)
|
||||||
|
///
|
||||||
|
/// Create an [`Account`] with [`Account::create()`] or restore it from serialized data
|
||||||
|
/// by passing deserialized [`AccountCredentials`] to [`Account::from_credentials()`].
|
||||||
|
///
|
||||||
|
/// The [`Account`] type is cheap to clone.
|
||||||
|
///
|
||||||
|
/// <https://datatracker.ietf.org/doc/html/rfc8555#section-7.1.2>
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Account {
|
pub struct Account {
|
||||||
inner: Arc<AccountInner>,
|
inner: Arc<AccountInner>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Account {
|
impl Account {
|
||||||
|
/// Restore an existing account from the given credentials
|
||||||
|
///
|
||||||
|
/// The [`AccountCredentials`] type is opaque, but supports deserialization.
|
||||||
pub fn from_credentials(credentials: AccountCredentials<'_>) -> Result<Self, Error> {
|
pub fn from_credentials(credentials: AccountCredentials<'_>) -> Result<Self, Error> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
inner: Arc::new(AccountInner::from_credentials(credentials)?),
|
inner: Arc::new(AccountInner::from_credentials(credentials)?),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a new account on the `server_url` with the information in [`NewAccount`]
|
||||||
pub async fn create(account: &NewAccount<'_>, server_url: &str) -> Result<Account, Error> {
|
pub async fn create(account: &NewAccount<'_>, server_url: &str) -> Result<Account, Error> {
|
||||||
let client = Client::new(server_url).await?;
|
let client = Client::new(server_url).await?;
|
||||||
let key = Key::generate()?;
|
let key = Key::generate()?;
|
||||||
@ -131,6 +182,9 @@ impl Account {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a new order based on the given [`NewOrder`]
|
||||||
|
///
|
||||||
|
/// Returns both an [`Order`] instance and the initial [`OrderState`].
|
||||||
pub async fn new_order<'a>(
|
pub async fn new_order<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
order: &NewOrder<'_>,
|
order: &NewOrder<'_>,
|
||||||
@ -158,7 +212,7 @@ impl Account {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the account's credentials, which can be serialized.
|
/// Get the account's credentials, which can be serialized
|
||||||
///
|
///
|
||||||
/// Pass the credentials to [`Account::from_credentials`] to regain access to the `Account`.
|
/// Pass the credentials to [`Account::from_credentials`] to regain access to the `Account`.
|
||||||
pub fn credentials(&self) -> AccountCredentials<'_> {
|
pub fn credentials(&self) -> AccountCredentials<'_> {
|
||||||
|
78
src/types.rs
78
src/types.rs
@ -9,22 +9,33 @@ use serde::de::DeserializeOwned;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
/// Error type for instant-acme
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
/// An JSON problem as returned by the ACME server
|
||||||
|
///
|
||||||
|
/// RFC 8555 uses problem documents as described in RFC 7807.
|
||||||
#[error("API error: {0}")]
|
#[error("API error: {0}")]
|
||||||
Api(#[from] Problem),
|
Api(#[from] Problem),
|
||||||
|
/// Failed to base64-decode data
|
||||||
#[error("base64 decoding failed: {0}")]
|
#[error("base64 decoding failed: {0}")]
|
||||||
Base64(#[from] base64::DecodeError),
|
Base64(#[from] base64::DecodeError),
|
||||||
|
/// Failed from cryptographic operations
|
||||||
#[error("cryptographic operation failed: {0}")]
|
#[error("cryptographic operation failed: {0}")]
|
||||||
Crypto(#[from] ring::error::Unspecified),
|
Crypto(#[from] ring::error::Unspecified),
|
||||||
|
/// Failed to instantiate a private key
|
||||||
#[error("invalid key bytes: {0}")]
|
#[error("invalid key bytes: {0}")]
|
||||||
CryptoKey(#[from] ring::error::KeyRejected),
|
CryptoKey(#[from] ring::error::KeyRejected),
|
||||||
|
/// HTTP request failure
|
||||||
#[error("HTTP request failure: {0}")]
|
#[error("HTTP request failure: {0}")]
|
||||||
Http(#[from] hyper::Error),
|
Http(#[from] hyper::Error),
|
||||||
|
/// Invalid ACME server URL
|
||||||
#[error("invalid URI: {0}")]
|
#[error("invalid URI: {0}")]
|
||||||
InvalidUri(#[from] hyper::http::uri::InvalidUri),
|
InvalidUri(#[from] hyper::http::uri::InvalidUri),
|
||||||
|
/// Failed to (de)serialize a JSON object
|
||||||
#[error("failed to (de)serialize JSON: {0}")]
|
#[error("failed to (de)serialize JSON: {0}")]
|
||||||
Json(#[from] serde_json::Error),
|
Json(#[from] serde_json::Error),
|
||||||
|
/// Miscellaneous errors
|
||||||
#[error("missing data: {0}")]
|
#[error("missing data: {0}")]
|
||||||
Str(&'static str),
|
Str(&'static str),
|
||||||
}
|
}
|
||||||
@ -35,6 +46,12 @@ impl From<&'static str> for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// ACME account credentials
|
||||||
|
///
|
||||||
|
/// This opaque type contains the account ID, the private key data and the
|
||||||
|
/// server URLs from the relevant ACME server. This can be used to serialize
|
||||||
|
/// the account credentials to a file or secret manager and restore the
|
||||||
|
/// account from persistent storage.
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
pub struct AccountCredentials<'a> {
|
pub struct AccountCredentials<'a> {
|
||||||
pub(crate) id: Cow<'a, str>,
|
pub(crate) id: Cow<'a, str>,
|
||||||
@ -42,11 +59,17 @@ pub struct AccountCredentials<'a> {
|
|||||||
pub(crate) urls: Cow<'a, DirectoryUrls>,
|
pub(crate) urls: Cow<'a, DirectoryUrls>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An RFC 7807 problem document as returned by the ACME server
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Problem {
|
pub struct Problem {
|
||||||
|
/// One of an enumerated list of problem types
|
||||||
|
///
|
||||||
|
/// See <https://datatracker.ietf.org/doc/html/rfc8555#section-6.7>
|
||||||
pub r#type: String,
|
pub r#type: String,
|
||||||
|
/// A human-readable explanation of the problem
|
||||||
pub detail: String,
|
pub detail: String,
|
||||||
|
/// The HTTP status code returned for this response
|
||||||
pub status: u16,
|
pub status: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,13 +100,21 @@ impl fmt::Display for Problem {
|
|||||||
|
|
||||||
impl std::error::Error for Problem {}
|
impl std::error::Error for Problem {}
|
||||||
|
|
||||||
|
/// The response value to use for challenge responses
|
||||||
|
///
|
||||||
|
/// Use [`KeyAuthorization::dns_value()`] for DNS challenges and
|
||||||
|
/// [`KeyAuthorization::as_str()`] for other challenge types.
|
||||||
|
///
|
||||||
|
/// <https://datatracker.ietf.org/doc/html/rfc8555#section-8.1>
|
||||||
pub struct KeyAuthorization(pub(crate) String);
|
pub struct KeyAuthorization(pub(crate) String);
|
||||||
|
|
||||||
impl KeyAuthorization {
|
impl KeyAuthorization {
|
||||||
|
/// Get the key authorization value
|
||||||
pub fn as_str(&self) -> &str {
|
pub fn as_str(&self) -> &str {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the base64-encoded SHA256 digest of the key authorization
|
||||||
pub fn dns_value(&self) -> String {
|
pub fn dns_value(&self) -> String {
|
||||||
base64::encode_config(digest(&SHA256, self.0.as_bytes()), URL_SAFE_NO_PAD)
|
base64::encode_config(digest(&SHA256, self.0.as_bytes()), URL_SAFE_NO_PAD)
|
||||||
}
|
}
|
||||||
@ -176,36 +207,68 @@ struct JwkThumb<'a> {
|
|||||||
y: &'a str,
|
y: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An ACME challenge as described in RFC 8555 (section 7.1.5)
|
||||||
|
///
|
||||||
|
/// <https://datatracker.ietf.org/doc/html/rfc8555#section-7.1.5>
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct Challenge {
|
pub struct Challenge {
|
||||||
|
/// Type of challenge
|
||||||
pub r#type: ChallengeType,
|
pub r#type: ChallengeType,
|
||||||
|
/// Challenge identifier
|
||||||
pub url: String,
|
pub url: String,
|
||||||
|
/// Token for this challenge
|
||||||
pub token: String,
|
pub token: String,
|
||||||
|
/// Current status
|
||||||
pub status: ChallengeStatus,
|
pub status: ChallengeStatus,
|
||||||
|
/// Potential error state
|
||||||
pub error: Option<Problem>,
|
pub error: Option<Problem>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Contents of an ACME order as described in RFC 8555 (section 7.1.3)
|
||||||
|
///
|
||||||
|
/// The order identity will usually be represented by an [Order](crate::Order).
|
||||||
|
///
|
||||||
|
/// <https://datatracker.ietf.org/doc/html/rfc8555#section-7.1.3>
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct OrderState {
|
pub struct OrderState {
|
||||||
|
/// Current status
|
||||||
pub status: OrderStatus,
|
pub status: OrderStatus,
|
||||||
|
/// Authorization URLs for this order
|
||||||
|
///
|
||||||
|
/// There should be one authorization per identifier in the order.
|
||||||
pub authorizations: Vec<String>,
|
pub authorizations: Vec<String>,
|
||||||
|
/// Potential error state
|
||||||
pub error: Option<Problem>,
|
pub error: Option<Problem>,
|
||||||
|
/// A finalization URL, to be used once status becomes `Ready`
|
||||||
pub finalize: String,
|
pub finalize: String,
|
||||||
|
/// The certificate URL, which becomes available after finalization
|
||||||
pub certificate: Option<String>,
|
pub certificate: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Input data for [Order](crate::Order) creation
|
||||||
|
///
|
||||||
|
/// To be passed into [Account::new_order()](crate::Account::new_order()).
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct NewOrder<'a> {
|
pub struct NewOrder<'a> {
|
||||||
|
/// Identifiers to be included in the order
|
||||||
pub identifiers: &'a [Identifier],
|
pub identifiers: &'a [Identifier],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Input data for [Account](crate::Account) creation
|
||||||
|
///
|
||||||
|
/// To be passed into [Account::create()](crate::Account::create()).
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct NewAccount<'a> {
|
pub struct NewAccount<'a> {
|
||||||
|
/// A list of contact URIs (like `mailto:info@example.com`)
|
||||||
pub contact: &'a [&'a str],
|
pub contact: &'a [&'a str],
|
||||||
|
/// Whether you agree to the terms of service
|
||||||
pub terms_of_service_agreed: bool,
|
pub terms_of_service_agreed: bool,
|
||||||
|
/// Set to `true` in order to retrieve an existing account
|
||||||
|
///
|
||||||
|
/// Setting this to `false` has not been tested.
|
||||||
pub only_return_existing: bool,
|
pub only_return_existing: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,14 +287,20 @@ pub(crate) struct JoseJson {
|
|||||||
pub(crate) signature: String,
|
pub(crate) signature: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An ACME authorization as described in RFC 8555 (section 7.1.4)
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Authorization {
|
pub struct Authorization {
|
||||||
|
/// The identifier that the account is authorized to represent
|
||||||
pub identifier: Identifier,
|
pub identifier: Identifier,
|
||||||
|
/// Current state of the authorization
|
||||||
pub status: AuthorizationStatus,
|
pub status: AuthorizationStatus,
|
||||||
|
/// Possible challenges for the authorization
|
||||||
pub challenges: Vec<Challenge>,
|
pub challenges: Vec<Challenge>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Status for an [`Authorization`]
|
||||||
|
#[allow(missing_docs)]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub enum AuthorizationStatus {
|
pub enum AuthorizationStatus {
|
||||||
@ -242,13 +311,17 @@ pub enum AuthorizationStatus {
|
|||||||
Expired,
|
Expired,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represent an identifier in an ACME [Order](crate::Order)
|
||||||
|
#[allow(missing_docs)]
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[serde(tag = "type", content = "value", rename_all = "camelCase")]
|
#[serde(tag = "type", content = "value", rename_all = "camelCase")]
|
||||||
pub enum Identifier {
|
pub enum Identifier {
|
||||||
Dns(String),
|
Dns(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The challenge type
|
||||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub enum ChallengeType {
|
pub enum ChallengeType {
|
||||||
#[serde(rename = "http-01")]
|
#[serde(rename = "http-01")]
|
||||||
Http01,
|
Http01,
|
||||||
@ -267,6 +340,8 @@ pub enum ChallengeStatus {
|
|||||||
Invalid,
|
Invalid,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Status of an [Order](crate::Order)
|
||||||
|
#[allow(missing_docs)]
|
||||||
#[derive(Debug, Deserialize, PartialEq)]
|
#[derive(Debug, Deserialize, PartialEq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub enum OrderStatus {
|
pub enum OrderStatus {
|
||||||
@ -277,6 +352,8 @@ pub enum OrderStatus {
|
|||||||
Invalid,
|
Invalid,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper type to reference Let's Encrypt server URLs
|
||||||
|
#[allow(missing_docs)]
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum LetsEncrypt {
|
pub enum LetsEncrypt {
|
||||||
Production,
|
Production,
|
||||||
@ -284,6 +361,7 @@ pub enum LetsEncrypt {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl LetsEncrypt {
|
impl LetsEncrypt {
|
||||||
|
/// Get the directory URL for the given Let's Encrypt server
|
||||||
pub const fn url(&self) -> &'static str {
|
pub const fn url(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
LetsEncrypt::Production => "https://acme-v02.api.letsencrypt.org/directory",
|
LetsEncrypt::Production => "https://acme-v02.api.letsencrypt.org/directory",
|
||||||
|
Loading…
Reference in New Issue
Block a user