Skip to main content

crypto_core/
signature.rs

1//! Signature primitives - Ed25519 (RSA-PSS legacy path disabled)
2
3use 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/// Error type for key generation
11#[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/// FIPS mode configuration
21#[derive(Debug, Clone)]
22pub enum FipsMode {
23    /// FIPS 140-2/3 compliant mode - requires OS entropy
24    Enabled,
25    /// Non-FIPS mode - allows fallback to deterministic RNG
26    Disabled,
27    /// Strict FIPS mode - fails if entropy is insufficient
28    Strict,
29}
30
31impl Default for FipsMode {
32    fn default() -> Self {
33        // Check environment for FIPS mode
34        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/// Ed25519 key pair with secure memory handling
43#[derive(Zeroize, ZeroizeOnDrop)]
44pub struct Ed25519KeyPair {
45    // 32-byte Ed25519 private seed (zeroized on drop).
46    secret_seed: [u8; 32],
47    #[zeroize(skip)]
48    verifying_key: VerifyingKey,
49    #[zeroize(skip)]
50    metadata: KeyMetadata,
51}
52
53impl Ed25519KeyPair {
54    /// Create an Ed25519 key pair from an explicit 32-byte seed.
55    ///
56    /// This is primarily intended for deterministic service integrations and tests.
57    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    /// Deterministically derive a key pair from secret material (HKDF-SHA256).
74    ///
75    /// This is useful for service bootstrapping in environments where a proper key
76    /// management integration is not yet available. Prefer HSM/KMS in production.
77    pub fn derive_from_secret(secret: &[u8], key_id: Option<String>) -> Self {
78        // Context-bound HKDF derivation to avoid raw hash-as-key constructions.
79        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    /// Generate new Ed25519 key pair with FIPS-compliant entropy
87    ///
88    /// In FIPS mode (RUST_FIPS=1), this requires OS-level entropy.
89    /// In non-FIPS mode, falls back to deterministic RNG if OsRng fails.
90    ///
91    /// Returns Result to allow error handling in strict mode
92    pub fn generate() -> std::result::Result<Self, KeyGenerationError> {
93        let fips_mode = FipsMode::default();
94        Self::generate_with_mode(fips_mode)
95    }
96
97    /// Generate key pair with specific FIPS mode
98    pub fn generate_with_mode(
99        fips_mode: FipsMode,
100    ) -> std::result::Result<Self, KeyGenerationError> {
101        // Try OS entropy first (FIPS-compliant)
102        let secret_seed = match Self::generate_with_os_rng() {
103            Ok(seed) => seed,
104            Err(e) => {
105                // Handle entropy failure based on mode
106                match fips_mode {
107                    FipsMode::Strict => {
108                        // In strict mode, return error - don't panic
109                        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                        // Use fallback silently
127                        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    /// Generate key using OS entropy (FIPS-compliant)
149    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    /// Fallback RNG for when OS entropy is unavailable
155    /// WARNING: This is NOT cryptographically secure for production!
156    /// Use only as last resort or in development.
157    ///
158    /// Uses StdRng::from_entropy() which seeds from OS entropy,
159    /// providing better security than deterministic seeding.
160    fn generate_fallback() -> [u8; 32] {
161        // Use OS-entropy-seeded StdRng for fallback
162        // This is better than deterministic seeding
163        let mut rng = StdRng::from_entropy();
164        SigningKey::generate(&mut rng).to_bytes()
165    }
166
167    /// Generate key with explicit HSM (Hardware Security Module)
168    /// Returns error if HSM is not available
169    #[allow(dead_code)]
170    pub fn generate_with_hsm(_slot: u32) -> Result<Self> {
171        // HSM integration would go here
172        // For now, return error indicating HSM not implemented
173        Err(CryptoError::KeyError(
174            "HSM integration not implemented".to_string(),
175        ))
176    }
177
178    /// Sign data
179    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    /// Verify signature
186    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    /// Get verifying key (public)
198    pub fn verifying_key(&self) -> Vec<u8> {
199        self.verifying_key.to_bytes().to_vec()
200    }
201
202    /// Get metadata
203    pub fn metadata(&self) -> &KeyMetadata {
204        &self.metadata
205    }
206}
207
208/// Legacy RSA key pair support (disabled).
209#[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    /// Generate new RSA-PSS 4096 key pair
218    #[allow(dead_code)]
219    pub fn generate() -> Result<Self> {
220        Self::generate_with_bits(4096)
221    }
222
223    /// Generate RSA-PSS key pair with explicit key size.
224    #[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    /// Get key ID
233    #[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
249/// Sign data with specified algorithm
250pub fn sign(data: &[u8], key: &Ed25519KeyPair) -> Result<Vec<u8>> {
251    Ok(key.sign(data))
252}
253
254/// Verify signature with specified algorithm
255pub 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            // Reconstruct verifying key and verify
267            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/// Sign with RSA-PSS-SHA256 using PKCS#8 DER private key bytes.
293#[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/// Verify RSA-PSS-SHA256 using SubjectPublicKeyInfo DER public key bytes.
302#[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}