1use crate::db::{DBError, get_connection};
9use crate::user::User;
10use base64::{Engine as _, engine::general_purpose::STANDARD};
11use rsa::pkcs8::{EncodePrivateKey, EncodePublicKey, LineEnding};
12use rsa::{RsaPrivateKey, RsaPublicKey};
13use sqlx::types::Uuid;
14
15const KEY_BITS: usize = 2048;
19
20pub struct KeyPair {
22 pub private_pem_b64: String,
23 pub public_pem_b64: String,
24}
25
26pub async fn generate() -> Result<KeyPair, DBError> {
35 tokio::task::spawn_blocking(generate_blocking)
36 .await
37 .map_err(|_| DBError::KeyGen)?
38}
39
40fn generate_blocking() -> Result<KeyPair, DBError> {
41 let mut rng = rand::rng();
42 let private = RsaPrivateKey::new(&mut rng, KEY_BITS).map_err(|_| DBError::KeyGen)?;
43 let public = RsaPublicKey::from(&private);
44
45 let private_pem = private
46 .to_pkcs8_pem(LineEnding::LF)
47 .map_err(|_| DBError::KeyGen)?;
48 let public_pem = public
49 .to_public_key_pem(LineEnding::LF)
50 .map_err(|_| DBError::KeyGen)?;
51
52 Ok(KeyPair {
53 private_pem_b64: STANDARD.encode(private_pem.as_bytes()),
54 public_pem_b64: STANDARD.encode(public_pem.as_bytes()),
55 })
56}
57
58pub async fn private_key_for(user_id: Uuid) -> Result<String, DBError> {
67 let user = User { id: user_id };
68 let mut conn = user.get_connection().await?;
69 let key: Option<String> = sqlx::query_scalar("SELECT private_key FROM user_auth_keys LIMIT 1")
70 .fetch_optional(&mut *conn)
71 .await?;
72 key.ok_or(DBError::KeyGen)
73}
74
75pub struct TokenIdentity {
76 pub public_key: String,
77 pub verified: bool,
78 pub generation: i32,
79}
80
81pub async fn public_key_for(user_id: Uuid) -> Result<Option<TokenIdentity>, DBError> {
90 let mut conn = get_connection().await?;
91 let row = sqlx::query!(
92 "SELECT jwt_public_key, verified, session_generation FROM users WHERE id = $1",
93 user_id
94 )
95 .fetch_optional(&mut *conn)
96 .await?;
97
98 Ok(row.and_then(|row| {
99 row.jwt_public_key.map(|public_key| TokenIdentity {
100 public_key,
101 verified: row.verified,
102 generation: row.session_generation,
103 })
104 }))
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110 use rsa::pkcs8::{DecodePrivateKey, DecodePublicKey};
111
112 fn decode_pem(b64: &str) -> String {
113 String::from_utf8(STANDARD.decode(b64).expect("base64")).expect("utf8")
114 }
115
116 #[tokio::test]
117 async fn generate_produces_decodable_rsa_pem_pair() {
118 let pair = generate().await.expect("keygen");
119
120 let priv_pem = decode_pem(&pair.private_pem_b64);
121 let pub_pem = decode_pem(&pair.public_pem_b64);
122 assert!(priv_pem.contains("BEGIN PRIVATE KEY"));
123 assert!(pub_pem.contains("BEGIN PUBLIC KEY"));
124
125 let parsed_priv = RsaPrivateKey::from_pkcs8_pem(&priv_pem).expect("private parses");
129 let parsed_pub = RsaPublicKey::from_public_key_pem(&pub_pem).expect("public parses");
130 assert_eq!(RsaPublicKey::from(&parsed_priv), parsed_pub);
131 }
132
133 #[tokio::test]
134 async fn generate_yields_distinct_keys() {
135 let a = generate().await.expect("keygen a");
136 let b = generate().await.expect("keygen b");
137 assert_ne!(a.private_pem_b64, b.private_pem_b64);
138 assert_ne!(a.public_pem_b64, b.public_pem_b64);
139 }
140}