diff --git a/Cargo.lock b/Cargo.lock index 9d3db39..253065e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -311,20 +311,6 @@ name = "serde" version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.164" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] [[package]] name = "serde_json" @@ -382,7 +368,6 @@ dependencies = [ "bitreader", "clap", "openssl", - "serde", "serde_json", "thiserror", ] diff --git a/Cargo.toml b/Cargo.toml index aeeec04..f622acf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,10 +2,6 @@ name = "umskt" version = "0.1.0" edition = "2021" -crate-type = ["lib"] - -[[bin]] -name = "xpkey" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -14,6 +10,5 @@ anyhow = "1.0.71" bitreader = "0.3.7" clap = { version = "4.3.4", features = ["derive"] } openssl = { git = "https://github.com/anpage/rust-openssl.git" } -serde = { version = "1.0.164", features = ["derive"] } serde_json = "1.0" thiserror = "1.0.40" diff --git a/src/bin/xpkey/keys.rs b/src/bin/xpkey/keys.rs deleted file mode 100644 index 583b0cc..0000000 --- a/src/bin/xpkey/keys.rs +++ /dev/null @@ -1,36 +0,0 @@ -use std::collections::HashMap; - -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize)] -pub struct Keys { - #[serde(rename = "Products")] - pub products: HashMap, - #[serde(rename = "BINK")] - pub bink: HashMap, -} - -#[derive(Serialize, Deserialize)] -pub struct Product { - #[serde(rename = "BINK")] - pub bink: Vec, -} - -#[derive(Serialize, Deserialize)] -pub struct Bink { - pub p: String, - pub a: String, - pub b: String, - pub g: Point, - #[serde(rename = "pub")] - pub public: Point, - pub n: String, - #[serde(rename = "priv")] - pub private: String, -} - -#[derive(Serialize, Deserialize)] -pub struct Point { - pub x: String, - pub y: String, -} diff --git a/src/bin/xpkey/main.rs b/src/bin/xpkey/main.rs deleted file mode 100644 index 3a28017..0000000 --- a/src/bin/xpkey/main.rs +++ /dev/null @@ -1,289 +0,0 @@ -mod keys; - -use std::{fs::File, io::BufReader, path::Path}; - -use anyhow::{anyhow, Result}; -use clap::{Args, Parser, Subcommand}; -use keys::{Bink, Keys}; -use serde_json::{from_reader, from_str}; - -use umskt::{ - bink1998, bink2002, confid, - crypto::{EllipticCurve, PrivateKey}, -}; - -#[derive(Parser, Debug)] -#[command(author, about, version, long_about = None)] -struct Cli { - /// Enable verbose output - #[arg(short, long)] - verbose: bool, - - #[command(subcommand)] - command: Commands, -} - -#[derive(Subcommand, Clone, Debug)] -enum Commands { - /// Show which products/binks can be loaded - List(ListArgs), - /// Generate new product keys - Generate(GenerateArgs), - /// Validate a product key - Validate(ValidateArgs), - /// Generate a phone activation Confirmation ID from an Installation ID - Confid(ConfirmationIdArgs), -} - -#[derive(Args, Clone, Debug)] -struct ListArgs { - /// Specify which keys file to load - #[arg(short = 'f', long = "file")] - keys_path: Option, -} - -#[derive(Args, Clone, Debug)] -struct GenerateArgs { - /// Specify which BINK identifier to load - #[arg(short, long, default_value = "2E")] - binkid: String, - - /// Specify which Channel Identifier to use - #[arg(short = 'c', long = "channel", default_value = "640")] - channel_id: u32, - - /// Number of keys to generate - #[arg(short = 'n', long = "number", default_value = "1")] - num_keys: u64, - - /// Specify which keys file to load - #[arg(short = 'f', long = "file")] - keys_path: Option, -} - -#[derive(Args, Clone, Debug)] -struct ValidateArgs { - /// Specify which BINK identifier to load - #[arg(short, long, default_value = "2E")] - binkid: String, - - /// Specify which keys file to load - #[arg(short = 'f', long = "file")] - keys_path: Option, - - /// Product key to validate signature - key_to_check: String, -} - -#[derive(Args, Clone, Debug)] -struct ConfirmationIdArgs { - /// Installation ID used to generate confirmation ID - instid: String, -} - -fn main() -> Result<()> { - let args = Cli::parse(); - - match &args.command { - Commands::List(list_args) => { - let keys = load_keys(list_args.keys_path.as_ref(), args.verbose)?; - for (key, value) in keys.products.iter() { - println!("{}: {:?}", key, value.bink); - } - - println!("\n\n** Please note: any BINK ID other than 2E is considered experimental at this time **\n"); - } - Commands::Generate(generate_args) => { - if generate_args.channel_id > 999 { - return Err(anyhow!("Channel ID must be 3 digits or fewer")); - } - let keys = load_keys(generate_args.keys_path.as_ref(), args.verbose)?; - generate( - &keys, - &generate_args.binkid, - generate_args.channel_id, - generate_args.num_keys, - args.verbose, - )?; - } - Commands::Validate(validate_args) => { - let keys = load_keys(validate_args.keys_path.as_ref(), args.verbose)?; - validate( - &keys, - &validate_args.binkid, - &validate_args.key_to_check, - args.verbose, - )?; - } - Commands::Confid(confirmation_id_args) => { - confirmation_id(&confirmation_id_args.instid)?; - } - } - - Ok(()) -} - -fn load_keys + std::fmt::Display>(path: Option

