diff --git a/Cargo.lock b/Cargo.lock index 253065e..9d3db39 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -311,6 +311,20 @@ 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" @@ -368,6 +382,7 @@ dependencies = [ "bitreader", "clap", "openssl", + "serde", "serde_json", "thiserror", ] diff --git a/Cargo.toml b/Cargo.toml index 84db26a..aeeec04 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,5 +14,6 @@ anyhow = "1.0.71" bitreader = "0.3.7" clap = { version = "4.3.4", features = ["derive"] } openssl = { git = "https://github.com/anpage/rust-openssl.git" } +serde = { version = "1.0.164", features = ["derive"] } serde_json = "1.0" thiserror = "1.0.40" diff --git a/src/bin/xpkey.rs b/src/bin/xpkey.rs deleted file mode 100644 index cde93dc..0000000 --- a/src/bin/xpkey.rs +++ /dev/null @@ -1,253 +0,0 @@ -use std::{fs::File, io::BufReader, path::Path}; - -use anyhow::{anyhow, Result}; -use clap::Parser; -use serde_json::{from_reader, from_str}; - -use umskt::{ - bink1998, bink2002, confid, - crypto::{EllipticCurve, PrivateKey}, -}; - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Mode { - Bink1998Generate, - Bink2002Generate, - Bink1998Validate, - Bink2002Validate, - ConfirmationId, -} - -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: u64, - - /// Specify which keys file to load - #[arg(short = 'f', long = "file")] - keys_filename: Option, - - /// Installation ID used to generate confirmation ID - #[arg(short, long)] - instid: Option, - - /// Specify which BINK identifier to load - #[arg(short, long, default_value = "2E")] - binkid: String, - - /// Show which products/binks can be loaded - #[arg(short, long)] - list: bool, - - /// Specify which Channel Identifier to use - #[arg(short = 'c', long = "channel", default_value = "640")] - channel_id: u32, - - /// Product key to validate signature - #[arg(short = 'V', long = "validate")] - key_to_check: Option, - - #[clap(skip)] - application_mode: Mode, -} - -fn main() -> Result<()> { - let mut options = parse_command_line(); - let keys = 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 = bink["priv"].as_str().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 = bink["n"].as_str().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(); - - 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: {gen_order}"); - println!(" k: {private_key}"); - println!(); - } - - let curve = EllipticCurve::new(p, a, b, gx, gy, kx, ky)?; - - match options.application_mode { - Mode::Bink1998Generate => { - let private_key = PrivateKey::new(gen_order, private_key)?; - bink1998_generate(&options, &curve, &private_key)?; - } - Mode::Bink2002Generate => { - let private_key = PrivateKey::new(gen_order, private_key)?; - bink2002_generate(&options, &curve, &private_key)?; - } - Mode::Bink1998Validate => bink1998_validate(&options, &curve)?, - Mode::Bink2002Validate => bink2002_validate(&options, &curve)?, - Mode::ConfirmationId => confirmation_id(&options)?, - } - - Ok(()) -} - -fn parse_command_line() -> Options { - let mut args = Options::parse(); - if args.instid.is_some() { - args.application_mode = Mode::ConfirmationId; - } - args -} - -fn validate_command_line(options: &mut Options) -> Result { - let keys = { - if let Some(filename) = &options.keys_filename { - if options.verbose { - println!("Loading keys file {}", filename); - } - - let keys = load_json(filename)?; - - if options.verbose { - println!("Loaded keys from {} successfully", filename); - } - - keys - } else { - from_str(std::include_str!("../../keys.json"))? - } - }; - - if options.list { - let products = keys["Products"] - .as_object() - .ok_or(anyhow!("`Products` object not found in keys",))?; - for (key, value) in products.iter() { - println!("{}: {}", key, value["BINK"]); - } - - println!("\n\n** Please note: any BINK ID other than 2E is considered experimental at this time **\n"); - } - - let bink_id = u32::from_str_radix(&options.binkid, 16)?; - - if options.key_to_check.is_some() { - options.application_mode = Mode::Bink1998Validate; - } - - if bink_id >= 0x40 { - if options.key_to_check.is_some() { - options.application_mode = Mode::Bink2002Validate; - } else { - options.application_mode = Mode::Bink2002Generate; - } - } - - if options.channel_id > 999 { - return Err(anyhow!( - "Refusing to create a key with a Channel ID greater than 999" - )); - } - - Ok(keys) -} - -fn load_json>(path: P) -> Result { - let file = File::open(path)?; - let reader = BufReader::new(file); - let json = from_reader(reader)?; - Ok(json) -} - -fn bink1998_generate( - options: &Options, - curve: &EllipticCurve, - private_key: &PrivateKey, -) -> Result<()> { - for _ in 0..options.num_keys { - let product_key = - bink1998::ProductKey::new(curve, private_key, options.channel_id, None, None)?; - if options.verbose { - println!("{:?}", product_key); - } - println!("{product_key}"); - } - Ok(()) -} - -fn bink2002_generate( - options: &Options, - curve: &EllipticCurve, - private_key: &PrivateKey, -) -> Result<()> { - for _ in 0..options.num_keys { - let product_key = - bink2002::ProductKey::new(curve, private_key, options.channel_id, None, None)?; - if options.verbose { - println!("{:?}", product_key); - } - println!("{product_key}"); - } - Ok(()) -} - -fn bink1998_validate(options: &Options, curve: &EllipticCurve) -> Result<()> { - let product_key = - bink1998::ProductKey::from_key(curve, options.key_to_check.as_ref().unwrap())?; - if options.verbose { - println!("{:?}", product_key); - } - println!("{product_key}"); - println!("Key validated successfully!"); - Ok(()) -} - -fn bink2002_validate(options: &Options, curve: &EllipticCurve) -> Result<()> { - let product_key = - bink2002::ProductKey::from_key(curve, options.key_to_check.as_ref().unwrap())?; - if options.verbose { - println!("{:?}", product_key); - } - println!("{product_key}"); - println!("Key validated successfully!"); - Ok(()) -} - -fn confirmation_id(options: &Options) -> Result<()> { - if let Some(instid) = &options.instid { - let confirmation_id = confid::generate(instid)?; - println!("Confirmation ID: {confirmation_id}"); - }; - Ok(()) -} diff --git a/src/bin/xpkey/keys.rs b/src/bin/xpkey/keys.rs new file mode 100644 index 0000000..583b0cc --- /dev/null +++ b/src/bin/xpkey/keys.rs @@ -0,0 +1,36 @@ +use std::collections::HashMap; + +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize)] +pub struct Keys { + #[serde(rename = "Products")] + pub products: HashMap, + #[serde(rename = "BINK")] + pub bink: HashMap, +} + +#[derive(Serialize, Deserialize)] +pub struct Product { + #[serde(rename = "BINK")] + pub bink: Vec, +} + +#[derive(Serialize, Deserialize)] +pub struct Bink { + pub p: String, + pub a: String, + pub b: String, + pub g: Point, + #[serde(rename = "pub")] + pub public: Point, + pub n: String, + #[serde(rename = "priv")] + pub private: String, +} + +#[derive(Serialize, Deserialize)] +pub struct Point { + pub x: String, + pub y: String, +} diff --git a/src/bin/xpkey/main.rs b/src/bin/xpkey/main.rs new file mode 100644 index 0000000..3a28017 --- /dev/null +++ b/src/bin/xpkey/main.rs @@ -0,0 +1,289 @@ +mod keys; + +use std::{fs::File, io::BufReader, path::Path}; + +use anyhow::{anyhow, Result}; +use clap::{Args, Parser, Subcommand}; +use keys::{Bink, Keys}; +use serde_json::{from_reader, from_str}; + +use umskt::{ + bink1998, bink2002, confid, + crypto::{EllipticCurve, PrivateKey}, +}; + +#[derive(Parser, Debug)] +#[command(author, about, version, long_about = None)] +struct Cli { + /// Enable verbose output + #[arg(short, long)] + verbose: bool, + + #[command(subcommand)] + command: Commands, +} + +#[derive(Subcommand, Clone, Debug)] +enum Commands { + /// Show which products/binks can be loaded + List(ListArgs), + /// Generate new product keys + Generate(GenerateArgs), + /// Validate a product key + Validate(ValidateArgs), + /// Generate a phone activation Confirmation ID from an Installation ID + Confid(ConfirmationIdArgs), +} + +#[derive(Args, Clone, Debug)] +struct ListArgs { + /// Specify which keys file to load + #[arg(short = 'f', long = "file")] + keys_path: Option, +} + +#[derive(Args, Clone, Debug)] +struct GenerateArgs { + /// Specify which BINK identifier to load + #[arg(short, long, default_value = "2E")] + binkid: String, + + /// Specify which Channel Identifier to use + #[arg(short = 'c', long = "channel", default_value = "640")] + channel_id: u32, + + /// Number of keys to generate + #[arg(short = 'n', long = "number", default_value = "1")] + num_keys: u64, + + /// Specify which keys file to load + #[arg(short = 'f', long = "file")] + keys_path: Option, +} + +#[derive(Args, Clone, Debug)] +struct ValidateArgs { + /// Specify which BINK identifier to load + #[arg(short, long, default_value = "2E")] + binkid: String, + + /// Specify which keys file to load + #[arg(short = 'f', long = "file")] + keys_path: Option, + + /// Product key to validate signature + key_to_check: String, +} + +#[derive(Args, Clone, Debug)] +struct ConfirmationIdArgs { + /// Installation ID used to generate confirmation ID + instid: String, +} + +fn main() -> Result<()> { + let args = Cli::parse(); + + match &args.command { + Commands::List(list_args) => { + let keys = load_keys(list_args.keys_path.as_ref(), args.verbose)?; + for (key, value) in keys.products.iter() { + println!("{}: {:?}", key, value.bink); + } + + println!("\n\n** Please note: any BINK ID other than 2E is considered experimental at this time **\n"); + } + Commands::Generate(generate_args) => { + if generate_args.channel_id > 999 { + return Err(anyhow!("Channel ID must be 3 digits or fewer")); + } + let keys = load_keys(generate_args.keys_path.as_ref(), args.verbose)?; + generate( + &keys, + &generate_args.binkid, + generate_args.channel_id, + generate_args.num_keys, + args.verbose, + )?; + } + Commands::Validate(validate_args) => { + let keys = load_keys(validate_args.keys_path.as_ref(), args.verbose)?; + validate( + &keys, + &validate_args.binkid, + &validate_args.key_to_check, + args.verbose, + )?; + } + Commands::Confid(confirmation_id_args) => { + confirmation_id(&confirmation_id_args.instid)?; + } + } + + Ok(()) +} + +fn load_keys + std::fmt::Display>(path: Option

