1#![warn(missing_docs)]
8#![deny(unsafe_code)]
9
10use aes::Aes128;
11use aes::cipher::{BlockEncrypt, KeyInit};
12use rsa::Pkcs1v15Encrypt;
13use rsa::pkcs8::EncodePublicKey;
14use rsa::{RsaPrivateKey, RsaPublicKey};
15use sha1::{Digest, Sha1};
16use thiserror::Error;
17
18#[derive(Debug, Error)]
24#[non_exhaustive]
25pub enum CryptoError {
26 #[error("RSA key generation failed: {0}")]
28 KeyGeneration(String),
29
30 #[error("RSA decryption failed: {0}")]
32 Decryption(String),
33
34 #[error("invalid shared secret length: expected 16, got {0}")]
36 InvalidSecretLength(usize),
37
38 #[error("public key encoding failed: {0}")]
40 PublicKeyEncoding(String),
41}
42
43pub struct ServerKeyPair {
53 private_key: RsaPrivateKey,
54 public_key: RsaPublicKey,
55 public_key_der: Vec<u8>,
57}
58
59impl ServerKeyPair {
60 pub fn generate() -> Result<Self, CryptoError> {
67 let mut rng = rsa::rand_core::OsRng;
68 let private_key = RsaPrivateKey::new(&mut rng, 1024)
69 .map_err(|e| CryptoError::KeyGeneration(e.to_string()))?;
70 let public_key = RsaPublicKey::from(&private_key);
71 let public_key_der = public_key
72 .to_public_key_der()
73 .map_err(|e| CryptoError::PublicKeyEncoding(e.to_string()))?
74 .to_vec();
75
76 Ok(Self {
77 private_key,
78 public_key,
79 public_key_der,
80 })
81 }
82
83 pub fn public_key_der(&self) -> &[u8] {
85 &self.public_key_der
86 }
87
88 pub fn public_key(&self) -> &RsaPublicKey {
90 &self.public_key
91 }
92
93 pub fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>, CryptoError> {
101 self.private_key
102 .decrypt(Pkcs1v15Encrypt, ciphertext)
103 .map_err(|e| CryptoError::Decryption(e.to_string()))
104 }
105
106 pub fn decrypt_shared_secret(&self, encrypted_secret: &[u8]) -> Result<[u8; 16], CryptoError> {
115 let decrypted = self.decrypt(encrypted_secret)?;
116 decrypted
117 .try_into()
118 .map_err(|v: Vec<u8>| CryptoError::InvalidSecretLength(v.len()))
119 }
120}
121
122impl std::fmt::Debug for ServerKeyPair {
123 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
124 f.debug_struct("ServerKeyPair")
125 .field("public_key_der_len", &self.public_key_der.len())
126 .finish()
127 }
128}
129
130pub struct CipherState {
147 cipher: Aes128,
148 enc_iv: [u8; 16],
149 dec_iv: [u8; 16],
150}
151
152impl CipherState {
153 pub fn new(shared_secret: &[u8; 16]) -> Self {
158 Self {
159 cipher: Aes128::new(shared_secret.into()),
160 enc_iv: *shared_secret,
161 dec_iv: *shared_secret,
162 }
163 }
164
165 pub fn decrypt(&mut self, data: &mut [u8]) {
169 for byte in data.iter_mut() {
170 let mut block = self.dec_iv.into();
171 self.cipher.encrypt_block(&mut block);
172 let ciphertext_byte = *byte;
173 *byte ^= block[0];
174 self.dec_iv.copy_within(1.., 0);
176 self.dec_iv[15] = ciphertext_byte;
177 }
178 }
179
180 pub fn encrypt(&mut self, data: &mut [u8]) {
184 for byte in data.iter_mut() {
185 let mut block = self.enc_iv.into();
186 self.cipher.encrypt_block(&mut block);
187 *byte ^= block[0];
188 self.enc_iv.copy_within(1.., 0);
190 self.enc_iv[15] = *byte;
191 }
192 }
193
194 pub fn split(self) -> (DecryptCipher, EncryptCipher) {
200 (
201 DecryptCipher {
202 cipher: self.cipher.clone(),
203 iv: self.dec_iv,
204 },
205 EncryptCipher {
206 cipher: self.cipher,
207 iv: self.enc_iv,
208 },
209 )
210 }
211}
212
213impl std::fmt::Debug for CipherState {
214 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
215 f.debug_struct("CipherState").finish()
216 }
217}
218
219pub struct DecryptCipher {
228 cipher: Aes128,
229 iv: [u8; 16],
230}
231
232impl DecryptCipher {
233 pub fn decrypt(&mut self, data: &mut [u8]) {
237 for byte in data.iter_mut() {
238 let mut block = self.iv.into();
239 self.cipher.encrypt_block(&mut block);
240 let ciphertext_byte = *byte;
241 *byte ^= block[0];
242 self.iv.copy_within(1.., 0);
243 self.iv[15] = ciphertext_byte;
244 }
245 }
246}
247
248impl std::fmt::Debug for DecryptCipher {
249 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
250 f.debug_struct("DecryptCipher").finish()
251 }
252}
253
254pub struct EncryptCipher {
259 cipher: Aes128,
260 iv: [u8; 16],
261}
262
263impl EncryptCipher {
264 pub fn encrypt(&mut self, data: &mut [u8]) {
268 for byte in data.iter_mut() {
269 let mut block = self.iv.into();
270 self.cipher.encrypt_block(&mut block);
271 *byte ^= block[0];
272 self.iv.copy_within(1.., 0);
273 self.iv[15] = *byte;
274 }
275 }
276}
277
278impl std::fmt::Debug for EncryptCipher {
279 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
280 f.debug_struct("EncryptCipher").finish()
281 }
282}
283
284pub fn minecraft_digest(server_id: &str, shared_secret: &[u8], public_key_der: &[u8]) -> String {
306 let mut hasher = Sha1::new();
307 hasher.update(server_id.as_bytes());
308 hasher.update(shared_secret);
309 hasher.update(public_key_der);
310 let hash = hasher.finalize();
311
312 let negative = hash[0] & 0x80 != 0;
315
316 if negative {
317 let mut bytes = hash.to_vec();
319 let mut carry = true;
320 for byte in bytes.iter_mut().rev() {
321 *byte = !*byte;
322 if carry {
323 let (result, overflow) = byte.overflowing_add(1);
324 *byte = result;
325 carry = overflow;
326 }
327 }
328 let hex: String = bytes.iter().map(|b| format!("{b:02x}")).collect();
330 let trimmed = hex.trim_start_matches('0');
331 format!("-{trimmed}")
332 } else {
333 let hex: String = hash.iter().map(|b| format!("{b:02x}")).collect();
334 let trimmed = hex.trim_start_matches('0');
335 if trimmed.is_empty() {
336 "0".to_string()
337 } else {
338 trimmed.to_string()
339 }
340 }
341}
342
343pub fn offline_uuid(name: &str) -> uuid::Uuid {
350 use md5::{Digest as Md5Digest, Md5};
351
352 let input = format!("OfflinePlayer:{name}");
353 let hash = Md5::digest(input.as_bytes());
354 let mut bytes: [u8; 16] = hash.into();
355
356 bytes[6] = (bytes[6] & 0x0f) | 0x30;
358 bytes[8] = (bytes[8] & 0x3f) | 0x80;
360
361 uuid::Uuid::from_bytes(bytes)
362}
363
364pub fn generate_challenge() -> [u8; 4] {
372 use rand::RngExt;
373 let mut buf = [0u8; 4];
374 rand::rng().fill(&mut buf[..]);
375 buf
376}
377
378#[cfg(test)]
379#[allow(clippy::unwrap_used, clippy::expect_used)]
380mod tests {
381 use super::*;
382 use rsa::rand_core::OsRng;
383
384 #[test]
389 fn test_cipher_encrypt_decrypt_roundtrip() {
390 let secret = [0x42u8; 16];
391 let mut cipher_enc = CipherState::new(&secret);
392 let mut cipher_dec = CipherState::new(&secret);
393
394 let original = b"Hello, Minecraft!".to_vec();
395 let mut data = original.clone();
396
397 cipher_enc.encrypt(&mut data);
398 assert_ne!(data, original, "encrypted data should differ from original");
399
400 cipher_dec.decrypt(&mut data);
401 assert_eq!(data, original, "decrypted data should match original");
402 }
403
404 #[test]
405 fn test_cipher_is_stateful() {
406 let secret = [0xAB; 16];
407 let mut cipher = CipherState::new(&secret);
408
409 let mut data1 = b"test".to_vec();
410 cipher.encrypt(&mut data1);
411
412 let mut data2 = b"test".to_vec();
413 cipher.encrypt(&mut data2);
414
415 assert_ne!(
416 data1, data2,
417 "same plaintext encrypted twice should differ (stateful cipher)"
418 );
419 }
420
421 #[test]
422 fn test_cipher_multi_chunk_roundtrip() {
423 let secret = [0x13; 16];
424 let mut enc = CipherState::new(&secret);
425 let mut dec = CipherState::new(&secret);
426
427 let chunk1 = b"first chunk ".to_vec();
428 let chunk2 = b"second chunk".to_vec();
429
430 let mut enc1 = chunk1.clone();
431 enc.encrypt(&mut enc1);
432
433 let mut enc2 = chunk2.clone();
434 enc.encrypt(&mut enc2);
435
436 dec.decrypt(&mut enc1);
437 dec.decrypt(&mut enc2);
438
439 assert_eq!(enc1, chunk1);
440 assert_eq!(enc2, chunk2);
441 }
442
443 #[test]
444 fn test_cipher_empty_data() {
445 let secret = [0x00; 16];
446 let mut cipher = CipherState::new(&secret);
447 let mut data = Vec::new();
448 cipher.encrypt(&mut data); cipher.decrypt(&mut data); }
451
452 #[test]
457 fn test_rsa_keygen_and_der() {
458 let keypair = ServerKeyPair::generate().expect("key generation");
459 assert!(
460 keypair.public_key_der().len() > 100,
461 "public key DER should be > 100 bytes"
462 );
463 assert!(
464 keypair.public_key_der().len() < 300,
465 "public key DER should be < 300 bytes"
466 );
467 }
468
469 #[test]
470 fn test_rsa_encrypt_decrypt_roundtrip() {
471 let keypair = ServerKeyPair::generate().expect("key generation");
472 let plaintext = b"shared_secret!!!"; let ciphertext = keypair
475 .public_key
476 .encrypt(&mut OsRng, Pkcs1v15Encrypt, plaintext)
477 .expect("encryption");
478
479 let decrypted = keypair.decrypt(&ciphertext).expect("decryption");
480 assert_eq!(decrypted, plaintext);
481 }
482
483 #[test]
484 fn test_decrypt_shared_secret_correct_length() {
485 let keypair = ServerKeyPair::generate().expect("key generation");
486 let secret = [0x42u8; 16];
487
488 let encrypted = keypair
489 .public_key
490 .encrypt(&mut OsRng, Pkcs1v15Encrypt, &secret)
491 .expect("encryption");
492
493 let decrypted = keypair
494 .decrypt_shared_secret(&encrypted)
495 .expect("decryption");
496 assert_eq!(decrypted, secret);
497 }
498
499 #[test]
500 fn test_decrypt_shared_secret_wrong_length() {
501 let keypair = ServerKeyPair::generate().expect("key generation");
502 let wrong = [0x42u8; 8]; let encrypted = keypair
505 .public_key
506 .encrypt(&mut OsRng, Pkcs1v15Encrypt, &wrong)
507 .expect("encryption");
508
509 let err = keypair.decrypt_shared_secret(&encrypted).unwrap_err();
510 assert!(matches!(err, CryptoError::InvalidSecretLength(8)));
511 }
512
513 #[test]
518 fn test_minecraft_digest_notch() {
519 let result = minecraft_digest("Notch", &[], &[]);
520 assert_eq!(result, "4ed1f46bbe04bc756bcb17c0c7ce3e4632f06a48");
521 }
522
523 #[test]
524 fn test_minecraft_digest_jeb() {
525 let result = minecraft_digest("jeb_", &[], &[]);
526 assert_eq!(result, "-7c9d5b0044c130109a5d7b5fb5c317c02b4e28c1");
527 }
528
529 #[test]
530 fn test_minecraft_digest_simon() {
531 let result = minecraft_digest("simon", &[], &[]);
532 assert_eq!(result, "88e16a1019277b15d58faf0541e11910eb756f6");
533 }
534
535 #[test]
540 fn test_offline_uuid_deterministic() {
541 let uuid1 = offline_uuid("TestPlayer");
542 let uuid2 = offline_uuid("TestPlayer");
543 assert_eq!(uuid1, uuid2, "same name should produce same UUID");
544 }
545
546 #[test]
547 fn test_offline_uuid_different_names() {
548 let uuid1 = offline_uuid("Alice");
549 let uuid2 = offline_uuid("Bob");
550 assert_ne!(
551 uuid1, uuid2,
552 "different names should produce different UUIDs"
553 );
554 }
555
556 #[test]
557 fn test_offline_uuid_is_v3() {
558 let uuid = offline_uuid("Steve");
559 assert_eq!(
560 uuid.get_version(),
561 Some(uuid::Version::Md5),
562 "offline UUID should be version 3 (MD5)"
563 );
564 }
565
566 #[test]
567 fn test_offline_uuid_matches_java() {
568 let uuid = offline_uuid("Notch");
569 assert_eq!(
570 uuid.to_string(),
571 "b50ad385-829d-3141-a216-7e7d7539ba7f",
572 "offline UUID should match Java's UUID.nameUUIDFromBytes"
573 );
574 }
575
576 #[test]
581 fn test_generate_challenge_length() {
582 let challenge = generate_challenge();
583 assert_eq!(challenge.len(), 4);
584 }
585
586 #[test]
587 fn test_generate_challenge_random() {
588 let c1 = generate_challenge();
589 let c2 = generate_challenge();
590 assert_ne!(c1, c2, "two challenges should almost certainly differ");
591 }
592
593 #[test]
598 fn test_cipher_split_roundtrip() {
599 let secret = [0x42u8; 16];
600 let cipher = CipherState::new(&secret);
601 let (mut decrypt, mut encrypt) = cipher.split();
602
603 let original = b"Hello, split cipher!".to_vec();
604 let mut data = original.clone();
605
606 encrypt.encrypt(&mut data);
607 assert_ne!(data, original);
608
609 decrypt.decrypt(&mut data);
610 assert_eq!(data, original);
611 }
612
613 #[test]
614 fn test_cipher_split_matches_unsplit() {
615 let secret = [0x13u8; 16];
616
617 let mut unsplit = CipherState::new(&secret);
618 let original = b"consistency check".to_vec();
619 let mut data_unsplit = original.clone();
620 unsplit.encrypt(&mut data_unsplit);
621
622 let cipher = CipherState::new(&secret);
623 let (_dec, mut enc) = cipher.split();
624 let mut data_split = original;
625 enc.encrypt(&mut data_split);
626
627 assert_eq!(
628 data_unsplit, data_split,
629 "split cipher should produce identical ciphertext"
630 );
631 }
632
633 #[test]
634 fn test_cipher_split_multi_chunk() {
635 let secret = [0xAB; 16];
636 let cipher = CipherState::new(&secret);
637 let (mut decrypt, mut encrypt) = cipher.split();
638
639 let chunk1 = b"first chunk".to_vec();
640 let chunk2 = b"second chunk".to_vec();
641 let chunk3 = b"third chunk".to_vec();
642
643 let mut enc1 = chunk1.clone();
644 let mut enc2 = chunk2.clone();
645 let mut enc3 = chunk3.clone();
646
647 encrypt.encrypt(&mut enc1);
648 encrypt.encrypt(&mut enc2);
649 encrypt.encrypt(&mut enc3);
650
651 decrypt.decrypt(&mut enc1);
652 decrypt.decrypt(&mut enc2);
653 decrypt.decrypt(&mut enc3);
654
655 assert_eq!(enc1, chunk1);
656 assert_eq!(enc2, chunk2);
657 assert_eq!(enc3, chunk3);
658 }
659}