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