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"
|
version = "1.0.164"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d"
|
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]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
|
@ -382,7 +368,6 @@ dependencies = [
|
||||||
"bitreader",
|
"bitreader",
|
||||||
"clap",
|
"clap",
|
||||||
"openssl",
|
"openssl",
|
||||||
"serde",
|
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
|
@ -2,10 +2,6 @@
|
||||||
name = "umskt"
|
name = "umskt"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
crate-type = ["lib"]
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "xpkey"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# 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"
|
bitreader = "0.3.7"
|
||||||
clap = { version = "4.3.4", features = ["derive"] }
|
clap = { version = "4.3.4", features = ["derive"] }
|
||||||
openssl = { git = "https://github.com/anpage/rust-openssl.git" }
|
openssl = { git = "https://github.com/anpage/rust-openssl.git" }
|
||||||
serde = { version = "1.0.164", features = ["derive"] }
|
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
thiserror = "1.0.40"
|
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(())
|
|
||||||
}
|
|
307
src/bink1998.rs
307
src/bink1998.rs
|
@ -1,6 +1,4 @@
|
||||||
use std::fmt::{Display, Formatter};
|
use anyhow::Result;
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
|
||||||
use bitreader::BitReader;
|
use bitreader::BitReader;
|
||||||
use openssl::{
|
use openssl::{
|
||||||
bn::{BigNum, BigNumContext, MsbOption},
|
bn::{BigNum, BigNumContext, MsbOption},
|
||||||
|
@ -8,102 +6,98 @@ use openssl::{
|
||||||
sha::sha1,
|
sha::sha1,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::key::{base24_decode, base24_encode};
|
||||||
crypto::{EllipticCurve, PrivateKey},
|
|
||||||
key::{base24_decode, base24_encode, strip_key},
|
|
||||||
math::bitmask,
|
|
||||||
};
|
|
||||||
|
|
||||||
const FIELD_BITS: i32 = 384;
|
const FIELD_BITS: i32 = 384;
|
||||||
const FIELD_BYTES: usize = 48;
|
const FIELD_BYTES: usize = 48;
|
||||||
const SHA_MSG_LENGTH: usize = 4 + 2 * FIELD_BYTES;
|
const SHA_MSG_LENGTH: usize = 4 + 2 * FIELD_BYTES;
|
||||||
|
|
||||||
const HASH_LENGTH_BITS: u8 = 28;
|
#[derive(Clone, Copy, Debug)]
|
||||||
const SERIAL_LENGTH_BITS: u8 = 30;
|
struct ProductKey {
|
||||||
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 {
|
|
||||||
upgrade: bool,
|
upgrade: bool,
|
||||||
channel_id: u32,
|
serial: u32,
|
||||||
sequence: u32,
|
|
||||||
hash: u32,
|
hash: u32,
|
||||||
signature: u64,
|
signature: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProductKey {
|
pub fn verify(
|
||||||
pub fn new(
|
e_curve: &EcGroup,
|
||||||
curve: &EllipticCurve,
|
base_point: &EcPoint,
|
||||||
private_key: &PrivateKey,
|
public_key: &EcPoint,
|
||||||
channel_id: u32,
|
p_key: &str,
|
||||||
sequence: Option<u32>,
|
verbose: bool,
|
||||||
upgrade: Option<bool>,
|
) -> Result<bool> {
|
||||||
) -> Result<Self> {
|
let mut num_context = BigNumContext::new()?;
|
||||||
// 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
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Default to upgrade=false
|
let p_raw = base24_decode(p_key);
|
||||||
let upgrade = upgrade.unwrap_or(false);
|
let product_key = unpack(&p_raw)?;
|
||||||
|
|
||||||
// Generate a new random key
|
let p_data = product_key.serial << 1 | product_key.upgrade as u32;
|
||||||
let product_key = Self::generate(
|
|
||||||
&curve.curve,
|
|
||||||
&curve.gen_point,
|
|
||||||
&private_key.gen_order,
|
|
||||||
&private_key.private_key,
|
|
||||||
channel_id,
|
|
||||||
sequence,
|
|
||||||
upgrade,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// Make sure the key is valid
|
if verbose {
|
||||||
product_key.verify(&curve.curve, &curve.gen_point, &curve.pub_point)?;
|
println!("Validation results:");
|
||||||
|
println!(" Upgrade: {}", product_key.upgrade);
|
||||||
// Ship it
|
println!(" Serial: {}", product_key.serial);
|
||||||
Ok(product_key)
|
println!(" Hash: {}", product_key.hash);
|
||||||
|
println!(" Signature: {}", product_key.signature);
|
||||||
|
println!();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_key(curve: &EllipticCurve, key: &str) -> Result<Self> {
|
let e = BigNum::from_u32(product_key.hash)?;
|
||||||
let key = strip_key(key)?;
|
let s = BigNum::from_slice(&product_key.signature.to_be_bytes())?;
|
||||||
let Ok(packed_key) = base24_decode(&key) else {
|
let mut x = BigNum::new()?;
|
||||||
bail!("Product key is in an incorrect format!")
|
let mut y = BigNum::new()?;
|
||||||
};
|
|
||||||
let product_key = Self::from_packed(&packed_key)?;
|
let mut t = EcPoint::new(e_curve)?;
|
||||||
product_key.verify(&curve.curve, &curve.gen_point, &curve.pub_point)?;
|
let mut p = EcPoint::new(e_curve)?;
|
||||||
Ok(product_key)
|
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; 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[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_le_bytes(msg_digest[0..4].try_into().unwrap()) >> 4 & bitmask(28) as u32;
|
||||||
|
|
||||||
|
Ok(hash == product_key.hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate(
|
pub fn generate(
|
||||||
e_curve: &EcGroup,
|
e_curve: &EcGroup,
|
||||||
base_point: &EcPoint,
|
base_point: &EcPoint,
|
||||||
gen_order: &BigNum,
|
gen_order: &BigNum,
|
||||||
private_key: &BigNum,
|
private_key: &BigNum,
|
||||||
channel_id: u32,
|
p_serial: u32,
|
||||||
sequence: u32,
|
p_upgrade: bool,
|
||||||
upgrade: bool,
|
) -> Result<String> {
|
||||||
) -> Result<Self> {
|
|
||||||
let mut num_context = BigNumContext::new().unwrap();
|
let mut num_context = BigNumContext::new().unwrap();
|
||||||
|
|
||||||
let mut c = BigNum::new()?;
|
let mut c = BigNum::new()?;
|
||||||
let mut s = BigNum::new()?;
|
let mut s = BigNum::new()?;
|
||||||
|
let mut s_2 = BigNum::new()?;
|
||||||
let mut x = BigNum::new()?;
|
let mut x = BigNum::new()?;
|
||||||
let mut y = BigNum::new()?;
|
let mut y = BigNum::new()?;
|
||||||
|
|
||||||
let mut ek: BigNum;
|
let p_data = p_serial << 1 | p_upgrade as u32;
|
||||||
|
|
||||||
let serial = channel_id * 1_000_000 + sequence;
|
let p_raw = loop {
|
||||||
let data = serial << 1 | upgrade as u32;
|
|
||||||
|
|
||||||
let product_key = loop {
|
|
||||||
let mut r = EcPoint::new(e_curve)?;
|
let mut r = EcPoint::new(e_curve)?;
|
||||||
|
|
||||||
// Generate a random number c consisting of 384 bits without any constraints.
|
// Generate a random number c consisting of 384 bits without any constraints.
|
||||||
|
@ -124,139 +118,75 @@ impl ProductKey {
|
||||||
let mut y_bin = y.to_vec_padded(FIELD_BYTES as i32)?;
|
let mut y_bin = y.to_vec_padded(FIELD_BYTES as i32)?;
|
||||||
y_bin.reverse();
|
y_bin.reverse();
|
||||||
|
|
||||||
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..4 + FIELD_BYTES].copy_from_slice(&x_bin);
|
||||||
msg_buffer[4 + FIELD_BYTES..4 + FIELD_BYTES * 2].copy_from_slice(&y_bin);
|
msg_buffer[4 + FIELD_BYTES..4 + FIELD_BYTES * 2].copy_from_slice(&y_bin);
|
||||||
|
|
||||||
let msg_digest = sha1(&msg_buffer);
|
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;
|
u32::from_le_bytes(msg_digest[0..4].try_into().unwrap()) >> 4 & bitmask(28) as u32;
|
||||||
|
|
||||||
ek = (*private_key).to_owned()?;
|
s_2.copy_from_slice(&private_key.to_vec())?;
|
||||||
ek.mul_word(hash)?;
|
s_2.mul_word(p_hash)?;
|
||||||
|
|
||||||
s.mod_add(&ek, &c, gen_order, &mut num_context)?;
|
s.mod_add(&s_2, &c, gen_order, &mut num_context)?;
|
||||||
|
|
||||||
let signature = u64::from_be_bytes(s.to_vec_padded(8)?.try_into().unwrap());
|
let p_signature = u64::from_be_bytes(s.to_vec_padded(8)?.try_into().unwrap());
|
||||||
|
|
||||||
if signature <= bitmask(55) {
|
if p_signature <= bitmask(55) {
|
||||||
break Self {
|
break pack(ProductKey {
|
||||||
upgrade,
|
upgrade: p_upgrade,
|
||||||
channel_id,
|
serial: p_serial,
|
||||||
sequence,
|
hash: p_hash,
|
||||||
hash,
|
signature: p_signature,
|
||||||
signature,
|
});
|
||||||
};
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(product_key)
|
Ok(base24_encode(&p_raw))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn verify(
|
const HASH_LENGTH_BITS: u8 = 28;
|
||||||
&self,
|
const SERIAL_LENGTH_BITS: u8 = 30;
|
||||||
e_curve: &EcGroup,
|
const UPGRADE_LENGTH_BITS: u8 = 1;
|
||||||
base_point: &EcPoint,
|
const EVERYTHING_ELSE: u8 = HASH_LENGTH_BITS + SERIAL_LENGTH_BITS + UPGRADE_LENGTH_BITS;
|
||||||
public_key: &EcPoint,
|
|
||||||
) -> Result<bool> {
|
|
||||||
let mut ctx = BigNumContext::new()?;
|
|
||||||
|
|
||||||
let e = BigNum::from_u32(self.hash)?;
|
fn unpack(p_raw: &[u8]) -> Result<ProductKey> {
|
||||||
let s = BigNum::from_slice(&self.signature.to_be_bytes())?;
|
let mut reader = BitReader::new(p_raw);
|
||||||
let mut x = BigNum::new()?;
|
// The signature length is unknown, but everything else is, so we can calculate it
|
||||||
let mut y = BigNum::new()?;
|
let signature_length_bits = (p_raw.len() * 8) as u8 - EVERYTHING_ELSE;
|
||||||
|
|
||||||
let mut t = EcPoint::new(e_curve)?;
|
let p_signature = reader.read_u64(signature_length_bits)?;
|
||||||
let mut p = EcPoint::new(e_curve)?;
|
let p_hash = reader.read_u32(HASH_LENGTH_BITS)?;
|
||||||
|
let p_serial = reader.read_u32(SERIAL_LENGTH_BITS)?;
|
||||||
|
let p_upgrade = reader.read_bool()?;
|
||||||
|
|
||||||
t.mul(e_curve, base_point, &s, &ctx)?;
|
Ok(ProductKey {
|
||||||
p.mul(e_curve, public_key, &e, &ctx)?;
|
upgrade: p_upgrade,
|
||||||
|
serial: p_serial,
|
||||||
{
|
hash: p_hash,
|
||||||
let p_copy = p.to_owned(e_curve)?;
|
signature: p_signature,
|
||||||
p.add(e_curve, &t, &p_copy, &mut ctx)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.affine_coordinates(e_curve, &mut x, &mut y, &mut ctx)?;
|
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
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[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_le_bytes(msg_digest[0..4].try_into().unwrap()) >> 4 & bitmask(28) as u32;
|
|
||||||
|
|
||||||
Ok(hash == self.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;
|
|
||||||
|
|
||||||
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 sequence = serial % 1_000_000;
|
|
||||||
let channel_id = serial / 1_000_000;
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
upgrade,
|
|
||||||
channel_id,
|
|
||||||
sequence,
|
|
||||||
hash,
|
|
||||||
signature,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pack(&self) -> Vec<u8> {
|
fn pack(p_key: ProductKey) -> Vec<u8> {
|
||||||
let mut packed_key: u128 = 0;
|
let mut p_raw: u128 = 0;
|
||||||
|
|
||||||
let serial = self.channel_id * 1_000_000 + self.sequence;
|
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;
|
||||||
|
|
||||||
packed_key |= (self.signature as u128) << EVERYTHING_ELSE;
|
p_raw
|
||||||
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()
|
.to_be_bytes()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.skip_while(|&x| x == 0)
|
.skip_while(|&x| x == 0)
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for ProductKey {
|
fn bitmask(n: u64) -> u64 {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
(1 << n) - 1
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -265,7 +195,7 @@ mod tests {
|
||||||
|
|
||||||
use serde_json::from_reader;
|
use serde_json::from_reader;
|
||||||
|
|
||||||
use crate::crypto::EllipticCurve;
|
use crate::crypto::initialize_elliptic_curve;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn verify_test() {
|
fn verify_test() {
|
||||||
|
@ -289,22 +219,35 @@ mod tests {
|
||||||
let kx = bink["pub"]["x"].as_str().unwrap();
|
let kx = bink["pub"]["x"].as_str().unwrap();
|
||||||
let ky = bink["pub"]["y"].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::verify(&e_curve, &gen_point, &pub_point, product_key, true).unwrap());
|
||||||
assert!(super::ProductKey::from_key(&curve, "11111-R6BG2-39J83-RYKHF-W47TT").is_err());
|
assert!(!super::verify(
|
||||||
|
&e_curve,
|
||||||
|
&gen_point,
|
||||||
|
&pub_point,
|
||||||
|
"11111-R6BG2-39J83-RYKHF-W47TT",
|
||||||
|
true
|
||||||
|
)
|
||||||
|
.unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn pack_test() {
|
fn pack_test() {
|
||||||
let key = super::ProductKey {
|
let p_key = super::ProductKey {
|
||||||
upgrade: false,
|
upgrade: false,
|
||||||
channel_id: 640,
|
serial: 640010550,
|
||||||
sequence: 10550,
|
|
||||||
hash: 39185432,
|
hash: 39185432,
|
||||||
signature: 6939952665262054,
|
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
|
||||||
|
]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
352
src/bink2002.rs
352
src/bink2002.rs
|
@ -1,33 +1,19 @@
|
||||||
use std::fmt::{Display, Formatter};
|
use anyhow::Result;
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
|
||||||
use bitreader::BitReader;
|
use bitreader::BitReader;
|
||||||
use openssl::{
|
use openssl::{
|
||||||
bn::{BigNum, BigNumContext, MsbOption},
|
bn::{BigNum, BigNumContext, MsbOption},
|
||||||
ec::{EcGroup, EcPoint},
|
ec::{EcGroup, EcPoint},
|
||||||
rand::rand_bytes,
|
|
||||||
sha::sha1,
|
sha::sha1,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::key::{base24_decode, base24_encode};
|
||||||
crypto::{EllipticCurve, PrivateKey},
|
|
||||||
key::{base24_decode, base24_encode, strip_key},
|
|
||||||
math::{bitmask, by_dword, next_sn_bits},
|
|
||||||
};
|
|
||||||
|
|
||||||
const FIELD_BITS: i32 = 512;
|
const FIELD_BITS: i32 = 512;
|
||||||
const FIELD_BYTES: usize = 64;
|
const FIELD_BYTES: usize = 64;
|
||||||
const SHA_MSG_LENGTH: usize = 3 + 2 * FIELD_BYTES;
|
const SHA_MSG_LENGTH: usize = 3 + 2 * FIELD_BYTES;
|
||||||
|
|
||||||
const SIGNATURE_LENGTH_BITS: u8 = 62;
|
#[derive(Clone, Copy, Debug)]
|
||||||
const HASH_LENGTH_BITS: u8 = 31;
|
struct ProductKey {
|
||||||
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 {
|
|
||||||
upgrade: bool,
|
upgrade: bool,
|
||||||
channel_id: u32,
|
channel_id: u32,
|
||||||
hash: u32,
|
hash: u32,
|
||||||
|
@ -35,77 +21,107 @@ pub struct ProductKey {
|
||||||
auth_info: u32,
|
auth_info: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProductKey {
|
pub fn verify(
|
||||||
pub fn new(
|
e_curve: &EcGroup,
|
||||||
curve: &EllipticCurve,
|
base_point: &EcPoint,
|
||||||
private_key: &PrivateKey,
|
public_key: &EcPoint,
|
||||||
channel_id: u32,
|
cd_key: &str,
|
||||||
auth_info: Option<u32>,
|
verbose: bool,
|
||||||
upgrade: Option<bool>,
|
) -> Result<bool> {
|
||||||
) -> Result<Self> {
|
let mut num_context = BigNumContext::new()?;
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Default to upgrade=false
|
let b_key = base24_decode(cd_key);
|
||||||
let upgrade = upgrade.unwrap_or(false);
|
let product_key = unpack(&b_key)?;
|
||||||
|
|
||||||
// Generate a new random key
|
let p_data = product_key.channel_id << 1 | product_key.upgrade as u32;
|
||||||
let product_key = Self::generate(
|
|
||||||
&curve.curve,
|
|
||||||
&curve.gen_point,
|
|
||||||
&private_key.gen_order,
|
|
||||||
&private_key.private_key,
|
|
||||||
channel_id,
|
|
||||||
auth_info,
|
|
||||||
upgrade,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// Make sure the key is valid
|
if verbose {
|
||||||
product_key.verify(&curve.curve, &curve.gen_point, &curve.pub_point)?;
|
println!("Validation results:");
|
||||||
|
println!(" Upgrade: {}", product_key.upgrade);
|
||||||
// Ship it
|
println!("Channel ID: {}", product_key.channel_id);
|
||||||
Ok(product_key)
|
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 mut msg_buffer: [u8; SHA_MSG_LENGTH] = [0; SHA_MSG_LENGTH];
|
||||||
let key = strip_key(key)?;
|
|
||||||
let Ok(packed_key) = base24_decode(&key) else {
|
msg_buffer[0x00] = 0x5D;
|
||||||
bail!("Product key is in an incorrect format!")
|
msg_buffer[0x01] = (p_data & 0x00FF) as u8;
|
||||||
};
|
msg_buffer[0x02] = ((p_data & 0xFF00) >> 8) as u8;
|
||||||
let product_key = Self::from_packed(&packed_key)?;
|
msg_buffer[0x03] = (product_key.hash & 0x000000FF) as u8;
|
||||||
let verified = product_key.verify(&curve.curve, &curve.gen_point, &curve.pub_point)?;
|
msg_buffer[0x04] = ((product_key.hash & 0x0000FF00) >> 8) as u8;
|
||||||
if !verified {
|
msg_buffer[0x05] = ((product_key.hash & 0x00FF0000) >> 16) as u8;
|
||||||
bail!("Product key is invalid! Wrong BINK ID?");
|
msg_buffer[0x06] = ((product_key.hash & 0xFF000000) >> 24) as u8;
|
||||||
}
|
msg_buffer[0x07] = (product_key.auth_info & 0x00FF) as u8;
|
||||||
Ok(product_key)
|
msg_buffer[0x08] = ((product_key.auth_info & 0xFF00) >> 8) as u8;
|
||||||
|
msg_buffer[0x09] = 0x00;
|
||||||
|
msg_buffer[0x0A] = 0x00;
|
||||||
|
|
||||||
|
let msg_digest = sha1(&msg_buffer[..=0x0A]);
|
||||||
|
|
||||||
|
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(&product_key.signature.to_be_bytes())?;
|
||||||
|
|
||||||
|
let mut x = BigNum::new()?;
|
||||||
|
let mut y = BigNum::new()?;
|
||||||
|
|
||||||
|
let mut p = EcPoint::new(e_curve)?;
|
||||||
|
let mut t = EcPoint::new(e_curve)?;
|
||||||
|
|
||||||
|
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)?;
|
||||||
|
|
||||||
|
p.add(e_curve, &t, &p_2, &mut num_context)?;
|
||||||
|
let p_2 = p.to_owned(e_curve)?;
|
||||||
|
|
||||||
|
p.mul(e_curve, &p_2, &s, &num_context)?;
|
||||||
|
|
||||||
|
p.affine_coordinates(e_curve, &mut x, &mut y, &mut num_context)?;
|
||||||
|
|
||||||
|
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 hash: u32 = by_dword(&msg_digest[0..4]) & bitmask(31) as u32;
|
||||||
|
|
||||||
|
Ok(hash == product_key.hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate(
|
pub fn generate(
|
||||||
e_curve: &EcGroup,
|
e_curve: &EcGroup,
|
||||||
base_point: &EcPoint,
|
base_point: &EcPoint,
|
||||||
gen_order: &BigNum,
|
gen_order: &BigNum,
|
||||||
private_key: &BigNum,
|
private_key: &BigNum,
|
||||||
channel_id: u32,
|
p_channel_id: u32,
|
||||||
auth_info: u32,
|
p_auth_info: u32,
|
||||||
upgrade: bool,
|
p_upgrade: bool,
|
||||||
) -> Result<Self> {
|
) -> Result<String> {
|
||||||
let mut num_context = BigNumContext::new().unwrap();
|
let mut num_context = BigNumContext::new().unwrap();
|
||||||
|
|
||||||
let mut c = BigNum::new()?;
|
let mut c = BigNum::new()?;
|
||||||
let mut x = BigNum::new()?;
|
let mut x = BigNum::new()?;
|
||||||
let mut y = BigNum::new()?;
|
let mut y = BigNum::new()?;
|
||||||
|
|
||||||
let data = channel_id << 1 | upgrade as u32;
|
let p_data = p_channel_id << 1 | p_upgrade as u32;
|
||||||
|
|
||||||
let mut no_square = false;
|
let mut no_square = false;
|
||||||
let key = loop {
|
let p_raw: Vec<u8> = loop {
|
||||||
let mut r = EcPoint::new(e_curve)?;
|
let mut r = EcPoint::new(e_curve)?;
|
||||||
|
|
||||||
c.rand(FIELD_BITS, MsbOption::MAYBE_ZERO, false)?;
|
c.rand(FIELD_BITS, MsbOption::MAYBE_ZERO, false)?;
|
||||||
|
@ -122,25 +138,25 @@ impl ProductKey {
|
||||||
y_bin.reverse();
|
y_bin.reverse();
|
||||||
|
|
||||||
msg_buffer[0x00] = 0x79;
|
msg_buffer[0x00] = 0x79;
|
||||||
msg_buffer[0x01] = (data & 0x00FF) as u8;
|
msg_buffer[0x01] = (p_data & 0x00FF) as u8;
|
||||||
msg_buffer[0x02] = ((data & 0xFF00) >> 8) 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..3 + FIELD_BYTES].copy_from_slice(&x_bin);
|
||||||
msg_buffer[3 + FIELD_BYTES..3 + FIELD_BYTES * 2].copy_from_slice(&y_bin);
|
msg_buffer[3 + FIELD_BYTES..3 + FIELD_BYTES * 2].copy_from_slice(&y_bin);
|
||||||
|
|
||||||
let msg_digest = sha1(&msg_buffer);
|
let msg_digest = sha1(&msg_buffer);
|
||||||
|
|
||||||
let hash: u32 = by_dword(&msg_digest[0..4]) & bitmask(31) as u32;
|
let p_hash: u32 = by_dword(&msg_digest[0..4]) & bitmask(31) as u32;
|
||||||
|
|
||||||
msg_buffer[0x00] = 0x5D;
|
msg_buffer[0x00] = 0x5D;
|
||||||
msg_buffer[0x01] = (data & 0x00FF) as u8;
|
msg_buffer[0x01] = (p_data & 0x00FF) as u8;
|
||||||
msg_buffer[0x02] = ((data & 0xFF00) >> 8) as u8;
|
msg_buffer[0x02] = ((p_data & 0xFF00) >> 8) as u8;
|
||||||
msg_buffer[0x03] = (hash & 0x000000FF) as u8;
|
msg_buffer[0x03] = (p_hash & 0x000000FF) as u8;
|
||||||
msg_buffer[0x04] = ((hash & 0x0000FF00) >> 8) as u8;
|
msg_buffer[0x04] = ((p_hash & 0x0000FF00) >> 8) as u8;
|
||||||
msg_buffer[0x05] = ((hash & 0x00FF0000) >> 16) as u8;
|
msg_buffer[0x05] = ((p_hash & 0x00FF0000) >> 16) as u8;
|
||||||
msg_buffer[0x06] = ((hash & 0xFF000000) >> 24) as u8;
|
msg_buffer[0x06] = ((p_hash & 0xFF000000) >> 24) as u8;
|
||||||
msg_buffer[0x07] = (auth_info & 0x00FF) as u8;
|
msg_buffer[0x07] = (p_auth_info & 0x00FF) as u8;
|
||||||
msg_buffer[0x08] = ((auth_info & 0xFF00) >> 8) as u8;
|
msg_buffer[0x08] = ((p_auth_info & 0xFF00) >> 8) as u8;
|
||||||
msg_buffer[0x09] = 0x00;
|
msg_buffer[0x09] = 0x00;
|
||||||
msg_buffer[0x0A] = 0x00;
|
msg_buffer[0x0A] = 0x00;
|
||||||
|
|
||||||
|
@ -179,163 +195,92 @@ impl ProductKey {
|
||||||
let s_2 = s.to_owned()?;
|
let s_2 = s.to_owned()?;
|
||||||
s.rshift1(&s_2)?;
|
s.rshift1(&s_2)?;
|
||||||
|
|
||||||
let signature = u64::from_be_bytes(s.to_vec_padded(8)?.try_into().unwrap());
|
let p_signature = u64::from_be_bytes(s.to_vec_padded(8)?.try_into().unwrap());
|
||||||
|
|
||||||
let product_key = Self {
|
let product_key = ProductKey {
|
||||||
upgrade,
|
upgrade: p_upgrade,
|
||||||
channel_id,
|
channel_id: p_channel_id,
|
||||||
hash,
|
hash: p_hash,
|
||||||
signature,
|
signature: p_signature,
|
||||||
auth_info,
|
auth_info: p_auth_info,
|
||||||
};
|
};
|
||||||
|
|
||||||
if signature <= bitmask(62) && !no_square {
|
if p_signature <= bitmask(62) && !no_square {
|
||||||
break product_key;
|
break pack(product_key);
|
||||||
}
|
}
|
||||||
|
|
||||||
no_square = false;
|
no_square = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(key)
|
Ok(base24_encode(&p_raw))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn verify(
|
const SIGNATURE_LENGTH_BITS: u8 = 62;
|
||||||
&self,
|
const HASH_LENGTH_BITS: u8 = 31;
|
||||||
e_curve: &EcGroup,
|
const CHANNEL_ID_LENGTH_BITS: u8 = 10;
|
||||||
base_point: &EcPoint,
|
const UPGRADE_LENGTH_BITS: u8 = 1;
|
||||||
public_key: &EcPoint,
|
const EVERYTHING_ELSE: u8 =
|
||||||
) -> Result<bool> {
|
SIGNATURE_LENGTH_BITS + HASH_LENGTH_BITS + CHANNEL_ID_LENGTH_BITS + UPGRADE_LENGTH_BITS;
|
||||||
let mut num_context = BigNumContext::new()?;
|
|
||||||
|
|
||||||
let data = self.channel_id << 1 | self.upgrade as u32;
|
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 mut msg_buffer: [u8; SHA_MSG_LENGTH] = [0; SHA_MSG_LENGTH];
|
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()?;
|
||||||
|
|
||||||
msg_buffer[0x00] = 0x5D;
|
Ok(ProductKey {
|
||||||
msg_buffer[0x01] = (data & 0x00FF) as u8;
|
upgrade: p_upgrade,
|
||||||
msg_buffer[0x02] = ((data & 0xFF00) >> 8) as u8;
|
channel_id: p_channel_id,
|
||||||
msg_buffer[0x03] = (self.hash & 0x000000FF) as u8;
|
hash: p_hash,
|
||||||
msg_buffer[0x04] = ((self.hash & 0x0000FF00) >> 8) as u8;
|
signature: p_signature,
|
||||||
msg_buffer[0x05] = ((self.hash & 0x00FF0000) >> 16) as u8;
|
auth_info: p_auth_info,
|
||||||
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[0x09] = 0x00;
|
|
||||||
msg_buffer[0x0A] = 0x00;
|
|
||||||
|
|
||||||
let msg_digest = sha1(&msg_buffer[..=0x0A]);
|
|
||||||
|
|
||||||
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 x = BigNum::new()?;
|
|
||||||
let mut y = BigNum::new()?;
|
|
||||||
|
|
||||||
let mut p = EcPoint::new(e_curve)?;
|
|
||||||
let mut t = EcPoint::new(e_curve)?;
|
|
||||||
|
|
||||||
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)?;
|
|
||||||
p.add(e_curve, &t, &p_2, &mut num_context)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
let p_2 = p.to_owned(e_curve)?;
|
|
||||||
p.mul(e_curve, &p_2, &s, &num_context)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.affine_coordinates(e_curve, &mut x, &mut y, &mut num_context)?;
|
|
||||||
|
|
||||||
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> {
|
fn pack(p_key: ProductKey) -> Vec<u8> {
|
||||||
let mut packed_key: u128 = 0;
|
let mut p_raw: u128 = 0;
|
||||||
|
|
||||||
packed_key |= (self.auth_info as u128)
|
p_raw |= (p_key.auth_info as u128)
|
||||||
<< (SIGNATURE_LENGTH_BITS
|
<< (SIGNATURE_LENGTH_BITS
|
||||||
+ HASH_LENGTH_BITS
|
+ HASH_LENGTH_BITS
|
||||||
+ CHANNEL_ID_LENGTH_BITS
|
+ CHANNEL_ID_LENGTH_BITS
|
||||||
+ UPGRADE_LENGTH_BITS);
|
+ UPGRADE_LENGTH_BITS);
|
||||||
packed_key |= (self.signature as u128)
|
p_raw |= (p_key.signature as u128)
|
||||||
<< (HASH_LENGTH_BITS + CHANNEL_ID_LENGTH_BITS + UPGRADE_LENGTH_BITS);
|
<< (HASH_LENGTH_BITS + CHANNEL_ID_LENGTH_BITS + UPGRADE_LENGTH_BITS);
|
||||||
packed_key |= (self.hash as u128) << (CHANNEL_ID_LENGTH_BITS + UPGRADE_LENGTH_BITS);
|
p_raw |= (p_key.hash as u128) << (CHANNEL_ID_LENGTH_BITS + UPGRADE_LENGTH_BITS);
|
||||||
packed_key |= (self.channel_id as u128) << UPGRADE_LENGTH_BITS;
|
p_raw |= (p_key.channel_id as u128) << UPGRADE_LENGTH_BITS;
|
||||||
packed_key |= self.upgrade as u128;
|
p_raw |= p_key.upgrade as u128;
|
||||||
|
|
||||||
packed_key
|
p_raw
|
||||||
.to_be_bytes()
|
.to_be_bytes()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.skip_while(|&x| x == 0)
|
.skip_while(|&x| x == 0)
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn bitmask(n: u64) -> u64 {
|
||||||
|
(1 << n) - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for ProductKey {
|
fn next_sn_bits(field: u64, n: u32, offset: u32) -> u64 {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
(field >> offset) & ((1u64 << n) - 1)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use serde_json::from_reader;
|
|
||||||
use std::{fs::File, io::BufReader};
|
use std::{fs::File, io::BufReader};
|
||||||
|
|
||||||
use crate::crypto::EllipticCurve;
|
use serde_json::from_reader;
|
||||||
|
|
||||||
|
use crate::crypto::initialize_elliptic_curve;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn verify_test() {
|
fn verify_test() {
|
||||||
|
@ -359,9 +304,8 @@ mod tests {
|
||||||
let kx = bink["pub"]["x"].as_str().unwrap();
|
let kx = bink["pub"]["x"].as_str().unwrap();
|
||||||
let ky = bink["pub"]["y"].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::verify(&e_curve, &gen_point, &pub_point, product_key, true).unwrap());
|
||||||
assert!(super::ProductKey::from_key(&curve, "11111-YRGC8-4KYTG-C3FCC-JCFDY").is_err());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
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
|
r as u64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `hi:lo * ceil(2**170/MOD) >> (64 + 64 + 42)`
|
||||||
fn ui128_quotient_mod(lo: u64, hi: u64) -> u64 {
|
fn ui128_quotient_mod(lo: u64, hi: u64) -> u64 {
|
||||||
// hi:lo * ceil(2**170/MOD) >> (64 + 64 + 42)
|
|
||||||
let mut prod1: u64 = 0;
|
let mut prod1: u64 = 0;
|
||||||
umul128(lo, 0x604fa6a1c6346a87_i64 as u64, &mut prod1);
|
umul128(lo, 0x604fa6a1c6346a87_i64 as u64, &mut prod1);
|
||||||
let mut part1hi: u64 = 0;
|
let mut part1hi: u64 = 0;
|
||||||
|
@ -286,6 +286,7 @@ unsafe fn find_divisor_v(d: *mut TDivisor) -> i32 {
|
||||||
1_i32
|
1_i32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// generic short slow code
|
||||||
unsafe fn polynomial_mul(
|
unsafe fn polynomial_mul(
|
||||||
adeg: i32,
|
adeg: i32,
|
||||||
a: *const u64,
|
a: *const u64,
|
||||||
|
@ -294,7 +295,6 @@ unsafe fn polynomial_mul(
|
||||||
mut resultprevdeg: i32,
|
mut resultprevdeg: i32,
|
||||||
result: *mut u64,
|
result: *mut u64,
|
||||||
) -> i32 {
|
) -> i32 {
|
||||||
// generic short slow code
|
|
||||||
if adeg < 0_i32 || bdeg < 0_i32 {
|
if adeg < 0_i32 || bdeg < 0_i32 {
|
||||||
return resultprevdeg;
|
return resultprevdeg;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ use thiserror::Error;
|
||||||
|
|
||||||
mod black_box;
|
mod black_box;
|
||||||
|
|
||||||
#[derive(Error, Debug, PartialEq, Eq)]
|
#[derive(Error, Debug)]
|
||||||
pub enum ConfirmationIdError {
|
pub enum ConfirmationIdError {
|
||||||
#[error("Installation ID is too short.")]
|
#[error("Installation ID is too short.")]
|
||||||
TooShort,
|
TooShort,
|
||||||
|
@ -21,12 +21,6 @@ pub enum ConfirmationIdError {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate(installation_id: &str) -> Result<String, 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 inst_id = CString::new(installation_id).unwrap();
|
||||||
let conf_id = [0u8; 49];
|
let conf_id = [0u8; 49];
|
||||||
let result = unsafe { black_box::generate(inst_id.as_ptr(), conf_id.as_ptr() as *mut i8) };
|
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())
|
.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::{
|
use openssl::{
|
||||||
bn::{BigNum, BigNumContext},
|
bn::{BigNum, BigNumContext},
|
||||||
ec::{EcGroup, EcPoint},
|
ec::{EcGroup, EcPoint},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct EllipticCurve {
|
pub fn initialize_elliptic_curve(
|
||||||
pub curve: EcGroup,
|
p_sel: &str,
|
||||||
pub gen_point: EcPoint,
|
a_sel: &str,
|
||||||
pub pub_point: EcPoint,
|
b_sel: &str,
|
||||||
}
|
generator_x_sel: &str,
|
||||||
|
generator_y_sel: &str,
|
||||||
pub struct PrivateKey {
|
public_key_x_sel: &str,
|
||||||
pub gen_order: BigNum,
|
public_key_y_sel: &str,
|
||||||
pub private_key: BigNum,
|
) -> (EcGroup, EcPoint, EcPoint) {
|
||||||
}
|
let mut context = BigNumContext::new().unwrap();
|
||||||
|
|
||||||
impl PrivateKey {
|
let p = BigNum::from_dec_str(p_sel).unwrap();
|
||||||
pub fn new(gen_order: &str, private_key: &str) -> Result<Self> {
|
let a = BigNum::from_dec_str(a_sel).unwrap();
|
||||||
let gen_order = BigNum::from_dec_str(gen_order)?;
|
let b = BigNum::from_dec_str(b_sel).unwrap();
|
||||||
let private_key = &gen_order - &BigNum::from_dec_str(private_key)?;
|
let generator_x = BigNum::from_dec_str(generator_x_sel).unwrap();
|
||||||
Ok(Self {
|
let generator_y = BigNum::from_dec_str(generator_y_sel).unwrap();
|
||||||
gen_order,
|
|
||||||
private_key,
|
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();
|
||||||
|
|
||||||
impl EllipticCurve {
|
let mut gen_point = EcPoint::new(&c_curve).unwrap();
|
||||||
pub fn new(
|
let _ =
|
||||||
p: &str,
|
gen_point.set_affine_coordinates_gfp(&c_curve, &generator_x, &generator_y, &mut context);
|
||||||
a: &str,
|
|
||||||
b: &str,
|
let mut pub_point = EcPoint::new(&c_curve).unwrap();
|
||||||
generator_x: &str,
|
let _ =
|
||||||
generator_y: &str,
|
pub_point.set_affine_coordinates_gfp(&c_curve, &public_key_x, &public_key_y, &mut context);
|
||||||
public_key_x: &str,
|
|
||||||
public_key_y: &str,
|
(c_curve, gen_point, pub_point)
|
||||||
) -> 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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
44
src/key.rs
44
src/key.rs
|
@ -1,56 +1,36 @@
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
|
||||||
use openssl::bn::BigNum;
|
use openssl::bn::BigNum;
|
||||||
|
|
||||||
const PK_LENGTH: usize = 25;
|
use crate::PK_LENGTH;
|
||||||
|
|
||||||
/// The allowed character set in a product key.
|
/// 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',
|
'B', 'C', 'D', 'F', 'G', 'H', 'J', 'K', 'M', 'P', 'Q', 'R', 'T', 'V', 'W', 'X', 'Y', '2', '3',
|
||||||
'4', '6', '7', '8', '9',
|
'4', '6', '7', '8', '9',
|
||||||
];
|
];
|
||||||
|
|
||||||
pub(crate) fn base24_decode(cd_key: &str) -> Result<Vec<u8>> {
|
pub fn base24_decode(cd_key: &str) -> Vec<u8> {
|
||||||
let decoded_key: Vec<u8> = cd_key
|
let p_decoded_key: Vec<u8> = cd_key
|
||||||
.chars()
|
.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();
|
.collect();
|
||||||
|
|
||||||
let mut y = BigNum::from_u32(0).unwrap();
|
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.mul_word((PK_LENGTH - 1) as u32).unwrap();
|
||||||
y.add_word(i.into()).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 z = BigNum::from_slice(byte_seq).unwrap();
|
||||||
let mut out: VecDeque<char> = VecDeque::new();
|
let mut out: VecDeque<char> = VecDeque::new();
|
||||||
(0..=24).for_each(|_| out.push_front(KEY_CHARSET[z.div_word(24).unwrap() as usize]));
|
(0..=24).for_each(|_| out.push_front(P_KEY_CHARSET[z.div_word(24).unwrap() as usize]));
|
||||||
Ok(out.iter().collect())
|
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"))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -58,9 +38,9 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_base24() {
|
fn test_base24() {
|
||||||
let input = "JTW3TJ7PFJ7V9CCMX84V9PFT8";
|
let input = "JTW3TJ7PFJ7V9CCMX84V9PFT8";
|
||||||
let unbase24 = super::base24_decode(input).unwrap();
|
let unbase24 = super::base24_decode(input);
|
||||||
println!("{:?}", unbase24);
|
println!("{:?}", unbase24);
|
||||||
let base24 = super::base24_encode(&unbase24).unwrap();
|
let base24 = super::base24_encode(&unbase24);
|
||||||
println!("{}", base24);
|
println!("{}", base24);
|
||||||
assert_eq!(input, 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