Skip to main content

voided_core/signing/
mod.rs

1//! Digital signature module providing Ed25519, ECDSA, and RSA-PSS.
2//!
3//! This module is only available with the `signing` feature flag.
4
5use crate::{Error, Result};
6use alloc::{string::String, vec::Vec};
7use serde::{Deserialize, Serialize};
8
9/// Supported signing algorithms
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
11#[repr(u8)]
12pub enum SigningAlgorithm {
13    /// Ed25519 (64-byte signatures)
14    Ed25519 = 0x01,
15    /// ECDSA with P-256 curve (DER encoded, variable size)
16    EcdsaP256 = 0x02,
17    /// RSA-PSS with 2048-bit key (256-byte signatures)
18    RsaPss2048 = 0x03,
19}
20
21impl SigningAlgorithm {
22    /// Get algorithm from byte identifier
23    pub fn from_byte(byte: u8) -> Result<Self> {
24        match byte {
25            0x01 => Ok(SigningAlgorithm::Ed25519),
26            0x02 => Ok(SigningAlgorithm::EcdsaP256),
27            0x03 => Ok(SigningAlgorithm::RsaPss2048),
28            _ => Err(Error::UnsupportedAlgorithm(byte)),
29        }
30    }
31
32    /// Get algorithm name as string
33    pub fn name(&self) -> &'static str {
34        match self {
35            SigningAlgorithm::Ed25519 => "ed25519",
36            SigningAlgorithm::EcdsaP256 => "ecdsa-p256",
37            SigningAlgorithm::RsaPss2048 => "rsa-pss-2048",
38        }
39    }
40}
41
42/// Generated key pair
43#[derive(Debug, Clone)]
44pub struct KeyPair {
45    /// Public key in PEM format
46    pub public_key_pem: String,
47    /// Private key in PEM format
48    pub private_key_pem: String,
49}
50
51/// Generate a signing key pair
52#[cfg(feature = "signing")]
53pub fn generate_key_pair(algorithm: SigningAlgorithm) -> Result<KeyPair> {
54    match algorithm {
55        SigningAlgorithm::Ed25519 => generate_ed25519_key_pair_pem(),
56        SigningAlgorithm::EcdsaP256 => generate_ecdsa_p256_key_pair(),
57        SigningAlgorithm::RsaPss2048 => generate_rsa_pss_key_pair(),
58    }
59}
60
61#[cfg(feature = "signing")]
62fn generate_ed25519_key_pair_pem() -> Result<KeyPair> {
63    use ed25519_dalek::SigningKey;
64    use rand::rngs::OsRng;
65
66    let signing_key = SigningKey::generate(&mut OsRng);
67    let verifying_key = signing_key.verifying_key();
68
69    // Convert to PEM format (simplified - in production use proper PEM encoding)
70    let private_pem = format!(
71        "-----BEGIN PRIVATE KEY-----\n{}\n-----END PRIVATE KEY-----",
72        base64::Engine::encode(
73            &base64::engine::general_purpose::STANDARD,
74            signing_key.as_bytes()
75        )
76    );
77
78    let public_pem = format!(
79        "-----BEGIN PUBLIC KEY-----\n{}\n-----END PUBLIC KEY-----",
80        base64::Engine::encode(
81            &base64::engine::general_purpose::STANDARD,
82            verifying_key.as_bytes()
83        )
84    );
85
86    Ok(KeyPair {
87        public_key_pem: public_pem,
88        private_key_pem: private_pem,
89    })
90}
91
92/// Generate an Ed25519 key pair, returning raw bytes (public_key, private_key)
93///
94/// Returns: (public_key: Vec<u8>, private_key: Vec<u8>)
95/// - Public key is 32 bytes
96/// - Private key (signing key) is 32 bytes
97#[cfg(feature = "signing")]
98pub fn generate_ed25519_key_pair() -> Result<(Vec<u8>, Vec<u8>)> {
99    use ed25519_dalek::SigningKey;
100    use rand::rngs::OsRng;
101
102    let signing_key = SigningKey::generate(&mut OsRng);
103    let verifying_key = signing_key.verifying_key();
104
105    Ok((
106        verifying_key.as_bytes().to_vec(),
107        signing_key.as_bytes().to_vec(),
108    ))
109}
110
111#[cfg(feature = "signing")]
112fn generate_ecdsa_p256_key_pair() -> Result<KeyPair> {
113    use p256::ecdsa::SigningKey;
114    use rand::rngs::OsRng;
115
116    let signing_key = SigningKey::random(&mut OsRng);
117    let verifying_key = signing_key.verifying_key();
118
119    // Simplified PEM encoding
120    let private_bytes = signing_key.to_bytes();
121    let public_bytes = verifying_key.to_encoded_point(false);
122
123    let private_pem = format!(
124        "-----BEGIN EC PRIVATE KEY-----\n{}\n-----END EC PRIVATE KEY-----",
125        base64::Engine::encode(&base64::engine::general_purpose::STANDARD, &private_bytes)
126    );
127
128    let public_pem = format!(
129        "-----BEGIN PUBLIC KEY-----\n{}\n-----END PUBLIC KEY-----",
130        base64::Engine::encode(
131            &base64::engine::general_purpose::STANDARD,
132            public_bytes.as_bytes()
133        )
134    );
135
136    Ok(KeyPair {
137        public_key_pem: public_pem,
138        private_key_pem: private_pem,
139    })
140}
141
142#[cfg(feature = "signing")]
143fn generate_rsa_pss_key_pair() -> Result<KeyPair> {
144    use rand::rngs::OsRng;
145    use rsa::{RsaPrivateKey, RsaPublicKey};
146
147    let bits = 2048;
148    let private_key = RsaPrivateKey::new(&mut OsRng, bits)
149        .map_err(|e| Error::KeyGenerationFailed(e.to_string()))?;
150    let _public_key = RsaPublicKey::from(&private_key);
151
152    // Simplified - in production use proper PKCS#8 encoding
153    let private_pem = format!(
154        "-----BEGIN RSA PRIVATE KEY-----\n{}\n-----END RSA PRIVATE KEY-----",
155        "... RSA PRIVATE KEY DATA ..."
156    );
157
158    let public_pem = format!(
159        "-----BEGIN RSA PUBLIC KEY-----\n{}\n-----END RSA PUBLIC KEY-----",
160        "... RSA PUBLIC KEY DATA ..."
161    );
162
163    Ok(KeyPair {
164        public_key_pem: public_pem,
165        private_key_pem: private_pem,
166    })
167}
168
169/// Sign data with a private key
170#[cfg(feature = "signing")]
171pub fn sign(data: &[u8], private_key_pem: &str, algorithm: SigningAlgorithm) -> Result<Vec<u8>> {
172    match algorithm {
173        SigningAlgorithm::Ed25519 => sign_ed25519_pem(data, private_key_pem),
174        SigningAlgorithm::EcdsaP256 => sign_ecdsa_p256(data, private_key_pem),
175        SigningAlgorithm::RsaPss2048 => sign_rsa_pss(data, private_key_pem),
176    }
177}
178
179#[cfg(feature = "signing")]
180fn sign_ed25519_pem(data: &[u8], private_key_pem: &str) -> Result<Vec<u8>> {
181    use ed25519_dalek::{Signer, SigningKey};
182
183    // Extract base64 from PEM
184    let pem_content = private_key_pem
185        .strip_prefix("-----BEGIN PRIVATE KEY-----\n")
186        .and_then(|s| s.strip_suffix("\n-----END PRIVATE KEY-----"))
187        .ok_or_else(|| Error::InvalidKeyFormat("Invalid PEM format".to_string()))?;
188
189    let key_bytes =
190        base64::Engine::decode(&base64::engine::general_purpose::STANDARD, pem_content)?;
191
192    if key_bytes.len() != 32 {
193        return Err(Error::InvalidKeyLength {
194            expected: 32,
195            actual: key_bytes.len(),
196        });
197    }
198
199    let signing_key = SigningKey::from_bytes(
200        &key_bytes
201            .try_into()
202            .map_err(|_| Error::InvalidKeyFormat("Failed to convert key bytes".to_string()))?,
203    );
204
205    let signature = signing_key.sign(data);
206    Ok(signature.to_bytes().to_vec())
207}
208
209/// Sign data with an Ed25519 private key (raw bytes)
210///
211/// # Arguments
212/// * `data` - The data to sign
213/// * `private_key` - The private key as 32 bytes
214///
215/// # Returns
216/// The signature as 64 bytes
217#[cfg(feature = "signing")]
218pub fn sign_ed25519(data: &[u8], private_key: &[u8]) -> Result<Vec<u8>> {
219    use ed25519_dalek::{Signer, SigningKey};
220
221    if private_key.len() != 32 {
222        return Err(Error::InvalidKeyLength {
223            expected: 32,
224            actual: private_key.len(),
225        });
226    }
227
228    let signing_key = SigningKey::from_bytes(
229        &private_key
230            .try_into()
231            .map_err(|_| Error::InvalidKeyFormat("Failed to convert key bytes".to_string()))?,
232    );
233
234    let signature = signing_key.sign(data);
235    Ok(signature.to_bytes().to_vec())
236}
237
238#[cfg(feature = "signing")]
239fn sign_ecdsa_p256(_data: &[u8], _private_key_pem: &str) -> Result<Vec<u8>> {
240    // TODO: Implement proper ECDSA signing with PEM parsing
241    Err(Error::SigningFailed("Not yet implemented".to_string()))
242}
243
244#[cfg(feature = "signing")]
245fn sign_rsa_pss(_data: &[u8], _private_key_pem: &str) -> Result<Vec<u8>> {
246    // TODO: Implement proper RSA-PSS signing with PEM parsing
247    Err(Error::SigningFailed("Not yet implemented".to_string()))
248}
249
250/// Verify a signature
251#[cfg(feature = "signing")]
252pub fn verify(
253    data: &[u8],
254    signature: &[u8],
255    public_key_pem: &str,
256    algorithm: SigningAlgorithm,
257) -> Result<bool> {
258    match algorithm {
259        SigningAlgorithm::Ed25519 => verify_ed25519_pem(data, signature, public_key_pem),
260        SigningAlgorithm::EcdsaP256 => verify_ecdsa_p256(data, signature, public_key_pem),
261        SigningAlgorithm::RsaPss2048 => verify_rsa_pss(data, signature, public_key_pem),
262    }
263}
264
265#[cfg(feature = "signing")]
266fn verify_ed25519_pem(data: &[u8], signature: &[u8], public_key_pem: &str) -> Result<bool> {
267    use ed25519_dalek::{Signature, VerifyingKey};
268
269    // Extract base64 from PEM
270    let pem_content = public_key_pem
271        .strip_prefix("-----BEGIN PUBLIC KEY-----\n")
272        .and_then(|s| s.strip_suffix("\n-----END PUBLIC KEY-----"))
273        .ok_or_else(|| Error::InvalidKeyFormat("Invalid PEM format".to_string()))?;
274
275    let key_bytes =
276        base64::Engine::decode(&base64::engine::general_purpose::STANDARD, pem_content)?;
277
278    if key_bytes.len() != 32 {
279        return Err(Error::InvalidKeyLength {
280            expected: 32,
281            actual: key_bytes.len(),
282        });
283    }
284
285    if signature.len() != 64 {
286        return Err(Error::InvalidKeyLength {
287            expected: 64,
288            actual: signature.len(),
289        });
290    }
291
292    let verifying_key = VerifyingKey::from_bytes(
293        &key_bytes
294            .try_into()
295            .map_err(|_| Error::InvalidKeyFormat("Failed to convert key bytes".to_string()))?,
296    )
297    .map_err(|_| Error::InvalidKeyFormat("Invalid public key".to_string()))?;
298
299    let sig =
300        Signature::from_bytes(&signature.try_into().map_err(|_| {
301            Error::InvalidKeyFormat("Failed to convert signature bytes".to_string())
302        })?);
303
304    verifying_key
305        .verify_strict(data, &sig)
306        .map(|_| true)
307        .map_err(|_| Error::SignatureVerificationFailed)
308}
309
310/// Verify an Ed25519 signature (raw bytes)
311///
312/// # Arguments
313/// * `data` - The original data that was signed
314/// * `signature` - The signature as 64 bytes
315/// * `public_key` - The public key as 32 bytes
316///
317/// # Returns
318/// `true` if the signature is valid, `false` otherwise
319#[cfg(feature = "signing")]
320pub fn verify_ed25519(data: &[u8], signature: &[u8], public_key: &[u8]) -> Result<bool> {
321    use ed25519_dalek::{Signature, VerifyingKey};
322
323    if public_key.len() != 32 {
324        return Err(Error::InvalidKeyLength {
325            expected: 32,
326            actual: public_key.len(),
327        });
328    }
329
330    if signature.len() != 64 {
331        return Err(Error::InvalidKeyLength {
332            expected: 64,
333            actual: signature.len(),
334        });
335    }
336
337    let verifying_key = VerifyingKey::from_bytes(
338        &public_key
339            .try_into()
340            .map_err(|_| Error::InvalidKeyFormat("Failed to convert key bytes".to_string()))?,
341    )
342    .map_err(|_| Error::InvalidKeyFormat("Invalid public key".to_string()))?;
343
344    let sig =
345        Signature::from_bytes(&signature.try_into().map_err(|_| {
346            Error::InvalidKeyFormat("Failed to convert signature bytes".to_string())
347        })?);
348
349    verifying_key
350        .verify_strict(data, &sig)
351        .map(|_| true)
352        .map_err(|_| Error::SignatureVerificationFailed)
353}
354
355#[cfg(feature = "signing")]
356fn verify_ecdsa_p256(_data: &[u8], _signature: &[u8], _public_key_pem: &str) -> Result<bool> {
357    // TODO: Implement proper ECDSA verification with PEM parsing
358    Err(Error::SignatureVerificationFailed)
359}
360
361#[cfg(feature = "signing")]
362fn verify_rsa_pss(_data: &[u8], _signature: &[u8], _public_key_pem: &str) -> Result<bool> {
363    // TODO: Implement proper RSA-PSS verification with PEM parsing
364    Err(Error::SignatureVerificationFailed)
365}
366
367#[cfg(test)]
368mod tests {
369    use super::*;
370
371    #[test]
372    fn test_algorithm_from_byte() {
373        assert_eq!(
374            SigningAlgorithm::from_byte(0x01).unwrap(),
375            SigningAlgorithm::Ed25519
376        );
377        assert_eq!(
378            SigningAlgorithm::from_byte(0x02).unwrap(),
379            SigningAlgorithm::EcdsaP256
380        );
381        assert_eq!(
382            SigningAlgorithm::from_byte(0x03).unwrap(),
383            SigningAlgorithm::RsaPss2048
384        );
385        assert!(SigningAlgorithm::from_byte(0xFF).is_err());
386    }
387
388    #[test]
389    fn test_algorithm_name() {
390        assert_eq!(SigningAlgorithm::Ed25519.name(), "ed25519");
391        assert_eq!(SigningAlgorithm::EcdsaP256.name(), "ecdsa-p256");
392        assert_eq!(SigningAlgorithm::RsaPss2048.name(), "rsa-pss-2048");
393    }
394}