Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 122 additions & 24 deletions SE050Sim/se050-sim/src/applet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,84 +21,182 @@

/// Applet personality selection.
///
/// The simulator can present itself as either of the two applet
/// generations that were bench-characterized on real silicon (August
/// 2026, see SE050Sim/HARDWARE_VALIDATION.md): an SE050C running applet
/// 3.1.1 or an SE051 running applet 7.2.0. Almost all behavior is
/// identical between the two; the differences the simulator models are:
/// The simulator can present itself as any of the three parts that were
/// bench-characterized on real silicon (August 2026, see
/// SE050Sim/HARDWARE_VALIDATION.md): an SE050C running applet 3.1.1, an
/// SE051 running applet 7.2.0, or an SE050E running applet 7.2.0 with
/// the RSA feature bits disabled. Almost all behavior is identical
/// across the three; the differences the simulator models are:
///
/// * SELECT / GetVersion version bytes.
/// * GetFreeMemory response width (2 bytes on 3.x, 4 bytes on 7.2) and
/// the reported per-type values.
/// * SELECT / GetVersion version bytes (the SE050E's appletConfig word
/// clears the RSA_PLAIN and RSA_CRT bits: 0x3f9f vs the SE051's
/// 0x3fff).
/// * GetFreeMemory per-type values. All three parts reply with a 2-byte
/// value (the SE050E clamps PERSISTENT at 0x7FFF); the v04.07.01
/// middleware parses U16 for every applet below minor version 0x10
/// and U32 only for the SE052F family.
/// * GetRandom maximum request size (880 bytes on the SE050C, 1018 on
/// the SE051).
/// the SE051 and SE050E).
/// * ReadType secure-object type codes for EC keys (generic 0x01/0x03
/// on 3.x, curve-specific on 7.2).
/// * CreateECCurve on an already existing curve: applet 7.2 refuses
/// with SW 0x6985; applet 3.1.1 returns 0x9000 and silently resets
/// the curve to a parameter-less state (subsequent key generation on
/// it fails 0x6985 until the parameters are uploaded again).
/// * RSA: the SE050E refuses key generation with SW 0x6985 and key
/// import with SW 0x6A80; wolfSSL's wolfcrypt suite consequently
/// fails its RSA test with WC_HW_E against real SE050E silicon.

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AppletVersion {
/// SE050C, applet 3.1.1 (ATR historical bytes "JCOP4").
V3_1_1,
/// SE051, applet 7.2.0 (ATR historical bytes "eSE051"). Default.
V7_2_0,
/// SE050E, applet 7.2.0 with RSA disabled in appletConfig (ATR
/// historical bytes also read "eSE051" on real parts).
V7_2_0E,
}

