stalwart_lib/http/src/management/
dkim.rs1use std::str::FromStr;
8
9use crate::common::{Server, auth::AccessToken, config::smtp::auth::simple_pem_parse};
10use crate::directory::{Permission, backend::internal::manage};
11use crate::store::write::now;
12use hyper::Method;
13use mail_auth::{
14 common::crypto::{Ed25519Key, RsaKey, Sha256},
15 dkim::generate::DkimKeyPair,
16};
17use mail_builder::encoders::base64::base64_encode;
18use mail_parser::DateTime;
19use pkcs8::Document;
20use rsa::pkcs1::DecodeRsaPublicKey;
21use rustls_pki_types::{PrivateKeyDer, PrivatePkcs1KeyDer};
22use serde::{Deserialize, Serialize};
23use serde_json::json;
24
25use crate::http_proto::{request::decode_path_element, *};
26use std::future::Future;
27
28#[derive(Debug, Serialize, Deserialize, Copy, Clone, PartialEq, Eq)]
29pub enum Algorithm {
30 Rsa,
31 Ed25519,
32}
33
34#[derive(Debug, Serialize, Deserialize)]
35struct DkimSignature {
36 id: Option<String>,
37 algorithm: Algorithm,
38 domain: String,
39 selector: Option<String>,
40}
41
42pub trait DkimManagement: Sync + Send {
43 fn handle_manage_dkim(
44 &self,
45 req: &HttpRequest,
46 path: Vec<&str>,
47 body: Option<Vec<u8>>,
48 access_token: &AccessToken,
49 ) -> impl Future<Output = crate::trc::Result<HttpResponse>> + Send;
50
51 fn handle_get_public_key(
52 &self,
53 path: Vec<&str>,
54 ) -> impl Future<Output = crate::trc::Result<HttpResponse>> + Send;
55
56 fn handle_create_signature(
57 &self,
58 body: Option<Vec<u8>>,
59 ) -> impl Future<Output = crate::trc::Result<HttpResponse>> + Send;
60
61 fn create_dkim_key(
62 &self,
63 algo: Algorithm,
64 id: impl AsRef<str> + Send,
65 domain: impl Into<String> + Send,
66 selector: impl Into<String> + Send,
67 ) -> impl Future<Output = crate::trc::Result<()>> + Send;
68}
69
70impl DkimManagement for Server {
71 async fn handle_manage_dkim(
72 &self,
73 req: &HttpRequest,
74 path: Vec<&str>,
75 body: Option<Vec<u8>>,
76 access_token: &AccessToken,
77 ) -> crate::trc::Result<HttpResponse> {
78 match *req.method() {
79 Method::GET => {
80 access_token.assert_has_permission(Permission::DkimSignatureGet)?;
82
83 self.handle_get_public_key(path).await
84 }
85 Method::POST => {
86 access_token.assert_has_permission(Permission::DkimSignatureCreate)?;
88
89 self.handle_create_signature(body).await
90 }
91 _ => Err(crate::trc::ResourceEvent::NotFound.into_err()),
92 }
93 }
94
95 async fn handle_get_public_key(&self, path: Vec<&str>) -> crate::trc::Result<HttpResponse> {
96 let signature_id = match path.get(1) {
97 Some(signature_id) => decode_path_element(signature_id),
98 None => {
99 return Err(crate::trc::ResourceEvent::NotFound.into_err());
100 }
101 };
102
103 let (pk, algo) = match (
104 self.core
105 .storage
106 .config
107 .get(&format!("signature.{signature_id}.private-key"))
108 .await,
109 self.core
110 .storage
111 .config
112 .get(&format!("signature.{signature_id}.algorithm"))
113 .await
114 .map(|algo| algo.and_then(|algo| algo.parse::<Algorithm>().ok())),
115 ) {
116 (Ok(Some(pk)), Ok(Some(algorithm))) => (pk, algorithm),
117 (Err(err), _) | (_, Err(err)) => return Err(err.caused_by(crate::trc::location!())),
118 _ => return Err(crate::trc::ResourceEvent::NotFound.into_err()),
119 };
120
121 Ok(JsonResponse::new(json!({
122 "data": obtain_dkim_public_key(algo, &pk)?,
123 }))
124 .into_http_response())
125 }
126
127 async fn handle_create_signature(
128 &self,
129 body: Option<Vec<u8>>,
130 ) -> crate::trc::Result<HttpResponse> {
131 let request =
132 match serde_json::from_slice::<DkimSignature>(body.as_deref().unwrap_or_default()) {
133 Ok(request) => request,
134 Err(err) => {
135 return Err(crate::trc::EventType::Resource(
136 crate::trc::ResourceEvent::BadParameters,
137 )
138 .reason(err));
139 }
140 };
141
142 let algo_str = match request.algorithm {
143 Algorithm::Rsa => "rsa",
144 Algorithm::Ed25519 => "ed25519",
145 };
146 let id = request
147 .id
148 .unwrap_or_else(|| format!("{algo_str}-{}", request.domain));
149 let selector = request.selector.unwrap_or_else(|| {
150 let dt = DateTime::from_timestamp(now() as i64);
151 format!(
152 "{:04}{:02}{}",
153 dt.year,
154 dt.month,
155 if Algorithm::Rsa == request.algorithm {
156 "r"
157 } else {
158 "e"
159 }
160 )
161 });
162
163 if let Some(value) = self
165 .core
166 .storage
167 .config
168 .get(&format!("signature.{id}.private-key"))
169 .await?
170 {
171 return Err(manage::err_exists(
172 format!("signature.{id}.private-key"),
173 value,
174 ));
175 }
176
177 self.create_dkim_key(request.algorithm, id, request.domain, selector)
179 .await?;
180
181 Ok(JsonResponse::new(json!({
182 "data": (),
183 }))
184 .into_http_response())
185 }
186
187 async fn create_dkim_key(
188 &self,
189 algo: Algorithm,
190 id: impl AsRef<str>,
191 domain: impl Into<String>,
192 selector: impl Into<String>,
193 ) -> crate::trc::Result<()> {
194 let id = id.as_ref();
195 let (algorithm, pk_type) = match algo {
196 Algorithm::Rsa => ("rsa-sha256", "RSA PRIVATE KEY"),
197 Algorithm::Ed25519 => ("ed25519-sha256", "PRIVATE KEY"),
198 };
199 let mut pk = format!("-----BEGIN {pk_type}-----\n").into_bytes();
200 let mut lf_count = 65;
201 for ch in base64_encode(
202 match algo {
203 Algorithm::Rsa => DkimKeyPair::generate_rsa(2048),
204 Algorithm::Ed25519 => DkimKeyPair::generate_ed25519(),
205 }
206 .map_err(|err| {
207 manage::error("Failed to generate key", err.to_string().into())
208 .caused_by(crate::trc::location!())
209 })?
210 .private_key(),
211 )
212 .unwrap_or_default()
213 {
214 pk.push(ch);
215 lf_count -= 1;
216 if lf_count == 0 {
217 pk.push(b'\n');
218 lf_count = 65;
219 }
220 }
221 if lf_count != 65 {
222 pk.push(b'\n');
223 }
224 pk.extend_from_slice(format!("-----END {pk_type}-----\n").as_bytes());
225
226 self.core
227 .storage
228 .config
229 .set(
230 [
231 (
232 format!("signature.{id}.private-key"),
233 String::from_utf8(pk).unwrap(),
234 ),
235 (format!("signature.{id}.domain"), domain.into()),
236 (format!("signature.{id}.selector"), selector.into()),
237 (format!("signature.{id}.algorithm"), algorithm.to_string()),
238 (
239 format!("signature.{id}.canonicalization"),
240 "relaxed/relaxed".to_string(),
241 ),
242 (format!("signature.{id}.headers.0"), "From".to_string()),
243 (format!("signature.{id}.headers.1"), "To".to_string()),
244 (format!("signature.{id}.headers.2"), "Date".to_string()),
245 (format!("signature.{id}.headers.3"), "Subject".to_string()),
246 (
247 format!("signature.{id}.headers.4"),
248 "Message-ID".to_string(),
249 ),
250 (format!("signature.{id}.report"), "false".to_string()),
251 ],
252 true,
253 )
254 .await
255 }
256}
257
258pub fn obtain_dkim_public_key(algo: Algorithm, pk: &str) -> crate::trc::Result<String> {
259 match simple_pem_parse(pk) {
260 Some(der) => match algo {
261 Algorithm::Rsa => match RsaKey::<Sha256>::from_key_der(PrivateKeyDer::Pkcs1(
262 PrivatePkcs1KeyDer::from(der),
263 ))
264 .and_then(|key| {
265 Document::from_pkcs1_der(&key.public_key())
266 .map_err(|err| mail_auth::Error::CryptoError(err.to_string()))
267 }) {
268 Ok(pk) => Ok(
269 String::from_utf8(base64_encode(pk.as_bytes()).unwrap_or_default())
270 .unwrap_or_default(),
271 ),
272 Err(err) => Err(manage::error(
273 "Failed to read RSA DER",
274 err.to_string().into(),
275 )),
276 },
277 Algorithm::Ed25519 => {
278 match Ed25519Key::from_pkcs8_maybe_unchecked_der(&der)
279 .map_err(|err| mail_auth::Error::CryptoError(err.to_string()))
280 {
281 Ok(pk) => Ok(String::from_utf8(
282 base64_encode(&pk.public_key()).unwrap_or_default(),
283 )
284 .unwrap_or_default()),
285 Err(err) => Err(manage::error("Crypto error", err.to_string().into())),
286 }
287 }
288 },
289 None => Err(manage::error("Failed to decode private key", None::<u32>)),
290 }
291}
292
293impl FromStr for Algorithm {
294 type Err = ();
295
296 fn from_str(s: &str) -> Result<Self, Self::Err> {
297 match s.split_once('-').map(|(algo, _)| algo) {
298 Some("rsa") => Ok(Algorithm::Rsa),
299 Some("ed25519") => Ok(Algorithm::Ed25519),
300 _ => Err(()),
301 }
302 }
303}