opensmtpd-filter-dkimout/src/key.rs
Rodolphe Bréard 9265550fc8 Generate keys
2023-04-09 23:31:16 +02:00

105 lines
2.4 KiB
Rust

use crate::config::Config;
use crate::Algorithm;
use sqlx::types::time::OffsetDateTime;
use sqlx::SqlitePool;
use tokio::time::Duration;
use uuid::Uuid;
const INSERT_KEY: &str = "INSERT INTO key_db (
selector,
sdid,
algorithm,
creation,
not_after,
revocation,
private_key,
public_key
) VALUES (
$1,
$2,
$3,
$4,
$5,
$6,
$7,
$8
)";
const SELECT_LATEST_KEY: &str = "SELECT not_after
FROM key_db
WHERE
sdid = $1
AND algorithm = $2
ORDER BY not_after DESC
LIMIT 1";
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);
}
}
durations.sort();
durations.reverse();
durations.pop().unwrap_or(Duration::from_secs(3600))
}
async fn renew_key_if_expired(
db: &SqlitePool,
cnf: &Config,
domain: &str,
algorithm: Algorithm,
expiration: Duration,
) -> Result<Duration, ()> {
let res: Option<(OffsetDateTime,)> = sqlx::query_as(SELECT_LATEST_KEY)
.bind(domain)
.bind(algorithm.to_string())
.fetch_optional(db)
.await
.map_err(|_| ())?;
match res {
Some((not_after,)) => {
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();
sqlx::query(INSERT_KEY)
.bind(selector)
.bind(domain)
.bind(algorithm.to_string())
.bind(now)
.bind(not_after)
.bind(revocation)
.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(())
}