Skip to main content

shared/application/key/
signing_key_service.rs

1use chrono::Utc;
2
3use crate::error::Result;
4
5use crate::config::AlgorithmConfig;
6use crate::domain::model::{SigningKey, SigningKeys};
7use crate::intern::key::KeyService;
8use crate::utils::crypto::JwtSigner;
9
10impl KeyService {
11    fn generate_keypair(&self) -> (String, String) {
12        self.crypto.openssl.gen_prv_pub_key()
13    }
14    fn build_signingkey(&self, private: &str, public: &str) -> Result<SigningKey> {
15        let algorithm = &self.configuration.auth.jwt()?.algorithm;
16        let kty = match algorithm {
17            AlgorithmConfig::EdDSA => "OKP",
18            AlgorithmConfig::ES256 | AlgorithmConfig::ES384 => "EC",
19            AlgorithmConfig::RS256
20            | AlgorithmConfig::RS384
21            | AlgorithmConfig::RS512
22            | AlgorithmConfig::PS256
23            | AlgorithmConfig::PS384
24            | AlgorithmConfig::PS512 => "RSA",
25        };
26
27        let encrypted_private_key = self.crypto.clone().aes.encrypt(private)?;
28        let kid = super::support::jwk_thumbprint_rsa(public);
29
30        Ok(SigningKey::new(&encrypted_private_key, public)
31            .with_algorithm(algorithm.as_str())
32            .with_kid(&kid)
33            .with_kty(kty))
34    }
35
36    #[tracing::instrument(name = "crypto.insert_signing_keys", skip(self))]
37    pub async fn save_new_key(&self) -> Result<String> {
38        let (private, public) = self.generate_keypair();
39        let key = self.build_signingkey(&private, &public)?;
40
41        self.signing_key_repository.insert(key).await
42    }
43
44    #[tracing::instrument(name = "crypto.find_signing_key", skip(self))]
45    pub async fn load_active_key(&self) -> Result<(String, SigningKey)> {
46        let key = self.signing_key_repository.find().await?;
47
48        let private_key = self
49            .crypto
50            .clone()
51            .aes
52            .decrypt(&key.encrypted_private_key)?;
53
54        Ok((private_key, key))
55    }
56
57    #[tracing::instrument(name = "crypto.find_signing_key", skip(self))]
58    pub async fn find_signing_keys(&self) -> Result<Vec<SigningKeys>> {
59        let signing_keys = self.signing_key_repository.find_valid_keys().await?;
60        let mut data: Vec<SigningKeys> = Vec::new();
61
62        for key in signing_keys {
63            let private_key = self
64                .crypto
65                .clone()
66                .aes
67                .decrypt(&key.encrypted_private_key)?;
68            data.push(SigningKeys { private_key, key })
69        }
70
71        Ok(data)
72    }
73
74    #[tracing::instrument(name = "crypto.find_all_keys", skip(self))]
75    pub async fn list_keys(&self) -> Result<Vec<SigningKey>> {
76        self.signing_key_repository.find_all().await
77    }
78
79    #[tracing::instrument(name = "crypto.rotate_signing_key", skip(self))]
80    pub async fn rotate_signing_key(&self) -> Result<(SigningKey, SigningKey)> {
81        // 1.
82        let max_token_ttl = self.configuration.auth.jwt()?.refresh_token_expires_in;
83        let retired_key = self
84            .signing_key_repository
85            .retire_oldkey(max_token_ttl)
86            .await?;
87
88        // 2. 3.
89        self.save_new_key().await?;
90        let (private, active_key) = self.load_active_key().await?;
91
92        // 4.
93        let jwt = JwtSigner::new(&private, &active_key, self.configuration.auth.jwt()?);
94        self.crypto.rotate_jwt(jwt);
95
96        Ok((retired_key, active_key))
97    }
98
99    #[tracing::instrument(name = "signing_key.prune", skip(self))]
100    pub async fn prune(&self) -> Result<Vec<SigningKey>> {
101        let keys = self.list_keys().await?;
102
103        let mut removed_keys: Vec<SigningKey> = Vec::new();
104        for key in keys {
105            let expired = key.expires_at.map(|dt| Utc::now() > dt).unwrap_or(false);
106            if key.status == "revoked" || expired {
107                let id = key.id()?;
108                self.signing_key_repository.delete(id).await?;
109
110                removed_keys.push(key);
111            }
112        }
113
114        Ok(removed_keys)
115    }
116
117    #[tracing::instrument(name = "signing_key.revoke", skip(self))]
118    pub async fn revoke(&self, id: &str) -> Result<SigningKey> {
119        self.signing_key_repository.revoke_key(id).await
120    }
121}