1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
//! ACME supports.
//!
//! Reference: <https://datatracker.ietf.org/doc/html/rfc8555>
//! Reference: <https://datatracker.ietf.org/doc/html/rfc8737>
//!
//! * HTTP-01
//!
//! # Example
//!
//! ```no_run
//! use salvo_core::prelude::*;
//!
//! #[handler]
//! async fn hello() -> &'static str {
//! "Hello World"
//! }
//!
//! #[tokio::main]
//! async fn main() {
//! let mut router = Router::new().get(hello);
//! let listener = TcpListener::new("0.0.0.0:443")
//! .acme()
//! // .directory("letsencrypt", salvo::conn::acme::LETS_ENCRYPT_STAGING)
//! .cache_path("acme/letsencrypt")
//! .add_domain("acme-http01.salvo.rs")
//! .http01_challege(&mut router);
//! let acceptor = listener.join(TcpListener::new("0.0.0.0:80")).bind().await;
//! Server::new(acceptor).serve(router).await;
//! }
//! ```
//!
//! * TLS ALPN-01
//!
//! # Example
//!
//! ```no_run
//! use salvo_core::prelude::*;
//!
//! #[handler]
//! async fn hello() -> &'static str {
//! "Hello World"
//! }
//!
//! #[tokio::main]
//! async fn main() {
//! let router = Router::new().get(hello);
//! let acceptor = TcpListener::new("0.0.0.0:443")
//! .acme()
//! // .directory("letsencrypt", salvo::conn::acme::LETS_ENCRYPT_STAGING)
//! .cache_path("acme/letsencrypt")
//! .add_domain("acme-tls-alpn01.salvo.rs")
//! .bind().await;
//! Server::new(acceptor).serve(router).await;
//! }
//! ```
pub mod cache;
mod client;
mod config;
mod issuer;
mod jose;
mod key_pair;
mod listener;
mod resolver;
use std::collections::HashMap;
use std::fmt::{self, Display, Formatter};
use std::sync::Arc;
use client::AcmeClient;
use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
use crate::http::StatusError;
use crate::{async_trait, Depot, FlowCtrl, Handler, Request, Response};
use cache::AcmeCache;
pub use config::{AcmeConfig, AcmeConfigBuilder};
pub use listener::AcmeListener;
cfg_feature! {
#![feature = "quinn"]
pub use listener::AcmeQuinnListener;
}
/// Letsencrypt production directory url
pub const LETS_ENCRYPT_PRODUCTION: &str = "https://acme-v02.api.letsencrypt.org/directory";
/// Letsencrypt staging directory url
pub const LETS_ENCRYPT_STAGING: &str = "https://acme-staging-v02.api.letsencrypt.org/directory";
/// Well known acme challenge path
pub(crate) const WELL_KNOWN_PATH: &str = "/.well-known/acme-challenge";
/// HTTP-01 challenge
const CHALLENGE_TYPE_HTTP_01: &str = "http-01";
/// TLS-ALPN-01 challenge
const CHALLENGE_TYPE_TLS_ALPN_01: &str = "tls-alpn-01";
/// Challenge type
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[non_exhaustive]
pub enum ChallengeType {
/// HTTP-01 challenge
///
/// Reference: <https://letsencrypt.org/docs/challenge-types/#http-01-challenge>
Http01,
/// TLS-ALPN-01
///
/// Reference: <https://letsencrypt.org/docs/challenge-types/#tls-alpn-01>
TlsAlpn01,
}
impl Display for ChallengeType {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
ChallengeType::Http01 => f.write_str(CHALLENGE_TYPE_HTTP_01),
ChallengeType::TlsAlpn01 => f.write_str(CHALLENGE_TYPE_TLS_ALPN_01),
}
}
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct Directory {
pub(crate) new_nonce: String,
pub(crate) new_account: String,
pub(crate) new_order: String,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct Identifier {
#[serde(rename = "type")]
pub(crate) kind: String,
pub(crate) value: String,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct Problem {
pub(crate) detail: String,
}
#[derive(Debug, Deserialize)]
pub(crate) struct Challenge {
#[serde(rename = "type")]
pub(crate) kind: String,
pub(crate) url: String,
pub(crate) token: String,
}
/// Handler for `HTTP-01` challenge.
pub(crate) struct Http01Handler {
pub(crate) keys: Arc<RwLock<HashMap<String, String>>>,
}
#[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();
if let Some(value) = keys.get(token) {
res.render(value);
} else {
tracing::error!(token, "keys not found for token");
res.render(token);
}
} else {
res.render(StatusError::not_found().brief("Token is not provide."));
}
}
}