Compare commits
No commits in common. "b6707e34b87cd69632a0361adef0cff75961c61c" and "1129212b2395c4bf360846a6e5f68123f769aad1" have entirely different histories.
b6707e34b8
...
1129212b23
14 changed files with 801 additions and 961 deletions
15
Cargo.lock
generated
15
Cargo.lock
generated
|
@ -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",
|
||||
]
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Keys {
|
||||
#[serde(rename = "Products")]
|
||||
pub products: HashMap<String, Product>,
|
||||
#[serde(rename = "BINK")]
|
||||
pub bink: HashMap<String, Bink>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Product {
|
||||
#[serde(rename = "BINK")]
|
||||
pub bink: Vec<String>,
|
||||
}
|
||||
|
||||
#[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,
|
||||
}
|
|
@ -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<String>,
|
||||
}
|
||||
|
||||
#[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<String>,
|
||||
}
|
||||
|
||||
#[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<String>,
|
||||
|
||||
/// 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<P: AsRef<Path> + std::fmt::Display>(path: Option<P>, verbose: bool) -> Result<Keys> {
|
||||
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<EllipticCurve> {
|
||||
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(())
|
||||
}
|
357
src/bink1998.rs
357
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<u32>,
|
||||
upgrade: Option<bool>,
|
||||
) -> Result<Self> {
|
||||
// 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<bool> {
|
||||
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<Self> {
|
||||
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<Self> {
|
||||
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<String> {
|
||||
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<bool> {
|
||||
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<Self> {
|
||||
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<u8> {
|
||||
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<ProductKey> {
|
||||
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<u8> {
|
||||
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
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
474
src/bink2002.rs
474
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<u32>,
|
||||
upgrade: Option<bool>,
|
||||
) -> Result<Self> {
|
||||
// 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<bool> {
|
||||
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<Self> {
|
||||
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<Self> {
|
||||
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<String> {
|
||||
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<u8> = 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<bool> {
|
||||
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<Self> {
|
||||
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<u8> {
|
||||
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<ProductKey> {
|
||||
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<u8> {
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
|
382
src/cli.rs
Normal file
382
src/cli.rs
Normal file
|
@ -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<String>,
|
||||
|
||||
/// Installation ID used to generate confirmation ID
|
||||
#[arg(short, long)]
|
||||
instid: Option<String>,
|
||||
|
||||
/// 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<String>,
|
||||
|
||||
#[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<Self> {
|
||||
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<serde_json::Value> {
|
||||
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<P: AsRef<Path>>(path: P) -> Result<serde_json::Value> {
|
||||
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<String> {
|
||||
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"))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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<String, ConfirmationIdError> {
|
||||
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<String, ConfirmationIdError> {
|
|||
.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),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Self> {
|
||||
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<Self> {
|
||||
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)
|
||||
}
|
||||
|
|
44
src/key.rs
44
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<Vec<u8>> {
|
||||
let decoded_key: Vec<u8> = cd_key
|
||||
pub fn base24_decode(cd_key: &str) -> Vec<u8> {
|
||||
let p_decoded_key: Vec<u8> = 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<String> {
|
||||
pub fn base24_encode(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(KEY_CHARSET[z.div_word(24).unwrap() as usize]));
|
||||
Ok(out.iter().collect())
|
||||
}
|
||||
|
||||
pub(crate) fn strip_key(in_key: &str) -> Result<String> {
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
pub mod bink1998;
|
||||
pub mod bink2002;
|
||||
pub mod confid;
|
||||
pub mod crypto;
|
||||
mod key;
|
||||
mod math;
|
14
src/main.rs
Normal file
14
src/main.rs
Normal file
|
@ -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()
|
||||
}
|
11
src/math.rs
11
src/math.rs
|
@ -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
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue