1use crate::{CryptoError, KeyMetadata, Result, SignatureAlgorithm};
4use ed25519_dalek::{Signature as Ed25519Signature, Signer, SigningKey, Verifier, VerifyingKey};
5use hkdf::Hkdf;
6use rand::{rngs::OsRng, rngs::StdRng, SeedableRng};
7use thiserror::Error;
8use zeroize::{Zeroize, ZeroizeOnDrop};
9
10#[derive(Error, Debug)]
12pub enum KeyGenerationError {
13 #[error("Failed to gather entropy: {0}")]
14 EntropyError(String),
15
16 #[error("Key generation failed: {0}")]
17 GenerationError(String),
18}
19
20#[derive(Debug, Clone)]
22pub enum FipsMode {
23 Enabled,
25 Disabled,
27 Strict,
29}
30
31impl Default for FipsMode {
32 fn default() -> Self {
33 match std::env::var("RUST_FIPS").unwrap_or_default().as_str() {
35 "1" | "strict" => FipsMode::Strict,
36 "true" | "enabled" => FipsMode::Enabled,
37 _ => FipsMode::Disabled,
38 }
39 }
40}
41
42#[derive(Zeroize, ZeroizeOnDrop)]
44pub struct Ed25519KeyPair {
45 secret_seed: [u8; 32],
47 #[zeroize(skip)]
48 verifying_key: VerifyingKey,
49 #[zeroize(skip)]
50 metadata: KeyMetadata,
51}
52
53impl Ed25519KeyPair {
54 pub fn from_seed(seed: [u8; 32], key_id: Option<String>) -> Self {
58 let signing_key = SigningKey::from_bytes(&seed);
59 let verifying_key = signing_key.verifying_key();
60 Ed25519KeyPair {
61 secret_seed: seed,
62 verifying_key,
63 metadata: KeyMetadata {
64 key_id: key_id.unwrap_or_else(|| format!("ed25519-{}", uuid::Uuid::new_v4())),
65 algorithm: SignatureAlgorithm::Ed25519,
66 created_at: chrono::Utc::now().timestamp(),
67 key_type: crate::KeyType::Signing,
68 hsm_slot: None,
69 },
70 }
71 }
72
73 pub fn derive_from_secret(secret: &[u8], key_id: Option<String>) -> Self {
78 let hk = Hkdf::<sha2::Sha256>::new(Some(b"rsrp-security-core-ed25519-v1"), secret);
80 let mut seed = [0u8; 32];
81 hk.expand(b"signing-seed", &mut seed)
82 .expect("HKDF expand to 32 bytes should never fail");
83 Self::from_seed(seed, key_id)
84 }
85
86 pub fn generate() -> std::result::Result<Self, KeyGenerationError> {
93 let fips_mode = FipsMode::default();
94 Self::generate_with_mode(fips_mode)
95 }
96
97 pub fn generate_with_mode(
99 fips_mode: FipsMode,
100 ) -> std::result::Result<Self, KeyGenerationError> {
101 let secret_seed = match Self::generate_with_os_rng() {
103 Ok(seed) => seed,
104 Err(e) => {
105 match fips_mode {
107 FipsMode::Strict => {
108 return Err(KeyGenerationError::EntropyError(format!(
110 "FIPS strict mode: OS entropy unavailable: {}",
111 e
112 )));
113 }
114 FipsMode::Enabled => {
115 tracing::warn!(
116 event = "crypto.fips_fallback",
117 error = %e,
118 "OS entropy unavailable, using fallback RNG (non-FIPS)"
119 );
120 return Err(KeyGenerationError::EntropyError(format!(
121 "FIPS enabled mode: OS entropy unavailable: {}",
122 e
123 )));
124 }
125 FipsMode::Disabled => {
126 Self::generate_fallback()
128 }
129 }
130 }
131 };
132 let signing_key = SigningKey::from_bytes(&secret_seed);
133 let verifying_key = signing_key.verifying_key();
134
135 Ok(Ed25519KeyPair {
136 secret_seed,
137 verifying_key,
138 metadata: KeyMetadata {
139 key_id: format!("ed25519-{}", uuid::Uuid::new_v4()),
140 algorithm: SignatureAlgorithm::Ed25519,
141 created_at: chrono::Utc::now().timestamp(),
142 key_type: crate::KeyType::Signing,
143 hsm_slot: None,
144 },
145 })
146 }
147
148 fn generate_with_os_rng() -> std::result::Result<[u8; 32], KeyGenerationError> {
150 let mut os_rng = OsRng;
151 Ok(SigningKey::generate(&mut os_rng).to_bytes())
152 }
153
154 fn generate_fallback() -> [u8; 32] {
161 let mut rng = StdRng::from_entropy();
164 SigningKey::generate(&mut rng).to_bytes()
165 }
166
167 #[allow(dead_code)]
170 pub fn generate_with_hsm(_slot: u32) -> Result<Self> {
171 Err(CryptoError::KeyError(
174 "HSM integration not implemented".to_string(),
175 ))
176 }
177
178 pub fn sign(&self, data: &[u8]) -> Vec<u8> {
180 let signing_key = SigningKey::from_bytes(&self.secret_seed);
181 let signature = signing_key.sign(data);
182 signature.to_bytes().to_vec()
183 }
184
185 pub fn verify(&self, data: &[u8], signature: &[u8]) -> bool {
187 if signature.len() != 64 {
188 return false;
189 }
190
191 let sig_array: [u8; 64] = signature.try_into().unwrap();
192 let ed25519_sig = Ed25519Signature::from_bytes(&sig_array);
193
194 self.verifying_key.verify(data, &ed25519_sig).is_ok()
195 }
196
197 pub fn verifying_key(&self) -> Vec<u8> {
199 self.verifying_key.to_bytes().to_vec()
200 }
201
202 pub fn metadata(&self) -> &KeyMetadata {
204 &self.metadata
205 }
206}
207
208#[allow(dead_code)]
210pub struct RsaKeyPair {
211 key_id: String,
212 public_key: Vec<u8>,
213 private_key: Vec<u8>,
214}
215
216impl RsaKeyPair {
217 #[allow(dead_code)]
219 pub fn generate() -> Result<Self> {
220 Self::generate_with_bits(4096)
221 }
222
223 #[allow(dead_code)]
225 pub fn generate_with_bits(bits: usize) -> Result<Self> {
226 let _ = bits;
227 Err(CryptoError::SignatureError(
228 "RSA-PSS support disabled in rsrp-security-core (legacy path removed)".to_string(),
229 ))
230 }
231
232 #[allow(dead_code)]
234 pub fn key_id(&self) -> &str {
235 &self.key_id
236 }
237
238 #[allow(dead_code)]
239 pub fn public_key_der(&self) -> &[u8] {
240 &self.public_key
241 }
242
243 #[allow(dead_code)]
244 pub fn private_key_der(&self) -> &[u8] {
245 &self.private_key
246 }
247}
248
249pub fn sign(data: &[u8], key: &Ed25519KeyPair) -> Result<Vec<u8>> {
251 Ok(key.sign(data))
252}
253
254pub fn verify(
256 data: &[u8],
257 signature: &[u8],
258 public_key: &[u8],
259 algorithm: SignatureAlgorithm,
260) -> Result<bool> {
261 match algorithm {
262 SignatureAlgorithm::RsaPss2048 | SignatureAlgorithm::RsaPss4096 => {
263 verify_rsa_pss(data, signature, public_key)
264 }
265 SignatureAlgorithm::Ed25519 => {
266 if public_key.len() != 32 {
268 return Err(CryptoError::InvalidKey);
269 }
270
271 let mut key_bytes = [0u8; 32];
272 key_bytes.copy_from_slice(public_key);
273 let verifying_key =
274 VerifyingKey::from_bytes(&key_bytes).map_err(|_| CryptoError::InvalidKey)?;
275
276 if signature.len() != 64 {
277 return Ok(false);
278 }
279
280 let mut sig_bytes = [0u8; 64];
281 sig_bytes.copy_from_slice(signature);
282 let ed25519_sig = Ed25519Signature::from_bytes(&sig_bytes);
283
284 Ok(verifying_key.verify(data, &ed25519_sig).is_ok())
285 }
286 _ => Err(CryptoError::SignatureError(
287 "Algorithm not implemented".to_string(),
288 )),
289 }
290}
291
292#[allow(dead_code)]
294pub fn sign_rsa_pss(data: &[u8], private_key: &[u8]) -> Result<Vec<u8>> {
295 let _ = (data, private_key);
296 Err(CryptoError::SignatureError(
297 "RSA-PSS support disabled in rsrp-security-core (legacy path removed)".to_string(),
298 ))
299}
300
301#[allow(dead_code)]
303pub fn verify_rsa_pss(data: &[u8], signature: &[u8], public_key: &[u8]) -> Result<bool> {
304 let _ = (data, signature, public_key);
305 Err(CryptoError::SignatureError(
306 "RSA-PSS support disabled in rsrp-security-core (legacy path removed)".to_string(),
307 ))
308}
309
310#[cfg(test)]
311mod tests {
312 use super::*;
313 use zeroize::Zeroize;
314
315 #[test]
316 fn test_ed25519_sign_verify() {
317 let key_pair = Ed25519KeyPair::generate().unwrap();
318
319 let data = b"Test message for signing";
320 let signature = key_pair.sign(data);
321
322 assert!(key_pair.verify(data, &signature));
323 assert!(!key_pair.verify(b"Wrong data", &signature));
324 }
325
326 #[test]
327 fn test_verify_with_public_key() {
328 let key_pair = Ed25519KeyPair::generate().unwrap();
329
330 let data = b"Test message";
331 let signature = key_pair.sign(data);
332 let public_key = key_pair.verifying_key();
333
334 let result = verify(data, &signature, &public_key, SignatureAlgorithm::Ed25519).unwrap();
335 assert!(result);
336 }
337
338 #[test]
339 fn test_ed25519_derive_from_secret_is_deterministic() {
340 let k1 = Ed25519KeyPair::derive_from_secret(b"secret-material", Some("k1".to_string()));
341 let k2 = Ed25519KeyPair::derive_from_secret(b"secret-material", Some("k2".to_string()));
342 let k3 = Ed25519KeyPair::derive_from_secret(b"other-secret", None);
343
344 assert_eq!(k1.verifying_key(), k2.verifying_key());
345 assert_ne!(k1.verifying_key(), k3.verifying_key());
346
347 let msg = b"publication payload";
348 let sig = k1.sign(msg);
349 assert!(k2.verify(msg, &sig));
350 }
351
352 #[test]
353 fn test_rsa_pss_disabled_by_default() {
354 let msg = b"rsa-pss-message";
355 let err = sign_rsa_pss(msg, b"not-a-real-key")
356 .unwrap_err()
357 .to_string();
358 assert!(err.contains("disabled"));
359 }
360
361 #[test]
362 fn test_ed25519_private_seed_zeroize() {
363 let mut key_pair =
364 Ed25519KeyPair::derive_from_secret(b"seed-material", Some("z".to_string()));
365 assert_ne!(key_pair.secret_seed, [0u8; 32]);
366 key_pair.zeroize();
367 assert_eq!(key_pair.secret_seed, [0u8; 32]);
368 }
369}