|
6 | 6 |
|
7 | 7 | #[cfg(feature = "serialport")] |
8 | 8 | use std::{borrow::Cow, io::Write, path::PathBuf, thread::sleep, time::Duration}; |
9 | | -use std::{fs, path::Path, str::FromStr}; |
| 9 | +use std::{collections::HashMap, fmt, fs, path::Path, str::FromStr}; |
10 | 10 |
|
11 | 11 | use esp_idf_part::PartitionTable; |
12 | 12 | #[cfg(feature = "serialport")] |
@@ -47,6 +47,176 @@ use crate::{ |
47 | 47 | #[cfg(feature = "serialport")] |
48 | 48 | pub(crate) mod stubs; |
49 | 49 |
|
| 50 | +/// Security Info Response containing |
| 51 | +#[derive(Debug)] |
| 52 | +pub struct SecurityInfo { |
| 53 | + /// 32 bits flags |
| 54 | + pub flags: u32, |
| 55 | + /// 1 byte flash_crypt_cnt |
| 56 | + pub flash_crypt_cnt: u8, |
| 57 | + /// 7 bytes key purposes |
| 58 | + pub key_purposes: [u8; 7], |
| 59 | + /// 32-bit word chip id |
| 60 | + pub chip_id: Option<u32>, |
| 61 | + /// 32-bit word eco version |
| 62 | + pub eco_version: Option<u32>, |
| 63 | +} |
| 64 | + |
| 65 | +impl SecurityInfo { |
| 66 | + fn security_flag_map() -> HashMap<&'static str, u32> { |
| 67 | + HashMap::from([ |
| 68 | + ("SECURE_BOOT_EN", 1 << 0), |
| 69 | + ("SECURE_BOOT_AGGRESSIVE_REVOKE", 1 << 1), |
| 70 | + ("SECURE_DOWNLOAD_ENABLE", 1 << 2), |
| 71 | + ("SECURE_BOOT_KEY_REVOKE0", 1 << 3), |
| 72 | + ("SECURE_BOOT_KEY_REVOKE1", 1 << 4), |
| 73 | + ("SECURE_BOOT_KEY_REVOKE2", 1 << 5), |
| 74 | + ("SOFT_DIS_JTAG", 1 << 6), |
| 75 | + ("HARD_DIS_JTAG", 1 << 7), |
| 76 | + ("DIS_USB", 1 << 8), |
| 77 | + ("DIS_DOWNLOAD_DCACHE", 1 << 9), |
| 78 | + ("DIS_DOWNLOAD_ICACHE", 1 << 10), |
| 79 | + ]) |
| 80 | + } |
| 81 | + |
| 82 | + fn get_security_flag_status(&self, flag_name: &str) -> bool { |
| 83 | + if let Some(&flag) = Self::security_flag_map().get(flag_name) { |
| 84 | + (self.flags & flag) != 0 |
| 85 | + } else { |
| 86 | + false |
| 87 | + } |
| 88 | + } |
| 89 | +} |
| 90 | + |
| 91 | +impl TryFrom<&[u8]> for SecurityInfo { |
| 92 | + type Error = crate::error::Error; |
| 93 | + |
| 94 | + fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> { |
| 95 | + let esp32s2 = bytes.len() == 12; |
| 96 | + |
| 97 | + if bytes.len() < 12 { |
| 98 | + return Err(Error::InvalidResponse(format!( |
| 99 | + "expected response of at least 12 bytes, received {} bytes", |
| 100 | + bytes.len() |
| 101 | + ))); |
| 102 | + } |
| 103 | + |
| 104 | + // Parse response bytes |
| 105 | + let flags = u32::from_le_bytes(bytes[0..4].try_into()?); |
| 106 | + let flash_crypt_cnt = bytes[4]; |
| 107 | + let key_purposes: [u8; 7] = bytes[5..12].try_into()?; |
| 108 | + |
| 109 | + let (chip_id, eco_version) = if esp32s2 { |
| 110 | + (None, None) // ESP32-S2 doesn't have these values |
| 111 | + } else { |
| 112 | + if bytes.len() < 20 { |
| 113 | + return Err(Error::InvalidResponse(format!( |
| 114 | + "expected response of at least 20 bytes, received {} bytes", |
| 115 | + bytes.len() |
| 116 | + ))); |
| 117 | + } |
| 118 | + let chip_id = u32::from_le_bytes(bytes[12..16].try_into()?); |
| 119 | + let eco_version = u32::from_le_bytes(bytes[16..20].try_into()?); |
| 120 | + (Some(chip_id), Some(eco_version)) |
| 121 | + }; |
| 122 | + |
| 123 | + Ok(SecurityInfo { |
| 124 | + flags, |
| 125 | + flash_crypt_cnt, |
| 126 | + key_purposes, |
| 127 | + chip_id, |
| 128 | + eco_version, |
| 129 | + }) |
| 130 | + } |
| 131 | +} |
| 132 | + |
| 133 | +impl fmt::Display for SecurityInfo { |
| 134 | + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 135 | + let key_purposes_str = self |
| 136 | + .key_purposes |
| 137 | + .iter() |
| 138 | + .map(|b| format!("{}", b)) |
| 139 | + .collect::<Vec<_>>() |
| 140 | + .join(", "); |
| 141 | + |
| 142 | + writeln!(f, "\nSecurity Information:")?; |
| 143 | + writeln!(f, "=====================")?; |
| 144 | + writeln!(f, "Flags: {:#010x} ({:b})", self.flags, self.flags)?; |
| 145 | + writeln!(f, "Key Purposes: [{}]", key_purposes_str)?; |
| 146 | + |
| 147 | + // Only print Chip ID if it's Some(value) |
| 148 | + if let Some(chip_id) = self.chip_id { |
| 149 | + writeln!(f, "Chip ID: {}", chip_id)?; |
| 150 | + } |
| 151 | + |
| 152 | + // Only print API Version if it's Some(value) |
| 153 | + if let Some(api_version) = self.eco_version { |
| 154 | + writeln!(f, "API Version: {}", api_version)?; |
| 155 | + } |
| 156 | + |
| 157 | + // Secure Boot |
| 158 | + if self.get_security_flag_status("SECURE_BOOT_EN") { |
| 159 | + writeln!(f, "Secure Boot: Enabled")?; |
| 160 | + if self.get_security_flag_status("SECURE_BOOT_AGGRESSIVE_REVOKE") { |
| 161 | + writeln!(f, "Secure Boot Aggressive key revocation: Enabled")?; |
| 162 | + } |
| 163 | + |
| 164 | + let revoked_keys: Vec<_> = [ |
| 165 | + "SECURE_BOOT_KEY_REVOKE0", |
| 166 | + "SECURE_BOOT_KEY_REVOKE1", |
| 167 | + "SECURE_BOOT_KEY_REVOKE2", |
| 168 | + ] |
| 169 | + .iter() |
| 170 | + .enumerate() |
| 171 | + .filter(|(_, &key)| self.get_security_flag_status(key)) |
| 172 | + .map(|(i, _)| format!("Secure Boot Key{} is Revoked", i)) |
| 173 | + .collect(); |
| 174 | + |
| 175 | + if !revoked_keys.is_empty() { |
| 176 | + writeln!( |
| 177 | + f, |
| 178 | + "Secure Boot Key Revocation Status:\n {}", |
| 179 | + revoked_keys.join("\n ") |
| 180 | + )?; |
| 181 | + } |
| 182 | + } else { |
| 183 | + writeln!(f, "Secure Boot: Disabled")?; |
| 184 | + } |
| 185 | + |
| 186 | + // Flash Encryption |
| 187 | + if self.flash_crypt_cnt.count_ones() % 2 != 0 { |
| 188 | + writeln!(f, "Flash Encryption: Enabled")?; |
| 189 | + } else { |
| 190 | + writeln!(f, "Flash Encryption: Disabled")?; |
| 191 | + } |
| 192 | + |
| 193 | + let crypt_cnt_str = "SPI Boot Crypt Count (SPI_BOOT_CRYPT_CNT)"; |
| 194 | + writeln!(f, "{}: 0x{:x}", crypt_cnt_str, self.flash_crypt_cnt)?; |
| 195 | + |
| 196 | + // Cache Disabling |
| 197 | + if self.get_security_flag_status("DIS_DOWNLOAD_DCACHE") { |
| 198 | + writeln!(f, "Dcache in UART download mode: Disabled")?; |
| 199 | + } |
| 200 | + if self.get_security_flag_status("DIS_DOWNLOAD_ICACHE") { |
| 201 | + writeln!(f, "Icache in UART download mode: Disabled")?; |
| 202 | + } |
| 203 | + |
| 204 | + // JTAG Status |
| 205 | + if self.get_security_flag_status("HARD_DIS_JTAG") { |
| 206 | + writeln!(f, "JTAG: Permanently Disabled")?; |
| 207 | + } else if self.get_security_flag_status("SOFT_DIS_JTAG") { |
| 208 | + writeln!(f, "JTAG: Software Access Disabled")?; |
| 209 | + } |
| 210 | + |
| 211 | + // USB Access |
| 212 | + if self.get_security_flag_status("DIS_USB") { |
| 213 | + writeln!(f, "USB Access: Disabled")?; |
| 214 | + } |
| 215 | + |
| 216 | + Ok(()) |
| 217 | + } |
| 218 | +} |
| 219 | + |
50 | 220 | /// Supported flash frequencies |
51 | 221 | /// |
52 | 222 | /// Note that not all frequencies are supported by each target device. |
@@ -1038,6 +1208,29 @@ impl Flasher { |
1038 | 1208 | }) |
1039 | 1209 | } |
1040 | 1210 |
|
| 1211 | + /// Get security info |
| 1212 | + pub fn get_security_info(&mut self) -> Result<SecurityInfo, Error> { |
| 1213 | + self.connection |
| 1214 | + .with_timeout(CommandType::GetSecurityInfo.timeout(), |connection| { |
| 1215 | + let response = connection.command(crate::command::Command::GetSecurityInfo)?; |
| 1216 | + // Extract raw bytes and convert them into `SecurityInfo` |
| 1217 | + if let crate::connection::CommandResponseValue::Vector(data) = response { |
| 1218 | + // HACK: Not quite sure why there seem to be 4 extra bytes at the end of the |
| 1219 | + // response when the stub is not being used... |
| 1220 | + let end = if self.use_stub { |
| 1221 | + data.len() |
| 1222 | + } else { |
| 1223 | + data.len() - 4 |
| 1224 | + }; |
| 1225 | + SecurityInfo::try_from(&data[..end]) |
| 1226 | + } else { |
| 1227 | + Err(Error::InvalidResponse( |
| 1228 | + "response was not a vector of bytes".into(), |
| 1229 | + )) |
| 1230 | + } |
| 1231 | + }) |
| 1232 | + } |
| 1233 | + |
1041 | 1234 | pub fn change_baud(&mut self, speed: u32) -> Result<(), Error> { |
1042 | 1235 | debug!("Change baud to: {}", speed); |
1043 | 1236 |
|
|
0 commit comments