this repo has no description
1#[allow(deprecated)]
2use aes_gcm::{
3 Aes256Gcm, KeyInit, Nonce,
4 aead::Aead,
5};
6use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
7use hkdf::Hkdf;
8use p256::ecdsa::SigningKey;
9use sha2::{Digest, Sha256};
10use std::sync::OnceLock;
11static CONFIG: OnceLock<AuthConfig> = OnceLock::new();
12pub const ENCRYPTION_VERSION: i32 = 1;
13pub struct AuthConfig {
14 jwt_secret: String,
15 dpop_secret: String,
16 #[allow(dead_code)]
17 signing_key: SigningKey,
18 pub signing_key_id: String,
19 pub signing_key_x: String,
20 pub signing_key_y: String,
21 key_encryption_key: [u8; 32],
22}
23impl AuthConfig {
24 pub fn init() -> &'static Self {
25 CONFIG.get_or_init(|| {
26 let jwt_secret = std::env::var("JWT_SECRET").unwrap_or_else(|_| {
27 if cfg!(test) || std::env::var("BSPDS_ALLOW_INSECURE_SECRETS").is_ok() {
28 "test-jwt-secret-not-for-production".to_string()
29 } else {
30 panic!(
31 "JWT_SECRET environment variable must be set in production. \
32 Set BSPDS_ALLOW_INSECURE_SECRETS=1 for development/testing."
33 );
34 }
35 });
36 let dpop_secret = std::env::var("DPOP_SECRET").unwrap_or_else(|_| {
37 if cfg!(test) || std::env::var("BSPDS_ALLOW_INSECURE_SECRETS").is_ok() {
38 "test-dpop-secret-not-for-production".to_string()
39 } else {
40 panic!(
41 "DPOP_SECRET environment variable must be set in production. \
42 Set BSPDS_ALLOW_INSECURE_SECRETS=1 for development/testing."
43 );
44 }
45 });
46 if jwt_secret.len() < 32 && std::env::var("BSPDS_ALLOW_INSECURE_SECRETS").is_err() {
47 panic!("JWT_SECRET must be at least 32 characters");
48 }
49 if dpop_secret.len() < 32 && std::env::var("BSPDS_ALLOW_INSECURE_SECRETS").is_err() {
50 panic!("DPOP_SECRET must be at least 32 characters");
51 }
52 let mut hasher = Sha256::new();
53 hasher.update(b"oauth-signing-key-derivation:");
54 hasher.update(jwt_secret.as_bytes());
55 let seed = hasher.finalize();
56 let signing_key = SigningKey::from_slice(&seed)
57 .unwrap_or_else(|e| panic!("Failed to create signing key from seed: {}. This is a bug.", e));
58 let verifying_key = signing_key.verifying_key();
59 let point = verifying_key.to_encoded_point(false);
60 let signing_key_x = URL_SAFE_NO_PAD.encode(
61 point.x().expect("EC point missing X coordinate - this should never happen")
62 );
63 let signing_key_y = URL_SAFE_NO_PAD.encode(
64 point.y().expect("EC point missing Y coordinate - this should never happen")
65 );
66 let mut kid_hasher = Sha256::new();
67 kid_hasher.update(signing_key_x.as_bytes());
68 kid_hasher.update(signing_key_y.as_bytes());
69 let kid_hash = kid_hasher.finalize();
70 let signing_key_id = URL_SAFE_NO_PAD.encode(&kid_hash[..8]);
71 let master_key = std::env::var("MASTER_KEY").unwrap_or_else(|_| {
72 if cfg!(test) || std::env::var("BSPDS_ALLOW_INSECURE_SECRETS").is_ok() {
73 "test-master-key-not-for-production".to_string()
74 } else {
75 panic!(
76 "MASTER_KEY environment variable must be set in production. \
77 Set BSPDS_ALLOW_INSECURE_SECRETS=1 for development/testing."
78 );
79 }
80 });
81 if master_key.len() < 32 && std::env::var("BSPDS_ALLOW_INSECURE_SECRETS").is_err() {
82 panic!("MASTER_KEY must be at least 32 characters");
83 }
84 let hk = Hkdf::<Sha256>::new(None, master_key.as_bytes());
85 let mut key_encryption_key = [0u8; 32];
86 hk.expand(b"bspds-user-key-encryption", &mut key_encryption_key)
87 .expect("HKDF expansion failed");
88 AuthConfig {
89 jwt_secret,
90 dpop_secret,
91 signing_key,
92 signing_key_id,
93 signing_key_x,
94 signing_key_y,
95 key_encryption_key,
96 }
97 })
98 }
99 pub fn get() -> &'static Self {
100 CONFIG.get().expect("AuthConfig not initialized - call AuthConfig::init() first")
101 }
102 pub fn jwt_secret(&self) -> &str {
103 &self.jwt_secret
104 }
105 pub fn dpop_secret(&self) -> &str {
106 &self.dpop_secret
107 }
108 pub fn encrypt_user_key(&self, plaintext: &[u8]) -> Result<Vec<u8>, String> {
109 use rand::RngCore;
110 let cipher = Aes256Gcm::new_from_slice(&self.key_encryption_key)
111 .map_err(|e| format!("Failed to create cipher: {}", e))?;
112 let mut nonce_bytes = [0u8; 12];
113 rand::thread_rng().fill_bytes(&mut nonce_bytes);
114 #[allow(deprecated)]
115 let nonce = Nonce::from_slice(&nonce_bytes);
116 let ciphertext = cipher
117 .encrypt(nonce, plaintext)
118 .map_err(|e| format!("Encryption failed: {}", e))?;
119 let mut result = Vec::with_capacity(12 + ciphertext.len());
120 result.extend_from_slice(&nonce_bytes);
121 result.extend_from_slice(&ciphertext);
122 Ok(result)
123 }
124 pub fn decrypt_user_key(&self, encrypted: &[u8]) -> Result<Vec<u8>, String> {
125 if encrypted.len() < 12 {
126 return Err("Encrypted data too short".to_string());
127 }
128 let cipher = Aes256Gcm::new_from_slice(&self.key_encryption_key)
129 .map_err(|e| format!("Failed to create cipher: {}", e))?;
130 #[allow(deprecated)]
131 let nonce = Nonce::from_slice(&encrypted[..12]);
132 let ciphertext = &encrypted[12..];
133 cipher
134 .decrypt(nonce, ciphertext)
135 .map_err(|e| format!("Decryption failed: {}", e))
136 }
137}
138pub fn encrypt_key(plaintext: &[u8]) -> Result<Vec<u8>, String> {
139 AuthConfig::get().encrypt_user_key(plaintext)
140}
141pub fn decrypt_key(encrypted: &[u8], version: Option<i32>) -> Result<Vec<u8>, String> {
142 match version.unwrap_or(0) {
143 0 => Ok(encrypted.to_vec()),
144 1 => AuthConfig::get().decrypt_user_key(encrypted),
145 v => Err(format!("Unknown encryption version: {}", v)),
146 }
147}