Skip to content

Commit f8a344f

Browse files
Use security info to detect chip (#953)
* feat: Move security_info to Connection struct and use it for chip detection * feat: Move SecurityInfo to connection module * fix: Update log level * revert: Moving SecurityInfo to connectionç * feat: Move SecurityInfo to connection module * docs: Update changelog * fix: Esp32 tests * test: Increase timeout and add log message
1 parent ed1053c commit f8a344f

File tree

4 files changed

+242
-219
lines changed

4 files changed

+242
-219
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
## [Unreleased]
99

1010
### Added
11+
- Add chip detection based on security info, where supported (#953)
1112

1213
### Changed
14+
- Moved `SecurityInfo` to the `connection` module from the `flasher` module (#953)
1315

1416
### Fixed
1517

espflash/src/connection/mod.rs

Lines changed: 216 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
//! device.
66
77
use std::{
8+
collections::HashMap,
9+
fmt,
810
io::{BufWriter, Read, Write},
911
iter::zip,
1012
thread::sleep,
@@ -13,6 +15,7 @@ use std::{
1315

1416
use log::{debug, info};
1517
use regex::Regex;
18+
use serde::{Deserialize, Serialize};
1619
use serialport::{SerialPort, UsbPortInfo};
1720
use slip_codec::SlipDecoder;
1821

@@ -52,6 +55,176 @@ pub type Port = serialport::TTYPort;
5255
/// Alias for the serial COMPort.
5356
pub type Port = serialport::COMPort;
5457

58+
/// Security Info Response containing chip security information
59+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize)]
60+
pub struct SecurityInfo {
61+
/// 32 bits flags
62+
pub flags: u32,
63+
/// 1 byte flash_crypt_cnt
64+
pub flash_crypt_cnt: u8,
65+
/// 7 bytes key purposes
66+
pub key_purposes: [u8; 7],
67+
/// 32-bit word chip id
68+
pub chip_id: Option<u32>,
69+
/// 32-bit word eco version
70+
pub eco_version: Option<u32>,
71+
}
72+
73+
impl SecurityInfo {
74+
fn security_flag_map() -> HashMap<&'static str, u32> {
75+
HashMap::from([
76+
("SECURE_BOOT_EN", 1 << 0),
77+
("SECURE_BOOT_AGGRESSIVE_REVOKE", 1 << 1),
78+
("SECURE_DOWNLOAD_ENABLE", 1 << 2),
79+
("SECURE_BOOT_KEY_REVOKE0", 1 << 3),
80+
("SECURE_BOOT_KEY_REVOKE1", 1 << 4),
81+
("SECURE_BOOT_KEY_REVOKE2", 1 << 5),
82+
("SOFT_DIS_JTAG", 1 << 6),
83+
("HARD_DIS_JTAG", 1 << 7),
84+
("DIS_USB", 1 << 8),
85+
("DIS_DOWNLOAD_DCACHE", 1 << 9),
86+
("DIS_DOWNLOAD_ICACHE", 1 << 10),
87+
])
88+
}
89+
90+
fn security_flag_status(&self, flag_name: &str) -> bool {
91+
if let Some(&flag) = Self::security_flag_map().get(flag_name) {
92+
(self.flags & flag) != 0
93+
} else {
94+
false
95+
}
96+
}
97+
}
98+
99+
impl TryFrom<&[u8]> for SecurityInfo {
100+
type Error = Error;
101+
102+
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
103+
let esp32s2 = bytes.len() == 12;
104+
105+
if bytes.len() < 12 {
106+
return Err(Error::InvalidResponse(format!(
107+
"expected response of at least 12 bytes, received {} bytes",
108+
bytes.len()
109+
)));
110+
}
111+
112+
// Parse response bytes
113+
let flags = u32::from_le_bytes(bytes[0..4].try_into()?);
114+
let flash_crypt_cnt = bytes[4];
115+
let key_purposes: [u8; 7] = bytes[5..12].try_into()?;
116+
117+
let (chip_id, eco_version) = if esp32s2 {
118+
(None, None) // ESP32-S2 doesn't have these values
119+
} else {
120+
if bytes.len() < 20 {
121+
return Err(Error::InvalidResponse(format!(
122+
"expected response of at least 20 bytes, received {} bytes",
123+
bytes.len()
124+
)));
125+
}
126+
let chip_id = u32::from_le_bytes(bytes[12..16].try_into()?);
127+
let eco_version = u32::from_le_bytes(bytes[16..20].try_into()?);
128+
(Some(chip_id), Some(eco_version))
129+
};
130+
131+
Ok(SecurityInfo {
132+
flags,
133+
flash_crypt_cnt,
134+
key_purposes,
135+
chip_id,
136+
eco_version,
137+
})
138+
}
139+
}
140+
141+
impl fmt::Display for SecurityInfo {
142+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143+
let key_purposes_str = self
144+
.key_purposes
145+
.iter()
146+
.map(|b| format!("{b}"))
147+
.collect::<Vec<_>>()
148+
.join(", ");
149+
150+
writeln!(f, "\nSecurity Information:")?;
151+
writeln!(f, "=====================")?;
152+
writeln!(f, "Flags: {:#010x} ({:b})", self.flags, self.flags)?;
153+
writeln!(f, "Key Purposes: [{key_purposes_str}]")?;
154+
155+
// Only print Chip ID if it's Some(value)
156+
if let Some(chip_id) = self.chip_id {
157+
writeln!(f, "Chip ID: {chip_id}")?;
158+
}
159+
160+
// Only print API Version if it's Some(value)
161+
if let Some(api_version) = self.eco_version {
162+
writeln!(f, "API Version: {api_version}")?;
163+
}
164+
165+
// Secure Boot
166+
if self.security_flag_status("SECURE_BOOT_EN") {
167+
writeln!(f, "Secure Boot: Enabled")?;
168+
if self.security_flag_status("SECURE_BOOT_AGGRESSIVE_REVOKE") {
169+
writeln!(f, "Secure Boot Aggressive key revocation: Enabled")?;
170+
}
171+
172+
let revoked_keys: Vec<_> = [
173+
"SECURE_BOOT_KEY_REVOKE0",
174+
"SECURE_BOOT_KEY_REVOKE1",
175+
"SECURE_BOOT_KEY_REVOKE2",
176+
]
177+
.iter()
178+
.enumerate()
179+
.filter(|(_, key)| self.security_flag_status(key))
180+
.map(|(i, _)| format!("Secure Boot Key{i} is Revoked"))
181+
.collect();
182+
183+
if !revoked_keys.is_empty() {
184+
writeln!(
185+
f,
186+
"Secure Boot Key Revocation Status:\n {}",
187+
revoked_keys.join("\n ")
188+
)?;
189+
}
190+
} else {
191+
writeln!(f, "Secure Boot: Disabled")?;
192+
}
193+
194+
// Flash Encryption
195+
if self.flash_crypt_cnt.count_ones() % 2 != 0 {
196+
writeln!(f, "Flash Encryption: Enabled")?;
197+
} else {
198+
writeln!(f, "Flash Encryption: Disabled")?;
199+
}
200+
201+
let crypt_cnt_str = "SPI Boot Crypt Count (SPI_BOOT_CRYPT_CNT)";
202+
writeln!(f, "{}: 0x{:x}", crypt_cnt_str, self.flash_crypt_cnt)?;
203+
204+
// Cache Disabling
205+
if self.security_flag_status("DIS_DOWNLOAD_DCACHE") {
206+
writeln!(f, "Dcache in UART download mode: Disabled")?;
207+
}
208+
if self.security_flag_status("DIS_DOWNLOAD_ICACHE") {
209+
writeln!(f, "Icache in UART download mode: Disabled")?;
210+
}
211+
212+
// JTAG Status
213+
if self.security_flag_status("HARD_DIS_JTAG") {
214+
writeln!(f, "JTAG: Permanently Disabled")?;
215+
} else if self.security_flag_status("SOFT_DIS_JTAG") {
216+
writeln!(f, "JTAG: Software Access Disabled")?;
217+
}
218+
219+
// USB Access
220+
if self.security_flag_status("DIS_USB") {
221+
writeln!(f, "USB Access: Disabled")?;
222+
}
223+
224+
Ok(())
225+
}
226+
}
227+
55228
/// An established connection with a target device.
56229
#[derive(Debug)]
57230
pub struct Connection {
@@ -552,24 +725,54 @@ impl Connection {
552725
self.before_operation
553726
}
554727

728+
/// Gets security information from the chip.
729+
#[cfg(feature = "serialport")]
730+
pub fn security_info(&mut self, use_stub: bool) -> Result<SecurityInfo, crate::error::Error> {
731+
self.with_timeout(CommandType::GetSecurityInfo.timeout(), |connection| {
732+
let response = connection.command(Command::GetSecurityInfo)?;
733+
// Extract raw bytes and convert them into `SecurityInfo`
734+
if let crate::command::CommandResponseValue::Vector(data) = response {
735+
// HACK: Not quite sure why there seem to be 4 extra bytes at the end of the
736+
// response when the stub is not being used...
737+
let end = if use_stub { data.len() } else { data.len() - 4 };
738+
SecurityInfo::try_from(&data[..end])
739+
} else {
740+
Err(Error::InvalidResponse(
741+
"response was not a vector of bytes".into(),
742+
))
743+
}
744+
})
745+
}
746+
555747
/// Detects which chip is connected to this connection.
748+
#[cfg(feature = "serialport")]
556749
pub fn detect_chip(
557750
&mut self,
558751
use_stub: bool,
559752
) -> Result<crate::target::Chip, crate::error::Error> {
560-
// Try to read the magic value from the chip
561-
let magic = if use_stub {
562-
self.with_timeout(CommandType::ReadReg.timeout(), |connection| {
563-
connection.command(Command::ReadReg {
564-
address: CHIP_DETECT_MAGIC_REG_ADDR,
565-
})
566-
})?
567-
.try_into()?
568-
} else {
569-
self.read_reg(CHIP_DETECT_MAGIC_REG_ADDR)?
570-
};
571-
debug!("Read chip magic value: 0x{magic:08x}");
572-
Chip::from_magic(magic)
753+
match self.security_info(use_stub) {
754+
Ok(info) if info.chip_id.is_some() => {
755+
let chip_id = info.chip_id.unwrap() as u16;
756+
let chip = Chip::try_from(chip_id)?;
757+
758+
Ok(chip)
759+
}
760+
_ => {
761+
// Fall back to reading the magic value from the chip
762+
let magic = if use_stub {
763+
self.with_timeout(CommandType::ReadReg.timeout(), |connection| {
764+
connection.command(Command::ReadReg {
765+
address: CHIP_DETECT_MAGIC_REG_ADDR,
766+
})
767+
})?
768+
.try_into()?
769+
} else {
770+
self.read_reg(CHIP_DETECT_MAGIC_REG_ADDR)?
771+
};
772+
debug!("Read chip magic value: 0x{magic:08x}");
773+
Chip::from_magic(magic)
774+
}
775+
}
573776
}
574777
}
575778

0 commit comments

Comments
 (0)