diff --git a/Cargo.lock b/Cargo.lock index 2d9fb41..e31da5f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -63,6 +63,15 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitreader" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f10043e4864d975e7f197f993ec4018636ad93946724b2571c4474d51845869b" +dependencies = [ + "cfg-if", +] + [[package]] name = "cc" version = "1.0.79" @@ -339,6 +348,7 @@ name = "umskt" version = "0.1.0" dependencies = [ "anyhow", + "bitreader", "clap", "openssl", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 55e2ded..eb34eed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" [dependencies] anyhow = "1.0.71" +bitreader = "0.3.7" clap = { version = "4.3.4", features = ["derive"] } openssl = "0.10.54" serde_json = "1.0" diff --git a/src/bink1998.rs b/src/bink1998.rs new file mode 100644 index 0000000..3c1061b --- /dev/null +++ b/src/bink1998.rs @@ -0,0 +1,142 @@ +use anyhow::Result; +use bitreader::BitReader; +use openssl::{ + bn::{BigNum, BigNumContext}, + ec::{EcGroup, EcPoint}, + sha::sha1, +}; + +use crate::{key::unbase24, FIELD_BYTES}; + +#[derive(Clone, Copy, Debug)] +struct ProductKey { + upgrade: bool, + serial: u32, + hash: u32, + signature: u64, +} + +pub fn verify( + e_curve: &EcGroup, + base_point: &EcPoint, + public_key: &EcPoint, + p_key: &str, +) -> Result { + let mut num_context = BigNumContext::new()?; + + let p_raw = unbase24(p_key); + for byte in &p_raw { + print!("{:02X}, ", *byte); + } + println!(); + let product_key = unpack(&p_raw)?; + + let p_data = product_key.serial << 1 | product_key.upgrade as u32; + + let e = BigNum::from_u32(product_key.hash)?; + let s = BigNum::from_slice(&product_key.signature.to_be_bytes())?; + let mut x = BigNum::new()?; + let mut y = BigNum::new()?; + + let mut t = EcPoint::new(e_curve)?; + let mut p = EcPoint::new(e_curve)?; + let mut p_2 = EcPoint::new(e_curve)?; + + t.mul(e_curve, base_point, &s, &num_context)?; + p.mul(e_curve, public_key, &e, &num_context)?; + p_2.mul(e_curve, public_key, &e, &num_context)?; + + p.add(e_curve, &t, &p_2, &mut num_context)?; + + p.affine_coordinates(e_curve, &mut x, &mut y, &mut num_context)?; + + let mut msg_buffer: [u8; 100] = [0; 100]; + + let mut x_bin = x.to_vec(); + x_bin.reverse(); + let mut y_bin = y.to_vec(); + y_bin.reverse(); + + msg_buffer[0..4].copy_from_slice(&p_data.to_le_bytes()); + msg_buffer[4..4 + FIELD_BYTES].copy_from_slice(&x_bin); + msg_buffer[4 + FIELD_BYTES..4 + FIELD_BYTES * 2].copy_from_slice(&y_bin); + + let msg_digest = sha1(&msg_buffer); + + let hash: u32 = u32::from_be_bytes(msg_digest[0..4].try_into().unwrap()) & ((1 << 28) - 1); + + Ok(hash == product_key.hash) +} + +pub fn generate( + e_curve: &EcGroup, + base_point: &EcPoint, + gen_order: &BigNum, + private_key: &BigNum, + p_serial: u32, + p_upgrade: bool, +) -> String { + todo!() +} + +fn unpack(p_raw: &[u8]) -> Result { + const HASH_LENGTH_BITS: u8 = 28; + const SERIAL_LENGTH_BITS: u8 = 30; + + let mut reader = BitReader::new(p_raw); + // The signature length is unknown, but everything else is, so we can calculate it + let signature_length_bits = + (p_raw.len() * 8) as u8 - (HASH_LENGTH_BITS + SERIAL_LENGTH_BITS + 1); + + let p_signature = reader.read_u64(signature_length_bits)?; + let p_hash = reader.read_u32(HASH_LENGTH_BITS)?; + let p_serial = reader.read_u32(SERIAL_LENGTH_BITS)?; + let p_upgrade = reader.read_bool()?; + + Ok(ProductKey { + upgrade: p_upgrade, + serial: p_serial, + hash: p_hash, + signature: p_signature, + }) +} + +fn pack(p_key: ProductKey) -> Vec { + todo!() +} + +#[cfg(test)] +mod tests { + use std::{fs::File, io::BufReader}; + + use serde_json::from_reader; + + use crate::crypto::initialize_elliptic_curve; + + #[test] + fn test() { + // Example product key and its BINK ID + let product_key = "D9924-R6BG2-39J83-RYKHF-W47TT"; + let bink_id = "2E"; + + // Load keys.json + let path = "keys.json"; + let file = File::open(path).unwrap(); + let reader = BufReader::new(file); + let keys: serde_json::Value = from_reader(reader).unwrap(); + + let bink = &keys["BINK"][&bink_id]; + + let p = bink["p"].as_str().unwrap(); + let a = bink["a"].as_str().unwrap(); + let b = bink["b"].as_str().unwrap(); + let gx = bink["g"]["x"].as_str().unwrap(); + let gy = bink["g"]["y"].as_str().unwrap(); + let kx = bink["pub"]["x"].as_str().unwrap(); + let ky = bink["pub"]["y"].as_str().unwrap(); + + let (e_curve, gen_point, pub_point) = initialize_elliptic_curve(p, a, b, gx, gy, kx, ky); + + assert!(super::verify(&e_curve, &gen_point, &pub_point, product_key).unwrap()); + } +} diff --git a/src/cli.rs b/src/cli.rs index 091f3c4..77bb25e 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -8,6 +8,8 @@ use openssl::{ }; use serde_json::from_reader; +use crate::crypto::initialize_elliptic_curve; + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Mode { Bink1998, @@ -111,8 +113,7 @@ impl Cli { println!(); } - let (e_curve, gen_point, pub_point) = - Cli::initialize_elliptic_curve(p, a, b, gx, gy, kx, ky); + let (e_curve, gen_point, pub_point) = initialize_elliptic_curve(p, a, b, gx, gy, kx, ky); Ok(Self { options, @@ -127,14 +128,6 @@ impl Cli { }) } - pub fn run(&mut self) -> Result<()> { - match self.options.application_mode { - Mode::Bink1998 => todo!(), - Mode::Bink2002 => todo!(), - Mode::ConfirmationId => todo!(), - } - } - fn parse_command_line() -> Options { let mut args = Options::parse(); if args.instid.is_some() { @@ -188,44 +181,39 @@ impl Cli { Ok(json) } - fn initialize_elliptic_curve( - p_sel: &str, - a_sel: &str, - b_sel: &str, - generator_x_sel: &str, - generator_y_sel: &str, - public_key_x_sel: &str, - public_key_y_sel: &str, - ) -> (EcGroup, EcPoint, EcPoint) { - let mut context = openssl::bn::BigNumContext::new().unwrap(); + pub fn run(&mut self) -> Result<()> { + match self.options.application_mode { + Mode::Bink1998 => self.bink1998(), + Mode::Bink2002 => self.bink2002(), + Mode::ConfirmationId => self.confirmation_id(), + } + } - let p = BigNum::from_dec_str(p_sel).unwrap(); - let a = BigNum::from_dec_str(a_sel).unwrap(); - let b = BigNum::from_dec_str(b_sel).unwrap(); - let generator_x = BigNum::from_dec_str(generator_x_sel).unwrap(); - let generator_y = BigNum::from_dec_str(generator_y_sel).unwrap(); + fn bink1998(&mut self) -> Result<()> { + Ok(()) + } - let public_key_x = BigNum::from_dec_str(public_key_x_sel).unwrap(); - let public_key_y = BigNum::from_dec_str(public_key_y_sel).unwrap(); + fn bink2002(&mut self) -> Result<()> { + todo!() + } - let c_curve = EcGroup::from_components(p, a, b, &mut context).unwrap(); + fn confirmation_id(&mut self) -> Result<()> { + todo!() + } - let mut gen_point = EcPoint::new(&c_curve).unwrap(); - let _ = gen_point.set_affine_coordinates_gfp( - &c_curve, - &generator_x, - &generator_y, - &mut context, + fn print_key(pk: &str) { + assert_eq!(pk.len(), 25); + println!( + "{}", + pk.chars() + .enumerate() + .fold(String::new(), |mut acc: String, (i, c)| { + if i > 0 && i % 5 == 0 { + acc.push('-'); + } + acc.push(c); + acc + }) ); - - let mut pub_point = EcPoint::new(&c_curve).unwrap(); - let _ = pub_point.set_affine_coordinates_gfp( - &c_curve, - &public_key_x, - &public_key_y, - &mut context, - ); - - (c_curve, gen_point, pub_point) } } diff --git a/src/crypto.rs b/src/crypto.rs new file mode 100644 index 0000000..9476fcb --- /dev/null +++ b/src/crypto.rs @@ -0,0 +1,37 @@ +use openssl::{ + bn::BigNum, + ec::{EcGroup, EcPoint}, +}; + +pub fn initialize_elliptic_curve( + p_sel: &str, + a_sel: &str, + b_sel: &str, + generator_x_sel: &str, + generator_y_sel: &str, + public_key_x_sel: &str, + public_key_y_sel: &str, +) -> (EcGroup, EcPoint, EcPoint) { + let mut context = openssl::bn::BigNumContext::new().unwrap(); + + let p = BigNum::from_dec_str(p_sel).unwrap(); + let a = BigNum::from_dec_str(a_sel).unwrap(); + let b = BigNum::from_dec_str(b_sel).unwrap(); + let generator_x = BigNum::from_dec_str(generator_x_sel).unwrap(); + let generator_y = BigNum::from_dec_str(generator_y_sel).unwrap(); + + let public_key_x = BigNum::from_dec_str(public_key_x_sel).unwrap(); + let public_key_y = BigNum::from_dec_str(public_key_y_sel).unwrap(); + + let c_curve = EcGroup::from_components(p, a, b, &mut context).unwrap(); + + let mut gen_point = EcPoint::new(&c_curve).unwrap(); + let _ = + gen_point.set_affine_coordinates_gfp(&c_curve, &generator_x, &generator_y, &mut context); + + let mut pub_point = EcPoint::new(&c_curve).unwrap(); + let _ = + pub_point.set_affine_coordinates_gfp(&c_curve, &public_key_x, &public_key_y, &mut context); + + (c_curve, gen_point, pub_point) +} diff --git a/src/key.rs b/src/key.rs new file mode 100644 index 0000000..583dce8 --- /dev/null +++ b/src/key.rs @@ -0,0 +1,46 @@ +use std::collections::VecDeque; + +use openssl::bn::BigNum; + +const P_CHARSET: [char; 24] = [ + 'B', 'C', 'D', 'F', 'G', 'H', 'J', 'K', 'M', 'P', 'Q', 'R', 'T', 'V', 'W', 'X', 'Y', '2', '3', + '4', '6', '7', '8', '9', +]; + +const PK_LENGTH: usize = 25; + +pub fn unbase24(cd_key: &str) -> Vec { + let p_decoded_key: Vec = cd_key + .chars() + .filter_map(|c| P_CHARSET.iter().position(|&x| x == c).map(|i| i as u8)) + .collect(); + + let mut y = BigNum::from_u32(0).unwrap(); + + for i in p_decoded_key { + y.mul_word((PK_LENGTH - 1) as u32).unwrap(); + y.add_word(i.into()).unwrap(); + } + + y.to_vec() +} + +pub fn base24(byte_seq: &[u8]) -> String { + let mut z = BigNum::from_slice(byte_seq).unwrap(); + let mut out: VecDeque = VecDeque::new(); + (0..=24).for_each(|_| out.push_front(P_CHARSET[z.div_word(24).unwrap() as usize])); + out.iter().collect() +} + +#[cfg(test)] +mod tests { + #[test] + fn test_base24() { + let input = "JTW3TJ7PFJ7V9CCMX84V9PFT8"; + let unbase24 = super::unbase24(input); + println!("{:?}", unbase24); + let base24 = super::base24(&unbase24); + println!("{}", base24); + assert_eq!(input, base24); + } +} diff --git a/src/main.rs b/src/main.rs index 5e60ce0..99ce75a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,11 @@ use anyhow::Result; +mod bink1998; mod cli; +mod crypto; +mod key; + +pub const FIELD_BYTES: usize = 48; fn main() -> Result<()> { cli::Cli::new()?.run()