use crate::prelude::SecurityLevel;
use crate::security_manager::crypto::{Check, Confirm, DHKey, MacKey, Nonce, PublicKey};
use crate::security_manager::types::{Command, PairingFeatures, UseOutOfBand};
use crate::security_manager::TxPacket;
use crate::{Address, Error, IoCapabilities, LongTermKey, PacketPool};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum PassKeyEntryAction {
Display,
Input,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum PairingMethod {
JustWorks,
NumericComparison,
PassKeyEntry {
central: PassKeyEntryAction,
peripheral: PassKeyEntryAction,
},
OutOfBand,
}
impl PairingMethod {
const CENTRAL_DISPLAYS: Self = Self::PassKeyEntry {
central: PassKeyEntryAction::Display,
peripheral: PassKeyEntryAction::Input,
};
const PERIPHERAL_DISPLAYS: Self = Self::PassKeyEntry {
central: PassKeyEntryAction::Input,
peripheral: PassKeyEntryAction::Display,
};
const BOTH_INPUT: Self = Self::PassKeyEntry {
central: PassKeyEntryAction::Input,
peripheral: PassKeyEntryAction::Input,
};
pub fn security_level(&self) -> SecurityLevel {
match self {
PairingMethod::JustWorks => SecurityLevel::Encrypted,
_ => SecurityLevel::EncryptedAuthenticated,
}
}
}
pub fn choose_pairing_method(central: PairingFeatures, peripheral: PairingFeatures) -> PairingMethod {
if matches!(central.use_oob, UseOutOfBand::Present) || matches!(peripheral.use_oob, UseOutOfBand::Present) {
PairingMethod::OutOfBand
} else if !central.security_properties.man_in_the_middle() && !peripheral.security_properties.man_in_the_middle() {
PairingMethod::JustWorks
} else {
match (central.io_capabilities, peripheral.io_capabilities) {
(IoCapabilities::NoInputNoOutput, _) | (_, IoCapabilities::NoInputNoOutput) => PairingMethod::JustWorks,
(IoCapabilities::DisplayOnly, IoCapabilities::DisplayOnly) => PairingMethod::JustWorks,
(IoCapabilities::DisplayOnly, IoCapabilities::DisplayYesNo) => PairingMethod::JustWorks,
(IoCapabilities::DisplayOnly, IoCapabilities::KeyboardOnly) => PairingMethod::CENTRAL_DISPLAYS,
(IoCapabilities::DisplayOnly, IoCapabilities::KeyboardDisplay) => PairingMethod::CENTRAL_DISPLAYS,
(IoCapabilities::DisplayYesNo, IoCapabilities::DisplayOnly) => PairingMethod::JustWorks,
(IoCapabilities::DisplayYesNo, IoCapabilities::DisplayYesNo) => PairingMethod::NumericComparison,
(IoCapabilities::DisplayYesNo, IoCapabilities::KeyboardOnly) => PairingMethod::CENTRAL_DISPLAYS,
(IoCapabilities::DisplayYesNo, IoCapabilities::KeyboardDisplay) => PairingMethod::NumericComparison,
(IoCapabilities::KeyboardOnly, IoCapabilities::DisplayOnly) => PairingMethod::PERIPHERAL_DISPLAYS,
(IoCapabilities::KeyboardOnly, IoCapabilities::DisplayYesNo) => PairingMethod::PERIPHERAL_DISPLAYS,
(IoCapabilities::KeyboardOnly, IoCapabilities::KeyboardOnly) => PairingMethod::BOTH_INPUT,
(IoCapabilities::KeyboardOnly, IoCapabilities::KeyboardDisplay) => PairingMethod::PERIPHERAL_DISPLAYS,
(IoCapabilities::KeyboardDisplay, IoCapabilities::DisplayOnly) => PairingMethod::PERIPHERAL_DISPLAYS,
(IoCapabilities::KeyboardDisplay, IoCapabilities::DisplayYesNo) => PairingMethod::NumericComparison,
(IoCapabilities::KeyboardDisplay, IoCapabilities::KeyboardOnly) => PairingMethod::CENTRAL_DISPLAYS,
(IoCapabilities::KeyboardDisplay, IoCapabilities::KeyboardDisplay) => PairingMethod::NumericComparison,
}
}
}
#[cfg(feature = "legacy-pairing")]
pub fn choose_legacy_pairing_method(central: PairingFeatures, peripheral: PairingFeatures) -> PairingMethod {
if matches!(central.use_oob, UseOutOfBand::Present) && matches!(peripheral.use_oob, UseOutOfBand::Present) {
PairingMethod::OutOfBand
} else if !central.security_properties.man_in_the_middle() && !peripheral.security_properties.man_in_the_middle() {
PairingMethod::JustWorks
} else {
match (central.io_capabilities, peripheral.io_capabilities) {
(IoCapabilities::NoInputNoOutput, _) | (_, IoCapabilities::NoInputNoOutput) => PairingMethod::JustWorks,
(IoCapabilities::DisplayOnly, IoCapabilities::DisplayOnly) => PairingMethod::JustWorks,
(IoCapabilities::DisplayOnly, IoCapabilities::DisplayYesNo) => PairingMethod::JustWorks,
(IoCapabilities::DisplayOnly, IoCapabilities::KeyboardOnly) => PairingMethod::CENTRAL_DISPLAYS,
(IoCapabilities::DisplayOnly, IoCapabilities::KeyboardDisplay) => PairingMethod::CENTRAL_DISPLAYS,
(IoCapabilities::DisplayYesNo, IoCapabilities::DisplayOnly) => PairingMethod::JustWorks,
(IoCapabilities::DisplayYesNo, IoCapabilities::DisplayYesNo) => PairingMethod::JustWorks,
(IoCapabilities::DisplayYesNo, IoCapabilities::KeyboardOnly) => PairingMethod::CENTRAL_DISPLAYS,
(IoCapabilities::DisplayYesNo, IoCapabilities::KeyboardDisplay) => PairingMethod::CENTRAL_DISPLAYS,
(IoCapabilities::KeyboardOnly, IoCapabilities::DisplayOnly) => PairingMethod::PERIPHERAL_DISPLAYS,
(IoCapabilities::KeyboardOnly, IoCapabilities::DisplayYesNo) => PairingMethod::PERIPHERAL_DISPLAYS,
(IoCapabilities::KeyboardOnly, IoCapabilities::KeyboardOnly) => PairingMethod::BOTH_INPUT,
(IoCapabilities::KeyboardOnly, IoCapabilities::KeyboardDisplay) => PairingMethod::PERIPHERAL_DISPLAYS,
(IoCapabilities::KeyboardDisplay, IoCapabilities::DisplayOnly) => PairingMethod::PERIPHERAL_DISPLAYS,
(IoCapabilities::KeyboardDisplay, IoCapabilities::DisplayYesNo) => PairingMethod::PERIPHERAL_DISPLAYS,
(IoCapabilities::KeyboardDisplay, IoCapabilities::KeyboardOnly) => PairingMethod::CENTRAL_DISPLAYS,
(IoCapabilities::KeyboardDisplay, IoCapabilities::KeyboardDisplay) => PairingMethod::CENTRAL_DISPLAYS,
}
}
}
pub fn prepare_packet<P: PacketPool>(command: Command) -> Result<TxPacket<P>, Error> {
let packet = P::allocate().ok_or(Error::OutOfMemory)?;
TxPacket::new(packet, command)
}
pub fn make_pairing_random<P: PacketPool>(nonce: &Nonce) -> Result<TxPacket<P>, Error> {
let mut packet = prepare_packet::<P>(Command::PairingRandom)?;
let response = packet.payload_mut();
response.copy_from_slice(&nonce.0.to_le_bytes());
Ok(packet)
}
pub fn make_public_key_packet<P: PacketPool>(public_key: &PublicKey) -> Result<TxPacket<P>, Error> {
let mut x = [0u8; 32];
let mut y = [0u8; 32];
x.copy_from_slice(public_key.x.as_be_bytes());
y.copy_from_slice(public_key.y.as_be_bytes());
x.reverse();
y.reverse();
let mut packet = prepare_packet(Command::PairingPublicKey)?;
let response = packet.payload_mut();
response[..x.len()].copy_from_slice(&x);
response[x.len()..y.len() + x.len()].copy_from_slice(&y);
Ok(packet)
}
pub fn make_dhkey_check_packet<P: PacketPool>(check: &Check) -> Result<TxPacket<P>, Error> {
let mut packet = prepare_packet(Command::PairingDhKeyCheck)?;
let response = packet.payload_mut();
let bytes = check.0.to_le_bytes();
response[..bytes.len()].copy_from_slice(&bytes);
Ok(packet)
}
pub fn make_mac_and_ltk(
dh_key: &DHKey,
central_nonce: &Nonce,
peripheral_nonce: &Nonce,
central_address: Address,
peripheral_address: Address,
) -> (MacKey, LongTermKey) {
dh_key.f5(*central_nonce, *peripheral_nonce, central_address, peripheral_address)
}
pub fn make_confirm_packet<P: PacketPool>(confirm: &Confirm) -> Result<TxPacket<P>, Error> {
let mut packet = prepare_packet::<P>(Command::PairingConfirm)?;
let response = packet.payload_mut();
response.copy_from_slice(&confirm.0.to_le_bytes());
Ok(packet)
}
#[cfg(feature = "legacy-pairing")]
pub fn make_encryption_information_packet<P: PacketPool>(ltk: &LongTermKey) -> Result<TxPacket<P>, Error> {
let mut packet = prepare_packet::<P>(Command::EncryptionInformation)?;
let payload = packet.payload_mut();
payload.copy_from_slice(<k.to_le_bytes());
Ok(packet)
}
#[cfg(feature = "legacy-pairing")]
pub fn make_central_identification_packet<P: PacketPool>(ediv: u16, rand: &[u8; 8]) -> Result<TxPacket<P>, Error> {
let mut packet = prepare_packet::<P>(Command::CentralIdentification)?;
let payload = packet.payload_mut();
payload[0..2].copy_from_slice(&ediv.to_le_bytes());
payload[2..10].copy_from_slice(rand);
Ok(packet)
}
pub fn make_identity_information_packet<P: PacketPool>(irk: &[u8; 16]) -> Result<TxPacket<P>, Error> {
let mut packet = prepare_packet::<P>(Command::IdentityInformation)?;
let payload = packet.payload_mut();
payload.copy_from_slice(irk);
Ok(packet)
}
pub fn make_identity_address_information_packet<P: PacketPool>(addr: &Address) -> Result<TxPacket<P>, Error> {
let mut packet = prepare_packet::<P>(Command::IdentityAddressInformation)?;
let payload = packet.payload_mut();
let addr_type = if addr.kind == bt_hci::param::AddrKind::PUBLIC {
0u8
} else {
1u8
};
payload[0] = addr_type;
payload[1..7].copy_from_slice(addr.addr.raw());
Ok(packet)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::security_manager::types::{AuthReq, BondingFlag};
#[cfg(feature = "legacy-pairing")]
#[test]
fn legacy_no_numeric_comparison() {
let central = PairingFeatures {
io_capabilities: IoCapabilities::DisplayYesNo,
use_oob: UseOutOfBand::NotPresent,
security_properties: AuthReq::new_legacy(BondingFlag::NoBonding).with_mitm(),
initiator_key_distribution: 0.into(),
responder_key_distribution: 0.into(),
maximum_encryption_key_size: 16,
};
let peripheral = PairingFeatures {
io_capabilities: IoCapabilities::DisplayYesNo,
use_oob: UseOutOfBand::NotPresent,
security_properties: AuthReq::new_legacy(BondingFlag::NoBonding).with_mitm(),
initiator_key_distribution: 0.into(),
responder_key_distribution: 0.into(),
maximum_encryption_key_size: 16,
};
assert_eq!(
choose_legacy_pairing_method(central, peripheral),
PairingMethod::JustWorks
);
assert_eq!(
choose_pairing_method(central, peripheral),
PairingMethod::NumericComparison
);
}
#[cfg(feature = "legacy-pairing")]
#[test]
fn legacy_keyboard_display_combos() {
let make = |io: IoCapabilities| PairingFeatures {
io_capabilities: io,
use_oob: UseOutOfBand::NotPresent,
security_properties: AuthReq::new_legacy(BondingFlag::NoBonding).with_mitm(),
initiator_key_distribution: 0.into(),
responder_key_distribution: 0.into(),
maximum_encryption_key_size: 16,
};
assert_eq!(
choose_legacy_pairing_method(
make(IoCapabilities::KeyboardDisplay),
make(IoCapabilities::DisplayYesNo)
),
PairingMethod::PERIPHERAL_DISPLAYS
);
assert_eq!(
choose_legacy_pairing_method(
make(IoCapabilities::KeyboardDisplay),
make(IoCapabilities::KeyboardDisplay)
),
PairingMethod::CENTRAL_DISPLAYS
);
}
#[test]
fn oob_used() {
for p_oob in 0..1 {
for c_oob in 0..1 {
let p_oob = if p_oob == 1 {
UseOutOfBand::Present
} else {
UseOutOfBand::NotPresent
};
let c_oob = if c_oob == 1 {
UseOutOfBand::Present
} else {
UseOutOfBand::NotPresent
};
for p in 0u8..5 {
for c in 0u8..5 {
let peripheral = PairingFeatures {
io_capabilities: p.try_into().unwrap(),
use_oob: p_oob,
security_properties: AuthReq::new(BondingFlag::NoBonding),
initiator_key_distribution: 0.into(),
responder_key_distribution: 0.into(),
maximum_encryption_key_size: 16,
};
let mut central = peripheral.clone();
central.use_oob = c_oob;
central.io_capabilities = c.try_into().unwrap();
if p_oob == UseOutOfBand::NotPresent && c_oob == UseOutOfBand::NotPresent {
assert_ne!(choose_pairing_method(central, peripheral), PairingMethod::OutOfBand);
} else {
assert_eq!(choose_pairing_method(central, peripheral), PairingMethod::OutOfBand);
}
}
}
}
}
}
}