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