All checks were successful
Build and Test / rust-boss (push) Successful in 6m44s
105 lines
2.9 KiB
Rust
105 lines
2.9 KiB
Rust
use aes::Aes128;
|
|
use ctr::cipher::{KeyIvInit, StreamCipher};
|
|
use hmac::{Hmac, KeyInit, Mac};
|
|
use sha2::Sha256;
|
|
use md5::{Md5, Digest};
|
|
use rand::Rng;
|
|
use anyhow::{Result, bail};
|
|
|
|
type HmacSha256 = Hmac<Sha256>;
|
|
type Aes128Ctr = ctr::Ctr128BE<Aes128>;
|
|
|
|
const BOSS_WUP_VER: u32 = 0x20001;
|
|
|
|
const BOSS_AES_KEY_HASH: [u8; 16] = hex_literal::hex!("5202ce5099232c3d365e28379790a919");
|
|
const BOSS_HMAC_KEY_HASH: [u8; 16] = hex_literal::hex!("b4482fef177b0100090ce0dbeb8ce977");
|
|
|
|
pub struct WupBossInfo {
|
|
pub hash_type: u16,
|
|
pub iv: Vec<u8>,
|
|
pub hmac: Vec<u8>,
|
|
pub content: Vec<u8>,
|
|
}
|
|
|
|
fn verify_keys(aes_key: &[u8], hmac_key: &[u8]) -> Result<()> {
|
|
let mut hasher = Md5::new();
|
|
hasher.update(aes_key);
|
|
let aes_md5 = hasher.finalize_reset();
|
|
|
|
if aes_md5[..] != BOSS_AES_KEY_HASH {
|
|
bail!("Invalid BOSS AES key");
|
|
}
|
|
|
|
hasher.update(hmac_key);
|
|
let hmac_md5 = hasher.finalize();
|
|
|
|
if hmac_md5[..] != BOSS_HMAC_KEY_HASH {
|
|
bail!("Invalid BOSS HMAC key");
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn decrypt_wiiu(data: &[u8], aes_key: &[u8], hmac_key: &[u8]) -> Result<WupBossInfo> {
|
|
verify_keys(aes_key, hmac_key)?;
|
|
|
|
let hash_type = u16::from_be_bytes([data[0xA], data[0xB]]);
|
|
if hash_type != 2 {
|
|
bail!("Unknown hash type");
|
|
}
|
|
|
|
let mut iv = Vec::with_capacity(16);
|
|
iv.extend_from_slice(&data[0xC..0x18]);
|
|
iv.extend_from_slice(&[0, 0, 0, 1]);
|
|
|
|
let mut cipher = Aes128Ctr::new_from_slices(aes_key, &iv)?;
|
|
let mut decrypted = data[0x20..].to_vec();
|
|
cipher.apply_keystream(&mut decrypted);
|
|
|
|
let hmac = decrypted[..0x20].to_vec();
|
|
let content = decrypted[0x20..].to_vec();
|
|
|
|
let mut mac = HmacSha256::new_from_slice(hmac_key)?;
|
|
mac.update(&content);
|
|
mac.verify_slice(&hmac)
|
|
.map_err(|_| anyhow::anyhow!("Content HMAC check failed"))?;
|
|
|
|
Ok(WupBossInfo {
|
|
hash_type,
|
|
iv,
|
|
hmac,
|
|
content,
|
|
})
|
|
}
|
|
|
|
pub fn encrypt_wiiu(content: &[u8], aes_key: &[u8], hmac_key: &[u8]) -> Result<Vec<u8>> {
|
|
verify_keys(aes_key, hmac_key)?;
|
|
|
|
let mut mac = HmacSha256::new_from_slice(hmac_key)?;
|
|
mac.update(content);
|
|
let hmac = mac.finalize().into_bytes();
|
|
|
|
let mut plaintext = Vec::new();
|
|
plaintext.extend_from_slice(&hmac);
|
|
plaintext.extend_from_slice(content);
|
|
|
|
let mut iv12 = [0u8; 12];
|
|
rand::rng().fill_bytes(&mut iv12);
|
|
|
|
let mut iv = Vec::with_capacity(16);
|
|
iv.extend_from_slice(&iv12);
|
|
iv.extend_from_slice(&[0, 0, 0, 1]);
|
|
|
|
let mut cipher = Aes128Ctr::new_from_slices(aes_key, &iv)?;
|
|
cipher.apply_keystream(&mut plaintext);
|
|
|
|
let mut header = vec![0u8; 0x20];
|
|
header[0..4].copy_from_slice(b"boss");
|
|
header[0x4..0x8].copy_from_slice(&BOSS_WUP_VER.to_be_bytes());
|
|
header[0x8..0xA].copy_from_slice(&1u16.to_be_bytes());
|
|
header[0xA..0xC].copy_from_slice(&2u16.to_be_bytes());
|
|
header[0xC..0x18].copy_from_slice(&iv12);
|
|
|
|
header.extend_from_slice(&plaintext);
|
|
Ok(header)
|
|
}
|