Skip to main content

steel_crypto/
signature.rs

1//! RSA signature creation and verification utilities.
2//!
3//! Ported from net/minecraft/util/Signer.java and SignatureValidator.java
4
5use rsa::pkcs1v15::SigningKey;
6use rsa::sha2::Sha256;
7use rsa::signature::{SignatureEncoding, Signer as RsaSigner, Verifier};
8use rsa::{RsaPrivateKey, RsaPublicKey};
9
10use crate::rsa_utils::CryptError;
11
12/// A function that updates signature data by writing bytes.
13///
14/// Equivalent to `SignatureUpdater` in Minecraft.
15pub trait SignatureUpdater {
16    /// Updates the signature with the given bytes.
17    fn update(&self, output: &mut dyn SignatureOutput) -> Result<(), CryptError>;
18}
19
20/// Output receiver for signature data.
21///
22/// Equivalent to SignatureUpdater.Output in Minecraft.
23pub trait SignatureOutput {
24    /// Receives bytes to be added to the signature.
25    fn update(&mut self, data: &[u8]) -> Result<(), CryptError>;
26}
27
28/// Implementation of `SignatureUpdater` for raw byte slices.
29impl SignatureUpdater for &[u8] {
30    fn update(&self, output: &mut dyn SignatureOutput) -> Result<(), CryptError> {
31        output.update(self)
32    }
33}
34
35/// A trait for signing data with an RSA private key.
36///
37/// Equivalent to Signer interface in Minecraft.
38pub trait Signer {
39    /// Signs the data provided by the updater and returns the signature bytes.
40    fn sign(&self, updater: &dyn SignatureUpdater) -> Result<Vec<u8>, CryptError>;
41}
42
43/// A trait for validating RSA signatures.
44///
45/// Equivalent to `SignatureValidator` interface in Minecraft.
46pub trait SignatureValidator {
47    /// Validates that the signature is valid for the data provided by the updater.
48    fn validate(
49        &self,
50        updater: &dyn SignatureUpdater,
51        signature: &[u8],
52    ) -> Result<bool, CryptError>;
53}
54
55/// Creates a signer from an RSA private key using `SHA256withRSA`.
56///
57/// Equivalent to Signer.from(PrivateKey, "`SHA256withRSA`") in Minecraft.
58pub struct RsaPrivateKeySigner {
59    signing_key: SigningKey<Sha256>,
60}
61
62impl RsaPrivateKeySigner {
63    #[must_use]
64    pub fn new(private_key: RsaPrivateKey) -> Self {
65        Self {
66            signing_key: SigningKey::new(private_key),
67        }
68    }
69}
70
71impl Signer for RsaPrivateKeySigner {
72    fn sign(&self, updater: &dyn SignatureUpdater) -> Result<Vec<u8>, CryptError> {
73        // Collect all bytes to sign
74        let mut collector = ByteCollector::new();
75        updater.update(&mut collector)?;
76
77        // Sign the collected data
78        let signature = self.signing_key.sign(&collector.bytes);
79        Ok(signature.to_bytes().as_ref().to_vec())
80    }
81}
82
83/// Creates a signature validator from an RSA public key using `SHA256withRSA`.
84///
85/// Equivalent to SignatureValidator.from(PublicKey, "`SHA256withRSA`") in Minecraft.
86pub struct RsaPublicKeyValidator {
87    verifying_key: rsa::pkcs1v15::VerifyingKey<Sha256>,
88}
89
90impl RsaPublicKeyValidator {
91    #[must_use]
92    pub fn new(public_key: RsaPublicKey) -> Self {
93        Self {
94            verifying_key: rsa::pkcs1v15::VerifyingKey::new(public_key),
95        }
96    }
97}
98
99impl SignatureValidator for RsaPublicKeyValidator {
100    fn validate(
101        &self,
102        updater: &dyn SignatureUpdater,
103        signature_bytes: &[u8],
104    ) -> Result<bool, CryptError> {
105        // Collect all bytes to verify
106        let mut collector = ByteCollector::new();
107        updater.update(&mut collector)?;
108
109        // Parse signature
110        let signature = match rsa::pkcs1v15::Signature::try_from(signature_bytes) {
111            Ok(sig) => sig,
112            Err(_) => return Ok(false),
113        };
114
115        // Verify the signature
116        Ok(self
117            .verifying_key
118            .verify(&collector.bytes, &signature)
119            .is_ok())
120    }
121}
122
123/// A multi-key validator that tries each public key until one validates.
124///
125/// Used for Mojang's multiple player certificate keys.
126pub struct MultiKeyValidator {
127    validators: Vec<RsaPublicKeyValidator>,
128}
129
130impl MultiKeyValidator {
131    pub fn new(public_keys: Vec<RsaPublicKey>) -> Self {
132        Self {
133            validators: public_keys
134                .into_iter()
135                .map(RsaPublicKeyValidator::new)
136                .collect(),
137        }
138    }
139}
140
141impl SignatureValidator for MultiKeyValidator {
142    fn validate(
143        &self,
144        updater: &dyn SignatureUpdater,
145        signature: &[u8],
146    ) -> Result<bool, CryptError> {
147        // Try each validator - if any succeeds, the signature is valid
148        for validator in &self.validators {
149            if validator.validate(updater, signature)? {
150                return Ok(true);
151            }
152        }
153        // None of the keys validated the signature
154        Ok(false)
155    }
156}
157
158/// A no-validation validator that always returns true.
159///
160/// Equivalent to `SignatureValidator.NO_VALIDATION` in Minecraft.
161pub struct NoValidation;
162
163impl SignatureValidator for NoValidation {
164    fn validate(
165        &self,
166        _updater: &dyn SignatureUpdater,
167        _signature: &[u8],
168    ) -> Result<bool, CryptError> {
169        Ok(true)
170    }
171}
172
173/// Helper struct to collect bytes during signature operations.
174struct ByteCollector {
175    bytes: Vec<u8>,
176}
177
178impl ByteCollector {
179    const fn new() -> Self {
180        Self { bytes: Vec::new() }
181    }
182}
183
184impl SignatureOutput for ByteCollector {
185    fn update(&mut self, data: &[u8]) -> Result<(), CryptError> {
186        self.bytes.extend_from_slice(data);
187        Ok(())
188    }
189}
190
191#[cfg(test)]
192mod tests {
193    use super::*;
194    use crate::rsa_utils::generate_key_pair;
195
196    struct TestUpdater {
197        data: Vec<u8>,
198    }
199
200    impl SignatureUpdater for TestUpdater {
201        fn update(&self, output: &mut dyn SignatureOutput) -> Result<(), CryptError> {
202            output.update(&self.data)
203        }
204    }
205
206    #[test]
207    fn test_sign_and_verify() {
208        let (private_key, public_key) = generate_key_pair().unwrap();
209
210        let signer = RsaPrivateKeySigner::new(private_key);
211        let validator = RsaPublicKeyValidator::new(public_key);
212
213        let data = b"Hello, signed chat!";
214        let updater = TestUpdater {
215            data: data.to_vec(),
216        };
217
218        let signature = signer.sign(&updater).unwrap();
219        // RSA 1024-bit produces 128-byte signatures, not 256
220        // Minecraft uses 256 bytes but that's for padding or 2048-bit keys
221        assert_eq!(signature.len(), 128);
222
223        let is_valid = validator.validate(&updater, &signature).unwrap();
224        assert!(is_valid);
225    }
226
227    #[test]
228    fn test_invalid_signature() {
229        let (_, public_key) = generate_key_pair().unwrap();
230        let validator = RsaPublicKeyValidator::new(public_key);
231
232        let data = b"Hello, signed chat!";
233        let updater = TestUpdater {
234            data: data.to_vec(),
235        };
236
237        let bad_signature = vec![0u8; crate::SIGNATURE_BYTES];
238        let is_valid = validator.validate(&updater, &bad_signature).unwrap();
239        assert!(!is_valid);
240    }
241
242    #[test]
243    fn test_no_validation() {
244        let validator = NoValidation;
245        let data = b"Any data";
246        let updater = TestUpdater {
247            data: data.to_vec(),
248        };
249        let signature = vec![0u8; 10];
250
251        let is_valid = validator.validate(&updater, &signature).unwrap();
252        assert!(is_valid);
253    }
254}