2023-04-09 17:21:17 +02:00
|
|
|
use crate::config::Config;
|
2023-04-09 23:31:16 +02:00
|
|
|
use crate::Algorithm;
|
|
|
|
use sqlx::types::time::OffsetDateTime;
|
2023-04-09 17:21:17 +02:00
|
|
|
use sqlx::SqlitePool;
|
2023-04-09 23:31:16 +02:00
|
|
|
use tokio::time::Duration;
|
|
|
|
use uuid::Uuid;
|
2023-04-09 17:21:17 +02:00
|
|
|
|
2023-04-09 23:31:16 +02:00
|
|
|
pub async fn key_rotation(db: &SqlitePool, cnf: &Config) -> Duration {
|
|
|
|
let mut durations = Vec::with_capacity(cnf.domains().len());
|
|
|
|
let expiration = cnf
|
|
|
|
.expiration()
|
|
|
|
.map(Duration::from_secs)
|
|
|
|
.unwrap_or_else(|| Duration::from_secs(cnf.cryptoperiod().get() / 10));
|
|
|
|
for domain in cnf.domains() {
|
|
|
|
if let Ok(d) = renew_key_if_expired(db, cnf, domain, cnf.algorithm(), expiration).await {
|
|
|
|
durations.push(d);
|
|
|
|
}
|
|
|
|
}
|
2023-04-10 11:18:11 +02:00
|
|
|
durations.push(Duration::from_secs(crate::KEY_CHECK_MIN_DELAY));
|
2023-04-09 23:31:16 +02:00
|
|
|
durations.sort();
|
2023-04-10 11:18:11 +02:00
|
|
|
durations[durations.len() - 1]
|
2023-04-09 23:31:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
async fn renew_key_if_expired(
|
|
|
|
db: &SqlitePool,
|
|
|
|
cnf: &Config,
|
|
|
|
domain: &str,
|
|
|
|
algorithm: Algorithm,
|
|
|
|
expiration: Duration,
|
|
|
|
) -> Result<Duration, ()> {
|
2023-04-10 11:21:53 +02:00
|
|
|
let res: Option<(i64,)> = sqlx::query_as(crate::db::SELECT_LATEST_KEY)
|
2023-04-09 23:31:16 +02:00
|
|
|
.bind(domain)
|
|
|
|
.bind(algorithm.to_string())
|
|
|
|
.fetch_optional(db)
|
|
|
|
.await
|
|
|
|
.map_err(|_| ())?;
|
|
|
|
match res {
|
|
|
|
Some((not_after,)) => {
|
2023-04-10 00:30:31 +02:00
|
|
|
let not_after = OffsetDateTime::from_unix_timestamp(not_after).map_err(|_| ())?;
|
2023-04-09 23:31:16 +02:00
|
|
|
log::debug!("{domain}: key is valid until {not_after}");
|
|
|
|
if not_after - expiration <= OffsetDateTime::now_utc() {
|
|
|
|
generate_key(db, cnf, domain, algorithm).await?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
None => {
|
|
|
|
log::debug!("no key found for domain {domain}");
|
|
|
|
generate_key(db, cnf, domain, algorithm).await?;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
Ok(Duration::from_secs(10))
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn generate_key(
|
|
|
|
db: &SqlitePool,
|
|
|
|
cnf: &Config,
|
|
|
|
domain: &str,
|
|
|
|
algorithm: Algorithm,
|
|
|
|
) -> Result<(), ()> {
|
|
|
|
let selector = format!("dkim-{}", Uuid::new_v4().simple());
|
|
|
|
let now = OffsetDateTime::now_utc();
|
|
|
|
let not_after = now + Duration::from_secs(cnf.cryptoperiod().get());
|
|
|
|
let revocation = not_after + Duration::from_secs(cnf.revocation());
|
|
|
|
let (priv_key, pub_key) = algorithm.gen_keys();
|
2023-04-10 11:21:53 +02:00
|
|
|
sqlx::query(crate::db::INSERT_KEY)
|
2023-04-09 23:31:16 +02:00
|
|
|
.bind(selector)
|
|
|
|
.bind(domain)
|
|
|
|
.bind(algorithm.to_string())
|
2023-04-10 00:30:31 +02:00
|
|
|
.bind(now.unix_timestamp())
|
|
|
|
.bind(not_after.unix_timestamp())
|
|
|
|
.bind(revocation.unix_timestamp())
|
2023-04-09 23:31:16 +02:00
|
|
|
.bind(priv_key)
|
|
|
|
.bind(pub_key)
|
|
|
|
.execute(db)
|
|
|
|
.await
|
|
|
|
.map_err(|_| ())?;
|
|
|
|
// TODO: dns_update_cmd
|
|
|
|
log::debug!("{domain}: new {} key generated", algorithm.to_string());
|
|
|
|
Ok(())
|
2023-04-09 17:21:17 +02:00
|
|
|
}
|