Skip to content

Commit 8c27120

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

File tree

2 files changed

+120
-1
lines changed

2 files changed

+120
-1
lines changed

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: 118 additions & 1 deletion
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,21 @@ 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+
108+
fn read_bytes(&self, ec: &CrosEc, addr: u16, len: u16) -> EcResult<Vec<u8>> {
109+
let i2c_response = i2c_read(ec, self.i2c_port, self.i2c_addr >> 1, addr, len)?;
110+
i2c_response.is_successful()?;
111+
Ok(i2c_response.data)
112+
}
113+
71114
fn read_bytes(&self, ec: &CrosEc, addr: u16, len: u16) -> EcResult<Vec<u8>> {
72115
let i2c_response = i2c_read(ec, self.i2c_port, self.i2c_addr >> 1, addr, len + 1)?;
73116
i2c_response.is_successful()?;
@@ -82,6 +125,7 @@ impl SmartBattery {
82125
i2c_response.data[1],
83126
]))
84127
}
128+
85129
fn read_string(&self, ec: &CrosEc, addr: u16) -> EcResult<String> {
86130
let i2c_response = i2c_read(ec, self.i2c_port, self.i2c_addr >> 1, addr, 32)?;
87131
i2c_response.is_successful()?;
@@ -91,6 +135,67 @@ impl SmartBattery {
91135
Ok(String::from_utf8_lossy(str_bytes).to_string())
92136
}
93137

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

265+
// Dummy code. Do not push real authentication key!
266+
self.authenticate_battery(ec, &[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
267+
268+
println!("Trying to unseal");
269+
// Try default key
270+
let default_unseal = &[0x0000, 0x0000];
271+
self.unseal(ec, default_unseal)?;
272+
160273
// Need to unseal for access
161274
// SE [US] [FA]
162275
let soh = self.read_bytes(ec, ManufReg::Soh as u16, 4)?;
@@ -166,6 +279,10 @@ impl SmartBattery {
166279
u16::from_le_bytes([soh[2], soh[3]]) / 100,
167280
u16::from_le_bytes([soh[2], soh[3]]) % 100,
168281
);
282+
println!(
283+
"OperationStatus{:?}",
284+
self.read_i16(&ec, ManufReg::OperationStatus as u16)?
285+
);
169286
println!(
170287
"Safety Alert: {:?}",
171288
self.read_i16(&ec, ManufReg::SafetyAlert as u16)?

0 commit comments

Comments
 (0)