Skip to main content

cdx_core/security/
rsa_pss.rs

1//! RSA-PSS (PS256) signature support.
2//!
3//! This module provides PS256 signing and verification using RSA with PSS padding
4//! and SHA-256.
5
6use crate::error::invalid_manifest;
7use crate::{DocumentId, Result};
8
9use super::signature::{Signature, SignatureAlgorithm, SignatureVerification, SignerInfo};
10use super::signer::{Signer, Verifier};
11
12/// RSA-PSS signer (PS256).
13pub struct Ps256Signer {
14    signing_key: rsa::RsaPrivateKey,
15    signer_info: SignerInfo,
16}
17
18impl Ps256Signer {
19    /// Create a new signer from a PEM-encoded private key.
20    ///
21    /// # Errors
22    ///
23    /// Returns an error if the PEM cannot be parsed.
24    pub fn from_pem(pem: &str, signer_info: SignerInfo) -> Result<Self> {
25        use rsa::pkcs8::DecodePrivateKey;
26
27        let signing_key = rsa::RsaPrivateKey::from_pkcs8_pem(pem)
28            .map_err(|e| invalid_manifest(format!("Failed to parse RSA private key PEM: {e}")))?;
29
30        Ok(Self {
31            signing_key,
32            signer_info,
33        })
34    }
35
36    /// Generate a new random signing key with the specified bit size.
37    ///
38    /// Common sizes are 2048, 3072, or 4096 bits.
39    /// Returns the signer and the public key in PEM format.
40    ///
41    /// # Errors
42    ///
43    /// Returns an error if key generation fails.
44    pub fn generate(signer_info: SignerInfo, bits: usize) -> Result<(Self, String)> {
45        use rsa::pkcs8::EncodePublicKey;
46
47        let signing_key =
48            rsa::RsaPrivateKey::new(&mut rand_core::UnwrapErr(getrandom::SysRng), bits)
49                .map_err(|e| invalid_manifest(format!("Failed to generate RSA key: {e}")))?;
50
51        let public_key = signing_key.to_public_key();
52        let public_key_pem = public_key
53            .to_public_key_pem(rsa::pkcs8::LineEnding::LF)
54            .map_err(|e| invalid_manifest(format!("Failed to encode RSA public key: {e}")))?;
55
56        Ok((
57            Self {
58                signing_key,
59                signer_info,
60            },
61            public_key_pem,
62        ))
63    }
64
65    /// Generate a new random signing key with 2048 bits (recommended minimum).
66    ///
67    /// Returns the signer and the public key in PEM format.
68    ///
69    /// # Errors
70    ///
71    /// Returns an error if key generation fails.
72    pub fn generate_2048(signer_info: SignerInfo) -> Result<(Self, String)> {
73        Self::generate(signer_info, 2048)
74    }
75
76    /// Get the public key in PEM format.
77    ///
78    /// # Errors
79    ///
80    /// Returns an error if encoding fails.
81    pub fn public_key_pem(&self) -> Result<String> {
82        use rsa::pkcs8::EncodePublicKey;
83
84        self.signing_key
85            .to_public_key()
86            .to_public_key_pem(rsa::pkcs8::LineEnding::LF)
87            .map_err(|e| invalid_manifest(format!("Failed to encode RSA public key: {e}")))
88    }
89}
90
91impl Signer for Ps256Signer {
92    fn algorithm(&self) -> SignatureAlgorithm {
93        SignatureAlgorithm::PS256
94    }
95
96    fn signer_info(&self) -> SignerInfo {
97        self.signer_info.clone()
98    }
99
100    fn sign(&self, document_id: &DocumentId) -> Result<Signature> {
101        use base64::Engine;
102        use rsa::signature::RandomizedSigner;
103        use rsa::signature::SignatureEncoding;
104
105        if document_id.is_pending() {
106            return Err(crate::Error::InvalidManifest {
107                reason: "Cannot sign a pending document ID".to_string(),
108            });
109        }
110
111        // Create PSS signing key
112        let signing_key = rsa::pss::SigningKey::<rsa::sha2::Sha256>::new(self.signing_key.clone());
113
114        // Sign the document ID bytes (PSS is randomized)
115        let signature = signing_key.sign_with_rng(
116            &mut rand_core::UnwrapErr(getrandom::SysRng),
117            document_id.digest(),
118        );
119
120        // Encode as base64
121        let value = base64::engine::general_purpose::STANDARD.encode(signature.to_bytes());
122
123        // Generate signature ID
124        let sig_id = format!(
125            "sig-{}",
126            &crate::Hasher::hash(crate::HashAlgorithm::Sha256, value.as_bytes()).hex_digest()[..8]
127        );
128
129        Ok(Signature::new(
130            sig_id,
131            SignatureAlgorithm::PS256,
132            self.signer_info.clone(),
133            value,
134        ))
135    }
136}
137
138/// RSA-PSS verifier (PS256).
139pub struct Ps256Verifier {
140    verifying_key: rsa::RsaPublicKey,
141}
142
143impl Ps256Verifier {
144    /// Create a new verifier from a PEM-encoded public key.
145    ///
146    /// # Errors
147    ///
148    /// Returns an error if the PEM cannot be parsed.
149    pub fn from_pem(pem: &str) -> Result<Self> {
150        use rsa::pkcs8::DecodePublicKey;
151
152        let verifying_key = rsa::RsaPublicKey::from_public_key_pem(pem)
153            .map_err(|e| invalid_manifest(format!("Failed to parse RSA public key PEM: {e}")))?;
154
155        Ok(Self { verifying_key })
156    }
157}
158
159impl Verifier for Ps256Verifier {
160    fn verify(
161        &self,
162        document_id: &DocumentId,
163        signature: &Signature,
164    ) -> Result<SignatureVerification> {
165        use base64::Engine;
166        use rsa::pss::VerifyingKey;
167        use rsa::signature::Verifier as RsaVerifierTrait;
168
169        if signature.algorithm != SignatureAlgorithm::PS256 {
170            return Ok(SignatureVerification::invalid(
171                &signature.id,
172                format!(
173                    "Algorithm mismatch: expected PS256, got {}",
174                    signature.algorithm
175                ),
176            ));
177        }
178
179        // Decode signature from base64
180        let sig_bytes = base64::engine::general_purpose::STANDARD
181            .decode(&signature.value)
182            .map_err(|e| invalid_manifest(format!("Failed to decode signature: {e}")))?;
183
184        // Create PSS verifying key
185        let verifying_key = VerifyingKey::<rsa::sha2::Sha256>::new(self.verifying_key.clone());
186
187        // Parse signature
188        let rsa_sig = rsa::pss::Signature::try_from(sig_bytes.as_slice())
189            .map_err(|e| invalid_manifest(format!("Invalid PS256 signature format: {e}")))?;
190
191        // Verify
192        match verifying_key.verify(document_id.digest(), &rsa_sig) {
193            Ok(()) => Ok(SignatureVerification::valid(&signature.id)),
194            Err(e) => Ok(SignatureVerification::invalid(
195                &signature.id,
196                format!("PS256 signature verification failed: {e}"),
197            )),
198        }
199    }
200}
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205    use crate::security::test_helpers;
206
207    fn generate_keypair() -> (Ps256Signer, Ps256Verifier) {
208        let signer_info = SignerInfo::new("Test PS256 Signer");
209        let (signer, public_key_pem) = Ps256Signer::generate_2048(signer_info).unwrap();
210        let verifier = Ps256Verifier::from_pem(&public_key_pem).unwrap();
211        (signer, verifier)
212    }
213
214    #[test]
215    fn test_generate_and_sign() {
216        let signer_info = SignerInfo::new("Test PS256 Signer");
217        let (signer, public_key_pem) = Ps256Signer::generate_2048(signer_info).unwrap();
218
219        assert!(!public_key_pem.is_empty());
220        assert!(public_key_pem.contains("BEGIN PUBLIC KEY"));
221
222        test_helpers::assert_sign_produces_valid_signature(&signer, SignatureAlgorithm::PS256);
223    }
224
225    #[test]
226    fn test_sign_and_verify() {
227        let (signer, verifier) = generate_keypair();
228        test_helpers::assert_sign_verify_roundtrip(&signer, &verifier);
229    }
230
231    #[test]
232    fn test_verify_wrong_document() {
233        let (signer, verifier) = generate_keypair();
234        test_helpers::assert_verify_wrong_document_fails(&signer, &verifier);
235    }
236
237    #[test]
238    fn test_cannot_sign_pending_id() {
239        let (signer, _) = generate_keypair();
240        test_helpers::assert_cannot_sign_pending_id(&signer);
241    }
242
243    #[test]
244    fn test_algorithm_mismatch() {
245        let (signer, verifier) = generate_keypair();
246        test_helpers::assert_algorithm_mismatch_rejected(
247            &signer,
248            &verifier,
249            SignatureAlgorithm::ES256,
250        );
251    }
252}