Skip to main content

voided_core/signing/
mod.rs

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