#![warn(missing_docs)]
#[cfg(not(any(feature = "ring", feature = "aws-lc-rs")))]
compile_error!("enable either the `ring` or `aws-lc-rs` feature");
use std::sync::atomic::AtomicU16;
pub static HTTP_PORT: AtomicU16 = AtomicU16::new(80);
pub static HTTPS_PORT: AtomicU16 = AtomicU16::new(443);
pub static HTTP_CHALLENGE_PORT: AtomicU16 = AtomicU16::new(80);
pub static TLS_ALPN_CHALLENGE_PORT: AtomicU16 = AtomicU16::new(443);
pub mod acme;
pub mod cache;
pub mod cert_store;
pub mod certificate;
pub mod clock;
pub mod config;
pub mod crypto;
pub mod dnsutil;
pub mod error;
pub mod events;
pub mod handshake;
pub mod http;
pub mod http_handler;
pub mod https;
pub mod issuer;
#[cfg(feature = "local-cache")]
pub mod localcache;
pub mod maintain;
pub mod ocsp;
pub mod pem;
pub mod ratelimiter;
pub mod runtime;
pub mod singleflight;
pub mod solvers;
pub mod storage;
pub mod tls_integration;
#[cfg(feature = "zerossl")]
pub mod zerossl;
pub use acme::acme_issuer::{
GOOGLE_TRUST_PRODUCTION_CA, GOOGLE_TRUST_STAGING_CA, LETS_ENCRYPT_PRODUCTION_CA,
LETS_ENCRYPT_STAGING_CA, ZEROSSL_PRODUCTION_CA,
};
pub use acme::acme_issuer::{
LETS_ENCRYPT_PRODUCTION_CA as LETS_ENCRYPT_PRODUCTION,
LETS_ENCRYPT_STAGING_CA as LETS_ENCRYPT_STAGING, ZEROSSL_PRODUCTION_CA as ZEROSSL_PRODUCTION,
};
pub use acme::order::ari_cert_id;
pub use acme::{
Account, AccountKey, AcmeClient, AcmeIssuer, AcmeIssuerBuilder, Directory, DirectoryMeta,
EabCredentials, Jwk, RenewalInfoResponse, SignatureAlgorithm, SuggestedWindow,
prompt_user_agreement, prompt_user_agreement_with_io, prompt_user_for_email,
prompt_user_for_email_with_io,
};
pub use cache::{Cache, CacheEvent, CacheOptions, SubjectIssuer};
pub type CertCache = Cache;
pub use acme::acme_issuer::ChainPreference;
#[cfg(feature = "remote-cert-store")]
pub use cert_store::remote::{ImmutableBlobStore, RemoteCertStore};
#[cfg(feature = "s3-cert-store")]
pub use cert_store::s3::{S3BlobStore, S3BlobStoreOptions, S3CertStore};
#[cfg(feature = "secrets-manager-cert-store")]
pub use cert_store::secrets_manager::{
SecretsManagerBlobStore, SecretsManagerCertStore, SecretsManagerOptions,
};
#[cfg(feature = "vault-cert-store")]
pub use cert_store::vault::{VaultCertStore, VaultKv2BlobStore, VaultKv2BlobStoreOptions};
pub use cert_store::{CertStore, KeyValueCertStore};
pub use certificate::{
Certificate, RenewalInfo, RenewalWindow, cert_needs_renewal, currently_in_renewal_window,
make_certificate, match_wildcard, normalize_sni, normalized_name, subject_is_internal,
subject_is_ip, subject_qualifies_for_cert, subject_qualifies_for_public_cert,
};
pub use config::{
CertificateSelector, Config, ConfigBuilder, ConfigOptions, DefaultCertificateSelector,
IssuerPolicy, Manager, OcspConfig, OnDemandConfig, Policy,
};
pub use crypto::{KeyGenerator, KeyType, StandardKeyGenerator};
pub use error::{Error, Result};
pub use https::{
HttpsRedirectHandler, HttpsRequest, HttpsResponse, https, https_on, start_https_redirect,
start_https_redirect_to_host, start_https_redirect_with_port,
};
pub use issuer::{
CertificateResource, Csr, IssuedCertificate, Issuer, PreChecker, RevocationReason, Revoker,
as_acme_issuer,
};
pub use maintain::{MaintenanceConfig, start_maintenance, stop_maintenance};
#[derive(Clone)]
pub struct CertManager(std::sync::Arc<Config>);
impl std::fmt::Debug for CertManager {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("CertManager").field(&self.0).finish()
}
}
impl CertManager {
#[must_use]
pub fn builder() -> CertManagerBuilder {
CertManagerBuilder(ConfigBuilder::default())
}
pub async fn manage(&self, domains: &[String]) -> Result<()> {
let ct = tokio_util::sync::CancellationToken::new();
self.0.manage_sync(&ct, domains).await
}
pub async fn manage_in_background(&self, domains: &[String]) -> Result<()> {
let ct = tokio_util::sync::CancellationToken::new();
self.0.manage_async(&ct, domains).await
}
#[must_use]
pub fn config(&self) -> &std::sync::Arc<Config> {
&self.0
}
}
impl std::ops::Deref for CertManager {
type Target = Config;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl AsRef<Config> for CertManager {
fn as_ref(&self) -> &Config {
&self.0
}
}
impl From<std::sync::Arc<Config>> for CertManager {
fn from(config: std::sync::Arc<Config>) -> Self {
Self(config)
}
}
#[derive(Clone, Debug, Default)]
pub struct CertManagerBuilder(ConfigBuilder);
impl CertManagerBuilder {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn options(mut self, options: ConfigOptions) -> Self {
self.0 = self.0.options(options);
self
}
#[must_use]
pub fn policy(mut self, policy: Policy) -> Self {
self.0 = self.0.policy(policy);
self
}
#[must_use]
pub fn cache(mut self, cache: std::sync::Arc<Cache>) -> Self {
self.0 = self.0.cache(cache);
self
}
#[must_use]
pub fn cache_options(mut self, options: CacheOptions) -> Self {
self.0 = self.0.cache_options(options);
self
}
#[must_use]
pub fn storage(mut self, storage: std::sync::Arc<dyn Storage>) -> Self {
self.0 = self.0.storage(storage);
self
}
#[must_use]
pub fn cert_store(mut self, store: std::sync::Arc<dyn CertStore>) -> Self {
self.0 = self.0.cert_store(store);
self
}
#[must_use]
pub fn certificates(self, store: std::sync::Arc<dyn CertStore>) -> Self {
self.cert_store(store)
}
#[must_use]
pub fn issuers(mut self, issuers: Vec<std::sync::Arc<dyn CertIssuer>>) -> Self {
self.0 = self.0.issuers(issuers);
self
}
#[must_use]
pub fn on_demand<T>(mut self, on_demand: T) -> Self
where
T: Into<OnDemandConfig>,
{
self.0 = self.0.on_demand(on_demand);
self
}
#[must_use]
pub fn on_event(mut self, on_event: crate::events::OnEventFn) -> Self {
self.0 = self.0.on_event(on_event);
self
}
#[must_use]
pub fn should_emit(mut self, should_emit: crate::events::ShouldEmitFn) -> Self {
self.0 = self.0.should_emit(should_emit);
self
}
#[must_use]
pub fn cert_selection(mut self, selector: std::sync::Arc<dyn CertificateSelector>) -> Self {
self.0 = self.0.cert_selection(selector);
self
}
#[must_use]
pub fn subject_transformer(mut self, transformer: crate::config::SubjectTransformerFn) -> Self {
self.0 = self.0.subject_transformer(transformer);
self
}
#[must_use]
pub fn key_type(mut self, key_type: KeyType) -> Self {
self.0 = self.0.key_type(key_type);
self
}
#[must_use]
pub fn ocsp(mut self, ocsp: OcspConfig) -> Self {
self.0 = self.0.ocsp(ocsp);
self
}
#[must_use]
pub fn reuse_private_keys(mut self, reuse: bool) -> Self {
self.0 = self.0.reuse_private_keys(reuse);
self
}
#[must_use]
pub fn renewal_window_ratio(mut self, ratio: f64) -> Self {
self.0 = self.0.renewal_window_ratio(ratio);
self
}
#[must_use]
pub fn default_server_name(mut self, name: impl Into<String>) -> Self {
self.0 = self.0.default_server_name(name);
self
}
#[must_use]
pub fn fallback_server_name(mut self, name: impl Into<String>) -> Self {
self.0 = self.0.fallback_server_name(name);
self
}
#[must_use]
pub fn disable_storage_check(mut self, disable: bool) -> Self {
self.0 = self.0.disable_storage_check(disable);
self
}
#[must_use]
pub fn disable_ari(mut self, disable: bool) -> Self {
self.0 = self.0.disable_ari(disable);
self
}
#[must_use]
pub fn issuer_policy(mut self, policy: IssuerPolicy) -> Self {
self.0 = self.0.issuer_policy(policy);
self
}
#[must_use]
pub fn must_staple(mut self, must_staple: bool) -> Self {
self.0 = self.0.must_staple(must_staple);
self
}
pub fn build(self) -> Result<CertManager> {
self.try_build()
}
pub fn try_build(self) -> Result<CertManager> {
self.0.build().map(CertManager)
}
pub fn build_or_panic(self) -> CertManager {
self.try_build()
.expect("CertManagerBuilder configuration must be valid")
}
}
pub type CertResolver = tls_integration::CertmagicResolver;
pub use config::Manager as CertificateManager;
pub use issuer::Issuer as CertIssuer;
pub use solvers::Solver;
pub use solvers::distributed::DistributedSolver;
pub use solvers::dns::Dns01Solver;
pub use solvers::dns::{DnsProvider, DnsResolver};
pub use solvers::http::Http01Solver;
pub use solvers::tls_alpn::{ChallengeCert, TlsAlpnSolver};
pub type TlsAlpn01Solver = TlsAlpnSolver;
pub use ratelimiter::RingBufferRateLimiter;
pub use solvers::http::{
HttpChallengeHandler, HttpChallengeMap, extract_http_challenge_token, handle_http_challenge,
handle_http_challenge_request, handle_http_challenge_request_async,
handle_http_challenge_request_blindly, is_valid_http_challenge_token,
looks_like_http_challenge, solve_http_challenge_blindly,
};
#[cfg(feature = "file-storage")]
pub use storage::FileStorage;
pub use storage::{
CERTS_PREFIX, CleanStorageOptions, KeyBuilder, KeyInfo, KeyValue, LockGuard, LockHandle,
Locker, OCSP_PREFIX, STORAGE_KEYS, Storage, StorageKeys, account_key_prefix,
account_private_key, account_registration, acme_ca_prefix, acme_hosts_prefix, acquire,
acquire_lock, acquire_with_timeout, certs_prefix, certs_site_prefix, clean_up_own_locks,
cleanup_own_locks, issuer_key, legacy_account_key_prefix, load_certificate, locks_key,
ocsp_key, ocsp_staple_key, safe_key, site_cert_key, site_meta_key, site_private_key,
store_certificate, store_tx, track_lock, try_acquire, try_acquire_lock, untrack_lock,
};
#[cfg(feature = "etcd-storage")]
pub use storage::{EtcdStorage, EtcdStorageOptions, EtcdTlsOptions};
#[cfg(feature = "redis-storage")]
pub use storage::{RedisStorage, RedisStorageOptions};
pub type RateLimiter = RingBufferRateLimiter;
pub use tls_integration::install_default_provider;
pub use tls_integration::install_default_provider as install_default_crypto_provider;
pub use tls_integration::{CertmagicAcceptor, CertmagicResolver, ResolverSource};
#[cfg(feature = "zerossl")]
pub use zerossl::{
ZEROSSL_HTTP_VALIDATION_PREFIX, ZeroSslApiIssuer, handle_zerossl_http_validation,
looks_like_zerossl_http_validation,
};
#[cfg(feature = "zerossl")]
pub use zerossl::{ZeroSslIssuer, ZeroSslIssuerBuilder};
pub static USER_AGENT: std::sync::OnceLock<String> = std::sync::OnceLock::new();
#[must_use]
pub fn user_agent() -> &'static str {
USER_AGENT.get_or_init(|| concat!("certmagic-rs/", env!("CARGO_PKG_VERSION")).to_string())
}
#[must_use]
pub fn default_config() -> ConfigOptions {
ConfigOptions::default()
}
#[must_use]
pub fn default_acme_issuer() -> AcmeIssuer {
AcmeIssuer::lets_encrypt()
}
pub fn set_user_agent(ua: String) -> bool {
USER_AGENT.set(ua).is_ok()
}
pub async fn manage_sync(domain_names: &[String]) -> Result<()> {
let ct = tokio_util::sync::CancellationToken::new();
Config::new_default()?.manage_sync(&ct, domain_names).await
}
pub async fn manage(domain_names: &[String]) -> Result<rustls::ServerConfig> {
tls(domain_names).await
}
pub async fn manage_async(
ct: &tokio_util::sync::CancellationToken,
domain_names: &[String],
) -> Result<()> {
Config::new_default()?.manage_async(ct, domain_names).await
}
pub async fn obtain_cert_sync(domain: &str) -> Result<()> {
let ct = tokio_util::sync::CancellationToken::new();
Config::new_default()?
.obtain_cert_sync(&ct, domain)
.await
.map(|_| ())
}
pub async fn obtain(domain: &str) -> Result<()> {
obtain_cert_sync(domain).await
}
pub fn obtain_in_background(domain: impl AsRef<str>) -> Result<()> {
let config = Config::new_default()?;
config.obtain_in_background(domain.as_ref());
Ok(())
}
pub async fn tls(domain_names: &[String]) -> Result<rustls::ServerConfig> {
let ct = tokio_util::sync::CancellationToken::new();
let config = Config::new_default()?;
config.manage_sync(&ct, domain_names).await?;
config.tls_config()
}
pub async fn server_config(domain_names: &[String]) -> Result<rustls::ServerConfig> {
tls(domain_names).await
}
pub async fn listen(
domain_names: &[String],
) -> Result<(tokio::net::TcpListener, tls_integration::CertmagicAcceptor)> {
let port = HTTPS_PORT.load(std::sync::atomic::Ordering::Relaxed);
listen_on(
std::net::SocketAddr::from(([0, 0, 0, 0], port)),
domain_names,
)
.await
}
pub async fn listen_on(
addr: std::net::SocketAddr,
domain_names: &[String],
) -> Result<(tokio::net::TcpListener, tls_integration::CertmagicAcceptor)> {
let cache = config::default_cache();
let options = ConfigOptions::default();
let config = Config::new(cache, options)?;
let ct = tokio_util::sync::CancellationToken::new();
config.manage_sync(&ct, domain_names).await?;
let acceptor = config.certmagic_acceptor()?;
let listener = tokio::net::TcpListener::bind(addr)
.await
.map_err(Error::from)?;
Ok((listener, acceptor))
}
pub async fn listen_acceptor(
domain_names: &[String],
addr: &str,
) -> Result<tokio_rustls::TlsAcceptor> {
let tls_config = tls(domain_names).await?;
tokio::net::TcpListener::bind(addr).await.map_err(|error| {
Error::Config(crate::error::ConfigError::Invalid(format!(
"failed to bind listener on {addr}: {error}"
)))
})?;
Ok(tokio_rustls::TlsAcceptor::from(std::sync::Arc::new(
tls_config,
)))
}
pub async fn listen_with_addr(
domain_names: &[String],
addr: &str,
) -> Result<tokio_rustls::TlsAcceptor> {
listen_acceptor(domain_names, addr).await
}
#[cfg(all(test, feature = "file-storage"))]
#[path = "../tests/support/csr.rs"]
pub(crate) mod test_csr;