systemprompt_security/keys/
mod.rs1use std::fs;
13use std::path::Path;
14
15use base64::Engine;
16use base64::engine::general_purpose::URL_SAFE_NO_PAD;
17use pkcs8::LineEnding;
18use rsa::pkcs8::{DecodePrivateKey, EncodePrivateKey, EncodePublicKey};
19use rsa::rand_core::OsRng;
20use rsa::{RsaPrivateKey, RsaPublicKey};
21use sha2::{Digest, Sha256};
22
23pub mod authority;
24pub mod jwks;
25pub mod jwks_client;
26
27pub use authority::{TokenAuthorityError, TokenAuthorityResult};
28pub use jwks::{Jwk, Jwks};
29pub use jwks_client::{JwksClient, JwksClientError};
30
31pub const DEFAULT_RSA_BITS: usize = 2048;
32
33#[derive(Debug, thiserror::Error)]
34pub enum KeyError {
35 #[error("RSA key generation failed: {0}")]
36 Generation(#[source] rsa::Error),
37 #[error("PKCS#8 encoding failed: {0}")]
38 Encode(#[source] pkcs8::Error),
39 #[error("SPKI encoding failed: {0}")]
40 EncodeSpki(#[source] pkcs8::spki::Error),
41 #[error("PKCS#8 decoding failed: {0}")]
42 Decode(#[source] pkcs8::Error),
43 #[error("I/O error for {path}: {source}")]
44 Io {
45 path: String,
46 #[source]
47 source: std::io::Error,
48 },
49}
50
51#[derive(Clone)]
52pub struct RsaSigningKey {
53 private_key: RsaPrivateKey,
54 public_key: RsaPublicKey,
55 kid: String,
56}
57
58impl std::fmt::Debug for RsaSigningKey {
59 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60 f.debug_struct("RsaSigningKey")
61 .field("kid", &self.kid)
62 .finish_non_exhaustive()
63 }
64}
65
66impl RsaSigningKey {
67 pub fn generate() -> Result<Self, KeyError> {
68 Self::generate_bits(DEFAULT_RSA_BITS)
69 }
70
71 pub fn generate_bits(bits: usize) -> Result<Self, KeyError> {
72 let mut rng = OsRng;
73 let private_key = RsaPrivateKey::new(&mut rng, bits).map_err(KeyError::Generation)?;
74 Self::from_private(private_key)
75 }
76
77 pub fn from_pkcs8_pem(pem: &str) -> Result<Self, KeyError> {
78 let private_key = RsaPrivateKey::from_pkcs8_pem(pem).map_err(KeyError::Decode)?;
79 Self::from_private(private_key)
80 }
81
82 pub fn load_from_pem_file(path: &Path) -> Result<Self, KeyError> {
83 let pem = fs::read_to_string(path).map_err(|source| KeyError::Io {
84 path: path.display().to_string(),
85 source,
86 })?;
87 Self::from_pkcs8_pem(&pem)
88 }
89
90 pub fn to_pkcs8_pem(&self) -> Result<String, KeyError> {
91 self.private_key
92 .to_pkcs8_pem(LineEnding::LF)
93 .map(|s| s.to_string())
94 .map_err(KeyError::Encode)
95 }
96
97 pub fn write_pem_file(&self, path: &Path) -> Result<(), KeyError> {
98 let pem = self.to_pkcs8_pem()?;
99 fs::write(path, pem).map_err(|source| KeyError::Io {
100 path: path.display().to_string(),
101 source,
102 })
103 }
104
105 pub const fn public_key(&self) -> &RsaPublicKey {
106 &self.public_key
107 }
108
109 pub const fn private_key(&self) -> &RsaPrivateKey {
110 &self.private_key
111 }
112
113 pub fn kid(&self) -> &str {
114 &self.kid
115 }
116
117 pub fn jwk(&self) -> Jwk {
118 Jwk::from_rsa_public_key(&self.public_key, self.kid.clone())
119 }
120
121 pub fn jwks(&self) -> Jwks {
122 Jwks {
123 keys: vec![self.jwk()],
124 }
125 }
126
127 fn from_private(private_key: RsaPrivateKey) -> Result<Self, KeyError> {
128 let public_key = RsaPublicKey::from(&private_key);
129 let kid = compute_kid(&public_key)?;
130 Ok(Self {
131 private_key,
132 public_key,
133 kid,
134 })
135 }
136}
137
138pub fn compute_kid(public_key: &RsaPublicKey) -> Result<String, KeyError> {
139 let der = public_key
140 .to_public_key_der()
141 .map_err(KeyError::EncodeSpki)?;
142 let digest = Sha256::digest(der.as_bytes());
143 Ok(URL_SAFE_NO_PAD.encode(digest))
144}