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}