<commit_before>//! This provides helper functions for various Yggdrasil/Protocol Encryption
//! related things.
use errors::{Result, ResultExt};

use std::fmt::Write;

use openssl::hash::{self, MessageDigest};
use openssl::rand;
use openssl::rsa::{PKCS1_PADDING, Padding, Rsa};

const PADDING: Padding = PKCS1_PADDING;

/// Create a shared secret as used for protocol encryption
///
/// # Panics
///
/// Panics if there's an error generating the random bytes.
pub fn create_shared_secret() -> [u8; 16] {
    let mut ret = [0; 16];
    match rand::rand_bytes(&mut ret) {
        Ok(()) => (),
        Err(_) => {
            panic!("yggdrasil.create_shared_secret encountered an error");
        },
    }
    ret
}

/// Generate an RSA key as used by MC.
///
/// Call rsa_key_binary to convert it to the format used in EncryptionRequest.
pub fn generate_rsa_key() -> Rsa {
    Rsa::generate(1024).expect("Error generating RSA key")
}

/// Convert a given RSA key's public part into the DER format, i.e. the format
/// used in the EncryptionRequest packet.
///
/// # Panics
///
/// Panics if the given key does not have a valid public part.
/// It should never panic if called with a key generated by generate_rsa_key
pub fn rsa_key_binary(key: &Rsa) -> Vec<u8> {
    key.public_key_to_der().expect("Error converting RSA key to DER format")
}

/// Given a public key in DER format (the format you get it in in the
/// EncryptionRequest packet), and some data, RSA encrypt the data
///
/// For use with the EncryptionResponse packet.
///
/// The data must be able to fit within 128 bytes encrypted.
pub fn rsa_encrypt(pubkey: &[u8], data: &[u8]) -> Result<Vec<u8>> {
    let key = Rsa::public_key_from_der(pubkey).chain_err(|| "yggdrasil::rsa_encrypt got error trying to read public key")?;
    let mut ret = vec![0; 128];
    match key.public_encrypt(data, &mut ret, PADDING) {
        Ok(128) => (),
        _ => bail!("yggdrasil::rsa_encrypt error encrypting data"),
    }

    Ok(ret)
}

/// Given a RSA key, and some data RSA encrypted with the key, decrypt the data
///
/// The given data to decrypt must be exactly 128 bytes long, and the data
/// decrypted must be able to fit within 128 bytes.
pub fn rsa_decrypt(key: &Rsa, data: &[u8]) -> Result<Vec<u8>> {
    if data.len() != 128 {
        bail!("yggdrasil::rsa_decrypt passed data was {}, not 16 bytes long", data.len());
    }

    let mut ret = vec![0; 128];

    let len = match key.private_decrypt(data, &mut ret, PADDING) {
        Ok(x) if x <= 128 => x,
        _ => bail!("yggdrasil::rsa_decrypt error decrypting data"),
    };
    ret.truncate(len);

    Ok(ret)
}

/// Given the server_id, shared_secret and server's public key, calculate the
/// sha1 that is to be used for posting to Mojang
pub fn post_sha1(server_id: &str,
                 shared_secret: &[u8],
                 server_public_key: &[u8])
                 -> String {

    let mut tmp = server_id.as_bytes().to_vec();
    tmp.extend(shared_secret);
    tmp.extend(server_public_key);
    sha1(&tmp)
}

/// Calculate a Minecraft-style sha1
fn sha1(data: &[u8]) -> String {
    let mut digest =
        hash::hash(MessageDigest::sha1(), data).expect("yggdrasil::sha1 error");

    let mut negative = false;

    if digest[0] >= 128 {
        /* This means we have to calculate the twos complement */
        negative = true;

        for byte in digest.iter_mut() {
            *byte ^= 0xff;
        }

        /* Add 1 to the number */
        for byte in digest.iter_mut().rev() {
            if *byte == 255 {
                *byte = 0;
            } else {
                *byte += 1;
                break;
            }
        }
    }

    let mut ret = String::new();
    if negative {
        write!(&mut ret, "-").expect("yggdrasil.sha1 failed writing to String");
    }

    let mut non_zero = false;
    for byte in &digest {
        if *byte >= 16 {
            non_zero = true;
        } else if non_zero == false && *byte > 0 {
            /* If the value is 0 < n < 16, we write the second (hexa)decimal
             * in it, but the first is 0 so is omitted */
            write!(&mut ret, "{:x}", byte)
                .expect("yggdrasil.sha1 failed writing to String");
            non_zero = true;
            continue;
        }
        if non_zero {
            write!(&mut ret, "{:02x}", byte)
                .expect("yggdrasil.sha1 failed writing to String");
        }
    }
    ret
}

#[cfg(test)]<commit_msg>Use openssl::hash::hash2 instead of hash<commit_after>//! This provides helper functions for various Yggdrasil/Protocol Encryption
//! related things.
use errors::{Result, ResultExt};

