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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion SE050Sim/se050-sim/src/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,8 @@ pub fn dispatch(apdu: &ParsedApdu, store: &mut ObjectStore) -> ApduResponse {
ApduResponse::success_with_tlvs(
&[crate::tlv::Tlv::new(crate::tlv::TAG_1, &curve_list)])
}
_ => handlers::object_mgmt::handle_read(apdu, store),
_ => handlers::object_mgmt::handle_read(
apdu, store, handlers::ec::strict_ecdh_from_env()),
},

INS_CRYPTO => match (cred_type, apdu.p2) {
Expand Down
85 changes: 80 additions & 5 deletions SE050Sim/se050-sim/src/handlers/aes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,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};
use crate::tlv::{self, Tlv, TAG_1, TAG_2, TAG_3, TAG_4, TAG_POLICY};

use aes::cipher::{BlockEncrypt, BlockDecrypt, KeyInit};
use aes::cipher::generic_array::GenericArray;
Expand Down Expand Up @@ -82,8 +82,10 @@ pub fn handle_write_aes_key(apdu: &ParsedApdu, store: &mut ObjectStore) -> ApduR
}

/// Handle WRITE HMAC key command (WriteSymmKey with P1=HMAC).
/// Tag1=obj_id(4B), Tag3=key_data. HMAC keys have no fixed length, so any
/// non-empty Tag3 value is accepted as-is.
/// Policy(opt), Tag1=obj_id(4B), Tag3=key_data. HMAC keys have no fixed
/// length, so any non-empty Tag3 value is accepted as-is. An attached
/// policy is recorded with the object; the applet 7.2 read-policy
/// contract (see handle_read_object) keys off it.
pub fn handle_write_hmac_key(apdu: &ParsedApdu, store: &mut ObjectStore) -> ApduResponse {
let tlvs = match apdu.parse_tlvs() {
Ok(t) => t,
Expand All @@ -99,9 +101,20 @@ 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,
};

match tlv::find_tlv(&tlvs, TAG_3) {
Some(t) if !t.value.is_empty() => {
store.insert(obj_id, SecureObject::HMACKey { key: t.value.clone() });
store.insert(obj_id, SecureObject::HMACKey { key: t.value.clone(), policy });
ApduResponse::success()
}
_ => ApduResponse::error(SW_WRONG_DATA),
Expand Down Expand Up @@ -441,11 +454,73 @@ mod hmac_write_tests {
let resp = dispatch(&apdu, &mut store);
assert_eq!(resp.sw, 0x9000);
match store.get(&[0x00, 0x00, 0x00, 0x66]) {
Some(SecureObject::HMACKey { key: stored }) => assert_eq!(stored, &key),
Some(SecureObject::HMACKey { key: stored, policy }) => {
assert_eq!(stored, &key);
assert_eq!(*policy, None);
}
_ => panic!("HMACKey object not stored"),
}
}

#[test]
fn test_write_hmac_key_records_policy() {
// WriteSymmKey with a leading policy TLV, as sent by
// sss_key_store_set_key when an sss_policy_t is attached. One
// entry: len=8, authId=0, AR header granting read.
let key = vec![0x5Au8; 32];
let header: u32 = crate::policy::POLICY_OBJ_ALLOW_READ | 0x0014_0000;
let mut data = vec![crate::tlv::TAG_POLICY, 0x09, 0x08, 0x00, 0x00, 0x00, 0x00];
data.extend_from_slice(&header.to_be_bytes());
data.extend_from_slice(&[TAG_1, 0x04, 0x00, 0x00, 0x00, 0x67]);
data.push(TAG_3);
data.push(key.len() as u8);
data.extend_from_slice(&key);

let apdu = ParsedApdu {
cla: 0x80,
ins: 0x21,
p1: P1_HMAC,
p2: P2_DEFAULT,
data,
le: None,
};
let mut store = ObjectStore::new();
let resp = dispatch(&apdu, &mut store);
assert_eq!(resp.sw, 0x9000);
match store.get(&[0x00, 0x00, 0x00, 0x67]) {
Some(SecureObject::HMACKey { key: stored, policy }) => {
assert_eq!(stored, &key);
assert_eq!(*policy, Some(header));
}
_ => panic!("HMACKey object not stored"),
}
}

#[test]
fn test_write_hmac_key_malformed_policy_rejected() {
// A policy TLV that does not parse as an entry sequence must fail
// the write with SW_WRONG_DATA, not be recorded as "no policy".
let key = vec![0xA5u8; 32];
let mut data = vec![crate::tlv::TAG_POLICY, 0x03, 0xDE, 0xAD, 0xBE];
data.extend_from_slice(&[TAG_1, 0x04, 0x00, 0x00, 0x00, 0x68]);
data.push(TAG_3);
data.push(key.len() as u8);
data.extend_from_slice(&key);

let apdu = ParsedApdu {
cla: 0x80,
ins: 0x21,
p1: P1_HMAC,
p2: P2_DEFAULT,
data,
le: None,
};
let mut store = ObjectStore::new();
let resp = dispatch(&apdu, &mut store);
assert_eq!(resp.sw, SW_WRONG_DATA);
assert!(store.get(&[0x00, 0x00, 0x00, 0x68]).is_none());
}

#[test]
fn test_write_hmac_key_empty_value_rejected() {
let data = vec![TAG_1, 0x04, 0x00, 0x00, 0x00, 0x66, TAG_3, 0x00];
Expand Down
47 changes: 37 additions & 10 deletions SE050Sim/se050-sim/src/handlers/ec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,10 @@ pub fn handle_verify(apdu: &ParsedApdu, store: &mut ObjectStore) -> ApduResponse

/// Whether the applet 7.2 strict ECDH InObject contract is enforced.
/// Off by default so hosts that predate the contract keep working; set
/// SE050_SIM_STRICT_ECDH=1 to enforce it.
/// SE050_SIM_STRICT_ECDH=1 to enforce it. Strict mode also enforces the
/// derive-target read policy: ReadObject on an HMACKey object is refused
/// with SW_COMMAND_NOT_ALLOWED unless the policy attached at creation
/// grants POLICY_OBJ_ALLOW_READ (see object_mgmt::handle_read).
pub fn strict_ecdh_from_env() -> bool {
std::env::var("SE050_SIM_STRICT_ECDH").map(|v| v == "1").unwrap_or(false)
}
Expand Down Expand Up @@ -518,9 +521,9 @@ pub fn handle_ecdh(apdu: &ParsedApdu, store: &mut ObjectStore, strict: bool) ->
// On the real applet the Tag7 target must already exist as an HMACKey
// object; the applet refuses to create it implicitly. In lenient mode a
// missing (or non-HMACKey) target keeps the legacy implicit-create
// behavior instead (target_len None).
let target_len = match store.get(&output_id) {
Some(SecureObject::HMACKey { key }) => Some(key.len()),
// behavior instead (target None).
let target = match store.get(&output_id) {
Some(SecureObject::HMACKey { key, policy }) => Some((key.len(), *policy)),
_ if strict => return ApduResponse::error(SW_CONDITIONS_NOT_SATISFIED),
_ => None,
};
Expand All @@ -543,12 +546,14 @@ pub fn handle_ecdh(apdu: &ParsedApdu, store: &mut ObjectStore, strict: bool) ->

match shared_secret {
Some(secret) => {
match target_len {
Some(len) => {
match target {
Some((len, policy)) => {
if secret.len() != len {
return ApduResponse::error(SW_CONDITIONS_NOT_SATISFIED);
}
store.insert(output_id, SecureObject::HMACKey { key: secret });
// The derive overwrites the object's value; the policy
// attached at creation stays with the object.
store.insert(output_id, SecureObject::HMACKey { key: secret, policy });
}
None => {
// Legacy lenient behavior: implicitly create the target
Expand Down Expand Up @@ -726,7 +731,8 @@ mod tests {
// HMACKey, in strict and lenient mode alike.
for strict in [true, false] {
let (apdu, mut store, _) = ecdh_inobject_fixture();
store.insert([0, 0, 0, 0x66], SecureObject::HMACKey { key: vec![0u8; 16] });
store.insert([0, 0, 0, 0x66],
SecureObject::HMACKey { key: vec![0u8; 16], policy: None });
let resp = handle_ecdh(&apdu, &mut store, strict);
assert_eq!(resp.sw, SW_CONDITIONS_NOT_SATISFIED, "strict={}", strict);
}
Expand All @@ -736,16 +742,37 @@ mod tests {
fn test_ecdh_tag7_precreated_hmackey_succeeds_both_modes() {
for strict in [true, false] {
let (apdu, mut store, expected) = ecdh_inobject_fixture();
store.insert([0, 0, 0, 0x66], SecureObject::HMACKey { key: vec![0u8; 32] });
store.insert([0, 0, 0, 0x66],
SecureObject::HMACKey { key: vec![0u8; 32], policy: None });
let resp = handle_ecdh(&apdu, &mut store, strict);
assert_eq!(resp.sw, 0x9000, "strict={}", strict);
match store.get(&[0, 0, 0, 0x66]) {
Some(SecureObject::HMACKey { key }) => assert_eq!(key, &expected),
Some(SecureObject::HMACKey { key, .. }) => assert_eq!(key, &expected),
other => panic!("expected HMACKey with shared secret, got {:?}", other.is_some()),
}
}
}

#[test]
fn test_ecdh_preserves_target_policy() {
// The policy attached when the derive target was created must
// survive the derive overwriting the value, or a subsequent
// strict-mode ReadObject of the shared secret would be refused.
let (apdu, mut store, expected) = ecdh_inobject_fixture();
let policy = Some(crate::policy::POLICY_OBJ_ALLOW_READ);
store.insert([0, 0, 0, 0x66],
SecureObject::HMACKey { key: vec![0u8; 32], policy });
let resp = handle_ecdh(&apdu, &mut store, true);
assert_eq!(resp.sw, 0x9000);
match store.get(&[0, 0, 0, 0x66]) {
Some(SecureObject::HMACKey { key, policy: p }) => {
assert_eq!(key, &expected);
assert_eq!(*p, policy);
}
other => panic!("expected HMACKey, got {:?}", other.is_some()),
}
}

#[test]
fn test_import_private_only_p256_derives_public() {
// wc_ecc_use_key_id imports a private-only key pair and then reads
Expand Down
105 changes: 100 additions & 5 deletions SE050Sim/se050-sim/src/handlers/object_mgmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,12 @@ pub fn handle_write(apdu: &ParsedApdu, store: &mut ObjectStore) -> ApduResponse
}
}

/// Handle READ commands for objects.
pub fn handle_read(apdu: &ParsedApdu, store: &mut ObjectStore) -> ApduResponse {
/// Handle READ commands for objects. With `strict` (the applet 7.2
/// contract, see ec::strict_ecdh_from_env) ReadObject enforces the
/// symmetric key read policy; size/list/type reads are unaffected.
pub fn handle_read(apdu: &ParsedApdu, store: &mut ObjectStore, strict: bool) -> ApduResponse {
match apdu.p2 {
P2_DEFAULT => handle_read_object(apdu, store),
P2_DEFAULT => handle_read_object(apdu, store, strict),
P2_SIZE => handle_read_size(apdu, store),
P2_LIST => handle_read_id_list(apdu, store),
P2_TYPE => handle_read_type(apdu, store),
Expand Down Expand Up @@ -190,7 +192,7 @@ fn handle_write_counter(apdu: &ParsedApdu, store: &mut ObjectStore) -> ApduRespo
ApduResponse::success()
}

fn handle_read_object(apdu: &ParsedApdu, store: &mut ObjectStore) -> ApduResponse {
fn handle_read_object(apdu: &ParsedApdu, store: &mut ObjectStore, strict: bool) -> ApduResponse {
let tlvs = match apdu.parse_tlvs() {
Ok(t) => t,
Err(_) => return ApduResponse::error(SW_WRONG_DATA),
Expand Down Expand Up @@ -238,7 +240,21 @@ fn handle_read_object(apdu: &ParsedApdu, store: &mut ObjectStore) -> ApduRespons
SecureObject::AESKey { key } => key.clone(),
SecureObject::UserID { value } => value.clone(),
SecureObject::Counter { value } => value.to_be_bytes().to_vec(),
SecureObject::HMACKey { key } => key.clone(),
SecureObject::HMACKey { key, policy } => {
// Applet 7.2 refuses to export a symmetric key object
// unless the policy attached at creation grants
// POLICY_OBJ_ALLOW_READ; an object created with no
// policy attached is not readable. This is the ECDH
// derive target readback contract, enforced only in
// strict mode so hosts that predate it keep working.
if strict
&& policy.map_or(true,
|p| p & crate::policy::POLICY_OBJ_ALLOW_READ == 0)
{
return ApduResponse::error(SW_COMMAND_NOT_ALLOWED);
}
key.clone()
}
};
ApduResponse::success_with_tlvs(&[Tlv::new(TAG_1, &data)])
}
Expand Down Expand Up @@ -347,3 +363,82 @@ fn handle_delete(apdu: &ParsedApdu, store: &mut ObjectStore) -> ApduResponse {
store.remove(&obj_id);
ApduResponse::success()
}

#[cfg(test)]
mod read_policy_tests {
use super::*;
use crate::policy::POLICY_OBJ_ALLOW_READ;

fn read_apdu(obj_id: [u8; 4]) -> ParsedApdu {
ParsedApdu {
cla: 0x80,
ins: INS_READ,
p1: P1_DEFAULT,
p2: P2_DEFAULT,
data: vec![TAG_1, 0x04, obj_id[0], obj_id[1], obj_id[2], obj_id[3]],
le: None,
}
}

#[test]
fn test_strict_read_hmackey_without_policy_returns_6986() {
// Applet 7.2 behavior seen on SE05x hardware: an HMACKey object
// created with no policy attached cannot be read back, so the
// ECDH shared secret export fails unless the derive target was
// created with a read policy.
let mut store = ObjectStore::new();
store.insert([0, 0, 0, 0x66],
SecureObject::HMACKey { key: vec![0xAB; 32], policy: None });
let resp = handle_read(&read_apdu([0, 0, 0, 0x66]), &mut store, true);
assert_eq!(resp.sw, SW_COMMAND_NOT_ALLOWED);
}

#[test]
fn test_strict_read_hmackey_policy_without_read_returns_6986() {
let mut store = ObjectStore::new();
store.insert([0, 0, 0, 0x66],
SecureObject::HMACKey { key: vec![0xAB; 32], policy: Some(0x0014_0000) });
let resp = handle_read(&read_apdu([0, 0, 0, 0x66]), &mut store, true);
assert_eq!(resp.sw, SW_COMMAND_NOT_ALLOWED);
}

#[test]
fn test_strict_read_hmackey_with_read_policy_succeeds() {
let key = vec![0xABu8; 32];
let mut store = ObjectStore::new();
store.insert([0, 0, 0, 0x66],
SecureObject::HMACKey {
key: key.clone(),
policy: Some(POLICY_OBJ_ALLOW_READ | 0x0014_0000),
});
let resp = handle_read(&read_apdu([0, 0, 0, 0x66]), &mut store, true);
assert_eq!(resp.sw, 0x9000);
let tlvs = crate::tlv::parse_tlvs(&resp.data).unwrap();
assert_eq!(tlv::find_tlv(&tlvs, TAG_1).unwrap().value, key);
}

#[test]
fn test_lenient_read_hmackey_without_policy_succeeds() {
// Hosts that predate the applet 7.2 contract keep working when
// strict mode is off.
let key = vec![0xABu8; 32];
let mut store = ObjectStore::new();
store.insert([0, 0, 0, 0x66],
SecureObject::HMACKey { key: key.clone(), policy: None });
let resp = handle_read(&read_apdu([0, 0, 0, 0x66]), &mut store, false);
assert_eq!(resp.sw, 0x9000);
}

#[test]
fn test_strict_read_size_of_unreadable_hmackey_succeeds() {
// Only the object content is policy-guarded; ReadSize must keep
// working since sss_key_store_get_key sizes its buffer with it.
let mut store = ObjectStore::new();
store.insert([0, 0, 0, 0x66],
SecureObject::HMACKey { key: vec![0xAB; 32], policy: None });
let mut apdu = read_apdu([0, 0, 0, 0x66]);
apdu.p2 = P2_SIZE;
let resp = handle_read(&apdu, &mut store, true);
assert_eq!(resp.sw, 0x9000);
}
}
1 change: 1 addition & 0 deletions SE050Sim/se050-sim/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ pub mod apdu;
pub mod dispatch;
pub mod handlers;
pub mod object_store;
pub mod policy;
pub mod t1;
pub mod tlv;
pub mod transport;
Expand Down
8 changes: 7 additions & 1 deletion SE050Sim/se050-sim/src/object_store/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,12 @@ pub enum SecureObject {
},
HMACKey {
key: Vec<u8>,
/// Union of the AR headers from the TAG_POLICY TLV attached at
/// creation; None when the object was created with no policy.
/// The applet 7.2 read-policy contract keys off this (see
/// crate::policy).
#[serde(default)]
policy: Option<u32>,
},
}

Expand Down Expand Up @@ -166,7 +172,7 @@ impl SecureObject {
SecureObject::Binary { data } => data.len(),
SecureObject::UserID { value } => value.len(),
SecureObject::Counter { .. } => 8,
SecureObject::HMACKey { key } => key.len(),
SecureObject::HMACKey { key, .. } => key.len(),
}
}
}
Loading
Loading