mod db;
mod host;
pub use db::{open_kobo_db, KoboDbError};
use crate::{KeyError, Result};
use flamberge_crypto::digest;
pub const KOBO_HASH_KEYS: [&str; 4] = ["88b3a2e13", "XzUhGYdFp", "NoCanLook", "QJhwzAtXL"];
pub fn derive_userkeys(macaddrs_and_serials: &[String], userids: &[String]) -> Vec<[u8; 16]> {
let mut keys = Vec::new();
for hash in KOBO_HASH_KEYS {
for mac in macaddrs_and_serials {
let deviceid = digest::sha256_hex(format!("{hash}{mac}").as_bytes());
for userid in userids {
let full = digest::sha256_hex(format!("{deviceid}{userid}").as_bytes());
let half = &full[32..];
if let Ok(bytes) = hex::decode(half) {
if bytes.len() == 16 {
let mut k = [0u8; 16];
k.copy_from_slice(&bytes);
keys.push(k);
}
}
}
}
}
keys
}
#[derive(Debug, Clone)]
pub struct Discovery {
pub user_keys: Vec<[u8; 16]>,
pub db: Vec<u8>,
}
pub fn discover() -> Result<Discovery> {
let host::LocatedDb {
db_bytes,
device_root,
} = host::find_kobo_db()?;
let userids = db::read_userids(db_bytes.clone())?;
if userids.is_empty() {
return Err(KeyError::NotFound(
"the Kobo database has no UserID rows".into(),
));
}
let mut macaddrs = host::enumerate_macaddrs();
if let Some(root) = &device_root {
if let Some(serial) = host::device_serial(root) {
macaddrs.push(serial);
}
}
if macaddrs.is_empty() {
return Err(KeyError::NotFound(
"no MAC addresses or device serials found on this host".into(),
));
}
Ok(Discovery {
user_keys: derive_userkeys(&macaddrs, &userids),
db: db_bytes,
})
}
pub fn discover_userkeys() -> Result<Vec<[u8; 16]>> {
Ok(discover()?.user_keys)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn derives_expected_count_and_size() {
let macs = vec!["00:11:22:33:44:55".to_string(), "N1234567890".to_string()];
let users = vec!["11111111-2222-3333-4444-555555555555".to_string()];
let keys = derive_userkeys(&macs, &users);
assert_eq!(keys.len(), 8);
assert_eq!(keys[0].len(), 16);
}
#[test]
fn derives_from_fixture_userids() {
let file = tempfile::NamedTempFile::new().unwrap();
{
let conn = rusqlite::Connection::open(file.path()).unwrap();
conn.execute("CREATE TABLE user (UserID TEXT)", []).unwrap();
conn.execute(
"INSERT INTO user (UserID) VALUES ('11111111-2222-3333-4444-555555555555')",
[],
)
.unwrap();
}
let db_bytes = std::fs::read(file.path()).unwrap();
let userids = db::read_userids(db_bytes).unwrap();
assert!(!userids.is_empty());
let macs = vec!["A4:83:E7:1B:2C:3D".to_string()];
let keys = derive_userkeys(&macs, &userids);
assert_eq!(keys.len(), KOBO_HASH_KEYS.len());
assert!(!keys.is_empty());
}
}