ble: encrypt wifi password
BLE "just works" pairing dos not protect against active MITM, so we need to handle this ourselves. Use nacl/libsodium crypto_box to encrypt and authenticate the wifi password this also adds a step to the interaction between client and device: after writing the password, we now write a "true" to the "connecting" characteristic to kick off the actual conection attempt.
This commit is contained in:
+70
-13
@@ -6,7 +6,13 @@ use log::{warn,info};
|
||||
use embassy_futures::join::join;
|
||||
use embassy_futures::select::select;
|
||||
|
||||
use crypto_box::{
|
||||
aead::Aead,
|
||||
SalsaBox, PublicKey, SecretKey, Nonce
|
||||
};
|
||||
|
||||
use alloc::vec::Vec;
|
||||
use alloc::string::String;
|
||||
|
||||
use crate::ota::Flasher;
|
||||
|
||||
@@ -26,6 +32,9 @@ struct Server {
|
||||
|
||||
#[gatt_service(uuid = "76be25c2-0000-44fc-a97c-bcb56c69c926")]
|
||||
struct WifiProvisioningService {
|
||||
#[characteristic(uuid = "76be25c2-0801-44fc-a97c-bcb56c69c926", read, notify, value = false)]
|
||||
connecting: bool,
|
||||
|
||||
#[characteristic(uuid = "76be25c2-0802-44fc-a97c-bcb56c69c926", read, notify, value = 0)]
|
||||
max_network_index: u8,
|
||||
|
||||
@@ -40,9 +49,9 @@ struct WifiProvisioningService {
|
||||
current_network: [u8; 40],
|
||||
|
||||
#[characteristic(uuid = "76be25c2-0805-44fc-a97c-bcb56c69c926",
|
||||
value = [0; 64],
|
||||
value = [0; (24 + 16 + 64 + 1)],
|
||||
write)]
|
||||
secret: [u8; 64]
|
||||
secret: [u8; 24 + 16 + 64 + 1]
|
||||
}
|
||||
|
||||
|
||||
@@ -103,6 +112,40 @@ async fn ble_task<C: Controller, P: PacketPool>(mut runner: Runner<'_, C, P>) {
|
||||
}
|
||||
}
|
||||
|
||||
const CLIENT_PUBLIC_KEY_BYTES: &[u8; 32] = include_bytes!("../../keys/client.pub");
|
||||
const SECRET_KEY_BYTES : &[u8; 32] = include_bytes!("../../keys/device.key");
|
||||
|
||||
#[derive(Debug)]
|
||||
enum DecryptError {
|
||||
Crypto(crypto_box::aead::Error),
|
||||
Encoding(alloc::str::Utf8Error)
|
||||
}
|
||||
impl From<crypto_box::aead::Error> for DecryptError {
|
||||
fn from(e: crypto_box::aead::Error) -> Self {
|
||||
Self::Crypto(e)
|
||||
}
|
||||
}
|
||||
impl From<alloc::str::Utf8Error> for DecryptError {
|
||||
fn from(e: alloc::str::Utf8Error) -> Self { Self::Encoding(e) }
|
||||
}
|
||||
|
||||
fn decrypt_secret(ciphertext : &[u8]) -> Result<String,DecryptError> {
|
||||
let client_public_key = PublicKey::from(*CLIENT_PUBLIC_KEY_BYTES);
|
||||
let secret_key= SecretKey::from(*SECRET_KEY_BYTES);
|
||||
|
||||
let len : usize = ciphertext[0].into();
|
||||
let ciphertext = &ciphertext[1..len+1];
|
||||
|
||||
let (nonce, encrypted) = ciphertext.split_at(24);
|
||||
let nonce = Nonce::from_slice(nonce);
|
||||
|
||||
let sbox = SalsaBox::new(&client_public_key, &secret_key);
|
||||
let plain_bytes = sbox.decrypt(nonce, encrypted)?;
|
||||
|
||||
Ok(String::from(core::str::from_utf8(&plain_bytes[..])?))
|
||||
}
|
||||
|
||||
|
||||
/// Stream Events until the connection closes.
|
||||
///
|
||||
/// This function will handle the GATT events and process them.
|
||||
@@ -135,17 +178,20 @@ async fn gatt_events_task<P: PacketPool>(server: &Server<'_>,
|
||||
} else {
|
||||
warn!("index {:?} out of range", i);
|
||||
}
|
||||
} else if event.handle() == wps.secret.handle {
|
||||
info!("[gatt] got secret {:?}", event.data());
|
||||
let n = server.get(&wps.current_network).unwrap();
|
||||
let n_s = core::str::from_utf8(&n[8..]).unwrap();
|
||||
let n0 =
|
||||
if let Some(i) = n_s.find("\0") { i } else { n_s.len() };
|
||||
flasher.set_var("ssid", Some(&n_s[..n0]));
|
||||
flasher.set_var("secret", Some(core::str::from_utf8(event.data()).unwrap()));
|
||||
info!("[gatt] deep breath then reboot");
|
||||
Timer::after(Duration::from_millis(2000)).await;
|
||||
esp_hal::system::software_reset();
|
||||
} else if event.handle() == wps.connecting.handle {
|
||||
let network = server.get(&wps.current_network).unwrap();
|
||||
let password = decrypt_secret(&server.get(&wps.secret).unwrap());
|
||||
|
||||
match password {
|
||||
Ok(plaintext) => {
|
||||
write_wifi_config(
|
||||
flasher, &network[8..], &plaintext[..]
|
||||
).await;
|
||||
},
|
||||
Err(e) => {
|
||||
warn!("decdrpt error: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
@@ -165,6 +211,17 @@ async fn gatt_events_task<P: PacketPool>(server: &Server<'_>,
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn write_wifi_config(flasher: &mut Flasher<'_>, ssid: &[u8], password: &str) -> Result<(),esp_nvs::error::Error> {
|
||||
let n_s = core::str::from_utf8(&ssid).unwrap();
|
||||
let n0 =
|
||||
if let Some(i) = n_s.find("\0") { i } else { n_s.len() };
|
||||
flasher.set_var("ssid", Some(&n_s[..n0]))?;
|
||||
flasher.set_var("secret", Some(password))?;
|
||||
info!("[gatt] deep breath then reboot");
|
||||
Timer::after(Duration::from_millis(2000)).await;
|
||||
esp_hal::system::software_reset();
|
||||
}
|
||||
|
||||
/// Create an advertiser to use to connect to a BLE Central, and wait for it to connect.
|
||||
async fn advertise<'values, 'server, C: Controller>(
|
||||
name: &'values str,
|
||||
|
||||
Reference in New Issue
Block a user