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

Commit 3b2a4f8a authored by Martin Brabham's avatar Martin Brabham
Browse files

Add IRK Calculator tool

Bug: 229104990
Test: cd system/tools/irk-calculator && cargo test
Tag: #stability
Change-Id: Ieec48a9bc9c41edda24eb32cb5784428dea1265b
parent e58fab27
Loading
Loading
Loading
Loading
+13 −0
Original line number Original line 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 Original line 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 Original line 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);
}