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