Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5bbd302e authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge "Add IRK Calculator tool"

parents faa507ef 3b2a4f8a
Loading
Loading
Loading
Loading
+13 −0
Original line number Diff line number Diff line
[package]
name = "irk-calculator"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
clap = { version = "3.1.3", features = ["derive"] }
aes = "*"
rand = "*"

[workspace]
+86 −0
Original line number Diff line number Diff line
IRK Calculator
==============
author: optedoblivion@google.com

This tool is used to verify an IRK + RPA pair and generate an RPA from an IRK.

```
irk-calculator 0.1.0

USAGE:
    irk-calculator --command <COMMAND> --irk <IRK> --address <ADDRESS>

OPTIONS:
    -a, --address <ADDRESS>
    -c, --command <COMMAND>
    -h, --help                 Print help information
    -i, --irk <IRK>
    -V, --version              Print version information
```

```
Legend:

IRK = Identity Resolving Key (see BT Spec for more)
RPA = Resolvable Private Address (see BT spec for more)
```

Example Data:

```
IRK: 0102030405060708090a0b0c0d0e0f10
RPA: 79:CB:92:70:BE:B3

IRK: 0102030405060708090a0b0c0d0e0f10
RPA: 58:9B:3E:A3:5B:24
```

Example Usage:

```
$ cargo build && RUST_BACKTRACE=1 ./target/debug/irk-calculator -c verify -a "79:CB:92:70:BE:B3" -i "0102030405060708090a0b0c0d0e0f10"
 Verifying 0102030405060708090a0b0c0d0e0f10 and 79:CB:92:70:BE:B3
 IRK Byte Array: [01, 02, 03, 04, 05, 06, 07, 08, 09, 0A, 0B, 0C, 0D, 0E, 0F, 10]
 Address Byte Array: [79, CB, 92, 70, BE, B3]
 prand: [79, CB, 92]
 Given Hash: [70, BE, B3]
 irk slice: [01, 02, 03, 04, 05, 06, 07, 08, 09, 0A, 0B, 0C, 0D, 0E, 0F, 10]
 =====[ ah ]=====
 K: [01, 02, 03, 04, 05, 06, 07, 08, 09, 0A, 0B, 0C, 0D, 0E, 0F, 10]
 R: [79, CB, 92]
 R': [00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 79, CB, 92]
 =====[ e ]=====
 key_reversed: [10, 0F, 0E, 0D, 0C, 0B, 0A, 09, 08, 07, 06, 05, 04, 03, 02, 01]
 block: [00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 79, CB, 92]
 e-block: [05, 5A, 53, 46, BB, 26, 28, 67, AC, 24, 73, 66, 41, 70, BE, B3]
 =====[ /e ]=====
 ED: [05, 5A, 53, 46, BB, 26, 28, 67, AC, 24, 73, 66, 41, 70, BE, B3]
 ret: [70, BE, B3]
 =====[ /ah ]=====
 given hash: [70, BE, B3]
 calcd hash: [70, BE, B3]
 IRK + Address combination is valid: true

$ cargo build && RUST_BACKTRACE=1 ./target/debug/irk-calculator -c verify -a "58:9B:3E:A3:5B:24" -i "0102030405060708090a0b0c0d0e0f10"
 Verifying 0102030405060708090a0b0c0d0e0f10 and 58:9B:3E:A3:5B:24
 IRK Byte Array: [01, 02, 03, 04, 05, 06, 07, 08, 09, 0A, 0B, 0C, 0D, 0E, 0F, 10]
 Address Byte Array: [58, 9B, 3E, A3, 5B, 24]
 prand: [58, 9B, 3E]
 Given Hash: [A3, 5B, 24]
 irk slice: [01, 02, 03, 04, 05, 06, 07, 08, 09, 0A, 0B, 0C, 0D, 0E, 0F, 10]
 =====[ ah ]=====
 K: [01, 02, 03, 04, 05, 06, 07, 08, 09, 0A, 0B, 0C, 0D, 0E, 0F, 10]
 R: [58, 9B, 3E]
 R': [00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 58, 9B, 3E]
 =====[ e ]=====
 key_reversed: [10, 0F, 0E, 0D, 0C, 0B, 0A, 09, 08, 07, 06, 05, 04, 03, 02, 01]
 block: [00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 58, 9B, 3E]
 e-block: [A9, EC, 77, CE, BB, BC, 24, A7, 45, 1E, 5E, 23, F7, A3, 5B, 24]
 =====[ /e ]=====
 ED: [A9, EC, 77, CE, BB, BC, 24, A7, 45, 1E, 5E, 23, F7, A3, 5B, 24]
 ret: [A3, 5B, 24]
 =====[ /ah ]=====
 given hash: [A3, 5B, 24]
 calcd hash: [A3, 5B, 24]
 IRK + Address combination is valid: true
```
+267 −0
Original line number Diff line number Diff line
use aes::cipher::{generic_array::GenericArray, BlockEncrypt, KeyInit};
use aes::Aes128;
use clap::Parser;
use rand::Rng;

