Skip to content

Commit 696abdc

Browse files
committed
battery: Try to implement authentication mechanism
Signed-off-by: Daniel Schaefer <[email protected]>
1 parent 09ec865 commit 696abdc

File tree

3 files changed

+195
-4
lines changed

3 files changed

+195
-4
lines changed

Cargo.lock

Lines changed: 91 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

framework_lib/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ built = { version = "0.5", features = ["chrono", "git2"] }
2525
[dependencies]
2626
lazy_static = "1.4.0"
2727
sha2 = { version = "0.10.8", default-features = false, features = [ "force-soft" ] }
28+
sha1 = "0.10"
29+
rand = "0.8"
2830
regex = { version = "1.11.1", default-features = false }
2931
num = { version = "0.4", default-features = false }
3032
num-derive = { version = "0.4", default-features = false }

framework_lib/src/battery.rs

Lines changed: 102 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,12 @@
33
// include/battery_smart.h
44
use alloc::vec::Vec;
55

6+
use sha1::{Sha1, Digest};
7+
use std::thread;
8+
use std::time::Duration;
9+
610
use crate::chromium_ec::i2c_passthrough::*;
7-
use crate::chromium_ec::{CrosEc, EcResult};
11+
use crate::chromium_ec::{CrosEc, EcResult, EcError};
812