, verbose: bool) -> Result { + let keys = { + if let Some(path) = path { + if verbose { + println!("Loading keys file {}", path); + } + + let file = File::open(&path)?; + let reader = BufReader::new(file); + let keys: Keys = from_reader(reader)?; + + if verbose { + println!("Loaded keys from {} successfully", path); + } + + keys + } else { + from_str(std::include_str!("../../../keys.json"))? + } + }; + + // let bink_id = u32::from_str_radix(&options.binkid, 16)?; + + // if bink_id >= 0x40 { + // if options.key_to_check.is_some() { + // options.application_mode = Commands::Bink2002Validate; + // } else { + // options.application_mode = Commands::Bink2002Generate; + // } + // } + + Ok(keys) +} + +fn generate(keys: &Keys, bink_id: &str, channel_id: u32, count: u64, verbose: bool) -> Result<()> { + let bink_id = bink_id.to_ascii_uppercase(); + let bink = &keys.bink[&bink_id]; + + // We cannot produce a valid key without knowing the private key k. The reason for this is that + // we need the result of the function K(x; y) = kG(x; y). + let private_key = &bink.private; + + // We can, however, validate any given key using the available public key: {p, a, b, G, K}. + // genOrder the order of the generator G, a value we have to reverse -> Schoof's Algorithm. + let gen_order = &bink.n; + + let curve = initialize_curve(bink, &bink_id, verbose)?; + if verbose { + println!(" n: {gen_order}"); + println!(" k: {private_key}"); + println!(); + } + let private_key = PrivateKey::new(gen_order, private_key)?; + + if u32::from_str_radix(&bink_id, 16)? < 0x40 { + bink1998_generate(&curve, &private_key, channel_id, count, verbose)?; + } else { + bink2002_generate(&curve, &private_key, channel_id, count, verbose)?; + } + + Ok(()) +} + +fn validate(keys: &Keys, bink_id: &str, key: &str, verbose: bool) -> Result<()> { + let bink_id = bink_id.to_ascii_uppercase(); + let bink = &keys.bink[&bink_id]; + let curve = initialize_curve(bink, &bink_id, verbose)?; + + if u32::from_str_radix(&bink_id, 16)? < 0x40 { + bink1998_validate(&curve, key, verbose)?; + } else { + bink2002_validate(&curve, key, verbose)?; + } + + Ok(()) +} + +fn initialize_curve(bink: &Bink, bink_id: &str, verbose: bool) -> Result { + let p = &bink.p; + let a = &bink.a; + let b = &bink.b; + let gx = &bink.g.x; + let gy = &bink.g.y; + let kx = &bink.public.x; + let ky = &bink.public.y; + + if verbose { + println!("-----------------------------------------------------------"); + println!( + "Loaded the following elliptic curve parameters: BINK[{}]", + bink_id + ); + println!("-----------------------------------------------------------"); + println!(" P: {p}"); + println!(" a: {a}"); + println!(" b: {b}"); + println!("Gx: {gx}"); + println!("Gy: {gy}"); + println!("Kx: {kx}"); + println!("Ky: {ky}"); + } + + EllipticCurve::new(p, a, b, gx, gy, kx, ky) +} + +fn bink1998_generate( + curve: &EllipticCurve, + private_key: &PrivateKey, + channel_id: u32, + count: u64, + verbose: bool, +) -> Result<()> { + for _ in 0..count { + let product_key = bink1998::ProductKey::new(curve, private_key, channel_id, None, None)?; + if verbose { + println!("{:?}", product_key); + } + println!("{product_key}"); + } + Ok(()) +} + +fn bink2002_generate( + curve: &EllipticCurve, + private_key: &PrivateKey, + channel_id: u32, + count: u64, + verbose: bool, +) -> Result<()> { + for _ in 0..count { + let product_key = bink2002::ProductKey::new(curve, private_key, channel_id, None, None)?; + if verbose { + println!("{:?}", product_key); + } + println!("{product_key}"); + } + Ok(()) +} + +fn bink1998_validate(curve: &EllipticCurve, key: &str, verbose: bool) -> Result<()> { + let product_key = bink1998::ProductKey::from_key(curve, key)?; + if verbose { + println!("{:?}", product_key); + } + println!("{product_key}"); + println!("Key validated successfully!"); + Ok(()) +} + +fn bink2002_validate(curve: &EllipticCurve, key: &str, verbose: bool) -> Result<()> { + let product_key = bink2002::ProductKey::from_key(curve, key)?; + if verbose { + println!("{:?}", product_key); + } + println!("{product_key}"); + println!("Key validated successfully!"); + Ok(()) +} + +fn confirmation_id(installation_id: &str) -> Result<()> { + let confirmation_id = confid::generate(installation_id)?; + println!("Confirmation ID: {confirmation_id}"); + Ok(()) +}