diff --git a/SE050Sim/se050-sim/src/applet.rs b/SE050Sim/se050-sim/src/applet.rs index 9fda316..ce52bc9 100644 --- a/SE050Sim/se050-sim/src/applet.rs +++ b/SE050Sim/se050-sim/src/applet.rs @@ -21,31 +21,39 @@ /// Applet personality selection. /// -/// The simulator can present itself as any of the three parts that were +/// The simulator can present itself as any of the four 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: +/// SE051 running applet 7.2.0, an SE050E running applet 7.2.0 with the +/// RSA feature bits disabled, or an SE052F running applet 7.2.22. Almost +/// all behavior is identical across the four; the differences the +/// simulator models are: /// -/// * 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. +/// * SELECT / GetVersion version bytes. The appletConfig word carries +/// the feature bits: SE051 0x3fff, SE050E 0x3f9f (clears RSA_PLAIN and +/// RSA_CRT), SE052F 0x26f2 (clears EDDSA, DH_MONT, DES, MIFARE and +/// RFU1 but keeps both RSA bits). +/// * GetFreeMemory reply *width* and per-type values. The 3.1.1, 7.2.0 +/// and SE050E parts all reply with a 2-byte value (the SE050E clamps +/// PERSISTENT at 0x7FFF). The SE052F replies with 4 bytes -- its +/// PERSISTENT figure of 86336 does not fit in a U16 at all. See +/// `free_memory_is_u32`. /// * GetRandom maximum request size (880 bytes on the SE050C, 1018 on -/// the SE051 and SE050E). +/// the SE051 and SE050E, 1003 on the SE052F). /// * 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). +/// on 3.x, curve-specific on 7.2 and later). +/// * CreateECCurve on an already existing curve: applets 7.2 and later +/// refuse 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. +/// fails its RSA test with WC_HW_E against real SE050E silicon. The +/// SE052F has RSA but restricts it -- see `rsa_min_key_bits` and +/// `supports_rsa_public_import`. +/// * Ed25519 / X25519: absent on the SE052F, which refuses key writes +/// on those curves with SW 0x6985 (see `supports_25519`). #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum AppletVersion { @@ -56,6 +64,11 @@ pub enum AppletVersion { /// SE050E, applet 7.2.0 with RSA disabled in appletConfig (ATR /// historical bytes also read "eSE051" on real parts). V7_2_0E, + /// SE052F, applet 7.2.22. Keeps RSA (with restrictions) but has no + /// Ed25519 / X25519, and is the only part whose GetFreeMemory reply + /// is 4 bytes wide. Real parts ship locked behind Platform SCP03 and + /// their ATR historical bytes also read "SE051". + V7_2_22F, } impl AppletVersion { @@ -70,13 +83,19 @@ impl AppletVersion { } } - /// 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. + /// Parse a personality token. Accepts "f", "52f", "se052f" -- any + /// value ending in "f" -- plus exactly "7.2.22", for the SE052F; "e", + /// "se050e", "7.2.0e" -- any value ending in "e" -- for the SE050E; + /// "3", "3.1.1" for the SE050C; and anything else ("7", "7.2", + /// "7.2.0", unrecognized) for the SE051. + /// + /// The suffix rules are checked before the leading-digit rule, so + /// "3f" selects the SE052F and "3e" the SE050E. pub fn from_token(token: &str) -> Self { let t = token.trim().to_ascii_lowercase(); - if t.ends_with('e') { + if t.ends_with('f') || t == "7.2.22" { + AppletVersion::V7_2_22F + } else if t.ends_with('e') { AppletVersion::V7_2_0E } else if t.starts_with('3') { AppletVersion::V3_1_1 @@ -91,25 +110,69 @@ impl AppletVersion { /// 03 01 01 6f ff 01 0b, SE051 applet 7.2.0 returns /// 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). + /// and RSA_CRT 0x0040), SE052F applet 7.2.22 returns + /// 07 02 16 26 f2 ff ff (appletConfig clears EDDSA 0x0004, + /// DH_MONT 0x0008, DES 0x0100, MIFARE 0x0800, RFU1 0x1000 and the + /// undocumented bit 0x0001, keeping both RSA bits). + /// + /// The patch byte matters beyond cosmetics: the v04.07.01 + /// middleware's SE05X_CHECK_52F_VERSION treats a patch of + /// 0x10..=0x1F as the SE052 family, which switches GetFreeMemory to + /// a U32 parse (see `free_memory_is_u32`). 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], + AppletVersion::V7_2_22F => [0x07, 0x02, 0x16, 0x26, 0xF2, 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 and SE050E). + /// SE051 7.2.0 and SE050E, 1003 on the SE052F). + /// + /// The SE052F figure was measured over Platform SCP03, which is the + /// only channel a real SE052F serves -- the part ships locked and + /// refuses plain GetRandom outright, so its plain-channel cap could + /// not be measured. R-MAC and R-ENC overhead is the likely reason it + /// sits below the 1018 the other 7.2 parts allow in plain mode. pub fn get_random_max(self) -> usize { match self { AppletVersion::V3_1_1 => 880, AppletVersion::V7_2_0 | AppletVersion::V7_2_0E => 1018, + AppletVersion::V7_2_22F => 1003, } } + /// Whether the applet supports the 25519 curves (Ed25519 signing and + /// X25519 key agreement). The SE052F's appletConfig clears both + /// EDDSA (0x0004) and DH_MONT (0x0008); bench-verified that key + /// generation on either curve is refused with SW 0x6985. Key import + /// on those curves is refused the same way here, which follows from + /// the feature bits being clear but was not separately measured. + pub fn supports_25519(self) -> bool { + !matches!(self, AppletVersion::V7_2_22F) + } + + /// Smallest RSA key size the applet will generate. Bench-verified on + /// the SE052F: 1024-bit CRT key generation is refused 0x6985 while + /// 2048-bit CRT generation (and RSASign with it) works. + pub fn rsa_min_key_bits(self) -> u16 { + match self { + AppletVersion::V7_2_22F => 2048, + _ => 1024, + } + } + + /// Whether the applet accepts an RSA *public* key import (modulus + + /// exponent). Bench-verified on the SE052F: refused with SW 0x6A80, + /// while key generation on the same part works. Private-component + /// import was not exercised on that part and is left enabled. + pub fn supports_rsa_public_import(self) -> bool { + !matches!(self, AppletVersion::V7_2_22F) + } + /// 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). @@ -126,18 +189,40 @@ impl AppletVersion { !matches!(self, AppletVersion::V3_1_1) } + /// Whether GetFreeMemory replies with a 4-byte (U32) value instead + /// of the usual 2-byte (U16) one. + /// + /// `Se05x_API_GetFreeMemory` picks its parser from + /// `SE05X_CHECK_52F_VERSION(applet_version)`, which tests + /// `(applet_version >> 8) & 0xFF` for the range 0x10..=0x1F. Because + /// `applet_version` is `major<<24 | minor<<16 | patch<<8`, that byte + /// is the applet *patch* number, which NXP overloads to mark the + /// SE052 family. Applet 7.2.22 (patch 0x16) therefore takes the + /// `tlvGet_U32` branch, while 3.1.1 / 7.2.0 / SE050E (patch 0x00) + /// take `tlvGet_U16`. + /// + /// Getting this wrong is a hard failure rather than a subtle one: + /// `tlvGet_U32` only accepts a 4-byte TLV value and `tlvGet_U16` + /// only a 2-byte one, so a mismatched width makes the host call fail + /// outright. Bench-confirmed on real SE052F silicon, where the call + /// returns 0x9000 through the U32 path. + pub fn free_memory_is_u32(self) -> bool { + matches!(self, AppletVersion::V7_2_22F) + } + /// GetFreeMemory reply for a memory type, as measured on the bench - /// 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.) + /// parts. The 3.1.1, 7.2.0 and SE050E parts reply with a 2-byte + /// big-endian value (the SE050E reports PERSISTENT clamped at + /// 0x7FFF); the SE052F replies with 4 bytes, and its PERSISTENT + /// figure of 86336 could not be expressed in 2 bytes at all, which + /// is precisely why the U32 path exists. pub fn free_memory_bytes(self, memory_type: u8) -> Option> { - let (persistent, transient_reset, transient_deselect): (u16, u16, u16) = + let (persistent, transient_reset, transient_deselect): (u32, u32, u32) = match self { AppletVersion::V3_1_1 => (31304, 575, 560), AppletVersion::V7_2_0 => (21000, 605, 592), AppletVersion::V7_2_0E => (32767, 796, 784), + AppletVersion::V7_2_22F => (86336, 1157, 1152), }; let value = match memory_type { 0x01 => persistent, @@ -145,7 +230,11 @@ impl AppletVersion { 0x03 => transient_deselect, _ => return None, }; - Some(value.to_be_bytes().to_vec()) + Some(if self.free_memory_is_u32() { + value.to_be_bytes().to_vec() + } else { + (value as u16).to_be_bytes().to_vec() + }) } } @@ -165,9 +254,21 @@ mod tests { ("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. + ("f", AppletVersion::V7_2_22F), + ("F", AppletVersion::V7_2_22F), + ("52f", AppletVersion::V7_2_22F), + ("se052f", AppletVersion::V7_2_22F), + ("SE052F", AppletVersion::V7_2_22F), + ("7.2.22", AppletVersion::V7_2_22F), + (" se052f ", AppletVersion::V7_2_22F), + // The version token matches exactly: a longer string that + // merely starts with it must not select the SE052F. + ("7.2.220", AppletVersion::V7_2_0), + ("7.2.22e", AppletVersion::V7_2_0E), + // The suffix rules take precedence over the leading-3 rule, + // matching the documented behavior. ("3e", AppletVersion::V7_2_0E), + ("3f", AppletVersion::V7_2_22F), (" se050e ", AppletVersion::V7_2_0E), ("bogus", AppletVersion::V7_2_0), ("", AppletVersion::V7_2_0), @@ -182,21 +283,54 @@ mod tests { 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. + // The SE052F keeps both RSA appletConfig bits, unlike the E. + assert!(AppletVersion::V7_2_22F.supports_rsa()); + // The SE050E and SE052F are 7.2-generation parts: they 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, + assert!(AppletVersion::V7_2_22F.is_v7()); + // 25519 curves: present everywhere except the SE052F. + assert!(AppletVersion::V3_1_1.supports_25519()); + assert!(AppletVersion::V7_2_0.supports_25519()); + assert!(AppletVersion::V7_2_0E.supports_25519()); + assert!(!AppletVersion::V7_2_22F.supports_25519()); + // RSA restrictions are SE052F-only. + assert_eq!(AppletVersion::V7_2_0.rsa_min_key_bits(), 1024); + assert_eq!(AppletVersion::V7_2_22F.rsa_min_key_bits(), 2048); + assert!(AppletVersion::V7_2_0.supports_rsa_public_import()); + assert!(!AppletVersion::V7_2_22F.supports_rsa_public_import()); + for mem_type in [0x01u8, 0x02, 0x03] { + assert!(AppletVersion::V7_2_22F.free_memory_bytes(mem_type).is_some()); + } + } + + #[test] + fn test_free_memory_width_per_applet() { + // A mismatched width is a hard host-side failure: tlvGet_U16 + // rejects a 4-byte value and tlvGet_U32 rejects a 2-byte one. + for (v, want_len) in [ + (AppletVersion::V3_1_1, 2usize), + (AppletVersion::V7_2_0, 2), + (AppletVersion::V7_2_0E, 2), + (AppletVersion::V7_2_22F, 4), ] { for mem_type in [0x01u8, 0x02, 0x03] { - assert_eq!(v.free_memory_bytes(mem_type).unwrap().len(), 2); + assert_eq!( + v.free_memory_bytes(mem_type).unwrap().len(), + want_len, + "{:?} type {:#04x}", v, mem_type + ); } - assert!(v.free_memory_bytes(0x04).is_none()); + assert!(v.free_memory_bytes(0x04).is_none(), "{:?}", v); } + // The SE052F's PERSISTENT figure does not fit in a U16, which is + // why the applet reports it over four bytes in the first place. + let bytes = AppletVersion::V7_2_22F.free_memory_bytes(0x01).unwrap(); + assert_eq!(bytes, vec![0x00, 0x01, 0x51, 0x40]); + assert_eq!(u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]), + 86336); + assert!(86336u32 > u16::MAX as u32); } } diff --git a/SE050Sim/se050-sim/src/dispatch.rs b/SE050Sim/se050-sim/src/dispatch.rs index 6cf222b..4f67663 100644 --- a/SE050Sim/se050-sim/src/dispatch.rs +++ b/SE050Sim/se050-sim/src/dispatch.rs @@ -54,7 +54,7 @@ pub fn dispatch(apdu: &ParsedApdu, store: &mut ObjectStore, scp_active: bool) -> match base_ins { INS_WRITE => match cred_type { - P1_EC => handlers::ec::handle_write_ec_key(apdu, store), + P1_EC => handlers::ec::handle_write_ec_key(apdu, store, version), 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), diff --git a/SE050Sim/se050-sim/src/handlers/curve.rs b/SE050Sim/se050-sim/src/handlers/curve.rs index e8ba18f..ebe4c35 100644 --- a/SE050Sim/se050-sim/src/handlers/curve.rs +++ b/SE050Sim/se050-sim/src/handlers/curve.rs @@ -70,9 +70,12 @@ pub fn handle_create( } if store.curve_exists(curve_id) { return match version { - // 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 => { + // Bench-verified on the SE051, SE050E and SE052F: + // re-creating an existing curve is refused and the curve is + // left intact. + AppletVersion::V7_2_0 + | AppletVersion::V7_2_0E + | AppletVersion::V7_2_22F => { ApduResponse::error(SW_CONDITIONS_NOT_SATISFIED) } // Bench-verified on the SE050C: the duplicate create is diff --git a/SE050Sim/se050-sim/src/handlers/ec.rs b/SE050Sim/se050-sim/src/handlers/ec.rs index 1ed552b..301db78 100644 --- a/SE050Sim/se050-sim/src/handlers/ec.rs +++ b/SE050Sim/se050-sim/src/handlers/ec.rs @@ -48,7 +48,11 @@ fn pad_hash(data: &[u8], scalar_len: usize) -> Vec { } /// Handle WRITE EC key command (key generation when P2=Default and no private key data). -pub fn handle_write_ec_key(apdu: &ParsedApdu, store: &mut ObjectStore) -> ApduResponse { +pub fn handle_write_ec_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), @@ -73,6 +77,17 @@ pub fn handle_write_ec_key(apdu: &ParsedApdu, store: &mut ObjectStore) -> ApduRe _ => return ApduResponse::error(SW_WRONG_DATA), }; + // The SE052F's appletConfig clears both the EDDSA and DH_MONT + // feature bits, and the part refuses key generation on either 25519 + // curve with 0x6985 (bench-verified). Imports are refused here too, + // which follows from the feature being absent rather than from a + // separate measurement. + if matches!(curve, ECCurve::Ed25519 | ECCurve::Curve25519) + && !version.supports_25519() + { + return ApduResponse::error(SW_CONDITIONS_NOT_SATISFIED); + } + // Weierstrass curves must exist as fully parameterized curve // objects before any key can be created on them; key generation on // a missing or param-less curve fails 0x6985 (bench-verified on @@ -1270,3 +1285,54 @@ mod test_ed25519_vector { "Ed25519 signature mismatch for empty message"); } } + +#[cfg(test)] +mod test_se052f_curves { + use super::*; + use crate::applet::AppletVersion; + + fn keygen_apdu(obj_id: [u8; 4], curve_byte: u8) -> ParsedApdu { + let mut data = Vec::new(); + data.extend_from_slice(&Tlv::new(TAG_1, &obj_id).encode()); + data.extend_from_slice(&Tlv::new(TAG_2, &[curve_byte]).encode()); + ParsedApdu { + cla: 0x80, + ins: crate::apdu::INS_WRITE, + p1: crate::apdu::P1_EC | P1_KEY_PAIR, + p2: crate::apdu::P2_DEFAULT, + data, + le: None, + } + } + + #[test] + fn se052f_refuses_25519_curves() { + // Bench-verified on real SE052F silicon: its appletConfig + // (0x26f2) clears EDDSA and DH_MONT, and key generation on + // either 25519 curve is refused 0x6985. NIST curves still work, + // and the other personalities keep serving 25519. + let mut store = ObjectStore::new(); + let obj_id = [0x7Fu8, 0x50, 0x00, 0x01]; + + for (name, curve_byte) in [("Ed25519", 0x40u8), ("X25519", 0x41)] { + let resp = handle_write_ec_key( + &keygen_apdu(obj_id, curve_byte), &mut store, + AppletVersion::V7_2_22F); + assert_eq!(resp.sw, SW_CONDITIONS_NOT_SATISFIED, "{}", name); + assert!(!store.exists(&obj_id), "{} must not be created", name); + + let resp = handle_write_ec_key( + &keygen_apdu(obj_id, curve_byte), &mut store, + AppletVersion::V7_2_0); + assert_eq!(resp.sw, 0x9000, "{} on the SE051 personality", name); + assert!(store.exists(&obj_id)); + store.remove(&obj_id); + } + + // P-256 (curve id 0x03) is unaffected on the SE052F. + let resp = handle_write_ec_key( + &keygen_apdu(obj_id, 0x03), &mut store, AppletVersion::V7_2_22F); + assert_eq!(resp.sw, 0x9000, "SE052F still generates NIST keys"); + assert!(store.exists(&obj_id)); + } +} diff --git a/SE050Sim/se050-sim/src/handlers/management.rs b/SE050Sim/se050-sim/src/handlers/management.rs index 343d1ba..852f206 100644 --- a/SE050Sim/se050-sim/src/handlers/management.rs +++ b/SE050Sim/se050-sim/src/handlers/management.rs @@ -155,6 +155,9 @@ mod tests { (AppletVersion::V3_1_1, 880u16), (AppletVersion::V7_2_0, 1018u16), (AppletVersion::V7_2_0E, 1018u16), + // SE052F, measured over Platform SCP03 (the only channel a + // real SE052F serves). + (AppletVersion::V7_2_22F, 1003u16), ] { let resp = handle(&random_apdu(0), &mut store, version); assert_eq!(resp.sw, SW_CONDITIONS_NOT_SATISFIED); @@ -186,15 +189,20 @@ mod tests { 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]); + let resp = handle(&apdu, &mut store, AppletVersion::V7_2_22F); + let tlvs = crate::tlv::parse_tlvs(&resp.data).unwrap(); + assert_eq!(tlvs[0].value, [0x07, 0x02, 0x16, 0x26, 0xF2, 0xFF, 0xFF]); } #[test] 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. + // Per-type values as measured on the bench (the SE050E clamps + // PERSISTENT at 0x7FFF). The reply width is applet-dependent and + // load-bearing: the v04.07.01 middleware parses these applets + // with tlvGet_U16, which rejects any TLV value longer than 2 + // bytes, so a 4-byte reply here would make + // Se05x_API_GetFreeMemory fail host-side. The SE052F is the + // opposite case and is covered separately below. let mut store = ObjectStore::new(); for (version, persistent) in [ (AppletVersion::V3_1_1, 31304u16), @@ -215,4 +223,26 @@ mod tests { ); } } + + #[test] + fn test_get_free_memory_is_u32_on_se052f() { + // Applet 7.2.22 has patch byte 0x16, so SE05X_CHECK_52F_VERSION + // routes the host to tlvGet_U32, which only accepts a 4-byte + // value. Bench-confirmed on SE052F silicon: 86336/1157/1152, + // and PERSISTENT is above 0xFFFF so it could not be reported + // over two bytes at all. + let mut store = ObjectStore::new(); + for (mem_type, expected) in [(0x01u8, 86336u32), (0x02, 1157), (0x03, 1152)] { + let apdu = ParsedApdu { + cla: 0x80, ins: INS_MGMT, p1: P1_DEFAULT, p2: P2_MEMORY, + data: vec![TAG_1, 0x01, mem_type], le: None, + }; + let resp = handle(&apdu, &mut store, AppletVersion::V7_2_22F); + assert_eq!(resp.sw, 0x9000); + let tlvs = crate::tlv::parse_tlvs(&resp.data).unwrap(); + let v = &tlvs[0].value; + assert_eq!(v.len(), 4, "memory type {:#04x}", mem_type); + assert_eq!(u32::from_be_bytes([v[0], v[1], v[2], v[3]]), expected); + } + } } diff --git a/SE050Sim/se050-sim/src/handlers/rsa.rs b/SE050Sim/se050-sim/src/handlers/rsa.rs index 346d6e2..856fb80 100644 --- a/SE050Sim/se050-sim/src/handlers/rsa.rs +++ b/SE050Sim/se050-sim/src/handlers/rsa.rs @@ -76,6 +76,40 @@ pub fn handle_write_rsa_key( }); } + // The SE052F has RSA but refuses a public-key import with 0x6A80, + // while generating a key pair on the same part works. Bench-verified + // with the key part set to Public and modulus + exponent in one + // APDU. + // + // Two APDU shapes mean "import a public key", and both are refused + // so the restriction cannot be sidestepped: the applet's own + // discriminator (the P1 key-part bits), and an APDU that simply + // carries a complete public key -- modulus and exponent -- with no + // private component. The second form was not exercised on silicon; + // it is refused because it is the same operation reached through a + // different encoding. + // + // The private-key import path is deliberately left alone. The + // wolfCrypt port imports (E, D, N) across three separate APDUs, so + // no single APDU there carries modulus and exponent together + // without private material, and none of them sets the public key + // part. Refusing on the mere presence of a public component would + // break that flow at its first APDU. + if !version.supports_rsa_public_import() { + let has_private_component = [ + TAG_RSA_P, TAG_RSA_Q, TAG_RSA_DP, TAG_RSA_DQ, TAG_RSA_QINV, + TAG_RSA_PRIV, + ] + .iter() + .any(|tag| tlv::find_tlv(&tlvs, *tag).is_some()); + let carries_whole_public_key = tlv::find_tlv(&tlvs, TAG_RSA_PUB_MOD).is_some() + && tlv::find_tlv(&tlvs, TAG_RSA_PUB_EXP).is_some() + && !has_private_component; + if apdu.key_type() == P1_PUBLIC_KEY || carries_whole_public_key { + return ApduResponse::error(SW_WRONG_DATA); + } + } + let obj_id = match tlv::find_tlv(&tlvs, TAG_1) { Some(t) if t.value.len() == 4 => { let mut id = [0u8; 4]; @@ -115,6 +149,11 @@ pub fn handle_write_rsa_key( if ![1024, 2048, 3072, 4096].contains(&key_size_usize) { return ApduResponse::error(SW_WRONG_DATA); } + // The SE052F refuses anything below 2048 bits with 0x6985 + // (bench-verified: 1024-bit CRT generation fails, 2048 works). + if key_size_bits < version.rsa_min_key_bits() { + return ApduResponse::error(SW_CONDITIONS_NOT_SATISFIED); + } let Ok(private_key) = RsaPrivateKey::new(&mut OsRng, key_size_usize) else { return ApduResponse::error(SW_CONDITIONS_NOT_SATISFIED); }; @@ -602,4 +641,132 @@ mod tests { assert_eq!(resp.sw, 0x9000, "SE051 personality keeps RSA keygen"); assert!(store.exists(&obj_id)); } + + fn write_rsa_pub_apdu(data: Vec) -> crate::apdu::ParsedApdu { + crate::apdu::ParsedApdu { + cla: 0x80, + ins: crate::apdu::INS_WRITE, + p1: crate::apdu::P1_RSA | crate::apdu::P1_PUBLIC_KEY, + p2: crate::apdu::P2_DEFAULT, + data, + le: None, + } + } + + #[test] + fn se052f_rsa_restrictions() { + // Bench-verified on real SE052F silicon: RSA is present (unlike + // the SE050E) but 1024-bit generation refuses 0x6985, 2048-bit + // generation works, and a public-key import refuses 0x6A80. + use crate::applet::AppletVersion; + let mut store = ObjectStore::new(); + let obj_id = [0x7Fu8, 0x40, 0x00, 0x02]; + + 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_22F); + assert_eq!(resp.sw, SW_CONDITIONS_NOT_SATISFIED, + "SE052F refuses RSA below 2048 bits"); + assert!(!store.exists(&obj_id)); + + let mut pub_import = Vec::new(); + pub_import.extend_from_slice(&Tlv::new(TAG_1, &obj_id).encode()); + pub_import.extend_from_slice(&Tlv::new(TAG_2, &2048u16.to_be_bytes()).encode()); + pub_import.extend_from_slice( + &Tlv::new(TAG_RSA_PUB_EXP, &[0x01, 0x00, 0x01]).encode()); + pub_import.extend_from_slice(&Tlv::new(TAG_RSA_PUB_MOD, &[0xB1; 256]).encode()); + let resp = handle_write_rsa_key( + &write_rsa_pub_apdu(pub_import.clone()), &mut store, + AppletVersion::V7_2_22F); + assert_eq!(resp.sw, SW_WRONG_DATA, + "SE052F refuses RSA public key import"); + assert!(!store.exists(&obj_id)); + + // The same import is served by the SE051 personality. + let resp = handle_write_rsa_key( + &write_rsa_pub_apdu(pub_import), &mut store, AppletVersion::V7_2_0); + assert_eq!(resp.sw, 0x9000); + assert!(store.exists(&obj_id)); + + // 2048-bit generation works on the SE052F. + let obj_2k = [0x7Fu8, 0x40, 0x00, 0x03]; + let mut keygen_2k = Vec::new(); + keygen_2k.extend_from_slice(&Tlv::new(TAG_1, &obj_2k).encode()); + keygen_2k.extend_from_slice(&Tlv::new(TAG_2, &2048u16.to_be_bytes()).encode()); + let resp = handle_write_rsa_key( + &write_rsa_apdu(keygen_2k), &mut store, AppletVersion::V7_2_22F); + assert_eq!(resp.sw, 0x9000, "SE052F generates 2048-bit RSA keys"); + assert!(store.exists(&obj_2k)); + } + + #[test] + fn se052f_public_import_refused_without_key_part_bits() { + // The key-part bits are the applet's own discriminator, but an + // APDU carrying a whole public key means the same thing even + // with those bits clear. Refusing only on the bits would let a + // caller sidestep the SE052F restriction. + use crate::applet::AppletVersion; + let mut store = ObjectStore::new(); + let obj_id = [0x7Fu8, 0x40, 0x00, 0x04]; + + 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()); + + // write_rsa_apdu leaves P1 as bare P1_RSA: no key-part bits. + let resp = handle_write_rsa_key( + &write_rsa_apdu(import.clone()), &mut store, AppletVersion::V7_2_22F); + assert_eq!(resp.sw, SW_WRONG_DATA); + assert!(!store.exists(&obj_id), "no public key object may be created"); + + // Unaffected on a personality that allows public import. + let resp = handle_write_rsa_key( + &write_rsa_apdu(import), &mut store, AppletVersion::V7_2_0); + assert_eq!(resp.sw, 0x9000); + assert!(store.exists(&obj_id)); + } + + #[test] + fn se052f_staged_private_import_still_works() { + // The wolfCrypt port imports a private key as (E, D, N) across + // three separate APDUs. None of them carries modulus and + // exponent together without private material, so the SE052F + // public-import refusal must not touch this flow -- refusing on + // the mere presence of a public component would break it at the + // very first APDU. + use crate::applet::AppletVersion; + let mut store = ObjectStore::new(); + let obj_id = [0x7Fu8, 0x40, 0x00, 0x05]; + + let step = |tag: u8, value: &[u8]| { + let mut data = Vec::new(); + data.extend_from_slice(&Tlv::new(TAG_1, &obj_id).encode()); + data.extend_from_slice(&Tlv::new(TAG_2, &2048u16.to_be_bytes()).encode()); + data.extend_from_slice(&Tlv::new(tag, value).encode()); + crate::apdu::ParsedApdu { + cla: 0x80, + ins: crate::apdu::INS_WRITE, + p1: crate::apdu::P1_RSA | crate::apdu::P1_KEY_PAIR, + p2: crate::apdu::P2_DEFAULT, + data, + le: None, + } + }; + + for (name, tag, value) in [ + ("exponent", TAG_RSA_PUB_EXP, vec![0x01, 0x00, 0x01]), + ("private exponent", TAG_RSA_PRIV, vec![0xC3; 256]), + ("modulus", TAG_RSA_PUB_MOD, vec![0xB1; 256]), + ] { + let resp = handle_write_rsa_key( + &step(tag, &value), &mut store, AppletVersion::V7_2_22F); + assert_eq!(resp.sw, 0x9000, "SE052F staged import step: {}", name); + } + assert!(store.exists(&obj_id)); + } }