913
#[repr(u16)]
1014
enum SmartBatReg {
@@ -21,6 +25,7 @@ enum SmartBatReg {
2125
CellVoltage2 = 0x3D,
2226
CellVoltage3 = 0x3E,
2327
CellVoltage4 = 0x3F,
28+
Authenticate = 0x2F,
2429
}
2530

2631
#[repr(u16)]
@@ -33,6 +38,8 @@ enum ManufReg {
3338
SafetyAlert = 0x50,
3439
SafetyStatus = 0x51,
3540
PFAlert = 0x52,
41+
PFStatus = 0x53,
42+
OperationStatus = 0x54,
3643
LifeTimeDataBlock1 = 0x60,
3744
LifeTimeDataBlock2 = 0x61,
3845
LifeTimeDataBlock3 = 0x62,
@@ -46,6 +53,27 @@ pub struct SmartBattery {
4653
i2c_addr: u16,
4754
}
4855

56+
/// Calculates the HMAC using TI's specific nested SHA-1 method.
57+
/// Formula: SHA1( Key || SHA1( Key || Challenge ) )
58+
fn calculate_ti_hmac(key: &[u8; 16], challenge: &[u8; 20]) -> [u8; 20] {
59+
// 1. Inner Hash: SHA1( Key + Challenge )
60+
let mut inner_hasher = Sha1::new();
61+
inner_hasher.update(key);
62+
inner_hasher.update(challenge);
63+
let inner_digest = inner_hasher.finalize();
64+
65+
// 2. Outer Hash: SHA1( Key + Inner_Digest )
66+
let mut outer_hasher = Sha1::new();
67+
outer_hasher.update(key);
68+
outer_hasher.update(inner_digest);
69+
let outer_digest = outer_hasher.finalize();
70+
71+
// Convert GenericArray to standard [u8; 20]
72+
let mut result = [0u8; 20];
73+
result.copy_from_slice(&outer_digest);
74+
result
75+
}
76+
4977
impl SmartBattery {
5078
pub fn new() -> Self {
5179
SmartBattery {
@@ -68,6 +96,15 @@ impl SmartBattery {
6896
Ok(())
6997
}
7098

99+
100+
fn i2c_write(&self, ec: &CrosEc, addr: u16, data: &[u8]) -> EcResult<()> {
101+
i2c_write(ec, self.i2c_port, self.i2c_addr >> 1, addr, data)?;
102+
Ok(())
103+
}
104+
fn i2c_read(&self, ec: &CrosEc, addr: u16, len: u16) -> EcResult<Vec<u8>> {
105+
self.read_bytes(ec, addr, len)
106+
}
107+
71108
fn read_bytes(&self, ec: &CrosEc, addr: u16, len: u16) -> EcResult<Vec<u8>> {
72109
let i2c_response = i2c_read(ec, self.i2c_port, self.i2c_addr >> 1, addr, len + 1)?;
73110
i2c_response.is_successful()?;
@@ -82,6 +119,7 @@ impl SmartBattery {
82119
i2c_response.data[1],
83120
]))
84121
}
122+
85123
fn read_string(&self, ec: &CrosEc, addr: u16) -> EcResult<String> {
86124
let i2c_response = i2c_read(ec, self.i2c_port, self.i2c_addr >> 1, addr, 32)?;
87125
i2c_response.is_successful()?;
@@ -91,6 +129,61 @@ impl SmartBattery {
91129
Ok(String::from_utf8_lossy(str_bytes).to_string())
92130
}
93131

132+
pub fn authenticate_battery(&self, ec: &CrosEc, auth_key: &[u8; 16]) -> EcResult<bool> {
133+
// 1. Generate a random 20-byte challenge
134+
// In production, use `rand::random()` to generate this.
135+
let challenge: [u8; 20] = [
136+
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
137+
0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10,
138+
0x11, 0x12, 0x13, 0x14
139+
];
140+
141+
println!("Step 1: Sending Challenge...");
142+
143+
// SMBus Block Write format: [Command, Byte_Count, Data...]
144+
let mut write_buf = Vec::new();
145+
write_buf.push(20); // Byte Count (0x14)
146+
write_buf.extend_from_slice(&challenge);
147+
148+
self.i2c_write(ec, SmartBatReg::Authenticate as u16, &write_buf)?;
149+
150+
// 2. Wait for the gauge to calculate (Datasheet says 250ms)
151+
println!("Step 2: Waiting 250ms...");
152+
thread::sleep(Duration::from_millis(250));
153+
154+
// 3. Calculate expected result locally while waiting
155+
let expected_response = calculate_ti_hmac(&auth_key, &challenge);
156+
157+
// 4. Read Response
158+
// SMBus Block Read: Write Command -> Repeated Start -> Read [Len] + [Data]
159+
println!("Step 3: Reading Response...");
160+
161+
// For block read, we usually write the command register first
162+
self.i2c_write(ec, SmartBatReg::Authenticate as u16, &[])?;
163+
164+
// Read 21 bytes (1 byte length + 20 bytes signature)
165+
// TODO: Read without writing register first
166+
let raw_response = self.i2c_read(ec, 0x00, 20)?;
167+
168+
// 5. Parse and Compare
169+
if raw_response.len() < 20 {
170+
return Err(EcError::DeviceError("Response too short".to_string()));
171+
}
172+
173+
let device_response = &raw_response[1..20];
174+
175+
println!("Expected: {:02X?}", expected_response);
176+
println!("Received: {:02X?}", device_response);
177+
178+
if device_response == expected_response {
179+
println!("SUCCESS: Battery is genuine.");
180+
Ok(true)
181+
} else {
182+
println!("FAILURE: Signature mismatch.");
183+
Ok(false)
184+
}
185+
}
186+
94187
pub fn dump_data(&self, ec: &CrosEc) -> EcResult<()> {
95188
// Check mode
96189
println!(
@@ -157,6 +250,9 @@ impl SmartBattery {
157250
// Default key - does not work on our battery, it's changed during manufacturing!
158251
self.unseal(ec, 0x0414, 0x3672).unwrap();
159252

253+
// Dummy code. Do not push real authentication key!
254+
// self.authenticate_battery(ec, &[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
255+
160256
// Need to unseal for access
161257
// SE [US] [FA]
162258
let soh = self.read_bytes(ec, ManufReg::Soh as u16, 4)?;
@@ -166,6 +262,10 @@ impl SmartBattery {
166262
u16::from_le_bytes([soh[2], soh[3]]) / 100,
167263
u16::from_le_bytes([soh[2], soh[3]]) % 100,
168264
);
265+
println!(
266+
"OperationStatus{:?}",
267+
self.read_i16(&ec, ManufReg::OperationStatus as u16)?
268+
);
169269
println!(
170270
"Safety Alert: {:?}",
171271
self.read_i16(&ec, ManufReg::SafetyAlert as u16)?
@@ -180,7 +280,7 @@ impl SmartBattery {
180280
);
181281
println!(
182282
"PFStatus: {:?}",
183-
self.read_i16(&ec, ManufReg::SafetyAlert as u16)?
283+
self.read_i16(&ec, ManufReg::PFStatus as u16)?
184284
);
185285
let lifetime1 = self.read_bytes(ec, ManufReg::LifeTimeDataBlock1 as u16, 32)?;
186286
println!("LifeTime1");

0 commit comments

Comments
 (0)