, verbose: bool) -> Result { - let keys = { - if let Some(path) = path { - if verbose { - println!("Loading keys file {}", path); - } - - let file = File::open(&path)?; - let reader = BufReader::new(file); - let keys: Keys = from_reader(reader)?; - - if verbose { - println!("Loaded keys from {} successfully", path); - } - - keys - } else { - from_str(std::include_str!("../../../keys.json"))? - } - }; - - // let bink_id = u32::from_str_radix(&options.binkid, 16)?; - - // if bink_id >= 0x40 { - // if options.key_to_check.is_some() { - // options.application_mode = Commands::Bink2002Validate; - // } else { - // options.application_mode = Commands::Bink2002Generate; - // } - // } - - Ok(keys) -} - -fn generate(keys: &Keys, bink_id: &str, channel_id: u32, count: u64, verbose: bool) -> Result<()> { - let bink_id = bink_id.to_ascii_uppercase(); - let bink = &keys.bink[&bink_id]; - - // We cannot produce a valid key without knowing the private key k. The reason for this is that - // we need the result of the function K(x; y) = kG(x; y). - let private_key = &bink.private; - - // We can, however, validate any given key using the available public key: {p, a, b, G, K}. - // genOrder the order of the generator G, a value we have to reverse -> Schoof's Algorithm. - let gen_order = &bink.n; - - let curve = initialize_curve(bink, &bink_id, verbose)?; - if verbose { - println!(" n: {gen_order}"); - println!(" k: {private_key}"); - println!(); - } - let private_key = PrivateKey::new(gen_order, private_key)?; - - if u32::from_str_radix(&bink_id, 16)? < 0x40 { - bink1998_generate(&curve, &private_key, channel_id, count, verbose)?; - } else { - bink2002_generate(&curve, &private_key, channel_id, count, verbose)?; - } - - Ok(()) -} - -fn validate(keys: &Keys, bink_id: &str, key: &str, verbose: bool) -> Result<()> { - let bink_id = bink_id.to_ascii_uppercase(); - let bink = &keys.bink[&bink_id]; - let curve = initialize_curve(bink, &bink_id, verbose)?; - - if u32::from_str_radix(&bink_id, 16)? < 0x40 { - bink1998_validate(&curve, key, verbose)?; - } else { - bink2002_validate(&curve, key, verbose)?; - } - - Ok(()) -} - -fn initialize_curve(bink: &Bink, bink_id: &str, verbose: bool) -> Result { - let p = &bink.p; - let a = &bink.a; - let b = &bink.b; - let gx = &bink.g.x; - let gy = &bink.g.y; - let kx = &bink.public.x; - let ky = &bink.public.y; - - if verbose { - println!("-----------------------------------------------------------"); - println!( - "Loaded the following elliptic curve parameters: BINK[{}]", - bink_id - ); - println!("-----------------------------------------------------------"); - println!(" P: {p}"); - println!(" a: {a}"); - println!(" b: {b}"); - println!("Gx: {gx}"); - println!("Gy: {gy}"); - println!("Kx: {kx}"); - println!("Ky: {ky}"); - } - - EllipticCurve::new(p, a, b, gx, gy, kx, ky) -} - -fn bink1998_generate( - curve: &EllipticCurve, - private_key: &PrivateKey, - channel_id: u32, - count: u64, - verbose: bool, -) -> Result<()> { - for _ in 0..count { - let product_key = bink1998::ProductKey::new(curve, private_key, channel_id, None, None)?; - if verbose { - println!("{:?}", product_key); - } - println!("{product_key}"); - } - Ok(()) -} - -fn bink2002_generate( - curve: &EllipticCurve, - private_key: &PrivateKey, - channel_id: u32, - count: u64, - verbose: bool, -) -> Result<()> { - for _ in 0..count { - let product_key = bink2002::ProductKey::new(curve, private_key, channel_id, None, None)?; - if verbose { - println!("{:?}", product_key); - } - println!("{product_key}"); - } - Ok(()) -} - -fn bink1998_validate(curve: &EllipticCurve, key: &str, verbose: bool) -> Result<()> { - let product_key = bink1998::ProductKey::from_key(curve, key)?; - if verbose { - println!("{:?}", product_key); - } - println!("{product_key}"); - println!("Key validated successfully!"); - Ok(()) -} - -fn bink2002_validate(curve: &EllipticCurve, key: &str, verbose: bool) -> Result<()> { - let product_key = bink2002::ProductKey::from_key(curve, key)?; - if verbose { - println!("{:?}", product_key); - } - println!("{product_key}"); - println!("Key validated successfully!"); - Ok(()) -} - -fn confirmation_id(installation_id: &str) -> Result<()> { - let confirmation_id = confid::generate(installation_id)?; - println!("Confirmation ID: {confirmation_id}"); - Ok(()) -} diff --git a/src/bink1998.rs b/src/bink1998.rs index d4dc575..1c9f12b 100644 --- a/src/bink1998.rs +++ b/src/bink1998.rs @@ -1,6 +1,4 @@ -use std::fmt::{Display, Formatter}; - -use anyhow::{bail, Result}; +use anyhow::Result; use bitreader::BitReader; use openssl::{ bn::{BigNum, BigNumContext, MsbOption}, @@ -8,177 +6,110 @@ use openssl::{ sha::sha1, }; -use crate::{ - crypto::{EllipticCurve, PrivateKey}, - key::{base24_decode, base24_encode, strip_key}, - math::bitmask, -}; +use crate::key::{base24_decode, base24_encode}; const FIELD_BITS: i32 = 384; const FIELD_BYTES: usize = 48; const SHA_MSG_LENGTH: usize = 4 + 2 * FIELD_BYTES; -const HASH_LENGTH_BITS: u8 = 28; -const SERIAL_LENGTH_BITS: u8 = 30; -const UPGRADE_LENGTH_BITS: u8 = 1; -const EVERYTHING_ELSE: u8 = HASH_LENGTH_BITS + SERIAL_LENGTH_BITS + UPGRADE_LENGTH_BITS; - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct ProductKey { +#[derive(Clone, Copy, Debug)] +struct ProductKey { upgrade: bool, - channel_id: u32, - sequence: u32, + serial: u32, hash: u32, signature: u64, } -impl ProductKey { - pub fn new( - curve: &EllipticCurve, - private_key: &PrivateKey, - channel_id: u32, - sequence: Option, - upgrade: Option, - ) -> Result { - // Generate random sequence if none supplied - let sequence = match sequence { - Some(serial) => serial, - None => { - let mut bn_rand = BigNum::new()?; - bn_rand.rand(19, MsbOption::MAYBE_ZERO, false)?; - let o_raw = u32::from_be_bytes(bn_rand.to_vec_padded(4)?.try_into().unwrap()); - o_raw % 999999 - } - }; +pub fn verify( + e_curve: &EcGroup, + base_point: &EcPoint, + public_key: &EcPoint, + p_key: &str, + verbose: bool, +) -> Result { + let mut num_context = BigNumContext::new()?; - // Default to upgrade=false - let upgrade = upgrade.unwrap_or(false); + let p_raw = base24_decode(p_key); + let product_key = unpack(&p_raw)?; - // Generate a new random key - let product_key = Self::generate( - &curve.curve, - &curve.gen_point, - &private_key.gen_order, - &private_key.private_key, - channel_id, - sequence, - upgrade, - )?; + let p_data = product_key.serial << 1 | product_key.upgrade as u32; - // Make sure the key is valid - product_key.verify(&curve.curve, &curve.gen_point, &curve.pub_point)?; - - // Ship it - Ok(product_key) + if verbose { + println!("Validation results:"); + println!(" Upgrade: {}", product_key.upgrade); + println!(" Serial: {}", product_key.serial); + println!(" Hash: {}", product_key.hash); + println!(" Signature: {}", product_key.signature); + println!(); } - pub fn from_key(curve: &EllipticCurve, key: &str) -> Result { - let key = strip_key(key)?; - let Ok(packed_key) = base24_decode(&key) else { - bail!("Product key is in an incorrect format!") - }; - let product_key = Self::from_packed(&packed_key)?; - product_key.verify(&curve.curve, &curve.gen_point, &curve.pub_point)?; - Ok(product_key) - } + 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()?; - fn generate( - e_curve: &EcGroup, - base_point: &EcPoint, - gen_order: &BigNum, - private_key: &BigNum, - channel_id: u32, - sequence: u32, - upgrade: bool, - ) -> Result { - let mut num_context = BigNumContext::new().unwrap(); + let mut t = EcPoint::new(e_curve)?; + let mut p = EcPoint::new(e_curve)?; + let mut p_2 = EcPoint::new(e_curve)?; - let mut c = BigNum::new()?; - let mut s = BigNum::new()?; - let mut x = BigNum::new()?; - let mut y = BigNum::new()?; + 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)?; - let mut ek: BigNum; + p.add(e_curve, &t, &p_2, &mut num_context)?; - let serial = channel_id * 1_000_000 + sequence; - let data = serial << 1 | upgrade as u32; + p.affine_coordinates(e_curve, &mut x, &mut y, &mut num_context)?; - let product_key = loop { - let mut r = EcPoint::new(e_curve)?; + let mut msg_buffer: [u8; SHA_MSG_LENGTH] = [0; SHA_MSG_LENGTH]; - // Generate a random number c consisting of 384 bits without any constraints. - c.rand(FIELD_BITS, MsbOption::MAYBE_ZERO, false)?; + let mut x_bin = x.to_vec_padded(FIELD_BYTES as i32)?; + x_bin.reverse(); + let mut y_bin = y.to_vec_padded(FIELD_BYTES as i32)?; + y_bin.reverse(); - // Pick a random derivative of the base point on the elliptic curve. - // R = cG; - r.mul(e_curve, base_point, &c, &num_context)?; + 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); - // Acquire its coordinates. - // x = R.x; y = R.y; - r.affine_coordinates(e_curve, &mut x, &mut y, &mut num_context)?; + let msg_digest = sha1(&msg_buffer); - let mut msg_buffer: [u8; SHA_MSG_LENGTH] = [0; SHA_MSG_LENGTH]; + let hash: u32 = + u32::from_le_bytes(msg_digest[0..4].try_into().unwrap()) >> 4 & bitmask(28) as u32; - let mut x_bin = x.to_vec_padded(FIELD_BYTES as i32)?; - x_bin.reverse(); - let mut y_bin = y.to_vec_padded(FIELD_BYTES as i32)?; - y_bin.reverse(); + Ok(hash == product_key.hash) +} - msg_buffer[0..4].copy_from_slice(&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); +pub fn generate( + e_curve: &EcGroup, + base_point: &EcPoint, + gen_order: &BigNum, + private_key: &BigNum, + p_serial: u32, + p_upgrade: bool, +) -> Result { + let mut num_context = BigNumContext::new().unwrap(); - let msg_digest = sha1(&msg_buffer); + let mut c = BigNum::new()?; + let mut s = BigNum::new()?; + let mut s_2 = BigNum::new()?; + let mut x = BigNum::new()?; + let mut y = BigNum::new()?; - let hash: u32 = - u32::from_le_bytes(msg_digest[0..4].try_into().unwrap()) >> 4 & bitmask(28) as u32; + let p_data = p_serial << 1 | p_upgrade as u32; - ek = (*private_key).to_owned()?; - ek.mul_word(hash)?; + let p_raw = loop { + let mut r = EcPoint::new(e_curve)?; - s.mod_add(&ek, &c, gen_order, &mut num_context)?; + // Generate a random number c consisting of 384 bits without any constraints. + c.rand(FIELD_BITS, MsbOption::MAYBE_ZERO, false)?; - let signature = u64::from_be_bytes(s.to_vec_padded(8)?.try_into().unwrap()); + // Pick a random derivative of the base point on the elliptic curve. + // R = cG; + r.mul(e_curve, base_point, &c, &num_context)?; - if signature <= bitmask(55) { - break Self { - upgrade, - channel_id, - sequence, - hash, - signature, - }; - } - }; - - Ok(product_key) - } - - fn verify( - &self, - e_curve: &EcGroup, - base_point: &EcPoint, - public_key: &EcPoint, - ) -> Result { - let mut ctx = BigNumContext::new()?; - - let e = BigNum::from_u32(self.hash)?; - let s = BigNum::from_slice(&self.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)?; - - t.mul(e_curve, base_point, &s, &ctx)?; - p.mul(e_curve, public_key, &e, &ctx)?; - - { - let p_copy = p.to_owned(e_curve)?; - p.add(e_curve, &t, &p_copy, &mut ctx)?; - } - - p.affine_coordinates(e_curve, &mut x, &mut y, &mut ctx)?; + // Acquire its coordinates. + // x = R.x; y = R.y; + r.affine_coordinates(e_curve, &mut x, &mut y, &mut num_context)?; let mut msg_buffer: [u8; SHA_MSG_LENGTH] = [0; SHA_MSG_LENGTH]; @@ -187,76 +118,75 @@ impl ProductKey { let mut y_bin = y.to_vec_padded(FIELD_BYTES as i32)?; y_bin.reverse(); - let serial = self.channel_id * 1_000_000 + self.sequence; - let data = serial << 1 | self.upgrade as u32; - - msg_buffer[0..4].copy_from_slice(&data.to_le_bytes()); + 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 = + let p_hash: u32 = u32::from_le_bytes(msg_digest[0..4].try_into().unwrap()) >> 4 & bitmask(28) as u32; - Ok(hash == self.hash) - } + s_2.copy_from_slice(&private_key.to_vec())?; + s_2.mul_word(p_hash)?; - fn from_packed(packed_key: &[u8]) -> Result { - let mut reader = BitReader::new(packed_key); - // The signature length isn't known, but everything else is, so we can calculate it - let signature_length_bits = (packed_key.len() * 8) as u8 - EVERYTHING_ELSE; + s.mod_add(&s_2, &c, gen_order, &mut num_context)?; - let signature = reader.read_u64(signature_length_bits)?; - let hash = reader.read_u32(HASH_LENGTH_BITS)?; - let serial = reader.read_u32(SERIAL_LENGTH_BITS)?; - let upgrade = reader.read_bool()?; + let p_signature = u64::from_be_bytes(s.to_vec_padded(8)?.try_into().unwrap()); - let sequence = serial % 1_000_000; - let channel_id = serial / 1_000_000; + if p_signature <= bitmask(55) { + break pack(ProductKey { + upgrade: p_upgrade, + serial: p_serial, + hash: p_hash, + signature: p_signature, + }); + } + }; - Ok(Self { - upgrade, - channel_id, - sequence, - hash, - signature, - }) - } - - fn pack(&self) -> Vec { - let mut packed_key: u128 = 0; - - let serial = self.channel_id * 1_000_000 + self.sequence; - - packed_key |= (self.signature as u128) << EVERYTHING_ELSE; - packed_key |= (self.hash as u128) << (SERIAL_LENGTH_BITS + UPGRADE_LENGTH_BITS); - packed_key |= (serial as u128) << UPGRADE_LENGTH_BITS; - packed_key |= self.upgrade as u128; - - packed_key - .to_be_bytes() - .into_iter() - .skip_while(|&x| x == 0) - .collect() - } + Ok(base24_encode(&p_raw)) } -impl Display for ProductKey { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let pk = base24_encode(&self.pack()).unwrap(); - let key = pk - .chars() - .enumerate() - .fold(String::new(), |mut acc: String, (i, c)| { - if i > 0 && i % 5 == 0 { - acc.push('-'); - } - acc.push(c); - acc - }); - write!(f, "{}", key) - } +const HASH_LENGTH_BITS: u8 = 28; +const SERIAL_LENGTH_BITS: u8 = 30; +const UPGRADE_LENGTH_BITS: u8 = 1; +const EVERYTHING_ELSE: u8 = HASH_LENGTH_BITS + SERIAL_LENGTH_BITS + UPGRADE_LENGTH_BITS; + +fn unpack(p_raw: &[u8]) -> Result { + 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 - EVERYTHING_ELSE; + + 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 { + let mut p_raw: u128 = 0; + + p_raw |= (p_key.signature as u128) << EVERYTHING_ELSE; + p_raw |= (p_key.hash as u128) << (SERIAL_LENGTH_BITS + UPGRADE_LENGTH_BITS); + p_raw |= (p_key.serial as u128) << UPGRADE_LENGTH_BITS; + p_raw |= p_key.upgrade as u128; + + p_raw + .to_be_bytes() + .into_iter() + .skip_while(|&x| x == 0) + .collect() +} + +fn bitmask(n: u64) -> u64 { + (1 << n) - 1 } #[cfg(test)] @@ -265,7 +195,7 @@ mod tests { use serde_json::from_reader; - use crate::crypto::EllipticCurve; + use crate::crypto::initialize_elliptic_curve; #[test] fn verify_test() { @@ -289,22 +219,35 @@ mod tests { let kx = bink["pub"]["x"].as_str().unwrap(); let ky = bink["pub"]["y"].as_str().unwrap(); - let curve = EllipticCurve::new(p, a, b, gx, gy, kx, ky).unwrap(); + let (e_curve, gen_point, pub_point) = initialize_elliptic_curve(p, a, b, gx, gy, kx, ky); - assert!(super::ProductKey::from_key(&curve, product_key).is_ok()); - assert!(super::ProductKey::from_key(&curve, "11111-R6BG2-39J83-RYKHF-W47TT").is_err()); + assert!(super::verify(&e_curve, &gen_point, &pub_point, product_key, true).unwrap()); + assert!(!super::verify( + &e_curve, + &gen_point, + &pub_point, + "11111-R6BG2-39J83-RYKHF-W47TT", + true + ) + .unwrap()); } #[test] fn pack_test() { - let key = super::ProductKey { + let p_key = super::ProductKey { upgrade: false, - channel_id: 640, - sequence: 10550, + serial: 640010550, hash: 39185432, signature: 6939952665262054, }; - assert_eq!(key.to_string(), "D9924-R6BG2-39J83-RYKHF-W47TT"); + let p_raw = super::pack(p_key); + + assert_eq!( + p_raw, + vec![ + 0xC5, 0x3E, 0xCD, 0x2A, 0xF7, 0xBF, 0x31, 0x2A, 0xF6, 0x0C, 0x4C, 0x4B, 0x92, 0x6C + ] + ); } } diff --git a/src/bink2002.rs b/src/bink2002.rs index 173e763..ce5655c 100644 --- a/src/bink2002.rs +++ b/src/bink2002.rs @@ -1,33 +1,19 @@ -use std::fmt::{Display, Formatter}; - -use anyhow::{bail, Result}; +use anyhow::Result; use bitreader::BitReader; use openssl::{ bn::{BigNum, BigNumContext, MsbOption}, ec::{EcGroup, EcPoint}, - rand::rand_bytes, sha::sha1, }; -use crate::{ - crypto::{EllipticCurve, PrivateKey}, - key::{base24_decode, base24_encode, strip_key}, - math::{bitmask, by_dword, next_sn_bits}, -}; +use crate::key::{base24_decode, base24_encode}; const FIELD_BITS: i32 = 512; const FIELD_BYTES: usize = 64; const SHA_MSG_LENGTH: usize = 3 + 2 * FIELD_BYTES; -const SIGNATURE_LENGTH_BITS: u8 = 62; -const HASH_LENGTH_BITS: u8 = 31; -const CHANNEL_ID_LENGTH_BITS: u8 = 10; -const UPGRADE_LENGTH_BITS: u8 = 1; -const EVERYTHING_ELSE: u8 = - SIGNATURE_LENGTH_BITS + HASH_LENGTH_BITS + CHANNEL_ID_LENGTH_BITS + UPGRADE_LENGTH_BITS; - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct ProductKey { +#[derive(Clone, Copy, Debug)] +struct ProductKey { upgrade: bool, channel_id: u32, hash: u32, @@ -35,191 +21,142 @@ pub struct ProductKey { auth_info: u32, } -impl ProductKey { - pub fn new( - curve: &EllipticCurve, - private_key: &PrivateKey, - channel_id: u32, - auth_info: Option, - upgrade: Option, - ) -> Result { - // Generate random auth info if none supplied - let auth_info = match auth_info { - Some(auth_info) => auth_info, - None => { - let mut auth_info_bytes = [0_u8; 4]; - rand_bytes(&mut auth_info_bytes)?; - u32::from_ne_bytes(auth_info_bytes) & ((1 << 10) - 1) - } - }; +pub fn verify( + e_curve: &EcGroup, + base_point: &EcPoint, + public_key: &EcPoint, + cd_key: &str, + verbose: bool, +) -> Result { + let mut num_context = BigNumContext::new()?; - // Default to upgrade=false - let upgrade = upgrade.unwrap_or(false); + let b_key = base24_decode(cd_key); + let product_key = unpack(&b_key)?; - // Generate a new random key - let product_key = Self::generate( - &curve.curve, - &curve.gen_point, - &private_key.gen_order, - &private_key.private_key, - channel_id, - auth_info, - upgrade, - )?; + let p_data = product_key.channel_id << 1 | product_key.upgrade as u32; - // Make sure the key is valid - product_key.verify(&curve.curve, &curve.gen_point, &curve.pub_point)?; - - // Ship it - Ok(product_key) + if verbose { + println!("Validation results:"); + println!(" Upgrade: {}", product_key.upgrade); + println!("Channel ID: {}", product_key.channel_id); + println!(" Hash: {}", product_key.hash); + println!(" Signature: {}", product_key.signature); + println!(" AuthInfo: {}", product_key.auth_info); + println!(); } - pub fn from_key(curve: &EllipticCurve, key: &str) -> Result { - let key = strip_key(key)?; - let Ok(packed_key) = base24_decode(&key) else { - bail!("Product key is in an incorrect format!") - }; - let product_key = Self::from_packed(&packed_key)?; - let verified = product_key.verify(&curve.curve, &curve.gen_point, &curve.pub_point)?; - if !verified { - bail!("Product key is invalid! Wrong BINK ID?"); - } - Ok(product_key) - } + let mut msg_buffer: [u8; SHA_MSG_LENGTH] = [0; SHA_MSG_LENGTH]; - fn generate( - e_curve: &EcGroup, - base_point: &EcPoint, - gen_order: &BigNum, - private_key: &BigNum, - channel_id: u32, - auth_info: u32, - upgrade: bool, - ) -> Result { - let mut num_context = BigNumContext::new().unwrap(); + msg_buffer[0x00] = 0x5D; + msg_buffer[0x01] = (p_data & 0x00FF) as u8; + msg_buffer[0x02] = ((p_data & 0xFF00) >> 8) as u8; + msg_buffer[0x03] = (product_key.hash & 0x000000FF) as u8; + msg_buffer[0x04] = ((product_key.hash & 0x0000FF00) >> 8) as u8; + msg_buffer[0x05] = ((product_key.hash & 0x00FF0000) >> 16) as u8; + msg_buffer[0x06] = ((product_key.hash & 0xFF000000) >> 24) as u8; + msg_buffer[0x07] = (product_key.auth_info & 0x00FF) as u8; + msg_buffer[0x08] = ((product_key.auth_info & 0xFF00) >> 8) as u8; + msg_buffer[0x09] = 0x00; + msg_buffer[0x0A] = 0x00; - let mut c = BigNum::new()?; - let mut x = BigNum::new()?; - let mut y = BigNum::new()?; + let msg_digest = sha1(&msg_buffer[..=0x0A]); - let data = channel_id << 1 | upgrade as u32; + let i_signature = next_sn_bits(by_dword(&msg_digest[4..8]) as u64, 30, 2) << 32 + | by_dword(&msg_digest[0..4]) as u64; - let mut no_square = false; - let key = loop { - let mut r = EcPoint::new(e_curve)?; + let e = BigNum::from_slice(&i_signature.to_be_bytes())?; + let s = BigNum::from_slice(&product_key.signature.to_be_bytes())?; - c.rand(FIELD_BITS, MsbOption::MAYBE_ZERO, false)?; + let mut x = BigNum::new()?; + let mut y = BigNum::new()?; - r.mul(e_curve, base_point, &c, &num_context)?; + let mut p = EcPoint::new(e_curve)?; + let mut t = EcPoint::new(e_curve)?; - r.affine_coordinates(e_curve, &mut x, &mut y, &mut num_context)?; + t.mul(e_curve, base_point, &s, &num_context)?; + p.mul(e_curve, public_key, &e, &num_context)?; + let p_2 = p.to_owned(e_curve)?; - let mut msg_buffer: [u8; SHA_MSG_LENGTH] = [0; SHA_MSG_LENGTH]; + p.add(e_curve, &t, &p_2, &mut num_context)?; + let p_2 = p.to_owned(e_curve)?; - let mut x_bin = x.to_vec_padded(FIELD_BYTES as i32)?; - x_bin.reverse(); - let mut y_bin = y.to_vec_padded(FIELD_BYTES as i32)?; - y_bin.reverse(); + p.mul(e_curve, &p_2, &s, &num_context)?; - msg_buffer[0x00] = 0x79; - msg_buffer[0x01] = (data & 0x00FF) as u8; - msg_buffer[0x02] = ((data & 0xFF00) >> 8) as u8; + p.affine_coordinates(e_curve, &mut x, &mut y, &mut num_context)?; - msg_buffer[3..3 + FIELD_BYTES].copy_from_slice(&x_bin); - msg_buffer[3 + FIELD_BYTES..3 + FIELD_BYTES * 2].copy_from_slice(&y_bin); + let mut x_bin = x.to_vec_padded(FIELD_BYTES as i32)?; + x_bin.reverse(); + let mut y_bin = y.to_vec_padded(FIELD_BYTES as i32)?; + y_bin.reverse(); - let msg_digest = sha1(&msg_buffer); + msg_buffer[0x00] = 0x79; + msg_buffer[0x01] = (p_data & 0x00FF) as u8; + msg_buffer[0x02] = ((p_data & 0xFF00) >> 8) as u8; - let hash: u32 = by_dword(&msg_digest[0..4]) & bitmask(31) as u32; + msg_buffer[3..3 + FIELD_BYTES].copy_from_slice(&x_bin); + msg_buffer[3 + FIELD_BYTES..3 + FIELD_BYTES * 2].copy_from_slice(&y_bin); - msg_buffer[0x00] = 0x5D; - msg_buffer[0x01] = (data & 0x00FF) as u8; - msg_buffer[0x02] = ((data & 0xFF00) >> 8) as u8; - msg_buffer[0x03] = (hash & 0x000000FF) as u8; - msg_buffer[0x04] = ((hash & 0x0000FF00) >> 8) as u8; - msg_buffer[0x05] = ((hash & 0x00FF0000) >> 16) as u8; - msg_buffer[0x06] = ((hash & 0xFF000000) >> 24) as u8; - msg_buffer[0x07] = (auth_info & 0x00FF) as u8; - msg_buffer[0x08] = ((auth_info & 0xFF00) >> 8) as u8; - msg_buffer[0x09] = 0x00; - msg_buffer[0x0A] = 0x00; + let msg_digest = sha1(&msg_buffer); - let msg_digest = sha1(&msg_buffer[..=0x0A]); + let hash: u32 = by_dword(&msg_digest[0..4]) & bitmask(31) as u32; - let i_signature = next_sn_bits(by_dword(&msg_digest[4..8]) as u64, 30, 2) << 32 - | by_dword(&msg_digest[0..4]) as u64; + Ok(hash == product_key.hash) +} - let mut e = BigNum::from_slice(&i_signature.to_be_bytes())?; +pub fn generate( + e_curve: &EcGroup, + base_point: &EcPoint, + gen_order: &BigNum, + private_key: &BigNum, + p_channel_id: u32, + p_auth_info: u32, + p_upgrade: bool, +) -> Result { + let mut num_context = BigNumContext::new().unwrap(); - let e_2 = e.to_owned()?; - e.mod_mul(&e_2, private_key, gen_order, &mut num_context)?; + let mut c = BigNum::new()?; + let mut x = BigNum::new()?; + let mut y = BigNum::new()?; - let mut s = e.to_owned()?; + let p_data = p_channel_id << 1 | p_upgrade as u32; - let s_2 = s.to_owned()?; - s.mod_sqr(&s_2, gen_order, &mut num_context)?; + let mut no_square = false; + let p_raw: Vec = loop { + let mut r = EcPoint::new(e_curve)?; - let c_2 = c.to_owned()?; - c.lshift(&c_2, 2)?; + c.rand(FIELD_BITS, MsbOption::MAYBE_ZERO, false)?; - s = &s + &c; + r.mul(e_curve, base_point, &c, &num_context)?; - let s_2 = s.to_owned()?; - if s.mod_sqrt(&s_2, gen_order, &mut num_context).is_err() { - no_square = true; - }; - - let s_2 = s.to_owned()?; - s.mod_sub(&s_2, &e, gen_order, &mut num_context)?; - - if s.is_bit_set(0) { - s = &s + gen_order; - } - - let s_2 = s.to_owned()?; - s.rshift1(&s_2)?; - - let signature = u64::from_be_bytes(s.to_vec_padded(8)?.try_into().unwrap()); - - let product_key = Self { - upgrade, - channel_id, - hash, - signature, - auth_info, - }; - - if signature <= bitmask(62) && !no_square { - break product_key; - } - - no_square = false; - }; - - Ok(key) - } - - fn verify( - &self, - e_curve: &EcGroup, - base_point: &EcPoint, - public_key: &EcPoint, - ) -> Result { - let mut num_context = BigNumContext::new()?; - - let data = self.channel_id << 1 | self.upgrade as u32; + r.affine_coordinates(e_curve, &mut x, &mut y, &mut num_context)?; let mut msg_buffer: [u8; SHA_MSG_LENGTH] = [0; SHA_MSG_LENGTH]; + let mut x_bin = x.to_vec_padded(FIELD_BYTES as i32)?; + x_bin.reverse(); + let mut y_bin = y.to_vec_padded(FIELD_BYTES as i32)?; + y_bin.reverse(); + + msg_buffer[0x00] = 0x79; + msg_buffer[0x01] = (p_data & 0x00FF) as u8; + msg_buffer[0x02] = ((p_data & 0xFF00) >> 8) as u8; + + msg_buffer[3..3 + FIELD_BYTES].copy_from_slice(&x_bin); + msg_buffer[3 + FIELD_BYTES..3 + FIELD_BYTES * 2].copy_from_slice(&y_bin); + + let msg_digest = sha1(&msg_buffer); + + let p_hash: u32 = by_dword(&msg_digest[0..4]) & bitmask(31) as u32; + msg_buffer[0x00] = 0x5D; - msg_buffer[0x01] = (data & 0x00FF) as u8; - msg_buffer[0x02] = ((data & 0xFF00) >> 8) as u8; - msg_buffer[0x03] = (self.hash & 0x000000FF) as u8; - msg_buffer[0x04] = ((self.hash & 0x0000FF00) >> 8) as u8; - msg_buffer[0x05] = ((self.hash & 0x00FF0000) >> 16) as u8; - msg_buffer[0x06] = ((self.hash & 0xFF000000) >> 24) as u8; - msg_buffer[0x07] = (self.auth_info & 0x00FF) as u8; - msg_buffer[0x08] = ((self.auth_info & 0xFF00) >> 8) as u8; + msg_buffer[0x01] = (p_data & 0x00FF) as u8; + msg_buffer[0x02] = ((p_data & 0xFF00) >> 8) as u8; + msg_buffer[0x03] = (p_hash & 0x000000FF) as u8; + msg_buffer[0x04] = ((p_hash & 0x0000FF00) >> 8) as u8; + msg_buffer[0x05] = ((p_hash & 0x00FF0000) >> 16) as u8; + msg_buffer[0x06] = ((p_hash & 0xFF000000) >> 24) as u8; + msg_buffer[0x07] = (p_auth_info & 0x00FF) as u8; + msg_buffer[0x08] = ((p_auth_info & 0xFF00) >> 8) as u8; msg_buffer[0x09] = 0x00; msg_buffer[0x0A] = 0x00; @@ -228,114 +165,122 @@ impl ProductKey { let i_signature = next_sn_bits(by_dword(&msg_digest[4..8]) as u64, 30, 2) << 32 | by_dword(&msg_digest[0..4]) as u64; - let e = BigNum::from_slice(&i_signature.to_be_bytes())?; - let s = BigNum::from_slice(&self.signature.to_be_bytes())?; + let mut e = BigNum::from_slice(&i_signature.to_be_bytes())?; - let mut x = BigNum::new()?; - let mut y = BigNum::new()?; + let e_2 = e.to_owned()?; + e.mod_mul(&e_2, private_key, gen_order, &mut num_context)?; - let mut p = EcPoint::new(e_curve)?; - let mut t = EcPoint::new(e_curve)?; + let mut s = e.to_owned()?; - t.mul(e_curve, base_point, &s, &num_context)?; - p.mul(e_curve, public_key, &e, &num_context)?; + let s_2 = s.to_owned()?; + s.mod_sqr(&s_2, gen_order, &mut num_context)?; - { - let p_2 = p.to_owned(e_curve)?; - p.add(e_curve, &t, &p_2, &mut num_context)?; + let c_2 = c.to_owned()?; + c.lshift(&c_2, 2)?; + + s = &s + &c; + + let s_2 = s.to_owned()?; + if s.mod_sqrt(&s_2, gen_order, &mut num_context).is_err() { + no_square = true; + }; + + let s_2 = s.to_owned()?; + s.mod_sub(&s_2, &e, gen_order, &mut num_context)?; + + if s.is_bit_set(0) { + s = &s + gen_order; } - { - let p_2 = p.to_owned(e_curve)?; - p.mul(e_curve, &p_2, &s, &num_context)?; + let s_2 = s.to_owned()?; + s.rshift1(&s_2)?; + + let p_signature = u64::from_be_bytes(s.to_vec_padded(8)?.try_into().unwrap()); + + let product_key = ProductKey { + upgrade: p_upgrade, + channel_id: p_channel_id, + hash: p_hash, + signature: p_signature, + auth_info: p_auth_info, + }; + + if p_signature <= bitmask(62) && !no_square { + break pack(product_key); } - p.affine_coordinates(e_curve, &mut x, &mut y, &mut num_context)?; + no_square = false; + }; - let mut x_bin = x.to_vec_padded(FIELD_BYTES as i32)?; - x_bin.reverse(); - let mut y_bin = y.to_vec_padded(FIELD_BYTES as i32)?; - y_bin.reverse(); - - msg_buffer[0x00] = 0x79; - msg_buffer[0x01] = (data & 0x00FF) as u8; - msg_buffer[0x02] = ((data & 0xFF00) >> 8) as u8; - - msg_buffer[3..3 + FIELD_BYTES].copy_from_slice(&x_bin); - msg_buffer[3 + FIELD_BYTES..3 + FIELD_BYTES * 2].copy_from_slice(&y_bin); - - let msg_digest = sha1(&msg_buffer); - - let hash: u32 = by_dword(&msg_digest[0..4]) & bitmask(31) as u32; - - Ok(hash == self.hash) - } - - fn from_packed(packed_key: &[u8]) -> Result { - let mut reader = BitReader::new(packed_key); - // The auth info length isn't known, but everything else is, so we can calculate it - let auth_info_length_bits = (packed_key.len() * 8) as u8 - EVERYTHING_ELSE; - - let auth_info = reader.read_u32(auth_info_length_bits)?; - let signature = reader.read_u64(SIGNATURE_LENGTH_BITS)?; - let hash = reader.read_u32(HASH_LENGTH_BITS)?; - let channel_id = reader.read_u32(CHANNEL_ID_LENGTH_BITS)?; - let upgrade = reader.read_bool()?; - - Ok(Self { - upgrade, - channel_id, - hash, - signature, - auth_info, - }) - } - - fn pack(&self) -> Vec { - let mut packed_key: u128 = 0; - - packed_key |= (self.auth_info as u128) - << (SIGNATURE_LENGTH_BITS - + HASH_LENGTH_BITS - + CHANNEL_ID_LENGTH_BITS - + UPGRADE_LENGTH_BITS); - packed_key |= (self.signature as u128) - << (HASH_LENGTH_BITS + CHANNEL_ID_LENGTH_BITS + UPGRADE_LENGTH_BITS); - packed_key |= (self.hash as u128) << (CHANNEL_ID_LENGTH_BITS + UPGRADE_LENGTH_BITS); - packed_key |= (self.channel_id as u128) << UPGRADE_LENGTH_BITS; - packed_key |= self.upgrade as u128; - - packed_key - .to_be_bytes() - .into_iter() - .skip_while(|&x| x == 0) - .collect() - } + Ok(base24_encode(&p_raw)) } -impl Display for ProductKey { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let pk = base24_encode(&self.pack()).unwrap(); - let key = pk - .chars() - .enumerate() - .fold(String::new(), |mut acc: String, (i, c)| { - if i > 0 && i % 5 == 0 { - acc.push('-'); - } - acc.push(c); - acc - }); - write!(f, "{}", key) - } +const SIGNATURE_LENGTH_BITS: u8 = 62; +const HASH_LENGTH_BITS: u8 = 31; +const CHANNEL_ID_LENGTH_BITS: u8 = 10; +const UPGRADE_LENGTH_BITS: u8 = 1; +const EVERYTHING_ELSE: u8 = + SIGNATURE_LENGTH_BITS + HASH_LENGTH_BITS + CHANNEL_ID_LENGTH_BITS + UPGRADE_LENGTH_BITS; + +fn unpack(p_raw: &[u8]) -> Result { + let mut reader = BitReader::new(p_raw); + let auth_info_length_bits = (p_raw.len() * 8) as u8 - EVERYTHING_ELSE; + + let p_auth_info = reader.read_u32(auth_info_length_bits)?; + let p_signature = reader.read_u64(SIGNATURE_LENGTH_BITS)?; + let p_hash = reader.read_u32(HASH_LENGTH_BITS)?; + let p_channel_id = reader.read_u32(CHANNEL_ID_LENGTH_BITS)?; + let p_upgrade = reader.read_bool()?; + + Ok(ProductKey { + upgrade: p_upgrade, + channel_id: p_channel_id, + hash: p_hash, + signature: p_signature, + auth_info: p_auth_info, + }) +} + +fn pack(p_key: ProductKey) -> Vec { + let mut p_raw: u128 = 0; + + p_raw |= (p_key.auth_info as u128) + << (SIGNATURE_LENGTH_BITS + + HASH_LENGTH_BITS + + CHANNEL_ID_LENGTH_BITS + + UPGRADE_LENGTH_BITS); + p_raw |= (p_key.signature as u128) + << (HASH_LENGTH_BITS + CHANNEL_ID_LENGTH_BITS + UPGRADE_LENGTH_BITS); + p_raw |= (p_key.hash as u128) << (CHANNEL_ID_LENGTH_BITS + UPGRADE_LENGTH_BITS); + p_raw |= (p_key.channel_id as u128) << UPGRADE_LENGTH_BITS; + p_raw |= p_key.upgrade as u128; + + p_raw + .to_be_bytes() + .into_iter() + .skip_while(|&x| x == 0) + .collect() +} + +fn bitmask(n: u64) -> u64 { + (1 << n) - 1 +} + +fn next_sn_bits(field: u64, n: u32, offset: u32) -> u64 { + (field >> offset) & ((1u64 << n) - 1) +} + +fn by_dword(n: &[u8]) -> u32 { + (n[0] as u32) | (n[1] as u32) << 8 | (n[2] as u32) << 16 | (n[3] as u32) << 24 } #[cfg(test)] mod tests { - use serde_json::from_reader; use std::{fs::File, io::BufReader}; - use crate::crypto::EllipticCurve; + use serde_json::from_reader; + + use crate::crypto::initialize_elliptic_curve; #[test] fn verify_test() { @@ -359,9 +304,8 @@ mod tests { let kx = bink["pub"]["x"].as_str().unwrap(); let ky = bink["pub"]["y"].as_str().unwrap(); - let curve = EllipticCurve::new(p, a, b, gx, gy, kx, ky).unwrap(); + let (e_curve, gen_point, pub_point) = initialize_elliptic_curve(p, a, b, gx, gy, kx, ky); - assert!(super::ProductKey::from_key(&curve, product_key).is_ok()); - assert!(super::ProductKey::from_key(&curve, "11111-YRGC8-4KYTG-C3FCC-JCFDY").is_err()); + assert!(super::verify(&e_curve, &gen_point, &pub_point, product_key, true).unwrap()); } } diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 0000000..a422deb --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,382 @@ +use std::{fs::File, io::BufReader, path::Path}; + +use anyhow::{anyhow, Result}; +use clap::Parser; +use openssl::{ + bn::{BigNum, MsbOption}, + ec::{EcGroup, EcPoint}, + rand::rand_bytes, +}; +use serde_json::{from_reader, from_str}; + +use crate::{ + bink1998, bink2002, confid, crypto::initialize_elliptic_curve, key::P_KEY_CHARSET, PK_LENGTH, +}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Mode { + Bink1998Generate, + Bink2002Generate, + ConfirmationId, + Bink1998Validate, + Bink2002Validate, +} + +impl Default for Mode { + fn default() -> Self { + Self::Bink1998Generate + } +} + +#[derive(Parser, Debug)] +#[command(author, about, long_about = None)] +pub struct Options { + /// Enable verbose output + #[arg(short, long)] + verbose: bool, + + /// Number of keys to generate + #[arg(short = 'n', long = "number", default_value = "1")] + num_keys: i32, + + /// Specify which keys file to load + #[arg(short = 'f', long = "file")] + keys_filename: Option, + + /// Installation ID used to generate confirmation ID + #[arg(short, long)] + instid: Option, + + /// Specify which BINK identifier to load + #[arg(short, long, default_value = "2E")] + binkid: String, + + /// Show which products/binks can be loaded + #[arg(short, long)] + list: bool, + + /// Specify which Channel Identifier to use + #[arg(short = 'c', long = "channel", default_value = "640")] + channel_id: u32, + + /// Product key to validate signature + #[arg(short = 'V', long = "validate")] + key_to_check: Option, + + #[clap(skip)] + application_mode: Mode, +} + +pub struct Cli { + options: Options, + private_key: BigNum, + gen_order: BigNum, + gen_point: EcPoint, + pub_point: EcPoint, + e_curve: EcGroup, + count: u32, +} + +impl Cli { + pub fn new() -> Result { + let mut options = Self::parse_command_line(); + let keys = Self::validate_command_line(&mut options)?; + + let bink = &keys["BINK"][&options.binkid]; + // We cannot produce a valid key without knowing the private key k. The reason for this is that + // we need the result of the function K(x; y) = kG(x; y). + let private_key = BigNum::from_dec_str(bink["priv"].as_str().unwrap()).unwrap(); + + // We can, however, validate any given key using the available public key: {p, a, b, G, K}. + // genOrder the order of the generator G, a value we have to reverse -> Schoof's Algorithm. + let gen_order = BigNum::from_dec_str(bink["n"].as_str().unwrap()).unwrap(); + + 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 n = bink["n"].as_str().unwrap(); + let k = bink["priv"].as_str().unwrap(); + + if options.verbose { + println!("-----------------------------------------------------------"); + println!( + "Loaded the following elliptic curve parameters: BINK[{}]", + options.binkid + ); + println!("-----------------------------------------------------------"); + println!(" P: {p}"); + println!(" a: {a}"); + println!(" b: {b}"); + println!("Gx: {gx}"); + println!("Gy: {gy}"); + println!("Kx: {kx}"); + println!("Ky: {ky}"); + println!(" n: {n}"); + println!(" k: {k}"); + println!(); + } + + let (e_curve, gen_point, pub_point) = initialize_elliptic_curve(p, a, b, gx, gy, kx, ky); + + Ok(Self { + options, + private_key, + gen_order, + gen_point, + pub_point, + e_curve, + count: 0, + }) + } + + fn parse_command_line() -> Options { + let mut args = Options::parse(); + if args.instid.is_some() { + args.application_mode = Mode::ConfirmationId; + } + args + } + + fn validate_command_line(options: &mut Options) -> Result { + let keys = { + if let Some(filename) = &options.keys_filename { + if options.verbose { + println!("Loading keys file {}", filename); + } + + let keys = Self::load_json(filename)?; + + if options.verbose { + println!("Loaded keys from {} successfully", filename); + } + + keys + } else { + from_str(std::include_str!("../keys.json"))? + } + }; + + if options.list { + let products = keys["Products"] + .as_object() + .ok_or(anyhow!("`Products` object not found in keys",))?; + for (key, value) in products.iter() { + println!("{}: {}", key, value["BINK"]); + } + + println!("\n\n** Please note: any BINK ID other than 2E is considered experimental at this time **\n"); + } + + let bink_id = u32::from_str_radix(&options.binkid, 16)?; + + if options.key_to_check.is_some() { + options.application_mode = Mode::Bink1998Validate; + } + + if bink_id >= 0x40 { + if options.key_to_check.is_some() { + options.application_mode = Mode::Bink2002Validate; + } else { + options.application_mode = Mode::Bink2002Generate; + } + } + + if options.channel_id > 999 { + return Err(anyhow!( + "Refusing to create a key with a Channel ID greater than 999" + )); + } + + Ok(keys) + } + + fn load_json>(path: P) -> Result { + let file = File::open(path)?; + let reader = BufReader::new(file); + let json = from_reader(reader)?; + Ok(json) + } + + pub fn run(&mut self) -> Result<()> { + match self.options.application_mode { + Mode::Bink1998Generate => self.bink1998_generate(), + Mode::Bink2002Generate => self.bink2002_generate(), + Mode::ConfirmationId => self.confirmation_id(), + Mode::Bink1998Validate => self.bink1998_validate(), + Mode::Bink2002Validate => self.bink2002_validate(), + } + } + + fn bink1998_generate(&mut self) -> Result<()> { + let mut n_raw = self.options.channel_id * 1_000_000; // <- change + + let mut bn_rand = BigNum::new()?; + bn_rand.rand(19, MsbOption::MAYBE_ZERO, false)?; + + let o_raw: u32 = u32::from_be_bytes(bn_rand.to_vec_padded(4)?.try_into().unwrap()); + n_raw += o_raw % 999999; + + if self.options.verbose { + println!("> PID: {n_raw:09}"); + } + + let private_key = &self.gen_order - &self.private_key; + + let upgrade = false; + + for _ in 0..self.options.num_keys { + let p_key = bink1998::generate( + &self.e_curve, + &self.gen_point, + &self.gen_order, + &private_key, + n_raw, + upgrade, + )?; + Cli::print_key(&p_key); + + if bink1998::verify( + &self.e_curve, + &self.gen_point, + &self.pub_point, + &p_key, + self.options.verbose, + )? { + self.count += 1; + } + } + + println!("Success count: {}/{}", self.count, self.options.num_keys); + Ok(()) + } + + fn bink2002_generate(&mut self) -> Result<()> { + let p_channel_id = self.options.channel_id; + + if self.options.verbose { + println!("> Channel ID: {p_channel_id:03}"); + } + + for _ in 0..self.options.num_keys { + let mut p_auth_info_bytes = [0_u8; 4]; + rand_bytes(&mut p_auth_info_bytes)?; + let p_auth_info = u32::from_ne_bytes(p_auth_info_bytes) & ((1 << 10) - 1); + + if self.options.verbose { + println!("> AuthInfo: {p_auth_info}"); + } + + let p_key = bink2002::generate( + &self.e_curve, + &self.gen_point, + &self.gen_order, + &self.private_key, + p_channel_id, + p_auth_info, + false, + )?; + Cli::print_key(&p_key); + println!("\n"); + + if bink2002::verify( + &self.e_curve, + &self.gen_point, + &self.pub_point, + &p_key, + self.options.verbose, + )? { + self.count += 1; + } + } + + println!("Success count: {}/{}", self.count, self.options.num_keys); + Ok(()) + } + + fn bink1998_validate(&mut self) -> Result<()> { + let Ok(key) = Self::strip_key(self.options.key_to_check.as_ref().unwrap()) else { + return Err(anyhow!("Product key is in an incorrect format!")); + }; + + Self::print_key(&key); + if !bink1998::verify( + &self.e_curve, + &self.gen_point, + &self.pub_point, + &key, + self.options.verbose, + )? { + return Err(anyhow!("Product key is invalid! Wrong BINK ID?")); + } + + println!("Key validated successfully!"); + Ok(()) + } + + fn bink2002_validate(&mut self) -> Result<()> { + let Ok(key) = Self::strip_key(self.options.key_to_check.as_ref().unwrap()) else { + return Err(anyhow!("Product key is in an incorrect format!")); + }; + + Self::print_key(&key); + if !bink2002::verify( + &self.e_curve, + &self.gen_point, + &self.pub_point, + &key, + self.options.verbose, + )? { + return Err(anyhow!("Product key is invalid! Wrong BINK ID?")); + } + + println!("Key validated successfully!"); + Ok(()) + } + + fn confirmation_id(&mut self) -> Result<()> { + if let Some(instid) = &self.options.instid { + let confirmation_id = confid::generate(instid)?; + println!("Confirmation ID: {confirmation_id}"); + }; + Ok(()) + } + + fn print_key(pk: &str) { + assert!(pk.len() >= PK_LENGTH); + println!( + "{}", + pk.chars() + .enumerate() + .fold(String::new(), |mut acc: String, (i, c)| { + if i > 0 && i % 5 == 0 { + acc.push('-'); + } + acc.push(c); + acc + }) + ); + } + + fn strip_key(in_key: &str) -> Result { + let out_key: String = in_key + .chars() + .filter_map(|c| { + let c = c.to_ascii_uppercase(); + if P_KEY_CHARSET.into_iter().any(|x| x == c) { + Some(c) + } else { + None + } + }) + .collect(); + if out_key.len() == PK_LENGTH { + Ok(out_key) + } else { + Err(anyhow!("Invalid key length")) + } + } +} diff --git a/src/confid/black_box.rs b/src/confid/black_box.rs index 688552d..af491ea 100644 --- a/src/confid/black_box.rs +++ b/src/confid/black_box.rs @@ -79,8 +79,8 @@ fn umul128(a: u64, b: u64, hi: &mut u64) -> u64 { r as u64 } +/// `hi:lo * ceil(2**170/MOD) >> (64 + 64 + 42)` fn ui128_quotient_mod(lo: u64, hi: u64) -> u64 { - // hi:lo * ceil(2**170/MOD) >> (64 + 64 + 42) let mut prod1: u64 = 0; umul128(lo, 0x604fa6a1c6346a87_i64 as u64, &mut prod1); let mut part1hi: u64 = 0; @@ -286,6 +286,7 @@ unsafe fn find_divisor_v(d: *mut TDivisor) -> i32 { 1_i32 } +/// generic short slow code unsafe fn polynomial_mul( adeg: i32, a: *const u64, @@ -294,7 +295,6 @@ unsafe fn polynomial_mul( mut resultprevdeg: i32, result: *mut u64, ) -> i32 { - // generic short slow code if adeg < 0_i32 || bdeg < 0_i32 { return resultprevdeg; } diff --git a/src/confid/mod.rs b/src/confid/mod.rs index 077c2f5..397a938 100644 --- a/src/confid/mod.rs +++ b/src/confid/mod.rs @@ -4,7 +4,7 @@ use thiserror::Error; mod black_box; -#[derive(Error, Debug, PartialEq, Eq)] +#[derive(Error, Debug)] pub enum ConfirmationIdError { #[error("Installation ID is too short.")] TooShort, @@ -21,12 +21,6 @@ pub enum ConfirmationIdError { } pub fn generate(installation_id: &str) -> Result { - if installation_id.len() < 54 { - return Err(ConfirmationIdError::TooShort); - } - if installation_id.len() > 54 { - return Err(ConfirmationIdError::TooLarge); - } let inst_id = CString::new(installation_id).unwrap(); let conf_id = [0u8; 49]; let result = unsafe { black_box::generate(inst_id.as_ptr(), conf_id.as_ptr() as *mut i8) }; @@ -47,32 +41,3 @@ pub fn generate(installation_id: &str) -> Result { .to_string()) } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_generate() { - assert_eq!( - generate("334481558826870862843844566221823392794862457401103810").unwrap(), - "110281-200130-887120-647974-697175-027544-252733" - ); - assert!( - generate("33448155882687086284384456622182339279486245740110381") - .is_err_and(|err| err == ConfirmationIdError::TooShort), - ); - assert!( - generate("3344815588268708628438445662218233927948624574011038100") - .is_err_and(|err| err == ConfirmationIdError::TooLarge), - ); - assert!( - generate("33448155882687086284384456622182339279486245740110381!") - .is_err_and(|err| err == ConfirmationIdError::InvalidCharacter), - ); - assert!( - generate("334481558826870862843844566221823392794862457401103811") - .is_err_and(|err| err == ConfirmationIdError::InvalidCheckDigit), - ); - } -} diff --git a/src/crypto.rs b/src/crypto.rs index 831971a..ce406c3 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -1,63 +1,37 @@ -use anyhow::Result; use openssl::{ bn::{BigNum, BigNumContext}, ec::{EcGroup, EcPoint}, }; -pub struct EllipticCurve { - pub curve: EcGroup, - pub gen_point: EcPoint, - pub pub_point: EcPoint, -} - -pub struct PrivateKey { - pub gen_order: BigNum, - pub private_key: BigNum, -} - -impl PrivateKey { - pub fn new(gen_order: &str, private_key: &str) -> Result { - let gen_order = BigNum::from_dec_str(gen_order)?; - let private_key = &gen_order - &BigNum::from_dec_str(private_key)?; - Ok(Self { - gen_order, - private_key, - }) - } -} - -impl EllipticCurve { - pub fn new( - p: &str, - a: &str, - b: &str, - generator_x: &str, - generator_y: &str, - public_key_x: &str, - public_key_y: &str, - ) -> Result { - let mut context = BigNumContext::new()?; - - let p = BigNum::from_dec_str(p)?; - let a = BigNum::from_dec_str(a)?; - let b = BigNum::from_dec_str(b)?; - let generator_x = BigNum::from_dec_str(generator_x)?; - let generator_y = BigNum::from_dec_str(generator_y)?; - let public_key_x = BigNum::from_dec_str(public_key_x)?; - let public_key_y = BigNum::from_dec_str(public_key_y)?; - - let curve = EcGroup::from_components(p, a, b, &mut context)?; - - let mut gen_point = EcPoint::new(&curve)?; - gen_point.set_affine_coordinates_gfp(&curve, &generator_x, &generator_y, &mut context)?; - - let mut pub_point = EcPoint::new(&curve)?; - pub_point.set_affine_coordinates_gfp(&curve, &public_key_x, &public_key_y, &mut context)?; - - Ok(Self { - curve, - gen_point, - pub_point, - }) - } +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 = 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 index 978ade9..8e0f678 100644 --- a/src/key.rs +++ b/src/key.rs @@ -1,56 +1,36 @@ use std::collections::VecDeque; -use anyhow::{anyhow, Result}; use openssl::bn::BigNum; -const PK_LENGTH: usize = 25; +use crate::PK_LENGTH; /// The allowed character set in a product key. -pub const KEY_CHARSET: [char; 24] = [ +pub const P_KEY_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', ]; -pub(crate) fn base24_decode(cd_key: &str) -> Result> { - let decoded_key: Vec = cd_key +pub fn base24_decode(cd_key: &str) -> Vec { + let p_decoded_key: Vec = cd_key .chars() - .filter_map(|c| KEY_CHARSET.iter().position(|&x| x == c).map(|i| i as u8)) + .filter_map(|c| P_KEY_CHARSET.iter().position(|&x| x == c).map(|i| i as u8)) .collect(); let mut y = BigNum::from_u32(0).unwrap(); - for i in decoded_key { + for i in p_decoded_key { y.mul_word((PK_LENGTH - 1) as u32).unwrap(); y.add_word(i.into()).unwrap(); } - Ok(y.to_vec()) + y.to_vec() } -pub(crate) fn base24_encode(byte_seq: &[u8]) -> Result { +pub fn base24_encode(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(KEY_CHARSET[z.div_word(24).unwrap() as usize])); - Ok(out.iter().collect()) -} - -pub(crate) fn strip_key(in_key: &str) -> Result { - let out_key: String = in_key - .chars() - .filter_map(|c| { - let c = c.to_ascii_uppercase(); - if KEY_CHARSET.into_iter().any(|x| x == c) { - Some(c) - } else { - None - } - }) - .collect(); - if out_key.len() == PK_LENGTH { - Ok(out_key) - } else { - Err(anyhow!("Invalid key length")) - } + (0..=24).for_each(|_| out.push_front(P_KEY_CHARSET[z.div_word(24).unwrap() as usize])); + out.iter().collect() } #[cfg(test)] @@ -58,9 +38,9 @@ mod tests { #[test] fn test_base24() { let input = "JTW3TJ7PFJ7V9CCMX84V9PFT8"; - let unbase24 = super::base24_decode(input).unwrap(); + let unbase24 = super::base24_decode(input); println!("{:?}", unbase24); - let base24 = super::base24_encode(&unbase24).unwrap(); + let base24 = super::base24_encode(&unbase24); println!("{}", base24); assert_eq!(input, base24); } diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 2910152..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub mod bink1998; -pub mod bink2002; -pub mod confid; -pub mod crypto; -mod key; -mod math; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..1a5ccb1 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,14 @@ +use anyhow::Result; + +mod bink1998; +mod bink2002; +mod cli; +mod confid; +mod crypto; +mod key; + +const PK_LENGTH: usize = 25; + +fn main() -> Result<()> { + cli::Cli::new()?.run() +} diff --git a/src/math.rs b/src/math.rs deleted file mode 100644 index 9573f87..0000000 --- a/src/math.rs +++ /dev/null @@ -1,11 +0,0 @@ -pub(crate) fn bitmask(n: u64) -> u64 { - (1 << n) - 1 -} - -pub(crate) fn next_sn_bits(field: u64, n: u32, offset: u32) -> u64 { - (field >> offset) & ((1u64 << n) - 1) -} - -pub(crate) fn by_dword(n: &[u8]) -> u32 { - (n[0] as u32) | (n[1] as u32) << 8 | (n[2] as u32) << 16 | (n[3] as u32) << 24 -}