Skip to main content

steel_crypto/
rsa_utils.rs

1//! RSA key pair generation and encoding utilities.
2//!
3//! Ported from net/minecraft/util/Crypt.java
4
5use rsa::{RsaPrivateKey, RsaPublicKey};
6use thiserror::Error;
7
8/// Errors that can occur during cryptographic operations
9#[derive(Debug, Error)]
10pub enum CryptError {
11    #[error("RSA error: {0}")]
12    RsaError(#[from] rsa::Error),
13
14    #[error("PKCS8 error: {0}")]
15    Pkcs8Error(#[from] rsa::pkcs8::Error),
16
17    #[error("SPKI error: {0}")]
18    SpkiError(#[from] rsa::pkcs8::spki::Error),
19
20    #[error("Invalid key format")]
21    InvalidKeyFormat,
22
23    #[error("Cryptographic operation failed: {0}")]
24    OperationFailed(String),
25}
26
27/// Generates a 1024-bit RSA key pair.
28///
29/// Equivalent to `Crypt.generateKeyPair()` in Minecraft.
30pub fn generate_key_pair() -> Result<(RsaPrivateKey, RsaPublicKey), CryptError> {
31    let mut rng = rand::rng();
32    let private_key = RsaPrivateKey::new(&mut rng, crate::RSA_KEY_BITS)?;
33    let public_key = RsaPublicKey::from(&private_key);
34    Ok((private_key, public_key))
35}
36
37/// Converts an RSA public key to DER-encoded X.509 format bytes.
38///
39/// This is the format sent over the network.
40pub fn public_key_to_bytes(key: &RsaPublicKey) -> Result<Vec<u8>, CryptError> {
41    use rsa::pkcs8::EncodePublicKey;
42    key.to_public_key_der()
43        .map(|der| der.to_vec())
44        .map_err(CryptError::from)
45}
46
47/// Parses an RSA public key from DER-encoded X.509 format bytes.
48///
49/// Equivalent to `Crypt.byteToPublicKey()` in Minecraft.
50pub fn public_key_from_bytes(bytes: &[u8]) -> Result<RsaPublicKey, CryptError> {
51    use rsa::pkcs8::DecodePublicKey;
52    RsaPublicKey::from_public_key_der(bytes).map_err(CryptError::from)
53}
54
55/// Converts an RSA public key to PEM format string.
56///
57/// Format: "-----BEGIN RSA PUBLIC KEY-----\n{base64}\n-----END RSA PUBLIC KEY-----\n"
58pub fn public_key_to_pem(key: &RsaPublicKey) -> Result<String, CryptError> {
59    use rsa::pkcs8::EncodePublicKey;
60    key.to_public_key_pem(rsa::pkcs8::LineEnding::LF)
61        .map_err(CryptError::from)
62}
63
64/// Parses an RSA public key from PEM format string.
65///
66/// Equivalent to `Crypt.stringToRsaPublicKey()` in Minecraft.
67pub fn public_key_from_pem(pem: &str) -> Result<RsaPublicKey, CryptError> {
68    use rsa::pkcs8::DecodePublicKey;
69    RsaPublicKey::from_public_key_pem(pem).map_err(CryptError::from)
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75
76    #[test]
77    fn test_generate_key_pair() {
78        let result = generate_key_pair();
79        assert!(result.is_ok());
80    }
81
82    #[test]
83    fn test_public_key_round_trip() {
84        let (_, public_key) = generate_key_pair().unwrap();
85        let bytes = public_key_to_bytes(&public_key).unwrap();
86        let decoded = public_key_from_bytes(&bytes).unwrap();
87
88        let encoded_again = public_key_to_bytes(&decoded).unwrap();
89        assert_eq!(bytes, encoded_again);
90    }
91
92    #[test]
93    fn test_pem_round_trip() {
94        let (_, public_key) = generate_key_pair().unwrap();
95        let pem = public_key_to_pem(&public_key).unwrap();
96        let decoded = public_key_from_pem(&pem).unwrap();
97
98        let pem_again = public_key_to_pem(&decoded).unwrap();
99        assert_eq!(pem, pem_again);
100    }
101}