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