diff --git a/SE050Sim/README.md b/SE050Sim/README.md index 8b90072..84db93d 100644 --- a/SE050Sim/README.md +++ b/SE050Sim/README.md @@ -18,6 +18,8 @@ A software simulator for the NXP SE050 secure element, implementing the full I2C - Persistent object store (JSON file on disk) - WriteBinary, ReadObject, CheckObjectExists, DeleteSecureObject - ReadIDList, ReadType, ReadSize +- Per-object access policies, including read, write, delete, use, and secure-channel requirements +- ReadObjectAttributes policy and origin reporting - UserID, Counter objects - Crypto object lifecycle (Create, List, Delete) - EC public key import and verification @@ -46,7 +48,7 @@ docker build -f Dockerfile.sdk-test -t se050-sim-sdk-test . docker run se050-sim-sdk-test ``` -This tests the simulator through the NXP Plug&Trust SDK's SSS API, with independent verification using OpenSSL. **All 18 tests pass.** See [SDK Test Suite](#sdk-test-suite) for details. +This tests the simulator through the NXP Plug&Trust SDK's SSS API, with independent verification using OpenSSL. **All 32 tests pass.** See [SDK Test Suite](#sdk-test-suite) for details. ### Run the wolfCrypt test suite @@ -133,6 +135,7 @@ SE050Sim/ ├── i2c_a7.c Custom PAL: TCP socket transport ├── se05x_reset.c No-op reset stub for Docker ├── main.c wolfCrypt test wrapper with SE050 init + ├── test_api_improvements.c Focused policy/session/SCP03 API smoke test ├── CMakeLists.txt SDK library build ├── patch_ftr.py Enable EC curve features in SDK └── run_test.sh Test runner script @@ -144,7 +147,7 @@ The simulator has an independent test suite that uses the NXP Plug&Trust SDK's S ### Test results -All 30 tests pass: +All 32 tests pass: | Test | Description | |------|-------------| @@ -329,9 +332,15 @@ over the secure channel against the simulator in CI (`sdk-test-scp03` and `--build-arg SE05X_AUTH=PlatfSCP03` (default `None` keeps plain mode). - `SetPlatformSCPRequest` is modelled: setting SCP_REQUIRED inside a session makes plain commands fail 0x6985 (persisted). +- GlobalPlatform PUT KEY is modelled for platform SCP03 key rotation. It must + be sent through an active secure channel targeting the NXP Supplementary + Security Domain; an applet-targeted PUT KEY returns `0x6A80`, matching the + hardware. The simulator unwraps the ENC/MAC/DEK values with the current DEK, + checks each supplied KCV, and persists the new key set for subsequent + connections. The wolfSSL API-improvement smoke test exercises both + explicit-key and HKDF-seed rotation, then reconnects with the new keys. -Not modelled: SCP02, ECKey / AppletSCP03 authenticated sessions, and PUT KEY -(key rotation). +Not modelled: SCP02 and ECKey / AppletSCP03 authenticated sessions. ### Implementation notes (worth knowing before you change this code) @@ -376,6 +385,9 @@ these; treat them as required coverage, not a nice-to-have. - The `SE050_SIM_SCP03_ENC/_MAC` static keys default to well-known NXP development keys; do not treat a simulated SCP03 channel as confidential. +- The persisted simulator state contains the active platform SCP03 keys in + plaintext. This is intentional for test reproducibility and is not a model + for production key storage. ## License diff --git a/SE050Sim/sdk-test/test_se050.c b/SE050Sim/sdk-test/test_se050.c index f51f7bd..6655db3 100644 --- a/SE050Sim/sdk-test/test_se050.c +++ b/SE050Sim/sdk-test/test_se050.c @@ -1764,6 +1764,75 @@ static void test_object_delete(void) TEST_PASS(); } +/* ====================================================================== + * Test: immutable object policy, attributes, no-write and no-delete + * ====================================================================== */ +static void test_object_policy(void) +{ + TEST_BEGIN("Object-policy-no-write-no-delete"); + sss_status_t status; + sss_se05x_object_t obj; + sss_policy_u common; + sss_policy_u file; + sss_policy_t policy; + uint32_t obj_id = OBJ_ID_BASE + 202; + uint8_t data[] = "protected"; + uint8_t replacement[] = "replaced!"; + SE05x_Result_t exists = kSE05x_Result_FAILURE; +#if SSS_HAVE_SE05X_VER_GTE_07_02 + uint8_t attributes[MAX_POLICY_BUFFER_SIZE + 32] = {0}; + size_t attributes_len = sizeof(attributes); +#endif + + memset(&common, 0, sizeof(common)); + memset(&file, 0, sizeof(file)); + memset(&policy, 0, sizeof(policy)); + common.type = KPolicy_Common; + common.auth_obj_id = 0; + common.policy.common.can_Read = 1; + file.type = KPolicy_File; + file.auth_obj_id = 0; + file.policy.file.can_Read = 1; + policy.policies[0] = &common; + policy.policies[1] = &file; + policy.nPolicies = 2; + + cleanup_object(obj_id); + sss_key_object_init(&obj, &g_ks); + status = sss_key_object_allocate_handle(&obj, obj_id, + kSSS_KeyPart_Default, kSSS_CipherType_Binary, sizeof(data), + kKeyObject_Mode_Persistent); + ASSERT_OK(status, "policy object allocate"); + + status = sss_key_store_set_key(&g_ks, &obj, data, sizeof(data), + sizeof(data) * 8, &policy, 0); + ASSERT_OK(status, "policy object create"); + +#if SSS_HAVE_SE05X_VER_GTE_07_02 + status = (sss_status_t)Se05x_API_ReadObjectAttributes(&g_session->s_ctx, + obj_id, attributes, &attributes_len); + ASSERT_EQ(status, SM_OK, "ReadObjectAttributes"); + ASSERT_EQ(attributes[14], 8, "policy entry length"); + ASSERT_EQ(attributes[19], 0x00, "policy header byte 1"); + ASSERT_EQ(attributes[20], 0x20, "policy read permission"); + ASSERT_EQ(attributes[21], 0x00, "policy header byte 3"); + ASSERT_EQ(attributes[22], 0x00, "policy header byte 4"); + ASSERT_EQ(attributes[23], kSE05x_Origin_EXTERNAL, "object origin"); +#endif + + status = sss_key_store_set_key(&g_ks, &obj, replacement, + sizeof(replacement), sizeof(replacement) * 8, NULL, 0); + ASSERT_EQ(status, kStatus_SSS_Fail, "no-write object was overwritten"); + + status = sss_key_store_erase_key(&g_ks, &obj); + ASSERT_EQ(status, kStatus_SSS_Fail, "no-delete object was erased"); + Se05x_API_CheckObjectExists(&g_session->s_ctx, obj_id, &exists); + ASSERT_EQ(exists, kSE05x_Result_SUCCESS, "protected object disappeared"); + + sss_key_object_free(&obj); + TEST_PASS(); +} + /* ====================================================================== * Main * ====================================================================== */ @@ -1845,6 +1914,7 @@ int main(void) /* Object management */ test_object_write_read(); test_object_delete(); + test_object_policy(); /* Summary */ test_summary(); diff --git a/SE050Sim/se050-sim/src/apdu.rs b/SE050Sim/se050-sim/src/apdu.rs index 996c165..0581e01 100644 --- a/SE050Sim/se050-sim/src/apdu.rs +++ b/SE050Sim/se050-sim/src/apdu.rs @@ -220,6 +220,7 @@ pub const P2_DELETE_ALL: u8 = 0x2A; pub const P2_ID: u8 = 0x36; pub const P2_ENCRYPT_ONESHOT: u8 = 0x37; pub const P2_DECRYPT_ONESHOT: u8 = 0x38; +pub const P2_ATTRIBUTES: u8 = 0x3B; pub const P2_PARAM: u8 = 0x40; pub const P2_ENCRYPT_INIT: u8 = 0x42; pub const P2_DECRYPT_INIT: u8 = 0x43; diff --git a/SE050Sim/se050-sim/src/dispatch.rs b/SE050Sim/se050-sim/src/dispatch.rs index 4f67663..2661110 100644 --- a/SE050Sim/se050-sim/src/dispatch.rs +++ b/SE050Sim/se050-sim/src/dispatch.rs @@ -85,6 +85,11 @@ pub fn dispatch(apdu: &ParsedApdu, store: &mut ObjectStore, scp_active: bool) -> let component = crate::tlv::find_tlv(&tlvs, crate::tlv::TAG_4) .and_then(|t| t.value.first().copied()) .unwrap_or(0); + if let Some(id) = obj_id { + if !store.policy_allows(&id, crate::policy::POLICY_OBJ_ALLOW_READ) { + return ApduResponse::error(SW_COMMAND_NOT_ALLOWED); + } + } match obj_id.and_then(|id| store.get(&id)) { Some(crate::object_store::types::SecureObject::RSAKeyPair { private_key_der, .. }) => { use rsa::pkcs1::DecodeRsaPrivateKey; diff --git a/SE050Sim/se050-sim/src/handlers/aes.rs b/SE050Sim/se050-sim/src/handlers/aes.rs index e852b09..c188faf 100644 --- a/SE050Sim/se050-sim/src/handlers/aes.rs +++ b/SE050Sim/se050-sim/src/handlers/aes.rs @@ -37,7 +37,7 @@ use crate::apdu::*; use crate::object_store::types::SecureObject; use crate::object_store::{CryptoObjectState, ObjectStore}; -use crate::tlv::{self, Tlv, TAG_1, TAG_2, TAG_3, TAG_4, TAG_POLICY}; +use crate::tlv::{self, Tlv, TAG_1, TAG_2, TAG_3, TAG_4}; use aes::cipher::generic_array::GenericArray; use aes::cipher::{BlockDecrypt, BlockEncrypt, KeyInit}; @@ -73,7 +73,7 @@ impl AnyAes { } } - fn decrypt_block(&self, block: &mut [u8; 16]) { + pub(crate) fn decrypt_block(&self, block: &mut [u8; 16]) { let ga = GenericArray::from_mut_slice(block); match self { AnyAes::A128(c) => c.decrypt_block(ga), @@ -199,12 +199,24 @@ pub fn handle_write_aes_key(apdu: &ParsedApdu, store: &mut ObjectStore) -> ApduR } _ => return ApduResponse::error(SW_WRONG_DATA), }; + let creation_policy = match crate::policy::creation_policy(&tlvs) { + Ok(policy) => policy, + Err(_) => return ApduResponse::error(SW_WRONG_DATA), + }; + let object_existed = store.exists(&obj_id); + if object_existed + && !store.policy_allows(&obj_id, crate::policy::POLICY_OBJ_ALLOW_WRITE) + { + return ApduResponse::error(SW_CONDITIONS_NOT_SATISFIED); + } // Check if key data is provided in Tag3 let key_data = tlv::find_tlv(&tlvs, TAG_3).map(|t| t.value.clone()); // Check if this is key generation (P2=Generate) or has a key size tag - if apdu.p2 == P2_GENERATE || key_data.as_ref().map_or(false, |d| d.len() <= 2) { + let generated = apdu.p2 == P2_GENERATE + || key_data.as_ref().is_some_and(|d| d.len() <= 2); + let response = if generated { // Key generation: Tag3 contains 2-byte key size let key_len = key_data .as_ref() @@ -233,7 +245,15 @@ pub fn handle_write_aes_key(apdu: &ParsedApdu, store: &mut ObjectStore) -> ApduR ApduResponse::success() } else { ApduResponse::error(SW_WRONG_DATA) - } + }; + if response.sw == SW_NO_ERROR && !object_existed { + store.set_creation_metadata( + obj_id, + creation_policy, + if generated { 0x02 } else { 0x01 }, + ); + } + response } /// Handle WRITE HMAC key command (WriteSymmKey with P1=HMAC). @@ -257,20 +277,33 @@ pub fn handle_write_hmac_key(apdu: &ParsedApdu, store: &mut ObjectStore) -> Apdu _ => return ApduResponse::error(SW_WRONG_DATA), }; - // A present but malformed (or empty) policy TLV is rejected up front, - // like the applet would, rather than being recorded as "no policy" and - // surfacing later as a strict-mode read denial. - let policy = match tlv::find_tlv(&tlvs, TAG_POLICY) { - Some(t) => match crate::policy::ar_header_union(&t.value) { - Some(header) => Some(header), - None => return ApduResponse::error(SW_WRONG_DATA), - }, - None => None, + // An empty policy TLV is not a valid HMAC derive-target policy on the + // applet, even though other SDK wrappers use it to mean "not supplied". + if tlv::find_tlv(&tlvs, crate::tlv::TAG_POLICY) + .is_some_and(|tlv| tlv.value.is_empty()) + { + return ApduResponse::error(SW_WRONG_DATA); + } + let creation_policy = match crate::policy::creation_policy(&tlvs) { + Ok(policy) => policy, + Err(_) => return ApduResponse::error(SW_WRONG_DATA), }; + let policy = creation_policy + .as_deref() + .and_then(crate::policy::ar_header_union); + let object_existed = store.exists(&obj_id); + if object_existed + && !store.policy_allows(&obj_id, crate::policy::POLICY_OBJ_ALLOW_WRITE) + { + return ApduResponse::error(SW_CONDITIONS_NOT_SATISFIED); + } match tlv::find_tlv(&tlvs, TAG_3) { Some(t) if !t.value.is_empty() => { store.insert(obj_id, SecureObject::HMACKey { key: t.value.clone(), policy }); + if !object_existed { + store.set_creation_metadata(obj_id, creation_policy, 0x01); + } ApduResponse::success() } _ => ApduResponse::error(SW_WRONG_DATA), diff --git a/SE050Sim/se050-sim/src/handlers/ec.rs b/SE050Sim/se050-sim/src/handlers/ec.rs index 301db78..bff0a7d 100644 --- a/SE050Sim/se050-sim/src/handlers/ec.rs +++ b/SE050Sim/se050-sim/src/handlers/ec.rs @@ -67,6 +67,16 @@ pub fn handle_write_ec_key( } _ => return ApduResponse::error(SW_WRONG_DATA), }; + let creation_policy = match crate::policy::creation_policy(&tlvs) { + Ok(policy) => policy, + Err(_) => return ApduResponse::error(SW_WRONG_DATA), + }; + let object_existed = store.exists(&obj_id); + if object_existed + && !store.policy_allows(&obj_id, crate::policy::POLICY_OBJ_ALLOW_WRITE) + { + return ApduResponse::error(SW_CONDITIONS_NOT_SATISFIED); + } // Extract curve from Tag2 let (curve_byte, curve) = match tlv::find_tlv(&tlvs, TAG_2) { @@ -102,7 +112,8 @@ pub fn handle_write_ec_key( .or_else(|| tlv::find_tlv(&tlvs, TAG_2).filter(|t| t.value.len() > 4)) .map(|t| t.value.clone()); - if apdu.key_type() == P1_KEY_PAIR && private_key_data.is_none() { + let generated = apdu.key_type() == P1_KEY_PAIR && private_key_data.is_none(); + let response = if generated { // Generate a new key pair match curve { ECCurve::NistP192 => generate_p192_keypair(obj_id, store), @@ -129,7 +140,15 @@ pub fn handle_write_ec_key( ApduResponse::success() } else { ApduResponse::error(SW_WRONG_DATA) + }; + if response.sw == SW_NO_ERROR && !object_existed { + store.set_creation_metadata( + obj_id, + creation_policy, + if generated { 0x02 } else { 0x01 }, + ); } + response } fn generate_p192_keypair(obj_id: [u8; 4], store: &mut ObjectStore) -> ApduResponse { diff --git a/SE050Sim/se050-sim/src/handlers/object_mgmt.rs b/SE050Sim/se050-sim/src/handlers/object_mgmt.rs index 6d717ce..8ffb5d3 100644 --- a/SE050Sim/se050-sim/src/handlers/object_mgmt.rs +++ b/SE050Sim/se050-sim/src/handlers/object_mgmt.rs @@ -51,6 +51,7 @@ pub fn handle_read(apdu: &ParsedApdu, store: &mut ObjectStore, v7: bool) -> Apdu P2_SIZE => handle_read_size(apdu, store), P2_LIST => handle_read_id_list(apdu, store, v7), P2_TYPE => handle_read_type(apdu, store, v7), + P2_ATTRIBUTES => handle_read_attributes(apdu, store, v7), _ => ApduResponse::error(SW_WRONG_P1P2), } } @@ -91,7 +92,7 @@ fn handle_write_binary(apdu: &ParsedApdu, store: &mut ObjectStore) -> ApduRespon for tlv in &tlvs { match tlv.tag { - TAG_POLICY => {} // Skip policy + TAG_POLICY => {} TAG_1 if obj_id.is_none() && tlv.value.len() == 4 => { let mut id = [0u8; 4]; id.copy_from_slice(&tlv.value); @@ -114,6 +115,16 @@ fn handle_write_binary(apdu: &ParsedApdu, store: &mut ObjectStore) -> ApduRespon Some(id) => id, None => return ApduResponse::error(SW_WRONG_DATA), }; + let creation_policy = match crate::policy::creation_policy(&tlvs) { + Ok(policy) => policy, + Err(_) => return ApduResponse::error(SW_WRONG_DATA), + }; + let object_existed = store.exists(&obj_id); + if object_existed + && !store.policy_allows(&obj_id, crate::policy::POLICY_OBJ_ALLOW_WRITE) + { + return ApduResponse::error(SW_CONDITIONS_NOT_SATISFIED); + } let write_data = data.unwrap_or_default(); @@ -139,6 +150,7 @@ fn handle_write_binary(apdu: &ParsedApdu, store: &mut ObjectStore) -> ApduRespon let mut full = vec![0u8; size]; full[offset..offset + write_data.len()].copy_from_slice(&write_data); store.insert(obj_id, SecureObject::Binary { data: full }); + store.set_creation_metadata(obj_id, creation_policy, 0x01); ApduResponse::success() } } @@ -159,6 +171,10 @@ fn handle_write_counter(apdu: &ParsedApdu, store: &mut ObjectStore) -> ApduRespo Some(id) => id, None => return ApduResponse::error(SW_WRONG_DATA), }; + let creation_policy = match crate::policy::creation_policy(&tlvs) { + Ok(policy) => policy, + Err(_) => return ApduResponse::error(SW_WRONG_DATA), + }; let size_tlv = tlv::find_tlv(&tlvs, TAG_2) .filter(|t| t.value.len() == 2) @@ -176,6 +192,11 @@ fn handle_write_counter(apdu: &ParsedApdu, store: &mut ObjectStore) -> ApduRespo Some(_) => return ApduResponse::error(SW_CONDITIONS_NOT_SATISFIED), None => None, }; + if existing.is_some() + && !store.policy_allows(&obj_id, crate::policy::POLICY_OBJ_ALLOW_WRITE) + { + return ApduResponse::error(SW_CONDITIONS_NOT_SATISFIED); + } match (existing, size_tlv, value_tlv) { // CreateCounter (optionally with an initial value) @@ -187,6 +208,7 @@ fn handle_write_counter(apdu: &ParsedApdu, store: &mut ObjectStore) -> ApduRespo value: value.unwrap_or(0), size, }); + store.set_creation_metadata(obj_id, creation_policy, 0x01); ApduResponse::success() } // SetCounterValue on an existing counter @@ -220,6 +242,9 @@ fn handle_read_object(apdu: &ParsedApdu, store: &mut ObjectStore) -> ApduRespons Some(id) => id, None => return ApduResponse::error(SW_WRONG_DATA), }; + if !store.policy_allows(&obj_id, crate::policy::POLICY_OBJ_ALLOW_READ) { + return ApduResponse::error(SW_COMMAND_NOT_ALLOWED); + } // Optional offset from Tag2 and length from Tag3 let offset = tlv::find_tlv(&tlvs, TAG_2) @@ -389,6 +414,49 @@ fn handle_read_type(apdu: &ParsedApdu, store: &mut ObjectStore, v7: bool) -> Apd } } +/// Applet 7.2 object attribute layout for a non-authentication object: +/// id, type, auth indicator, AEAD tag length, owner auth ID, RFU, policy, +/// origin and object version. Applet 3.1.1 rejects this command. +fn handle_read_attributes( + apdu: &ParsedApdu, + store: &mut ObjectStore, + v7: bool, +) -> ApduResponse { + if !v7 { + return ApduResponse::error(SW_CONDITIONS_NOT_SATISFIED); + } + let tlvs = match apdu.parse_tlvs() { + Ok(t) => t, + Err(_) => return ApduResponse::error(SW_WRONG_DATA), + }; + let obj_id = match extract_object_id(&tlvs) { + Some(id) => id, + None => return ApduResponse::error(SW_WRONG_DATA), + }; + let Some(object) = store.get(&obj_id) else { + return ApduResponse::error(SW_CONDITIONS_NOT_SATISFIED); + }; + + let mut attributes = Vec::new(); + attributes.extend_from_slice(&obj_id); + attributes.push(object.type_code(true)); + attributes.push(0x01); // kSE05x_SetIndicator_NOT_SET + attributes.extend_from_slice(&[0x00, 0x00]); // minimum AEAD tag length + attributes.extend_from_slice(&[0x00; 4]); // owner: unauthenticated session + attributes.extend_from_slice(&[0x00, 0x00]); // RFU + if let Some(metadata) = store.metadata(&obj_id) { + if let Some(policy) = &metadata.policy { + attributes.extend_from_slice(policy); + } + attributes.push(metadata.origin); + } else { + attributes.push(0x01); // legacy store: treat as externally created + } + attributes.extend_from_slice(&[0x00; 4]); // object version + + ApduResponse::success_with_tlvs(&[Tlv::new(TAG_3, &attributes)]) +} + fn handle_check_exists(apdu: &ParsedApdu, store: &mut ObjectStore) -> ApduResponse { let tlvs = match apdu.parse_tlvs() { Ok(t) => t, @@ -415,6 +483,10 @@ fn handle_delete(apdu: &ParsedApdu, store: &mut ObjectStore) -> ApduResponse { None => return ApduResponse::error(SW_WRONG_DATA), }; + if !store.policy_allows(&obj_id, crate::policy::POLICY_OBJ_ALLOW_DELETE) { + return ApduResponse::error(SW_CONDITIONS_NOT_SATISFIED); + } + // Deleting a nonexistent object fails 0x6985 (bench-verified on // applet 3.1.1 and 7.2.0; the SDK's erase-before-create pattern // logs a warning for it and continues). @@ -579,6 +651,89 @@ mod lifecycle_tests { data: body, le: None } } + fn attach_policy(apdu: &mut ParsedApdu, header: u32) -> Vec { + let mut raw = vec![0x08]; + raw.extend_from_slice(&0u32.to_be_bytes()); + raw.extend_from_slice(&header.to_be_bytes()); + let mut policy_tlv = vec![TAG_POLICY, raw.len() as u8]; + policy_tlv.extend_from_slice(&raw); + policy_tlv.extend_from_slice(&apdu.data); + apdu.data = policy_tlv; + raw + } + + #[test] + fn test_policy_denies_overwrite_and_delete_when_permissions_are_missing() { + let mut store = ObjectStore::new(); + let no_write_id = [0x7F, 0, 0, 0x31]; + let no_delete_id = [0x7F, 0, 0, 0x32]; + + let mut create = write_binary_apdu(no_write_id, 0, Some(4), &[1, 2, 3, 4]); + attach_policy( + &mut create, + crate::policy::POLICY_OBJ_ALLOW_READ + | crate::policy::POLICY_OBJ_ALLOW_DELETE, + ); + assert_eq!(handle_write(&create, &mut store).sw, SW_NO_ERROR); + let overwrite = write_binary_apdu(no_write_id, 0, Some(4), &[9, 9, 9, 9]); + assert_eq!( + handle_write(&overwrite, &mut store).sw, + SW_CONDITIONS_NOT_SATISFIED + ); + match store.get(&no_write_id) { + Some(SecureObject::Binary { data }) => assert_eq!(data, &[1, 2, 3, 4]), + _ => panic!("binary object missing"), + } + + let mut create = write_binary_apdu(no_delete_id, 0, Some(4), &[5, 6, 7, 8]); + attach_policy( + &mut create, + crate::policy::POLICY_OBJ_ALLOW_READ + | crate::policy::POLICY_OBJ_ALLOW_WRITE, + ); + assert_eq!(handle_write(&create, &mut store).sw, SW_NO_ERROR); + let delete = tag1_apdu( + INS_MGMT, + P1_DEFAULT, + P2_DELETE_OBJECT, + no_delete_id, + ); + assert_eq!( + handle_mgmt(&delete, &mut store).sw, + SW_CONDITIONS_NOT_SATISFIED + ); + assert!(store.exists(&no_delete_id)); + } + + #[test] + fn test_read_attributes_returns_raw_policy_and_origin_on_v7() { + let id = [0x7F, 0, 0, 0x33]; + let mut store = ObjectStore::new(); + let mut create = write_binary_apdu(id, 0, Some(3), &[1, 2, 3]); + let raw_policy = attach_policy( + &mut create, + crate::policy::POLICY_OBJ_ALLOW_READ + | crate::policy::POLICY_OBJ_ALLOW_DELETE, + ); + assert_eq!(handle_write(&create, &mut store).sw, SW_NO_ERROR); + + let read = tag1_apdu(INS_READ, P1_DEFAULT, P2_ATTRIBUTES, id); + let response = handle_read(&read, &mut store, true); + assert_eq!(response.sw, SW_NO_ERROR); + let tlvs = crate::tlv::parse_tlvs(&response.data).unwrap(); + let attributes = &tlv::find_tlv(&tlvs, TAG_3).unwrap().value; + assert_eq!(&attributes[0..4], &id); + assert_eq!(attributes[4], 0x0B); + assert_eq!(&attributes[14..14 + raw_policy.len()], &raw_policy); + assert_eq!(attributes[14 + raw_policy.len()], 0x01); + assert_eq!(attributes.len(), 14 + raw_policy.len() + 1 + 4); + + assert_eq!( + handle_read(&read, &mut store, false).sw, + SW_CONDITIONS_NOT_SATISFIED + ); + } + #[test] fn test_binary_bounds_enforced() { // Bench-verified on a 16-byte file: write at offset 8 with 16 diff --git a/SE050Sim/se050-sim/src/handlers/rsa.rs b/SE050Sim/se050-sim/src/handlers/rsa.rs index 856fb80..6934600 100644 --- a/SE050Sim/se050-sim/src/handlers/rsa.rs +++ b/SE050Sim/se050-sim/src/handlers/rsa.rs @@ -118,6 +118,11 @@ pub fn handle_write_rsa_key( } _ => return ApduResponse::error(SW_WRONG_DATA), }; + let creation_policy = match crate::policy::creation_policy(&tlvs) { + Ok(policy) => policy, + Err(_) => return ApduResponse::error(SW_WRONG_DATA), + }; + let object_existed = store.exists(&obj_id); let size_bits_opt = match tlv::find_tlv(&tlvs, TAG_2) { Some(t) if t.value.len() == 2 => { @@ -140,6 +145,29 @@ pub fn handle_write_rsa_key( || comp_dq.is_some() || comp_qinv.is_some() || comp_e.is_some() || comp_d.is_some() || comp_n.is_some(); + // A private-key import is a sequence of component APDUs. Missing WRITE + // permission must not interrupt that creation transaction, but it does + // deny a later replacement once a usable key has been materialized (or a + // public-only N+E set is complete). + let creation_in_progress = has_any_component && match store.get(&obj_id) { + Some(SecureObject::RSAKeyPair { private_key_der, staged, .. }) => { + if !private_key_der.is_empty() { + false + } else if apdu.key_type() == P1_PUBLIC_KEY { + staged.n.is_none() || staged.e.is_none() + } else { + staged.n.is_none() || staged.e.is_none() || staged.d.is_none() + } + } + _ => false, + }; + if object_existed + && !creation_in_progress + && !store.policy_allows(&obj_id, crate::policy::POLICY_OBJ_ALLOW_WRITE) + { + return ApduResponse::error(SW_CONDITIONS_NOT_SATISFIED); + } + // Keygen: size-only APDU, no component data — generate fresh. if !has_any_component { let Some(key_size_bits) = size_bits_opt else { @@ -168,6 +196,9 @@ pub fn handle_write_rsa_key( staged: RsaComponents::default(), }, ); + if !object_existed { + store.set_creation_metadata(obj_id, creation_policy, 0x02); + } return ApduResponse::success(); } @@ -205,6 +236,9 @@ pub fn handle_write_rsa_key( staged, }, ); + if !object_existed { + store.set_creation_metadata(obj_id, creation_policy, 0x01); + } ApduResponse::success() } diff --git a/SE050Sim/se050-sim/src/handlers/session.rs b/SE050Sim/se050-sim/src/handlers/session.rs index d7d279b..3cafb3d 100644 --- a/SE050Sim/se050-sim/src/handlers/session.rs +++ b/SE050Sim/se050-sim/src/handlers/session.rs @@ -24,11 +24,21 @@ use crate::applet::AppletVersion; use crate::object_store::ObjectStore; /// SE050 applet AID -const SE050_AID: [u8; 16] = [ +pub(crate) const SE050_AID: [u8; 16] = [ 0xA0, 0x00, 0x00, 0x03, 0x96, 0x54, 0x53, 0x00, 0x00, 0x00, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, ]; +/// Supplementary Security Domain selected by NXP's Platform SCP03 key +/// rotation example before it authenticates and sends GlobalPlatform PUT KEY. +pub(crate) const SSD_AID: [u8; 11] = [ + 0xD2, 0x76, 0x00, 0x00, 0x85, 0x30, 0x4A, 0x43, 0x4F, 0x90, 0x03, +]; + +pub(crate) fn selects_ssd(apdu: &ParsedApdu) -> bool { + apdu.data == SSD_AID +} + /// Handle SELECT applet command (CLA=0x00, INS=0xA4). /// The response is raw bytes (not TLV-wrapped), matching what the driver /// expects in receive_apdu_raw. The 7-byte body is the same version @@ -40,8 +50,10 @@ pub fn handle_select( version: AppletVersion, ) -> ApduResponse { // Verify the AID matches - if apdu.data.len() >= 16 && apdu.data[..16] == SE050_AID { + if apdu.data == SE050_AID { ApduResponse::success_with_data(version.version_bytes().to_vec()) + } else if selects_ssd(apdu) { + ApduResponse::success() } else { ApduResponse::error(0x6A82) // File not found } diff --git a/SE050Sim/se050-sim/src/object_store/mod.rs b/SE050Sim/se050-sim/src/object_store/mod.rs index d1b8a31..4c53ac8 100644 --- a/SE050Sim/se050-sim/src/object_store/mod.rs +++ b/SE050Sim/se050-sim/src/object_store/mod.rs @@ -23,7 +23,9 @@ pub mod types; use std::collections::HashMap; use std::path::PathBuf; -use types::SecureObject; +use crate::applet::AppletVersion; +use crate::scp03::keys::Scp03Config; +use types::{ObjectMetadata, SecureObject}; /// Hex-encoded 4-byte object ID used as JSON key. type ObjectIdKey = String; @@ -60,6 +62,10 @@ pub enum CryptoObjectState { /// Object store backed by an in-memory HashMap with optional JSON file persistence. pub struct ObjectStore { objects: HashMap<[u8; 4], SecureObject>, + /// Creation-time policy and origin. Kept separate from SecureObject so + /// policy behavior is uniform across every object type and persistence + /// remains backward-compatible with old enum payloads. + metadata: HashMap<[u8; 4], ObjectMetadata>, persist_path: Option, /// EC curve objects: curve ID -> bitmask of uploaded parameters /// (kSE05x_ECCurveParam bits; CURVE_PARAMS_COMPLETE = usable). @@ -74,6 +80,10 @@ pub struct ObjectStore { /// SetPlatformSCPRequest state: when true, plain (non-SCP03) commands are /// refused. Persisted, matching the boot-persistent flag on real silicon. scp_required: bool, + /// Rotated Platform SCP03 keys. None selects the applet-personality + /// defaults (or environment overrides). Factory reset deliberately does + /// not restore these keys, matching the irrecoverable real-card behavior. + platform_scp: Option, } fn default_curves() -> HashMap { @@ -85,22 +95,26 @@ impl ObjectStore { pub fn new() -> Self { Self { objects: HashMap::new(), + metadata: HashMap::new(), persist_path: None, ec_curves: default_curves(), crypto_objects: HashMap::new(), crypto_object_types: HashMap::new(), scp_required: false, + platform_scp: None, } } pub fn with_persistence(path: PathBuf) -> Self { let mut store = Self { objects: HashMap::new(), + metadata: HashMap::new(), persist_path: Some(path.clone()), ec_curves: default_curves(), crypto_objects: HashMap::new(), crypto_object_types: HashMap::new(), scp_required: false, + platform_scp: None, }; store.load(); store @@ -119,9 +133,39 @@ impl ObjectStore { self.objects.get_mut(id) } + /// Record immutable object metadata on first creation. Repeated RSA + /// component writes and ordinary overwrites cannot replace the policy. + pub fn set_creation_metadata( + &mut self, + id: [u8; 4], + policy: Option>, + origin: u8, + ) { + self.metadata + .entry(id) + .or_insert(ObjectMetadata { policy, origin }); + self.persist(); + } + + pub fn metadata(&self, id: &[u8; 4]) -> Option<&ObjectMetadata> { + self.metadata.get(id) + } + + /// No attached policy means the applet default allows the operation. + /// Once a policy is attached, an omitted permission is a denial. + pub fn policy_allows(&self, id: &[u8; 4], permission: u32) -> bool { + match self.metadata.get(id).and_then(|m| m.policy.as_deref()) { + Some(raw) => crate::policy::ar_header_union(raw) + .map(|header| header & permission != 0) + .unwrap_or(false), + None => true, + } + } + pub fn remove(&mut self, id: &[u8; 4]) -> Option { let result = self.objects.remove(id); if result.is_some() { + self.metadata.remove(id); self.persist(); } result @@ -141,6 +185,7 @@ impl ObjectStore { // curve set afterwards (see ec_curves) so key generation keeps // working for hosts that never create curves themselves. self.objects.clear(); + self.metadata.clear(); self.ec_curves = default_curves(); self.crypto_objects.clear(); self.crypto_object_types.clear(); @@ -206,6 +251,17 @@ impl ObjectStore { self.persist(); } + pub fn platform_scp_config(&self, version: AppletVersion) -> Scp03Config { + self.platform_scp + .clone() + .unwrap_or_else(|| Scp03Config::from_env(version)) + } + + pub fn set_platform_scp_config(&mut self, config: Scp03Config) { + self.platform_scp = Some(config); + self.persist(); + } + fn persist(&self) { let Some(path) = &self.persist_path else { return }; let objects: HashMap = self @@ -218,10 +274,17 @@ impl ObjectStore { .iter() .map(|(k, v)| (format!("{:02x}", k), *v)) .collect(); + let metadata: HashMap = self + .metadata + .iter() + .map(|(k, v)| (hex::encode(k), v)) + .collect(); let doc = serde_json::json!({ "objects": objects, + "metadata": metadata, "ec_curves": curves, "scp_required": self.scp_required, + "platform_scp": self.platform_scp, }); if let Ok(json) = serde_json::to_string_pretty(&doc) { let _ = std::fs::write(path, json); @@ -243,6 +306,9 @@ impl ObjectStore { .get("scp_required") .and_then(|v| v.as_bool()) .unwrap_or(false); + self.platform_scp = value + .get("platform_scp") + .and_then(|v| serde_json::from_value(v.clone()).ok()); if let Some(curves) = value.get("ec_curves").and_then(|v| v.as_object()) { self.ec_curves = curves .iter() @@ -253,6 +319,22 @@ impl ObjectStore { }) .collect(); } + if let Some(metadata) = value.get("metadata").and_then(|v| v.as_object()) { + for (hex_key, metadata_value) in metadata { + let Ok(bytes) = hex::decode(hex_key) else { continue }; + if bytes.len() != 4 { + continue; + } + let Ok(metadata) = serde_json::from_value::( + metadata_value.clone(), + ) else { + continue; + }; + let mut id = [0u8; 4]; + id.copy_from_slice(&bytes); + self.metadata.insert(id, metadata); + } + } value.get("objects").cloned().unwrap_or_default() } else { value @@ -335,4 +417,54 @@ mod persistence_tests { assert!(store.curve_ready(0x03)); let _ = std::fs::remove_file(&path); } + + #[test] + fn test_policy_metadata_round_trips_and_is_removed_with_object() { + let path = unique_store_path("metadata_store_test"); + let id = [0, 0, 0, 0x43]; + let policy = vec![ + 0x08, 0, 0, 0, 0, 0x00, 0x20, 0x00, 0x00, + ]; + { + let mut store = ObjectStore::with_persistence(path.clone()); + store.insert(id, SecureObject::Binary { data: vec![1, 2, 3] }); + store.set_creation_metadata(id, Some(policy.clone()), 0x02); + } + { + let mut store = ObjectStore::with_persistence(path.clone()); + let metadata = store.metadata(&id).expect("metadata missing"); + assert_eq!(metadata.policy.as_deref(), Some(policy.as_slice())); + assert_eq!(metadata.origin, 0x02); + assert!(store.policy_allows(&id, crate::policy::POLICY_OBJ_ALLOW_READ)); + assert!(!store.policy_allows(&id, crate::policy::POLICY_OBJ_ALLOW_DELETE)); + assert!(store.remove(&id).is_some()); + } + let store = ObjectStore::with_persistence(path.clone()); + assert!(store.metadata(&id).is_none()); + let _ = std::fs::remove_file(&path); + } + + + #[test] + fn test_platform_scp_keys_round_trip_and_survive_clear() { + let path = unique_store_path("scp_keys_store_test"); + let config = Scp03Config { + kvn: 0x0c, + enc: vec![0x11; 16], + mac: vec![0x22; 16], + dek: vec![0x33; 16], + }; + { + let mut store = ObjectStore::with_persistence(path.clone()); + store.set_platform_scp_config(config.clone()); + store.clear(); + } + let store = ObjectStore::with_persistence(path.clone()); + let loaded = store.platform_scp_config(AppletVersion::V7_2_22F); + assert_eq!(loaded.kvn, config.kvn); + assert_eq!(loaded.enc, config.enc); + assert_eq!(loaded.mac, config.mac); + assert_eq!(loaded.dek, config.dek); + let _ = std::fs::remove_file(&path); + } } diff --git a/SE050Sim/se050-sim/src/object_store/types.rs b/SE050Sim/se050-sim/src/object_store/types.rs index 195cbaf..d0a0f98 100644 --- a/SE050Sim/se050-sim/src/object_store/types.rs +++ b/SE050Sim/se050-sim/src/object_store/types.rs @@ -21,6 +21,32 @@ use serde::{Deserialize, Serialize}; +/// Immutable metadata attached when a secure object is created. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ObjectMetadata { + /// Raw TAG_POLICY value, including one or more length-prefixed access + /// rule entries. None means the applet's unrestricted default policy. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub policy: Option>, + /// SE05x_Origin_t: external/imported=1, internal/generated=2, + /// trust-provisioned=3. + #[serde(default = "default_object_origin")] + pub origin: u8, +} + +impl Default for ObjectMetadata { + fn default() -> Self { + Self { + policy: None, + origin: default_object_origin(), + } + } +} + +fn default_object_origin() -> u8 { + 0x01 +} + /// Types of EC curves supported by the simulator. #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)] pub enum ECCurve { diff --git a/SE050Sim/se050-sim/src/policy.rs b/SE050Sim/se050-sim/src/policy.rs index b6c5637..291d973 100644 --- a/SE050Sim/se050-sim/src/policy.rs +++ b/SE050Sim/se050-sim/src/policy.rs @@ -19,19 +19,35 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA */ -//! Minimal model of SE05x secure object policies. +//! Model of SE05x secure object policies used by object management. //! //! Covers only what the strict applet 7.2 ECDH derive-target contract //! needs: a TAG_POLICY TLV attached to an object creation is a sequence //! of entries `length(1) | authObjectId(4) | AR header(4, big endian) | //! extension...` (see se05x_const.h in the Plug & Trust middleware, where //! DEFAULT_OBJECT_POLICY_SIZE = 8 covers authObjectId + AR header), and -//! ReadObject on a symmetric key object is refused unless the policy -//! grants POLICY_OBJ_ALLOW_READ. +//! The applet uses default-deny semantics once a policy is attached. /// POLICY_OBJ_ALLOW_READ bit of the 4-byte object policy AR header, /// per se05x_const.h in the Plug & Trust middleware. pub const POLICY_OBJ_ALLOW_READ: u32 = 0x0020_0000; +pub const POLICY_OBJ_ALLOW_WRITE: u32 = 0x0010_0000; +pub const POLICY_OBJ_ALLOW_DELETE: u32 = 0x0004_0000; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct InvalidPolicy; + +/// Validate and copy the optional TAG_POLICY from a parsed command. +pub fn creation_policy( + tlvs: &[crate::tlv::Tlv], +) -> Result>, InvalidPolicy> { + match crate::tlv::find_tlv(tlvs, crate::tlv::TAG_POLICY) { + Some(tlv) if tlv.value.is_empty() => Ok(None), + Some(tlv) if ar_header_union(&tlv.value).is_some() => Ok(Some(tlv.value.clone())), + Some(_) => Err(InvalidPolicy), + None => Ok(None), + } +} /// Extract the union of all AR headers from a TAG_POLICY TLV value. /// diff --git a/SE050Sim/se050-sim/src/scp03/keys.rs b/SE050Sim/se050-sim/src/scp03/keys.rs index 5e05946..dd64f4b 100644 --- a/SE050Sim/se050-sim/src/scp03/keys.rs +++ b/SE050Sim/se050-sim/src/scp03/keys.rs @@ -33,6 +33,8 @@ //! SE050_SIM_SCP03_KVN (u8, e.g. 0x0B) use crate::applet::AppletVersion; +use crate::handlers::aes::AnyAes; +use serde::{Deserialize, Serialize}; /// SE05x platform SCP key version number /// (ex_sss_auth.h EX_SSS_AUTH_SE05X_KEY_VERSION_NO). Bench-verified: the @@ -75,15 +77,69 @@ const DEVKIT_DEK: [u8; 16] = [ ]; /// Static Platform SCP03 keys plus the key version number. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] pub struct Scp03Config { pub kvn: u8, pub enc: Vec, pub mac: Vec, - /// DEK is stored for completeness (PUT KEY) but is unused today. + /// Static key-encryption key used to unwrap PUT KEY data. pub dek: Vec, } +/// Parse and validate the GlobalPlatform PUT KEY payload used to replace the +/// three Platform SCP03 keys. Each incoming key is AES-ECB wrapped with the +/// current DEK and followed by its 3-byte check value. +pub fn put_keys( + current: &Scp03Config, + p1: u8, + p2: u8, + data: &[u8], +) -> Result<(Scp03Config, Vec), u16> { + const KEY_BLOCK_LEN: usize = 23; + const EXPECTED_LEN: usize = 1 + 3 * KEY_BLOCK_LEN; + + if p1 != current.kvn || p2 != 0x81 { + return Err(0x6A86); // incorrect P1/P2 + } + if data.len() != EXPECTED_LEN { + return Err(0x6700); + } + let cipher = AnyAes::new(¤t.dek).ok_or(0x6985u16)?; + let new_kvn = data[0]; + let mut keys = Vec::with_capacity(3); + let mut response = Vec::with_capacity(10); + response.push(new_kvn); + + for index in 0..3 { + let block = &data[1 + index * KEY_BLOCK_LEN..1 + (index + 1) * KEY_BLOCK_LEN]; + if block[0] != 0x88 || block[1] != 0x11 || block[2] != 0x10 || block[19] != 0x03 { + return Err(0x6A80); + } + let mut key = [0u8; 16]; + key.copy_from_slice(&block[3..19]); + cipher.decrypt_block(&mut key); + + let key_cipher = AnyAes::new(&key).ok_or(0x6A80u16)?; + let mut check = [1u8; 16]; + key_cipher.encrypt_block(&mut check); + if check[..3] != block[20..23] { + return Err(0x6A80); + } + response.extend_from_slice(&check[..3]); + keys.push(key.to_vec()); + } + + Ok(( + Scp03Config { + kvn: new_kvn, + enc: keys.remove(0), + mac: keys.remove(0), + dek: keys.remove(0), + }, + response, + )) +} + impl Scp03Config { /// Build the key set for the given applet personality, applying any env /// overrides. Re-read on every INITIALIZE UPDATE, matching the @@ -132,3 +188,43 @@ fn env_u8(name: &str) -> Option { let raw = raw.strip_prefix("0x").or_else(|| raw.strip_prefix("0X")).unwrap_or(raw); u8::from_str_radix(raw, 16).ok() } + +#[cfg(test)] +mod tests { + use super::*; + + fn wrap_key(dek: &[u8], key: &[u8; 16]) -> Vec { + let cipher = AnyAes::new(dek).unwrap(); + let mut encrypted = *key; + cipher.encrypt_block(&mut encrypted); + let key_cipher = AnyAes::new(key).unwrap(); + let mut check = [1u8; 16]; + key_cipher.encrypt_block(&mut check); + let mut out = vec![0x88, 0x11, 0x10]; + out.extend_from_slice(&encrypted); + out.push(0x03); + out.extend_from_slice(&check[..3]); + out + } + + #[test] + fn put_key_unwraps_all_keys_and_returns_check_values() { + let current = Scp03Config::from_env(AppletVersion::V7_2_22F); + let enc = [0x11; 16]; + let mac = [0x22; 16]; + let dek = [0x33; 16]; + let mut data = vec![current.kvn]; + data.extend_from_slice(&wrap_key(¤t.dek, &enc)); + data.extend_from_slice(&wrap_key(¤t.dek, &mac)); + data.extend_from_slice(&wrap_key(¤t.dek, &dek)); + + let (updated, response) = put_keys(¤t, current.kvn, 0x81, &data).unwrap(); + assert_eq!(updated.enc, enc); + assert_eq!(updated.mac, mac); + assert_eq!(updated.dek, dek); + assert_eq!(response.len(), 10); + + data[20] ^= 1; + assert_eq!(put_keys(¤t, current.kvn, 0x81, &data), Err(0x6A80)); + } +} diff --git a/SE050Sim/se050-sim/src/scp03/mod.rs b/SE050Sim/se050-sim/src/scp03/mod.rs index d01dae8..01544d5 100644 --- a/SE050Sim/se050-sim/src/scp03/mod.rs +++ b/SE050Sim/se050-sim/src/scp03/mod.rs @@ -128,12 +128,26 @@ impl Scp03State { /// the handshake. P1 is the requested key version number. The 8-byte host /// challenge is the command data field. pub fn initialize_update(&mut self, p1: u8, host_challenge: &[u8]) -> ApduResponse { + let version = AppletVersion::from_env(); + self.initialize_update_with_config( + p1, + host_challenge, + version, + Scp03Config::from_env(version), + ) + } + + pub fn initialize_update_with_config( + &mut self, + p1: u8, + host_challenge: &[u8], + version: AppletVersion, + cfg: Scp03Config, + ) -> ApduResponse { if host_challenge.len() != 8 { *self = Scp03State::Idle; return ApduResponse::error(SW_WRONG_LENGTH); } - let version = AppletVersion::from_env(); - let cfg = Scp03Config::from_env(version); // Applet versions below 4.3 get the older Platform SCP semantics. let legacy = matches!(version, AppletVersion::V3_1_1); diff --git a/SE050Sim/se050-sim/src/t1.rs b/SE050Sim/se050-sim/src/t1.rs index dc51c59..64fb2ff 100644 --- a/SE050Sim/se050-sim/src/t1.rs +++ b/SE050Sim/se050-sim/src/t1.rs @@ -163,6 +163,9 @@ pub struct T1Responder { apdu_reassembly: Vec, /// Per-connection SCP03 secure channel state. scp03: Scp03State, + /// PUT KEY is valid only after selecting the Security Domain, not while + /// the IoT applet is selected. + selected_ssd: bool, } impl T1Responder { @@ -175,6 +178,7 @@ impl T1Responder { iseq_snd: 0, apdu_reassembly: Vec::new(), scp03: Scp03State::new(), + selected_ssd: false, } } @@ -218,6 +222,7 @@ impl T1Responder { self.iseq_snd = 0; self.apdu_reassembly.clear(); self.scp03.reset(); + self.selected_ssd = false; let ft = FrameType::SFrame { code: T1_S_INTERFACE_SOFT_RESET, @@ -232,6 +237,7 @@ impl T1Responder { self.iseq_snd = 0; self.apdu_reassembly.clear(); self.scp03.reset(); + self.selected_ssd = false; let ft = FrameType::SFrame { code: 0x00, is_response: true }; let (header, payload_crc) = build_frame(self.nad_se2hd, ft, &[]); @@ -293,12 +299,24 @@ impl T1Responder { // SELECT terminates any secure channel, then proceeds plain. if cla == 0x00 && ins == 0xA4 { self.scp03.reset(); + self.selected_ssd = ParsedApdu::parse(apdu_bytes) + .map(|a| crate::handlers::session::selects_ssd(&a)) + .unwrap_or(false); } // INITIALIZE UPDATE (CLA 0x80 INS 0x50): valid from any state. if cla == 0x80 && ins == 0x50 { return match ParsedApdu::parse(apdu_bytes) { - Ok(a) => self.scp03.initialize_update(a.p1, &a.data), + Ok(a) => { + let version = crate::applet::AppletVersion::from_env(); + let config = store.platform_scp_config(version); + self.scp03.initialize_update_with_config( + a.p1, + &a.data, + version, + config, + ) + } Err(_) => ApduResponse::error(0x6700), }; } @@ -329,7 +347,28 @@ impl T1Responder { return ApduResponse::error(sw); } }; - let resp = dispatch::dispatch(&inner, store, true); + let resp = if inner.cla == 0x80 && inner.ins == 0xD8 { + if !self.selected_ssd { + ApduResponse::error(0x6A80) + } else { + let version = crate::applet::AppletVersion::from_env(); + let current = store.platform_scp_config(version); + match crate::scp03::keys::put_keys( + ¤t, + inner.p1, + inner.p2, + &inner.data, + ) { + Ok((updated, check_values)) => { + store.set_platform_scp_config(updated); + ApduResponse::success_with_data(check_values) + } + Err(sw) => ApduResponse::error(sw), + } + } + } else { + dispatch::dispatch(&inner, store, true) + }; return match &mut self.scp03 { Scp03State::Active(sess) => sess.wrap_response(resp), _ => ApduResponse::error(SW_SECURITY_STATUS), diff --git a/SE050Sim/wolfcrypt-test/test_api_improvements.c b/SE050Sim/wolfcrypt-test/test_api_improvements.c new file mode 100644 index 0000000..471f2ee --- /dev/null +++ b/SE050Sim/wolfcrypt-test/test_api_improvements.c @@ -0,0 +1,621 @@ +/* Focused end-to-end smoke test for the wolfSSL SE05x provisioning APIs. */ +#include +#include + +#include +#define USE_CERT_BUFFERS_2048 +#define USE_CERT_BUFFERS_4096 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TEST_OBJECT_ID 90U +#define TEST_ECC_ID 91U +#define TEST_RSA_ID 92U +#define TEST_LARGE_ID 93U +#define TEST_RSA4K_ID 94U +#define TEST_ECC_GEN_ID 95U +#define TEST_RSA_GEN_ID 96U +#define TEST_LARGE_SZ 900U + +static const byte eccPublicDer[] = { + 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, + 0x3D, 0x02, 0x01, 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, + 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, 0x04, 0x55, 0xBF, 0xF4, + 0x0F, 0x44, 0x50, 0x9A, 0x3D, 0xCE, 0x9B, 0xB7, 0xF0, 0xC5, + 0x4D, 0xF5, 0x70, 0x7B, 0xD4, 0xEC, 0x24, 0x8E, 0x19, 0x80, + 0xEC, 0x5A, 0x4C, 0xA2, 0x24, 0x03, 0x62, 0x2C, 0x9B, 0xDA, + 0xEF, 0xA2, 0x35, 0x12, 0x43, 0x84, 0x76, 0x16, 0xC6, 0x56, + 0x95, 0x06, 0xCC, 0x01, 0xA9, 0xBD, 0xF6, 0x75, 0x1A, 0x42, + 0xF7, 0xBD, 0xA9, 0xB2, 0x36, 0x22, 0x5F, 0xC7, 0x5D, 0x7F, + 0xB4 +}; + +static const wc_se050_scp03_keys currentKeys = { + {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F}, + {0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F}, + {0x67, 0x02, 0xDA, 0xC3, 0x09, 0x42, 0xB2, 0xC8, + 0x5E, 0x7F, 0x47, 0xB4, 0x2C, 0xED, 0x4E, 0x7F} +}; + +static const wc_se050_scp03_keys explicitKeys = { + {0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F}, + {0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F}, + {0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F} +}; + +static int fail(const char* operation, int ret) +{ + fprintf(stderr, "FAIL: %s returned %d\n", operation, ret); + return 1; +} + +#ifdef WOLFSSL_SE050_ONLY_KEY_ID +static int append_bytes(byte* out, word32 outSz, word32* offset, + const byte* in, word32 inSz) +{ + if ((out == NULL) || (offset == NULL) || (in == NULL) || + (*offset > outSz) || (inSz > (outSz - *offset))) { + return -1; + } + memcpy(out + *offset, in, inSz); + *offset += inSz; + return 0; +} + +static int append_tlv82(byte* out, word32 outSz, word32* offset, byte tag, + const byte* value, word32 valueSz) +{ + byte header[4]; + + header[0] = tag; + header[1] = 0x82; + header[2] = (byte)(valueSz >> 8); + header[3] = (byte)valueSz; + if (append_bytes(out, outSz, offset, header, sizeof(header)) != 0) { + return -1; + } + return append_bytes(out, outSz, offset, value, valueSz); +} + +static int append_short_tlv(byte* out, word32 outSz, word32* offset, + byte tag, const byte* value, word32 valueSz) +{ + byte header[2]; + + if (valueSz > 0x7FU) { + return -1; + } + header[0] = tag; + header[1] = (byte)valueSz; + if (append_bytes(out, outSz, offset, header, sizeof(header)) != 0) { + return -1; + } + return append_bytes(out, outSz, offset, value, valueSz); +} + +static int test_raw_curve_attestation(word32 cipherType, byte curveOid) +{ + static const byte spkiPrefix[] = { + 0x30, 0x2A, 0x30, 0x05, 0x06, 0x03, 0x2B, 0x65, 0x00, + 0x03, 0x21, 0x00 + }; + static const byte objectId[] = {0x00, 0x00, 0x00, 0x31}; + static const byte attestId[] = {0x00, 0x00, 0x00, 0x32}; + static const byte algorithm[] = {0x09}; + wc_se050_attst_result result; + sss_se05x_attst_comp_data_t* data; + RsaKey key; + WC_RNG rng; + byte component[32]; + byte commandDigest[WC_SHA256_DIGEST_SIZE]; + byte signedData[512]; + byte wrongRandom[16]; + word32 idx = 0; + word32 cmdOffset = 7U; + word32 signedOffset = 0U; + word32 sigSz; + word32 i; + int keyInit = 0; + int rngInit = 0; + int valid = 0; + int ret; + + memset(&result, 0, sizeof(result)); + result.hashAlgo = WC_HASH_TYPE_SHA256; + result.cipherType = cipherType; + result.raw.valid_number = 1; + data = &result.raw.data[0]; + for (i = 0U; i < sizeof(result.freshness); i++) { + result.freshness[i] = (byte)(0xA0U + i); + component[i] = (byte)(i + 1U); + } + + memcpy(result.value, spkiPrefix, sizeof(spkiPrefix)); + result.value[8] = curveOid; + for (i = 0U; i < sizeof(component); i++) { + result.value[sizeof(spkiPrefix) + i] = + component[sizeof(component) - 1U - i]; + } + result.valueSz = sizeof(spkiPrefix) + sizeof(component); + + data->cmd[0] = (byte)kSE05x_CLA; + data->cmd[1] = (byte)kSE05x_INS_READ_With_Attestation; + data->cmd[2] = 0; + data->cmd[3] = 0; + data->cmd[4] = 0; + data->cmd[5] = 0; + if ((append_short_tlv(data->cmd, sizeof(data->cmd), &cmdOffset, + (byte)kSE05x_TAG_1, objectId, sizeof(objectId)) != 0) || + (append_short_tlv(data->cmd, sizeof(data->cmd), &cmdOffset, + (byte)kSE05x_TAG_5, attestId, sizeof(attestId)) != 0) || + (append_short_tlv(data->cmd, sizeof(data->cmd), &cmdOffset, + (byte)kSE05x_TAG_6, algorithm, sizeof(algorithm)) != 0) || + (append_short_tlv(data->cmd, sizeof(data->cmd), &cmdOffset, + (byte)kSE05x_TAG_7, result.freshness, + sizeof(result.freshness)) != 0)) { + return fail("build synthetic attestation command", -1); + } + data->cmd[6] = (byte)(cmdOffset - 7U); + data->cmdLen = cmdOffset; + + data->chipIdLen = 18U; + data->attributeLen = 15U; + data->objSizeLen = 2U; + data->timeStampLen = sizeof(data->timeStamp.ts); + for (i = 0U; i < data->chipIdLen; i++) { + data->chipId[i] = (byte)(0x10U + i); + } + for (i = 0U; i < data->attributeLen; i++) { + data->attribute[i] = (byte)(0x30U + i); + } + data->objSize[0] = 0; + data->objSize[1] = sizeof(component); + for (i = 0U; i < data->timeStampLen; i++) { + data->timeStamp.ts[i] = (byte)(0x50U + i); + } + + ret = wc_Hash(WC_HASH_TYPE_SHA256, data->cmd, (word32)data->cmdLen, + commandDigest, sizeof(commandDigest)); + if ((ret != 0) || + (append_bytes(signedData, sizeof(signedData), &signedOffset, + commandDigest, sizeof(commandDigest)) != 0) || + (append_tlv82(signedData, sizeof(signedData), &signedOffset, + (byte)kSE05x_TAG_1, component, sizeof(component)) != 0) || + (append_tlv82(signedData, sizeof(signedData), &signedOffset, + (byte)kSE05x_TAG_2, data->chipId, + (word32)data->chipIdLen) != 0) || + (append_tlv82(signedData, sizeof(signedData), &signedOffset, + (byte)kSE05x_TAG_3, data->attribute, + (word32)data->attributeLen) != 0) || + (append_tlv82(signedData, sizeof(signedData), &signedOffset, + (byte)kSE05x_TAG_4, data->objSize, + (word32)data->objSizeLen) != 0) || + (append_tlv82(signedData, sizeof(signedData), &signedOffset, + (byte)kSE05x_TAG_TIMESTAMP, data->timeStamp.ts, + (word32)data->timeStampLen) != 0)) { + return fail("build synthetic attestation data", ret); + } + + ret = wc_InitRsaKey(&key, NULL); + if (ret == 0) { + keyInit = 1; + ret = wc_RsaPrivateKeyDecode(client_key_der_2048, &idx, &key, + sizeof_client_key_der_2048); + } + if (ret == 0) { + ret = wc_InitRng(&rng); + if (ret == 0) { + rngInit = 1; + } + } + sigSz = sizeof(data->signature); + if (ret == 0) { + ret = wc_SignatureGenerate(WC_HASH_TYPE_SHA256, + WC_SIGNATURE_TYPE_RSA_W_ENC, signedData, signedOffset, + data->signature, &sigSz, &key, sizeof(key), &rng); + } + data->signatureLen = sigSz; + if (ret == 0) { + ret = wc_se050_verify_attestation(&result, + client_keypub_der_2048, sizeof_client_keypub_der_2048, + result.freshness, sizeof(result.freshness), &valid); + } + if ((ret != 0) || !valid) { + ret = fail("raw curve attestation verification", ret); + } + + memcpy(wrongRandom, result.freshness, sizeof(wrongRandom)); + wrongRandom[0] ^= 1U; + valid = 1; + if (ret == 0) { + ret = wc_se050_verify_attestation(&result, + client_keypub_der_2048, sizeof_client_keypub_der_2048, + wrongRandom, sizeof(wrongRandom), &valid); + if ((ret != 0) || valid) { + ret = fail("replayed attestation accepted", ret); + } + } + + if (rngInit) { + wc_FreeRng(&rng); + } + if (keyInit) { + wc_FreeRsaKey(&key); + } + return ret; +} +#endif /* WOLFSSL_SE050_ONLY_KEY_ID */ + +int main(void) +{ + static const byte seed[] = "SE05x simulator rotation seed"; + static const byte value[] = "policy protected"; + static const byte replacement[] = "replacement"; + wc_se050_scp03_keys recoveredKeys; + wc_se050_scp03_keys rotatedKeys; + ecc_key generatedEcc; + RsaKey generatedRsa; + sss_session_t* session; + sss_key_store_t* hostKeyStore; + sss_key_store_t* keyStore; + byte attributes[128]; + byte readback[64]; + byte largeValue[TEST_LARGE_SZ]; + byte largeReadback[TEST_LARGE_SZ]; + word32 attributesSz = sizeof(attributes); + word32 readbackSz = sizeof(readback); + word32 largeReadbackSz = sizeof(largeReadback); + word32 generatedKeyId = 0U; + word32 i; + int generatedEccInit = 0; + int generatedRsaInit = 0; + int ret; + +#if !WOLFSSL_CRYPT_HW_MUTEX + return fail("SE05x hardware mutex is disabled", -1); +#endif + + for (i = 0U; i < sizeof(largeValue); i++) { + largeValue[i] = (byte)(i ^ (i >> 8)); + } + + ret = wc_se050_scp03_derive_keys_seed(seed, + (word32)sizeof(seed) - 1U, &recoveredKeys); + if (ret != 0) { + return fail("wc_se050_scp03_derive_keys_seed", ret); + } + + ret = wc_se050_init_ex(NULL, ¤tKeys); + if (ret != 0) { + return fail("wc_se050_init_ex(current)", ret); + } + ret = wolfCrypt_Init(); + if (ret != 0) { + (void)wc_se050_close(); + return fail("wolfCrypt_Init(after wc_se050_init_ex)", ret); + } +#ifdef WOLFSSL_SE050_ONLY_KEY_ID + /* Keep a session open while creating the synthetic RSA signature. The + * 5.9.1 SE05x port routes host RSA signing through the SE05x, whereas + * newer ports keep non-resident keys in software in ONLY_KEY_ID mode. */ + ret = test_raw_curve_attestation( + (word32)kSSS_CipherType_EC_MONTGOMERY, 0x6EU); + if (ret == 0) { + ret = test_raw_curve_attestation( + (word32)kSSS_CipherType_EC_TWISTED_ED, 0x70U); + } + if (ret != 0) { + (void)wolfCrypt_Cleanup(); + return ret; + } +#ifdef SE050_ATTEST_TEST_ONLY + ret = wolfCrypt_Cleanup(); + if (ret != 0) { + return fail("wolfCrypt_Cleanup(attestation only)", ret); + } + puts("PASS: raw-curve attestation and freshness verification"); + return 0; +#endif +#endif + ret = wc_se050_get_config(&session, &hostKeyStore, &keyStore); + if ((ret != 0) || (session == NULL) || (hostKeyStore == NULL) || + (keyStore == NULL) || (wc_se050_get_session() != session) || + (wc_se050_get_se05x_session() == NULL)) { + return fail("session accessors", ret); + } + ret = wc_se050_init_ex(NULL, ¤tKeys); + if (ret != BAD_STATE_E) { + return fail("double initialization was not rejected", ret); + } + ret = wc_se050_lock(); + if (ret != 0) { + return fail("wc_se050_lock", ret); + } + wc_se050_unlock(); +#ifndef WOLFSSL_SE050_NO_ATTEST + { + wc_se050_attst_result attestation; + + ret = wc_se050_attest_object(TEST_OBJECT_ID, TEST_ECC_ID, + WC_HASH_TYPE_SHA256, NULL, 0, &attestation); + if (ret != BAD_FUNC_ARG) { + return fail("attestation accepted no freshness challenge", ret); + } + } +#endif + + ret = wc_se050_scp03_rotate_keys(&explicitKeys, 0x0B); + if (ret != 0) { + return fail("wc_se050_scp03_rotate_keys", ret); + } + ret = wc_se050_close(); + if (ret != 0) { + return fail("wc_se050_close(explicit rotation)", ret); + } + + ret = wc_se050_init_ex(NULL, &explicitKeys); + if (ret != 0) { + return fail("wc_se050_init_ex(explicit)", ret); + } + + ret = wc_se050_scp03_rotate_keys_seed(seed, (word32)sizeof(seed) - 1U, + 0x0B, &rotatedKeys); + if (ret != 0) { + return fail("wc_se050_scp03_rotate_keys_seed", ret); + } + if (memcmp(&rotatedKeys, &recoveredKeys, sizeof(rotatedKeys)) != 0) { + return fail("power-cycle SCP03 key derivation mismatch", -1); + } + + ret = wc_se050_close(); + if (ret != 0) { + return fail("wc_se050_close(seed rotation)", ret); + } + + ret = wc_se050_init_ex(NULL, &recoveredKeys); + if (ret != 0) { + return fail("wc_se050_init_ex(rederived)", ret); + } +#ifdef WOLFSSL_SE050_CRYPT + { + static const byte aesKey1[16] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F + }; + static const byte aesKey2[16] = { + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F + }; + Aes aes; + int aesInit = 0; + + ret = wc_AesInit(&aes, NULL, INVALID_DEVID); + if (ret == 0) { + aesInit = 1; + ret = wc_AesSetKey(&aes, aesKey1, sizeof(aesKey1), NULL, + AES_ENCRYPTION); + } + if (ret == 0) { + ret = wc_AesSetKey(&aes, aesKey2, sizeof(aesKey2), NULL, + AES_ENCRYPTION); + } + if (aesInit) + wc_AesFree(&aes); + if (ret != 0) { + return fail("repeated SE05x AES key setup", ret); + } + } +#endif + + ret = wc_se050_insert_binary_object_ex(TEST_OBJECT_ID, value, + (word32)sizeof(value) - 1U, WC_SE050_POLICY_ALLOW_READ, 0); + if (ret != 0) { + return fail("policy insert", ret); + } + ret = wc_se050_get_object_attributes(TEST_OBJECT_ID, attributes, + &attributesSz); + if ((ret != 0) || (attributesSz < 24U)) { + return fail("attribute read", ret); + } + ret = wc_se050_get_binary_object(TEST_OBJECT_ID, readback, &readbackSz); + if ((ret != 0) || (readbackSz != sizeof(value) - 1U) || + (memcmp(readback, value, readbackSz) != 0)) { + return fail("policy object read", ret); + } + ret = wc_se050_insert_binary_object(TEST_OBJECT_ID, replacement, + (word32)sizeof(replacement) - 1U); + if (ret == 0) { + return fail("no-write overwrite unexpectedly succeeded", ret); + } + ret = wc_se050_erase_object(TEST_OBJECT_ID); + if (ret == 0) { + return fail("no-delete erase unexpectedly succeeded", ret); + } + + attributesSz = sizeof(attributes); + ret = wc_se050_ecc_insert_public_key_ex(TEST_ECC_ID, eccPublicDer, + sizeof(eccPublicDer), WC_SE050_POLICY_ALLOW_DELETE | + WC_SE050_POLICY_ALLOW_READ | WC_SE050_POLICY_ALLOW_VERIFY, 0); + if (ret != 0) { + return fail("combined ECC policy insert", ret); + } + ret = wc_se050_get_object_attributes(TEST_ECC_ID, attributes, + &attributesSz); + if ((ret != 0) || (attributesSz < 23U) || (attributes[14] != 8U) || + (attributes[19] != 0x08U) || (attributes[20] != 0x24U) || + (attributes[21] != 0x00U) || (attributes[22] != 0x00U)) { + return fail("combined ECC policy attributes", ret); + } + ret = wc_se050_erase_object(TEST_ECC_ID); + if (ret != 0) { + return fail("combined ECC policy delete", ret); + } + + ret = wc_se050_insert_binary_object_ex(TEST_LARGE_ID, largeValue, + sizeof(largeValue), WC_SE050_POLICY_ALLOW_READ | + WC_SE050_POLICY_ALLOW_WRITE | WC_SE050_POLICY_ALLOW_DELETE, 0); + if (ret != 0) { + return fail("chunked binary policy insert", ret); + } + ret = wc_se050_get_binary_object(TEST_LARGE_ID, largeReadback, + &largeReadbackSz); + if ((ret != 0) || (largeReadbackSz != sizeof(largeValue)) || + (memcmp(largeReadback, largeValue, sizeof(largeValue)) != 0)) { + return fail("chunked binary policy read", ret); + } + ret = wc_se050_insert_binary_object_ex(TEST_LARGE_ID, replacement, + (word32)sizeof(replacement) - 1U, 0, 0); + if (ret == 0) { + return fail("duplicate zero-policy insert unexpectedly succeeded", + ret); + } + ret = wc_se050_erase_object(TEST_LARGE_ID); + if (ret != 0) { + return fail("chunked binary policy delete", ret); + } + + attributesSz = sizeof(attributes); + ret = wc_se050_rsa_insert_public_key_ex(TEST_RSA_ID, + client_keypub_der_2048, sizeof_client_keypub_der_2048, + WC_SE050_POLICY_ALLOW_DELETE | WC_SE050_POLICY_ALLOW_READ | + WC_SE050_POLICY_ALLOW_VERIFY, 0); + if (ret != 0) { + return fail("combined RSA policy insert", ret); + } + ret = wc_se050_get_object_attributes(TEST_RSA_ID, attributes, + &attributesSz); + if ((ret != 0) || (attributesSz < 23U) || (attributes[14] != 8U) || + (attributes[19] != 0x08U) || (attributes[20] != 0x24U) || + (attributes[21] != 0x00U) || (attributes[22] != 0x00U)) { + return fail("combined RSA policy attributes", ret); + } + ret = wc_se050_erase_object(TEST_RSA_ID); + if (ret != 0) { + return fail("combined RSA policy delete", ret); + } + + ret = wc_se050_rsa_insert_public_key_ex(TEST_RSA4K_ID, + client_keypub_der_4096, sizeof_client_keypub_der_4096, + WC_SE050_POLICY_ALLOW_DELETE | WC_SE050_POLICY_ALLOW_READ | + WC_SE050_POLICY_ALLOW_VERIFY, 0); + if (ret != 0) { + return fail("RSA-4096 policy insert", ret); + } + ret = wc_se050_erase_object(TEST_RSA4K_ID); + if (ret != 0) { + return fail("RSA-4096 policy delete", ret); + } + + attributesSz = sizeof(attributes); + ret = wc_se050_ecc_generate_key_ex(TEST_ECC_GEN_ID, 32, + ECC_SECP256R1, WC_SE050_POLICY_ALLOW_DELETE | + WC_SE050_POLICY_ALLOW_READ | WC_SE050_POLICY_ALLOW_SIGN | + WC_SE050_POLICY_ALLOW_VERIFY, 0); + if (ret != 0) { + return fail("policy ECC key generation", ret); + } + ret = wc_se050_ecc_generate_key_ex(TEST_ECC_GEN_ID, 32, + ECC_SECP256R1, 0, 0); + if (ret == 0) { + return fail("duplicate ECC generation unexpectedly succeeded", ret); + } + ret = wc_se050_get_object_attributes(TEST_ECC_GEN_ID, attributes, + &attributesSz); + if ((ret != 0) || (attributesSz < 28U) || (attributes[14] != 8U) || + (attributes[19] != 0x18U) || (attributes[20] != 0x24U) || + (attributes[21] != 0x00U) || (attributes[22] != 0x00U) || + (attributes[23] != 0x02U)) { + return fail("generated ECC policy attributes and origin", ret); + } + ret = wc_ecc_init(&generatedEcc); + if (ret == 0) { + generatedEccInit = 1; + ret = wc_ecc_use_key_id(&generatedEcc, TEST_ECC_GEN_ID, 0); + } + if (ret == 0) { + ret = wc_ecc_get_key_id(&generatedEcc, &generatedKeyId); + } + if (generatedEccInit) { + wc_ecc_free(&generatedEcc); + } + if ((ret != 0) || (generatedKeyId != TEST_ECC_GEN_ID)) { + return fail("bind generated ECC key", ret); + } + ret = wc_se050_erase_object(TEST_ECC_GEN_ID); + if (ret != 0) { + return fail("generated ECC key delete", ret); + } + + attributesSz = sizeof(attributes); + generatedKeyId = 0U; + ret = wc_se050_rsa_generate_key_ex(TEST_RSA_GEN_ID, 2048, 65537, + WC_SE050_POLICY_ALLOW_DELETE | WC_SE050_POLICY_ALLOW_READ | + WC_SE050_POLICY_ALLOW_SIGN | WC_SE050_POLICY_ALLOW_VERIFY, 0); + if (ret != 0) { + return fail("policy RSA key generation", ret); + } + ret = wc_se050_rsa_generate_key_ex(TEST_RSA_GEN_ID, 2048, 65537, + 0, 0); + if (ret == 0) { + return fail("duplicate RSA generation unexpectedly succeeded", ret); + } + ret = wc_se050_get_object_attributes(TEST_RSA_GEN_ID, attributes, + &attributesSz); + if ((ret != 0) || (attributesSz < 28U) || (attributes[14] != 8U) || + (attributes[19] != 0x18U) || (attributes[20] != 0x24U) || + (attributes[21] != 0x00U) || (attributes[22] != 0x00U) || + (attributes[23] != 0x02U)) { + return fail("generated RSA policy attributes and origin", ret); + } + ret = wc_InitRsaKey(&generatedRsa, NULL); + if (ret == 0) { + generatedRsaInit = 1; + ret = wc_RsaUseKeyId(&generatedRsa, TEST_RSA_GEN_ID, 0); + } + if (ret == 0) { + ret = wc_RsaGetKeyId(&generatedRsa, &generatedKeyId); + } + if (generatedRsaInit) { + wc_FreeRsaKey(&generatedRsa); + } + if ((ret != 0) || (generatedKeyId != TEST_RSA_GEN_ID)) { + return fail("bind generated RSA key", ret); + } + ret = wc_se050_erase_object(TEST_RSA_GEN_ID); + if (ret != 0) { + return fail("generated RSA key delete", ret); + } + + ret = wolfCrypt_Cleanup(); + if (ret != 0) { + return fail("wolfCrypt_Cleanup", ret); + } + if (wc_se050_get_session() != NULL) { + return fail("wolfCrypt_Cleanup did not close SE05x", -1); + } + readbackSz = sizeof(readback); + ret = wc_se050_get_binary_object(TEST_OBJECT_ID, readback, &readbackSz); + if (ret != BAD_STATE_E) { + return fail("uninitialized object read was not rejected", ret); + } + ret = wc_se050_erase_object(TEST_OBJECT_ID); + if (ret != BAD_STATE_E) { + return fail("uninitialized object erase was not rejected", ret); + } + puts("PASS: runtime SCP03, power-cycle derivation, initialization, " + "rotation, session, policy insertion and policy key generation"); + return 0; +}