#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
struct Args {
    /// The command task to perform [verify|generate]
    /// e.g.
    ///     irk-calculator -c verify -i "..." -a "..."
    #[clap(short, long)]
    command: String,
    /// The Identity Resolving Key
    #[clap(short, long)]
    irk: String,
    /// The addres to verify if verifying
    #[clap(short, long, required(false), default_value = "00:00:00:00:00:00")]
    address: String,
}

fn e(key: [u8; 16], plaintext_data: [u8; 16]) -> [u8; 16] {
    println!("=====[ e ]=====");
    let mut key_reversed = key.clone();
    key_reversed.reverse();
    println!("Reversed Key: {:02X?}", key_reversed);
    let cipher = Aes128::new(&GenericArray::from(key_reversed));
    let mut block = GenericArray::from(plaintext_data);
    println!("Data: {:02X?}", block);
    cipher.encrypt_block(&mut block);
    println!("Encrypted data: {:02X?}", block);
    let mut ret = [0u8; 16];
    ret.clone_from_slice(block.iter().as_slice());
    println!("=====[ /e ]=====");
    ret
}

/// ah function as defined in BT Spec Core v5.2 pg 1626
///
/// k is 128 bits
/// r is 24 bits
/// padding is 104 bits
///
/// returns 3 byte array
fn _ah(k: [u8; 16], r: [u8; 3], padding: [u8; 13]) -> [u8; 3] {
    println!("=====[ ah ]=====");
    let mut padded_r: [u8; 16] = [0u8; 16];
    // Pad the r to become r'
    padded_r[..13].clone_from_slice(&padding);
    padded_r[13..].clone_from_slice(&r);
    println!("K: {:02X?}", k);
    println!("R: {:02X?}", r);
    println!("R': {:02X?}", padded_r);

    // Create data
    let encrypted_data = e(k, padded_r);
    // Mod 2^24 (Only take last 3 bytes)
    let mut ret = [0u8; 3];
    let mut i = 0;
    for b in &encrypted_data[13..] {
        ret[i] = *b;
        i += 1;
    }
    println!("Mod 2^24 data: {:02X?}", ret);
    println!("=====[ /ah ]=====");
    ret
}

fn ah(k: [u8; 16], r: [u8; 3]) -> [u8; 3] {
    let padding = [0u8; 13];
    _ah(k, r, padding)
}

fn to_hex_string(bytes: Vec<u8>) -> String {
    let s: Vec<String> = bytes.iter().map(|b| format!("{:02X}", b)).collect();

    s.join(":")
}

fn parse_hex(hexstr: &str) -> Vec<u8> {
    let mut hex_bytes = hexstr
        .as_bytes()
        .iter()
        .filter_map(|b| match b {
            b'0'..=b'9' => Some(b - b'0'),
            b'a'..=b'f' => Some(b - b'a' + 10),
            b'A'..=b'F' => Some(b - b'A' + 10),
            _ => None,
        })
        .fuse();

    let mut bytes = Vec::new();
    while let (Some(h), Some(l)) = (hex_bytes.next(), hex_bytes.next()) {
        bytes.push(h << 4 | l)
    }
    bytes
}

fn parse_irk(irk: String) -> Vec<u8> {
    let irk_byte_array = parse_hex(irk.as_str());
    assert_eq!(16, irk_byte_array.len(), "IRK '{:02X?}' must be 16 octets!", irk_byte_array);
    irk_byte_array
}

fn parse_address(address: String) -> Vec<u8> {
    let address_byte_array = parse_hex(address.as_str());
    assert_eq!(
        6,
        address_byte_array.len(),
        "Address '{:02X?}' must be 6 octets!",
        address_byte_array
    );
    address_byte_array
}

