systemprompt_security/keys/
authority.rs1use std::path::PathBuf;
10use std::sync::OnceLock;
11
12use jsonwebtoken::{DecodingKey, EncodingKey};
13use rsa::pkcs1::{EncodeRsaPrivateKey, EncodeRsaPublicKey};
14use thiserror::Error;
15
16use crate::keys::{KeyError, RsaSigningKey};
17
18#[derive(Debug, Error)]
19pub enum TokenAuthorityError {
20 #[error("signing_key_path is not configured")]
21 PathMissing,
22
23 #[error("signing key file not found at {0}")]
24 FileMissing(PathBuf),
25
26 #[error("config unavailable: {0}")]
27 Config(String),
28
29 #[error("key load failed: {0}")]
30 Key(#[from] KeyError),
31
32 #[error("jwt key conversion failed: {0}")]
33 KeyConvert(#[source] jsonwebtoken::errors::Error),
34
35 #[error("RSA DER encoding failed: {0}")]
36 Pkcs1Encode(#[source] rsa::pkcs1::Error),
37}
38
39pub type TokenAuthorityResult<T> = Result<T, TokenAuthorityError>;
40
41#[allow(clippy::struct_field_names)]
42struct Authority {
43 signing_key: RsaSigningKey,
44 encoding_key: EncodingKey,
45 decoding_key: DecodingKey,
46}
47
48static CELL: OnceLock<Authority> = OnceLock::new();
49
50fn load() -> TokenAuthorityResult<Authority> {
51 let config = systemprompt_models::Config::get()
52 .map_err(|e| TokenAuthorityError::Config(e.to_string()))?;
53 let path = &config.signing_key_path;
54 if path.as_os_str().is_empty() {
55 return Err(TokenAuthorityError::PathMissing);
56 }
57 if !path.exists() {
58 return Err(TokenAuthorityError::FileMissing(path.clone()));
59 }
60 let signing_key = RsaSigningKey::load_from_pem_file(path)?;
61 build(signing_key)
62}
63
64fn build(signing_key: RsaSigningKey) -> TokenAuthorityResult<Authority> {
65 let der = signing_key
66 .private_key()
67 .to_pkcs1_der()
68 .map_err(TokenAuthorityError::Pkcs1Encode)?;
69 let encoding_key = EncodingKey::from_rsa_der(der.as_bytes());
70 let pub_der = signing_key
71 .public_key()
72 .to_pkcs1_der()
73 .map_err(TokenAuthorityError::Pkcs1Encode)?;
74 let decoding_key = DecodingKey::from_rsa_der(pub_der.as_bytes());
75 Ok(Authority {
76 signing_key,
77 encoding_key,
78 decoding_key,
79 })
80}
81
82fn authority() -> TokenAuthorityResult<&'static Authority> {
83 if let Some(a) = CELL.get() {
84 return Ok(a);
85 }
86 let a = load()?;
87 drop(CELL.set(a));
88 CELL.get().ok_or(TokenAuthorityError::PathMissing)
89}
90
91pub fn signing_key() -> TokenAuthorityResult<&'static RsaSigningKey> {
92 Ok(&authority()?.signing_key)
93}
94
95pub fn encoding_key() -> TokenAuthorityResult<&'static EncodingKey> {
96 Ok(&authority()?.encoding_key)
97}
98
99pub fn active_kid() -> TokenAuthorityResult<&'static str> {
100 Ok(authority()?.signing_key.kid())
101}
102
103pub fn decoding_key_for_kid(kid: &str) -> TokenAuthorityResult<Option<&'static DecodingKey>> {
104 let a = authority()?;
105 if a.signing_key.kid() == kid {
106 Ok(Some(&a.decoding_key))
107 } else {
108 Ok(None)
109 }
110}
111
112pub fn decoding_key() -> TokenAuthorityResult<&'static DecodingKey> {
113 Ok(&authority()?.decoding_key)
114}
115
116#[doc(hidden)]
117pub fn install_for_test(key: RsaSigningKey) {
118 if CELL.get().is_some() {
119 return;
120 }
121 if let Ok(a) = build(key) {
122 drop(CELL.set(a));
123 }
124}