impl AppletVersion {
/// Read the personality from the SE050_SIM_APPLET environment
/// variable. Accepts "3", "3.1.1" (SE050C) and "7", "7.2", "7.2.0"
/// (SE051). Unset or unrecognized values select 7.2.0, matching the
/// version the simulator has always advertised.
/// variable. Unset selects 7.2.0, matching the version the
/// simulator has always advertised; see `from_token` for the
/// accepted values.
pub fn from_env() -> Self {
match std::env::var("SE050_SIM_APPLET") {
Ok(v) if v.starts_with('3') => AppletVersion::V3_1_1,
_ => AppletVersion::V7_2_0,
Ok(v) => Self::from_token(&v),
Err(_) => AppletVersion::V7_2_0,
}
}

/// Parse a personality token. Accepts "e", "se050e", "7.2.0e" --
/// any value ending in "e" or "E" -- for the SE050E; "3", "3.1.1"
/// for the SE050C; and anything else ("7", "7.2", "7.2.0",
/// unrecognized) for the SE051.
pub fn from_token(token: &str) -> Self {
let t = token.trim().to_ascii_lowercase();
if t.ends_with('e') {
AppletVersion::V7_2_0E
} else if t.starts_with('3') {
AppletVersion::V3_1_1
} else {
AppletVersion::V7_2_0
}
}

/// 7-byte version blob returned by SELECT and GetVersion:
/// major, minor, patch, appletConfig (2B), secureBox (2B).
/// Captured from real parts: SE050C applet 3.1.1 returns
/// 03 01 01 6f ff 01 0b, SE051 applet 7.2.0 returns
/// 07 02 00 3f ff ff ff.
/// 07 02 00 3f ff ff ff, SE050E applet 7.2.0 returns
/// 07 02 00 3f 9f ff ff (appletConfig clears RSA_PLAIN 0x0020
/// and RSA_CRT 0x0040).
pub fn version_bytes(self) -> [u8; 7] {
match self {
AppletVersion::V3_1_1 => [0x03, 0x01, 0x01, 0x6F, 0xFF, 0x01, 0x0B],
AppletVersion::V7_2_0 => [0x07, 0x02, 0x00, 0x3F, 0xFF, 0xFF, 0xFF],
AppletVersion::V7_2_0E => [0x07, 0x02, 0x00, 0x3F, 0x9F, 0xFF, 0xFF],
}
}

/// Largest GetRandom request the applet serves; one byte more
/// returns SW 0x6985 (bench-measured: 880 on SE050C 3.1.1, 1018 on
/// SE051 7.2.0).
/// SE051 7.2.0 and SE050E).
pub fn get_random_max(self) -> usize {
match self {
AppletVersion::V3_1_1 => 880,
AppletVersion::V7_2_0 => 1018,
AppletVersion::V7_2_0 | AppletVersion::V7_2_0E => 1018,
}
}

/// Whether the applet supports RSA at all. The SE050E's applet
/// build has the RSA_PLAIN / RSA_CRT feature bits cleared
/// (bench-verified: keygen refuses 0x6985, import refuses 0x6A80).
pub fn supports_rsa(self) -> bool {
!matches!(self, AppletVersion::V7_2_0E)
}

/// Whether this is a 7.2-generation applet (SE051 or SE050E).
/// Gates the 7.2-specific read behaviors: curve-specific ReadType
/// codes and ReadObjectAttributes support. Bench-verified: the
/// SE050E reports the same curve-specific type codes as the SE051
/// (P-256 pair 0x29, P-521 pair 0x31).
pub fn is_v7(self) -> bool {
!matches!(self, AppletVersion::V3_1_1)
}

/// GetFreeMemory reply for a memory type, as measured on the bench
/// parts. Applet 3.x replies with a 2-byte value, 7.2 with 4 bytes
/// (the v04.07.01 middleware parses U16 vs U32 accordingly).
/// parts. All three parts reply with a 2-byte big-endian value; the
/// SE050E reports PERSISTENT clamped at 0x7FFF. (An earlier revision
/// emitted 4 bytes for 7.2.0 after misreading the middleware's
/// SE052F-only U32 parse path; the v04.07.01 middleware rejects
/// TLV values longer than 2 bytes for these applets.)
pub fn free_memory_bytes(self, memory_type: u8) -> Option<Vec<u8>> {
let (persistent, transient_reset, transient_deselect): (u32, u32, u32) =
let (persistent, transient_reset, transient_deselect): (u16, u16, u16) =
match self {
AppletVersion::V3_1_1 => (31304, 575, 560),
AppletVersion::V7_2_0 => (21000, 605, 592),
AppletVersion::V7_2_0E => (32767, 796, 784),
};
let value = match memory_type {
0x01 => persistent,
0x02 => transient_reset,
0x03 => transient_deselect,
_ => return None,
};
Some(match self {
AppletVersion::V3_1_1 => (value as u16).to_be_bytes().to_vec(),
AppletVersion::V7_2_0 => value.to_be_bytes().to_vec(),
})
Some(value.to_be_bytes().to_vec())
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_from_token_mapping() {
for (token, expected) in [
("3", AppletVersion::V3_1_1),
("3.1.1", AppletVersion::V3_1_1),
("7", AppletVersion::V7_2_0),
("7.2.0", AppletVersion::V7_2_0),
("e", AppletVersion::V7_2_0E),
("E", AppletVersion::V7_2_0E),
("se050e", AppletVersion::V7_2_0E),
("SE050E", AppletVersion::V7_2_0E),
("7.2.0e", AppletVersion::V7_2_0E),
// The ending-in-e rule takes precedence over the leading-3
// rule, matching the documented behavior.
("3e", AppletVersion::V7_2_0E),
(" se050e ", AppletVersion::V7_2_0E),
("bogus", AppletVersion::V7_2_0),
("", AppletVersion::V7_2_0),
] {
assert_eq!(AppletVersion::from_token(token), expected,
"token {:?}", token);
}
}

#[test]
fn test_personality_traits() {
assert!(AppletVersion::V3_1_1.supports_rsa());
assert!(AppletVersion::V7_2_0.supports_rsa());
assert!(!AppletVersion::V7_2_0E.supports_rsa());
// The SE050E is a 7.2-generation part: it must get the
// curve-specific ReadType codes, not the 3.x generic ones.
assert!(!AppletVersion::V3_1_1.is_v7());
assert!(AppletVersion::V7_2_0.is_v7());
assert!(AppletVersion::V7_2_0E.is_v7());
// All personalities reply GetFreeMemory as 2-byte values.
for v in [
AppletVersion::V3_1_1,
AppletVersion::V7_2_0,
AppletVersion::V7_2_0E,
] {
for mem_type in [0x01u8, 0x02, 0x03] {
assert_eq!(v.free_memory_bytes(mem_type).unwrap().len(), 2);
}
assert!(v.free_memory_bytes(0x04).is_none());
}
}
}
4 changes: 2 additions & 2 deletions SE050Sim/se050-sim/src/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ pub fn dispatch(apdu: &ParsedApdu, store: &mut ObjectStore) -> ApduResponse {
// Applet personality (SE050_SIM_APPLET env var; defaults to the
// SE051 / applet 7.2.0 the simulator has always advertised).
let version = AppletVersion::from_env();
let v7 = version == AppletVersion::V7_2_0;
let v7 = version.is_v7();

// SELECT command (CLA=0x00, INS=0xA4)
if apdu.cla == 0x00 && apdu.ins == 0xA4 {
Expand All @@ -49,7 +49,7 @@ pub fn dispatch(apdu: &ParsedApdu, store: &mut ObjectStore) -> ApduResponse {
match base_ins {
INS_WRITE => match cred_type {
P1_EC => handlers::ec::handle_write_ec_key(apdu, store),
P1_RSA => handlers::rsa::handle_write_rsa_key(apdu, store),
P1_RSA => handlers::rsa::handle_write_rsa_key(apdu, store, version),
Comment thread
LinuxJedi marked this conversation as resolved.
P1_AES => handlers::aes::handle_write_aes_key(apdu, store),
P1_HMAC => handlers::aes::handle_write_hmac_key(apdu, store),
P1_CRYPTO_OBJ => handlers::crypto_obj::handle_create(apdu, store),
Expand Down
8 changes: 5 additions & 3 deletions SE050Sim/se050-sim/src/handlers/curve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,11 @@ pub fn handle_create(
}
if store.curve_exists(curve_id) {
return match version {
// Bench-verified on the SE051: re-creating an existing
// curve is refused and the curve is left intact.
AppletVersion::V7_2_0 => ApduResponse::error(SW_CONDITIONS_NOT_SATISFIED),
// Bench-verified on the SE051 and SE050E: re-creating an
// existing curve is refused and the curve is left intact.
AppletVersion::V7_2_0 | AppletVersion::V7_2_0E => {
ApduResponse::error(SW_CONDITIONS_NOT_SATISFIED)
}
// Bench-verified on the SE050C: the duplicate create is
// accepted and resets the curve to param-less, so key
// generation on it fails until the parameters are
Expand Down
61 changes: 38 additions & 23 deletions SE050Sim/se050-sim/src/handlers/management.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,9 @@ fn handle_get_version(version: AppletVersion) -> ApduResponse {
ApduResponse::success_with_tlvs(&[Tlv::new(TAG_1, &version.version_bytes())])
}

/// GetFreeMemory: Tag1 = memory type (1B). The response width is
/// applet-dependent: 2 bytes on 3.x, 4 bytes on 7.2 (the v04.07.01
/// middleware parses U16 vs U32 accordingly); values as measured on
/// the bench parts.
/// GetFreeMemory: Tag1 = memory type (1B). All bench parts (3.1.1,
/// 7.2.0, SE050E) reply with a 2-byte value; per-type values as
/// measured on the bench parts (see AppletVersion::free_memory_bytes).
fn handle_get_free_memory(apdu: &ParsedApdu, version: AppletVersion) -> ApduResponse {
let tlvs = match apdu.parse_tlvs() {
Ok(t) => t,
Expand All @@ -62,8 +61,8 @@ fn handle_get_free_memory(apdu: &ParsedApdu, version: AppletVersion) -> ApduResp

/// GetRandom: reads TLV[Tag1] as 2-byte requested length, returns
/// random bytes. Zero-length requests fail 0x6985 and there is a
/// per-applet maximum (880 bytes on 3.1.1, 1018 on 7.2.0), both
/// bench-verified.
/// per-applet maximum (880 bytes on 3.1.1, 1018 on 7.2.0 and the
/// SE050E), all bench-verified.
fn handle_get_random(apdu: &ParsedApdu, version: AppletVersion) -> ApduResponse {
let tlvs = match apdu.parse_tlvs() {
Ok(t) => t,
Expand Down Expand Up @@ -115,12 +114,14 @@ mod tests {

#[test]
fn test_get_random_bounds_per_version() {
// Bench-verified: size 0 fails on both parts; the cap is 880
// on the SE050C (3.1.1) and 1018 on the SE051 (7.2.0).
// Bench-verified: size 0 fails on all parts; the cap is 880
// on the SE050C (3.1.1) and 1018 on the SE051 (7.2.0) and
// SE050E.
let mut store = ObjectStore::new();
for (version, max) in [
(AppletVersion::V3_1_1, 880u16),
(AppletVersion::V7_2_0, 1018u16),
(AppletVersion::V7_2_0E, 1018u16),
] {
let resp = handle(&random_apdu(0), &mut store, version);
assert_eq!(resp.sw, SW_CONDITIONS_NOT_SATISFIED);
Expand All @@ -136,7 +137,8 @@ mod tests {
#[test]
fn test_get_version_per_applet() {
// Bench-captured blobs: SE050C 3.1.1 -> 03 01 01 6f ff 01 0b,
// SE051 7.2.0 -> 07 02 00 3f ff ff ff.
// SE051 7.2.0 -> 07 02 00 3f ff ff ff, SE050E 7.2.0 ->
// 07 02 00 3f 9f ff ff (appletConfig without the RSA bits).
let mut store = ObjectStore::new();
let apdu = ParsedApdu {
cla: 0x80, ins: INS_MGMT, p1: P1_DEFAULT, p2: P2_VERSION,
Expand All @@ -148,23 +150,36 @@ mod tests {
let resp = handle(&apdu, &mut store, AppletVersion::V7_2_0);
let tlvs = crate::tlv::parse_tlvs(&resp.data).unwrap();
assert_eq!(tlvs[0].value, [0x07, 0x02, 0x00, 0x3F, 0xFF, 0xFF, 0xFF]);
let resp = handle(&apdu, &mut store, AppletVersion::V7_2_0E);
let tlvs = crate::tlv::parse_tlvs(&resp.data).unwrap();
assert_eq!(tlvs[0].value, [0x07, 0x02, 0x00, 0x3F, 0x9F, 0xFF, 0xFF]);
}

#[test]
fn test_get_free_memory_width_per_applet() {
// 3.x replies U16, 7.2 replies U32 (middleware parses per
// version); values as measured on the bench parts.
fn test_get_free_memory_values_per_applet() {
// All bench parts reply with a 2-byte value; per-type values
// as measured on the bench (SE050E clamps PERSISTENT at
// 0x7FFF). The v04.07.01 middleware rejects TLV values longer
// than 2 bytes for these applets (tlvGet_U16), so a 4-byte
// reply would make Se05x_API_GetFreeMemory fail host-side.
let mut store = ObjectStore::new();
let apdu = ParsedApdu {
cla: 0x80, ins: INS_MGMT, p1: P1_DEFAULT, p2: P2_MEMORY,
data: vec![TAG_1, 0x01, 0x01], le: None,
};
let resp = handle(&apdu, &mut store, AppletVersion::V3_1_1);
let tlvs = crate::tlv::parse_tlvs(&resp.data).unwrap();
assert_eq!(tlvs[0].value.len(), 2);
assert_eq!(u16::from_be_bytes([tlvs[0].value[0], tlvs[0].value[1]]), 31304);
let resp = handle(&apdu, &mut store, AppletVersion::V7_2_0);
let tlvs = crate::tlv::parse_tlvs(&resp.data).unwrap();
assert_eq!(tlvs[0].value.len(), 4);
for (version, persistent) in [
(AppletVersion::V3_1_1, 31304u16),
(AppletVersion::V7_2_0, 21000u16),
(AppletVersion::V7_2_0E, 32767u16),
] {
let apdu = ParsedApdu {
cla: 0x80, ins: INS_MGMT, p1: P1_DEFAULT, p2: P2_MEMORY,
data: vec![TAG_1, 0x01, 0x01], le: None,
};
let resp = handle(&apdu, &mut store, version);
let tlvs = crate::tlv::parse_tlvs(&resp.data).unwrap();
assert_eq!(tlvs[0].value.len(), 2, "{:?}", version);
assert_eq!(
u16::from_be_bytes([tlvs[0].value[0], tlvs[0].value[1]]),
persistent,
"{:?}", version
);
}
}
}
Loading
Loading