// TODO(optedoblivion): Verify address is RPA
fn verify_irk_address(irk: String, address: String) -> bool {
    println!("Verifying '{}' matches '{}'", irk, address);

    // IRK
    let irk_byte_array = parse_irk(irk);
    println!("IRK Byte Array: {:02X?}", irk_byte_array);

    // Address
    let address_byte_array = parse_address(address);
    println!("Address Byte Array: {:02X?}", address_byte_array);

    // prand
    let mut prand = [0u8; 3];
    prand.clone_from_slice(&address_byte_array[..=2]);
    println!("prand: {:02X?}", prand);

    // Hash
    let given_hash = &address_byte_array[3..];
    println!("Given hash: {:02X?}", given_hash);

    let mut irk_slice = [0u8; 16];
    irk_slice.clone_from_slice(&irk_byte_array[..]);
    let hash = ah(irk_slice, prand);
    println!("Given hash: {:02X?}", given_hash);
    println!("Calculated hash: {:02X?}", hash);
    println!("IRK + Address combination is valid: {}", given_hash == hash);
    given_hash == hash
}

fn generate_irk_address(irk: String) -> String {
    println!("Generating new address with '{}'", irk);

    // IRK
    let irk_byte_array = parse_irk(irk);
    println!("IRK Byte Array: {:02X?}", irk_byte_array);

    // prand
    let prand = rand::thread_rng().gen::<[u8; 3]>();
    println!("prand: {:02X?}", prand);

    let mut irk_slice = [0u8; 16];
    irk_slice.clone_from_slice(&irk_byte_array[..]);
    let hash = ah(irk_slice, prand);
    println!("Calculated hash: {:02X?}", hash);

    let mut calculated_address = [0u8; 6];
    println!("len: {}", prand.len());
    calculated_address[0] = prand[0];
    calculated_address[1] = prand[1];
    calculated_address[2] = prand[2];
    calculated_address[3] = hash[0];
    calculated_address[4] = hash[1];
    calculated_address[5] = hash[2];
    println!("Calculated Address: {}", to_hex_string(calculated_address.to_vec()));
    to_hex_string(calculated_address.to_vec())
}

fn main() {
    let args = Args::parse();

    match args.command.as_str() {
        "verify" => {
            verify_irk_address(args.irk, args.address);
        }
        "generate" => {
            generate_irk_address(args.irk);
        }
        _ => {
            println!("Invalid command!");
        }
    }
}

#[test]
fn test_verify_good_combos() {
    assert_eq!(
        true,
        verify_irk_address(
            String::from("0102030405060708090a0b0c0d0e0f10"),
            String::from("5B:89:68:1E:4E:19"),
        )
    );
    assert_eq!(
        true,
        verify_irk_address(
            String::from("0102030405060708090a0b0c0d0e0f10"),
            String::from("79:CB:92:70:BE:B3"),
        )
    );
    assert_eq!(
        true,
        verify_irk_address(
            String::from("0102030405060708090a0b0c0d0e0f10"),
            String::from("5D:EC:DA:8C:33:AE"),
        )
    );
}

#[test]
fn test_verify_bad_combos() {
    assert_eq!(
        false,
        verify_irk_address(
            String::from("0102030405060708090a0b0c0d0e0f10"),
            String::from("60:89:68:1E:4E:19"),
        )
    );
}

fn _validate_address_byte(i: usize, address: &String) {
    println!("address: {:?}", address);
    let vs: Vec<String> = vec![
        address.chars().nth(i).unwrap().to_string(),
        address.chars().nth(i + 1).unwrap().to_string(),
    ];
    let byte_string = vs.join("");
    match parse_hex(&byte_string.as_str()) {
        a => {
            println!("herp: {:?}", a);
            assert_eq!(1, a.len());
        }
    }
}

#[test]
fn test_validate_good_address_byte() {
    let good_address = String::from("1A:34:AF:78:98:76");
    println!("{:?}", good_address.chars().nth(0).unwrap().to_string().as_str());
    _validate_address_byte(0, &good_address);
}

#[test]
#[should_panic]
fn test_validate_bad_address_byte() {
    let bad_address = String::from("ZX:12:34:56:78:90");
    _validate_address_byte(0, &bad_address);
}

#[test]
fn test_generate_rpa() {
    let address = generate_irk_address(String::from("0102030405060708090a0b0c0d0e0f10"));
    _validate_address_byte(0, &address);
    assert_eq!(address.chars().nth(2).unwrap(), ':');
    _validate_address_byte(3, &address);
    assert_eq!(address.chars().nth(5).unwrap(), ':');
    _validate_address_byte(6, &address);
    assert_eq!(address.chars().nth(8).unwrap(), ':');
    _validate_address_byte(9, &address);
    assert_eq!(address.chars().nth(11).unwrap(), ':');
    _validate_address_byte(12, &address);
}