Implement working unpack for bink1998
This commit is contained in:
parent
0208c863aa
commit
3993cafa35
7 changed files with 273 additions and 44 deletions
10
Cargo.lock
generated
10
Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
142
src/bink1998.rs
Normal file
142
src/bink1998.rs
Normal file
|
@ -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<bool> {
|
||||
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<ProductKey> {
|
||||
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<u8> {
|
||||
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());
|
||||
}
|
||||
}
|
86
src/cli.rs
86
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();
|
||||
|
||||
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)
|
||||
pub fn run(&mut self) -> Result<()> {
|
||||
match self.options.application_mode {
|
||||
Mode::Bink1998 => self.bink1998(),
|
||||
Mode::Bink2002 => self.bink2002(),
|
||||
Mode::ConfirmationId => self.confirmation_id(),
|
||||
}
|
||||
}
|
||||
|
||||
fn bink1998(&mut self) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn bink2002(&mut self) -> Result<()> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn confirmation_id(&mut self) -> Result<()> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
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
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
37
src/crypto.rs
Normal file
37
src/crypto.rs
Normal file
|
@ -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)
|
||||
}
|
46
src/key.rs
Normal file
46
src/key.rs
Normal file
|
@ -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<u8> {
|
||||
let p_decoded_key: Vec<u8> = 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<char> = 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);
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
|
|
Loading…
Add table
Reference in a new issue