diff --git a/SE050Sim/se050-sim/src/applet.rs b/SE050Sim/se050-sim/src/applet.rs index f5e59cb..9fda316 100644 --- a/SE050Sim/se050-sim/src/applet.rs +++ b/SE050Sim/se050-sim/src/applet.rs @@ -21,23 +21,31 @@ /// 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 { @@ -45,17 +53,35 @@ pub enum AppletVersion { 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 } } @@ -63,32 +89,55 @@ impl AppletVersion { /// 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> { - 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, @@ -96,9 +145,58 @@ impl AppletVersion { 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()); + } } } diff --git a/SE050Sim/se050-sim/src/dispatch.rs b/SE050Sim/se050-sim/src/dispatch.rs index 425d0ea..9dbcf91 100644 --- a/SE050Sim/se050-sim/src/dispatch.rs +++ b/SE050Sim/se050-sim/src/dispatch.rs @@ -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 { @@ -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), 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), diff --git a/SE050Sim/se050-sim/src/handlers/curve.rs b/SE050Sim/se050-sim/src/handlers/curve.rs index ba0424b..e8ba18f 100644 --- a/SE050Sim/se050-sim/src/handlers/curve.rs +++ b/SE050Sim/se050-sim/src/handlers/curve.rs @@ -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 diff --git a/SE050Sim/se050-sim/src/handlers/management.rs b/SE050Sim/se050-sim/src/handlers/management.rs index dc67ff0..8f9524c 100644 --- a/SE050Sim/se050-sim/src/handlers/management.rs +++ b/SE050Sim/se050-sim/src/handlers/management.rs @@ -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, @@ -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, @@ -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); @@ -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, @@ -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 + ); + } } } diff --git a/SE050Sim/se050-sim/src/handlers/rsa.rs b/SE050Sim/se050-sim/src/handlers/rsa.rs index ae6f8a6..346d6e2 100644 --- a/SE050Sim/se050-sim/src/handlers/rsa.rs +++ b/SE050Sim/se050-sim/src/handlers/rsa.rs @@ -50,13 +50,32 @@ const TAG_RSA_PUB_MOD: u8 = 0x4A; // TAG_10 /// simulator accumulates components in `RSAKeyPair::staged` and materializes /// a PKCS#1 DER once the set is sufficient (N+E+D, or CRT primes+E+N). /// -/// P1 = `P1_RSA | key_part`, P2 = `rsa_format`. Tags 3–10 per the map above. -pub fn handle_write_rsa_key(apdu: &ParsedApdu, store: &mut ObjectStore) -> ApduResponse { +/// P1 = `P1_RSA | key_part`, P2 = `rsa_format`. Tags 3-10 per the map above. +/// +/// On a personality without RSA support (SE050E) the applet refuses +/// keygen with SW 0x6985 and any APDU carrying key material with SW +/// 0x6A80 (both bench-verified on real SE050E silicon). +pub fn handle_write_rsa_key( + apdu: &ParsedApdu, + store: &mut ObjectStore, + version: crate::applet::AppletVersion, +) -> ApduResponse { let tlvs = match apdu.parse_tlvs() { Ok(t) => t, Err(_) => return ApduResponse::error(SW_WRONG_DATA), }; + if !version.supports_rsa() { + let has_component = tlvs.iter().any(|t| { + (TAG_RSA_P..=TAG_RSA_PUB_MOD).contains(&t.tag) + }); + return ApduResponse::error(if has_component { + SW_WRONG_DATA + } else { + SW_CONDITIONS_NOT_SATISFIED + }); + } + let obj_id = match tlv::find_tlv(&tlvs, TAG_1) { Some(t) if t.value.len() == 4 => { let mut id = [0u8; 4]; @@ -536,4 +555,51 @@ mod tests { .expect("decrypt"); assert_eq!(recovered, plaintext); } + + fn write_rsa_apdu(data: Vec) -> crate::apdu::ParsedApdu { + crate::apdu::ParsedApdu { + cla: 0x80, + ins: crate::apdu::INS_WRITE, + p1: crate::apdu::P1_RSA, + p2: crate::apdu::P2_DEFAULT, + data, + le: None, + } + } + + #[test] + fn se050e_refuses_rsa_keygen_and_import() { + // Bench-verified on real SE050E silicon: keygen (size-only + // APDU) refuses 0x6985; any APDU carrying key material + // refuses 0x6A80. The SE051 personality still serves both. + use crate::applet::AppletVersion; + let mut store = ObjectStore::new(); + let obj_id = [0x7Fu8, 0x40, 0x00, 0x01]; + + let mut keygen = Vec::new(); + keygen.extend_from_slice(&Tlv::new(TAG_1, &obj_id).encode()); + keygen.extend_from_slice(&Tlv::new(TAG_2, &2048u16.to_be_bytes()).encode()); + let resp = handle_write_rsa_key( + &write_rsa_apdu(keygen), &mut store, AppletVersion::V7_2_0E); + assert_eq!(resp.sw, SW_CONDITIONS_NOT_SATISFIED); + assert!(!store.exists(&obj_id)); + + let mut import = Vec::new(); + import.extend_from_slice(&Tlv::new(TAG_1, &obj_id).encode()); + import.extend_from_slice(&Tlv::new(TAG_2, &2048u16.to_be_bytes()).encode()); + import.extend_from_slice(&Tlv::new(TAG_RSA_PUB_EXP, &[0x01, 0x00, 0x01]).encode()); + import.extend_from_slice(&Tlv::new(TAG_RSA_PUB_MOD, &[0xB1; 256]).encode()); + let resp = handle_write_rsa_key( + &write_rsa_apdu(import), &mut store, AppletVersion::V7_2_0E); + assert_eq!(resp.sw, SW_WRONG_DATA); + assert!(!store.exists(&obj_id)); + + let mut keygen_1k = Vec::new(); + keygen_1k.extend_from_slice(&Tlv::new(TAG_1, &obj_id).encode()); + keygen_1k.extend_from_slice(&Tlv::new(TAG_2, &1024u16.to_be_bytes()).encode()); + let resp = handle_write_rsa_key( + &write_rsa_apdu(keygen_1k), &mut store, AppletVersion::V7_2_0); + assert_eq!(resp.sw, 0x9000, "SE051 personality keeps RSA keygen"); + assert!(store.exists(&obj_id)); + } }