#![cfg_attr(test, allow(clippy::unwrap_used))]
#[macro_use]
mod cfg;
mod config;
mod listener;
use std::collections::HashMap;
use std::sync::Arc;
pub use config::{AcmeConfig, AcmeConfigBuilder};
pub use listener::{AcmeAcceptor, AcmeListenerBuilder};
use salvo_core::conn::tcp::TcpListener;
use salvo_core::http::StatusError;
use salvo_core::{Depot, FlowCtrl, Handler, Request, Response, async_trait};
use tokio::net::ToSocketAddrs;
use tokio::sync::RwLock;
cfg_feature! {
#![feature = "quinn"]
pub use listener::AcmeQuinnListener;
}
pub use certon;
pub use certon::{
AcmeIssuer, AcmeIssuerBuilder, CertCache, CertIssuer, CertResolver, Certificate,
Config as CertonConfig, ConfigBuilder as CertonConfigBuilder, DistributedSolver, Dns01Solver,
DnsProvider, FileStorage, Http01Solver, IssuedCertificate, IssuerPolicy, KeyType,
MaintenanceConfig, Manager, OcspConfig, OnDemandConfig, PreChecker, Revoker, Solver, Storage,
TlsAlpn01Solver, ZeroSslIssuer,
};
pub const LETS_ENCRYPT_PRODUCTION: &str = certon::LETS_ENCRYPT_PRODUCTION;
pub const LETS_ENCRYPT_STAGING: &str = certon::LETS_ENCRYPT_STAGING;
pub const ZEROSSL_PRODUCTION: &str = certon::ZEROSSL_PRODUCTION;
pub(crate) const WELL_KNOWN_PATH: &str = "/.well-known/acme-challenge";
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[non_exhaustive]
pub enum ChallengeType {
Http01,
TlsAlpn01,
Dns01,
}
pub struct Http01Handler {
pub(crate) keys: Arc<RwLock<HashMap<String, String>>>,
}
impl std::fmt::Debug for Http01Handler {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Http01Handler").finish()
}
}
#[async_trait]
impl Handler for Http01Handler {
async fn handle(
&self,
req: &mut Request,
_depot: &mut Depot,
res: &mut Response,
_ctrl: &mut FlowCtrl,
) {
if let Some(token) = req.params().get("token") {
let keys = self.keys.read().await;
if let Some(value) = keys.get(token) {
res.render(value);
return;
}
drop(keys);
if let Some(value) = certon::solvers::get_active_challenge(token) {
res.render(value);
return;
}
tracing::error!(token, "key not found for ACME challenge token");
res.render(token);
} else {
res.render(StatusError::not_found().brief("missing token"));
}
}
}
pub trait AcmeListener {
fn acme(self) -> AcmeListenerBuilder<Self>
where
Self: Sized;
}
impl<T> AcmeListener for TcpListener<T>
where
T: ToSocketAddrs + Send + 'static,
{
fn acme(self) -> AcmeListenerBuilder<Self> {
AcmeListenerBuilder::new(self)
}
}