deltachat/net/tls/
spki.rs1use std::collections::BTreeMap;
8
9use anyhow::Result;
10use base64::Engine as _;
11use parking_lot::RwLock;
12use sha2::{Digest, Sha256};
13use tokio_rustls::rustls::pki_types::SubjectPublicKeyInfoDer;
14
15use crate::sql::Sql;
16use crate::tools::time;
17
18pub fn spki_hash(spki: &SubjectPublicKeyInfoDer) -> String {
30 let spki_hash = Sha256::digest(spki);
31 base64::engine::general_purpose::STANDARD.encode(spki_hash)
32}
33
34#[derive(Debug)]
36pub struct SpkiHashStore {
37 pub hash_store: RwLock<BTreeMap<String, String>>,
39}
40
41impl SpkiHashStore {
42 pub fn new() -> Self {
43 Self {
44 hash_store: RwLock::new(BTreeMap::new()),
45 }
46 }
47
48 pub async fn get_spki_hash(&self, hostname: &str, sql: &Sql) -> Result<Option<String>> {
50 if let Some(hash) = self.hash_store.read().get(hostname).cloned() {
51 return Ok(Some(hash));
52 }
53
54 match sql
55 .query_row_optional(
56 "SELECT spki_hash FROM tls_spki WHERE host=?",
57 (hostname,),
58 |row| {
59 let spki_hash: String = row.get(0)?;
60 Ok(spki_hash)
61 },
62 )
63 .await?
64 {
65 Some(hash) => {
66 self.hash_store
67 .write()
68 .insert(hostname.to_string(), hash.clone());
69 Ok(Some(hash))
70 }
71 None => Ok(None),
72 }
73 }
74
75 pub async fn save_spki(
77 &self,
78 hostname: &str,
79 spki: &SubjectPublicKeyInfoDer<'_>,
80 sql: &Sql,
81 timestamp: i64,
82 ) -> Result<()> {
83 let hash = spki_hash(spki);
84 self.hash_store
85 .write()
86 .insert(hostname.to_string(), hash.clone());
87 sql.execute(
88 "INSERT OR REPLACE INTO tls_spki (host, spki_hash, timestamp) VALUES (?, ?, ?)",
89 (hostname, hash, timestamp),
90 )
91 .await?;
92 Ok(())
93 }
94
95 pub async fn cleanup(&self, sql: &Sql) -> Result<()> {
97 let now = time();
98 let removed_hosts = sql
99 .transaction(|transaction| {
100 let mut stmt = transaction
101 .prepare("DELETE FROM tls_spki WHERE ? > timestamp + ? RETURNING host")?;
102 let mut res = Vec::new();
103 for row in stmt.query_map((now, 30 * 24 * 60 * 60), |row| {
104 let host: String = row.get(0)?;
105 Ok(host)
106 })? {
107 res.push(row?);
108 }
109
110 transaction.execute(
114 "UPDATE tls_spki SET timestamp = ?1 WHERE timestamp > ?1",
115 (now,),
116 )?;
117
118 Ok(res)
119 })
120 .await?;
121
122 let mut lock = self.hash_store.write();
123 for host in removed_hosts {
124 lock.remove(&host);
130 }
131 Ok(())
132 }
133}