use std::fmt::Write;

use openssl::hash::{self, MessageDigest};
use openssl::rand;
use openssl::rsa::{PKCS1_PADDING, Padding, Rsa};

const PADDING: Padding = PKCS1_PADDING;

/// Create a shared secret as used for protocol encryption
///
/// # Panics
///
/// Panics if there's an error generating the random bytes.
pub fn create_shared_secret() -> [u8; 16] {
    let mut ret = [0; 16];
    match rand::rand_bytes(&mut ret) {
        Ok(()) => (),
        Err(_) => {
            panic!("yggdrasil.create_shared_secret encountered an error");
        },
    }
    ret
}

/// Generate an RSA key as used by MC.
///
/// Call rsa_key_binary to convert it to the format used in EncryptionRequest.
pub fn generate_rsa_key() -> Rsa {
    Rsa::generate(1024).expect("Error generating RSA key")
}

/// Convert a given RSA key's public part into the DER format, i.e. the format
/// used in the EncryptionRequest packet.
///
/// # Panics
///
/// Panics if the given key does not have a valid public part.
/// It should never panic if called with a key generated by generate_rsa_key
pub fn rsa_key_binary(key: &Rsa) -> Vec<u8> {
    key.public_key_to_der().expect("Error converting RSA key to DER format")
}

/// Given a public key in DER format (the format you get it in in the
/// EncryptionRequest packet), and some data, RSA encrypt the data
///
/// For use with the EncryptionResponse packet.
///
/// The data must be able to fit within 128 bytes encrypted.
pub fn rsa_encrypt(pubkey: &[u8], data: &[u8]) -> Result<Vec<u8>> {
    let key = Rsa::public_key_from_der(pubkey).chain_err(|| "yggdrasil::rsa_encrypt got error trying to read public key")?;
    let mut ret = vec![0; 128];
    match key.public_encrypt(data, &mut ret, PADDING) {
        Ok(128) => (),
        _ => bail!("yggdrasil::rsa_encrypt error encrypting data"),
    }

    Ok(ret)
}

/// Given a RSA key, and some data RSA encrypted with the key, decrypt the data
///
/// The given data to decrypt must be exactly 128 bytes long, and the data
/// decrypted must be able to fit within 128 bytes.
pub fn rsa_decrypt(key: &Rsa, data: &[u8]) -> Result<Vec<u8>> {
    if data.len() != 128 {
        bail!("yggdrasil::rsa_decrypt passed data was {}, not 16 bytes long", data.len());
    }

    let mut ret = vec![0; 128];

    let len = match key.private_decrypt(data, &mut ret, PADDING) {
        Ok(x) if x <= 128 => x,
        _ => bail!("yggdrasil::rsa_decrypt error decrypting data"),
    };
    ret.truncate(len);

    Ok(ret)
}

/// Given the server_id, shared_secret and server's public key, calculate the
/// sha1 that is to be used for posting to Mojang
pub fn post_sha1(server_id: &str,
                 shared_secret: &[u8],
                 server_public_key: &[u8])
                 -> String {

    let mut tmp = server_id.as_bytes().to_vec();
    tmp.extend(shared_secret);
    tmp.extend(server_public_key);
    sha1(&tmp)
}

/// Calculate a Minecraft-style sha1
fn sha1(data: &[u8]) -> String {
    let mut digest =
        hash::hash2(MessageDigest::sha1(), data).expect("yggdrasil::sha1 error");

    let mut negative = false;

    if digest[0] >= 128 {
        /* This means we have to calculate the twos complement */
        negative = true;

        for byte in digest.iter_mut() {
            *byte ^= 0xff;
        }

        /* Add 1 to the number */
        for byte in digest.iter_mut().rev() {
            if *byte == 255 {
                *byte = 0;
            } else {
                *byte += 1;
                break;
            }
        }
    }

    let mut ret = String::new();
    if negative {
        write!(&mut ret, "-").expect("yggdrasil.sha1 failed writing to String");
    }

    let mut non_zero = false;
    for byte in &*digest {
        if *byte >= 16 {
            non_zero = true;
        } else if non_zero == false && *byte > 0 {
            /* If the value is 0 < n < 16, we write the second (hexa)decimal
             * in it, but the first is 0 so is omitted */
            write!(&mut ret, "{:x}", byte)
                .expect("yggdrasil.sha1 failed writing to String");
            non_zero = true;
            continue;
        }
        if non_zero {
            write!(&mut ret, "{:02x}", byte)
                .expect("yggdrasil.sha1 failed writing to String");
        }
    }
    ret
}

#[cfg(test)]
{'fraction_non_alphanumeric': 0.13264283327764784, 'fraction_numerical': 0.01781935627575454, 'mean_word_length': 2.32469455757127, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 45, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1408147', 'n_tokens_mistral': 2995, 'n_tokens_neox': 2836, 'n_words': 957}