Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
30 changes: 26 additions & 4 deletions SE050Sim/sdk-test/test_se050.c
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,7 @@ static void test_ecdh(const char *name, uint32_t obj_a, uint32_t obj_b,
uint8_t shared[64] = {0};
size_t shared_len = sizeof(shared);
size_t shared_bits = 0;
uint8_t dummy[64] = {0};

/* Generate two key pairs */
cleanup_object(obj_a);
Expand All @@ -357,7 +358,7 @@ static void test_ecdh(const char *name, uint32_t obj_a, uint32_t obj_b,
/* Compute ECDH(A_priv, B_pub) */
sss_key_object_init(&derived_key, &g_ks);
status = sss_key_object_allocate_handle(&derived_key, obj_ss,
kSSS_KeyPart_Default, kSSS_CipherType_Binary, key_bytes,
kSSS_KeyPart_Default, kSSS_CipherType_HMAC, key_bytes,
kKeyObject_Mode_Transient);
ASSERT_OK(status, "derived allocate");

Expand All @@ -366,12 +367,20 @@ static void test_ecdh(const char *name, uint32_t obj_a, uint32_t obj_b,
ASSERT_OK(status, "derive_key_context_init");

sss_key_store_erase_key(&g_ks, &derived_key);
/* Applet 7.2+ stores the shared secret into a pre-existing HMACKey
* object whose size must equal the secret exactly; create it before
* the derive */
status = sss_key_store_set_key(&g_ks, &derived_key, dummy,
key_bytes, key_bytes * 8, NULL, 0);
ASSERT_OK(status, "derived pre-create");
status = sss_derive_key_dh(&derive_ctx, &key_b, &derived_key);
ASSERT_OK(status, "derive_key_dh");

sss_derive_key_context_free(&derive_ctx);

/* Read shared secret */
/* Read shared secret. sss_key_store_get_key has no HMAC read case, so
* read the object back as AES type (both are a plain ReadObject). */
derived_key.cipherType = kSSS_CipherType_AES;
status = sss_key_store_get_key(&g_ks, &derived_key,
shared, &shared_len, &shared_bits);
ASSERT_OK(status, "get shared secret");
Expand Down Expand Up @@ -1390,6 +1399,7 @@ static void test_x25519_ecdh(void)
size_t shared_a_len = sizeof(shared_a), shared_b_len = sizeof(shared_b);
size_t shared_bits = 0;
uint8_t zeros[32] = {0};
uint8_t dummy[32] = {0};

cleanup_object(id_a);
cleanup_object(id_b);
Expand Down Expand Up @@ -1417,35 +1427,47 @@ static void test_x25519_ecdh(void)
/* ECDH(A_priv, B_pub) */
sss_key_object_init(&derived_a, &g_ks);
status = sss_key_object_allocate_handle(&derived_a, id_ss_a,
kSSS_KeyPart_Default, kSSS_CipherType_Binary, 32,
kSSS_KeyPart_Default, kSSS_CipherType_HMAC, 32,
kKeyObject_Mode_Transient);
ASSERT_OK(status, "derived_a allocate");

/* Applet 7.2+: derive target must be a pre-existing HMACKey object
* sized exactly to the shared secret */
status = sss_key_store_set_key(&g_ks, &derived_a, dummy,
sizeof(dummy), sizeof(dummy) * 8, NULL, 0);
ASSERT_OK(status, "derived_a pre-create");

status = sss_derive_key_context_init(&derive_ctx, &g_ctx.session,
&key_a, kAlgorithm_SSS_ECDH, kMode_SSS_ComputeSharedSecret);
ASSERT_OK(status, "derive_a context_init");
status = sss_derive_key_dh(&derive_ctx, &key_b, &derived_a);
ASSERT_OK(status, "derive_a dh");
sss_derive_key_context_free(&derive_ctx);

derived_a.cipherType = kSSS_CipherType_AES;
status = sss_key_store_get_key(&g_ks, &derived_a,
shared_a, &shared_a_len, &shared_bits);
ASSERT_OK(status, "get shared_a");

/* ECDH(B_priv, A_pub) */
sss_key_object_init(&derived_b, &g_ks);
status = sss_key_object_allocate_handle(&derived_b, id_ss_b,
kSSS_KeyPart_Default, kSSS_CipherType_Binary, 32,
kSSS_KeyPart_Default, kSSS_CipherType_HMAC, 32,
kKeyObject_Mode_Transient);
ASSERT_OK(status, "derived_b allocate");

status = sss_key_store_set_key(&g_ks, &derived_b, dummy,
sizeof(dummy), sizeof(dummy) * 8, NULL, 0);
ASSERT_OK(status, "derived_b pre-create");

status = sss_derive_key_context_init(&derive_ctx, &g_ctx.session,
&key_b, kAlgorithm_SSS_ECDH, kMode_SSS_ComputeSharedSecret);
ASSERT_OK(status, "derive_b context_init");
status = sss_derive_key_dh(&derive_ctx, &key_a, &derived_b);
ASSERT_OK(status, "derive_b dh");
sss_derive_key_context_free(&derive_ctx);

derived_b.cipherType = kSSS_CipherType_AES;
status = sss_key_store_get_key(&g_ks, &derived_b,
shared_b, &shared_b_len, &shared_bits);
ASSERT_OK(status, "get shared_b");
Expand Down
1 change: 1 addition & 0 deletions SE050Sim/se050-sim/src/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ pub fn dispatch(apdu: &ParsedApdu, store: &mut ObjectStore) -> ApduResponse {
P1_EC => handlers::ec::handle_write_ec_key(apdu, store),
P1_RSA => handlers::rsa::handle_write_rsa_key(apdu, store),
P1_AES => handlers::aes::handle_write_aes_key(apdu, store),
P1_HMAC => handlers::aes::handle_write_hmac_key(apdu, store),
P1_CRYPTO_OBJ => handlers::crypto_obj::handle_create(apdu, store),
P1_CURVE => {
// CreateECCurve / SetECCurveParam: our crypto libs have curves built-in
Expand Down
77 changes: 77 additions & 0 deletions SE050Sim/se050-sim/src/handlers/aes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,33 @@ 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.
pub fn handle_write_hmac_key(apdu: &ParsedApdu, store: &mut ObjectStore) -> ApduResponse {
let tlvs = match apdu.parse_tlvs() {
Ok(t) => t,
Err(_) => 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];
id.copy_from_slice(&t.value);
id
}
_ => return ApduResponse::error(SW_WRONG_DATA),
};

match tlv::find_tlv(&tlvs, TAG_3) {
Some(t) if !t.value.is_empty() => {
store.insert(obj_id, SecureObject::HMACKey { key: t.value.clone() });
ApduResponse::success()
}
_ => ApduResponse::error(SW_WRONG_DATA),
}
}

/// Handle AES Encrypt Oneshot.
/// INS=Crypto, P1=Cipher, P2=EncryptOneshot
/// Tag1=key_id(4B), Tag2=cipher_mode(1B), Tag3=plaintext, Tag4=IV(opt)
Expand Down Expand Up @@ -385,3 +412,53 @@ where

Some(result)
}

#[cfg(test)]
mod hmac_write_tests {
use super::*;
use crate::dispatch::dispatch;

#[test]
fn test_write_hmac_key_via_dispatch() {
// WriteSymmKey with P1=HMAC and the transient INS bit set
// (kSE05x_INS_WRITE | kSE05x_INS_TRANSIENT = 0x21), as sent by
// sss_key_store_set_key for a kSSS_CipherType_HMAC object.
let key = vec![0xA5u8; 32];
let mut data = vec![TAG_1, 0x04, 0x00, 0x00, 0x00, 0x66];
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, 0x66]) {
Some(SecureObject::HMACKey { key: stored }) => assert_eq!(stored, &key),
_ => panic!("HMACKey object not stored"),
}
}

#[test]
fn test_write_hmac_key_empty_value_rejected() {
let data = vec![TAG_1, 0x04, 0x00, 0x00, 0x00, 0x66, TAG_3, 0x00];
let apdu = ParsedApdu {
cla: 0x80,
ins: 0x01,
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);
}
}
98 changes: 96 additions & 2 deletions SE050Sim/se050-sim/src/handlers/ec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,11 @@ pub fn handle_verify(apdu: &ParsedApdu, store: &mut ObjectStore) -> ApduResponse
/// Handle ECDH shared secret generation.
/// INS=Crypto, P1=EC, P2=DH(0x0F)
/// Tag1=privateKeyID(4B), Tag2=peerPublicKey, Tag7=sharedSecretOutputID(4B)
/// The shared secret is stored as a binary object at sharedSecretOutputID.
/// Per the SE05x IoT applet spec (7.2+, see Se05x_API_ECDHGenerateSharedSecret
/// in the Plug & Trust middleware), the Tag7 target must reference an existing
/// HMACKey object whose size equals the shared secret exactly; otherwise the
/// applet returns SW_CONDITIONS_NOT_SATISFIED. The shared secret overwrites
/// that object's value.
pub fn handle_ecdh(apdu: &ParsedApdu, store: &mut ObjectStore) -> ApduResponse {
let tlvs = match apdu.parse_tlvs() {
Ok(t) => t,
Expand Down Expand Up @@ -468,6 +472,13 @@ pub fn handle_ecdh(apdu: &ParsedApdu, store: &mut ObjectStore) -> ApduResponse {
None => return ApduResponse::error(SW_FILE_NOT_FOUND),
};

// The Tag7 target must already exist as an HMACKey object; the applet
// refuses to create it implicitly.
let target_len = match store.get(&output_id) {
Some(SecureObject::HMACKey { key }) => key.len(),
_ => return ApduResponse::error(SW_CONDITIONS_NOT_SATISFIED),
};

let shared_secret = match &key_obj {
SecureObject::ECKeyPair { curve: ECCurve::NistP224, private_key, .. } => {
p224_ecdh(private_key, peer_pubkey)
Expand All @@ -486,7 +497,10 @@ pub fn handle_ecdh(apdu: &ParsedApdu, store: &mut ObjectStore) -> ApduResponse {

match shared_secret {
Some(secret) => {
store.insert(output_id, SecureObject::Binary { data: secret.clone() });
if secret.len() != target_len {
return ApduResponse::error(SW_CONDITIONS_NOT_SATISFIED);
}
store.insert(output_id, SecureObject::HMACKey { key: secret });
ApduResponse::success()
}
None => ApduResponse::error(SW_CONDITIONS_NOT_SATISFIED),
Expand Down Expand Up @@ -590,6 +604,86 @@ mod tests {
let tlvs = crate::tlv::parse_tlvs(&resp.data).unwrap();
assert!(p384_verify(&private_key, &hash, &tlvs[0].value));
}

fn tlv_bytes(tag: u8, value: &[u8]) -> Vec<u8> {
let mut v = vec![tag, value.len() as u8];
v.extend_from_slice(value);
v
}

/// Build an ECDHGenerateSharedSecret_InObject APDU and a store holding a
/// P-256 key pair at 0x64. Returns (apdu, store, expected_secret).
fn ecdh_inobject_fixture() -> (ParsedApdu, ObjectStore, Vec<u8>) {
let sk = p256::ecdsa::SigningKey::random(&mut OsRng);
let private_key = sk.to_bytes().to_vec();
let public_key = sk.verifying_key().to_encoded_point(false).as_bytes().to_vec();

let peer = p256::ecdsa::SigningKey::random(&mut OsRng);
let peer_pub = peer.verifying_key().to_encoded_point(false).as_bytes().to_vec();

let expected = p256_ecdh(&private_key, &peer_pub).unwrap();

let mut store = ObjectStore::new();
store.insert([0, 0, 0, 0x64], SecureObject::ECKeyPair {
curve: ECCurve::NistP256,
private_key,
public_key,
});

let mut data = tlv_bytes(TAG_1, &[0, 0, 0, 0x64]);
data.extend(tlv_bytes(TAG_2, &peer_pub));
data.extend(tlv_bytes(TAG_7, &[0, 0, 0, 0x66]));

let apdu = ParsedApdu {
cla: 0x80,
ins: 0x03,
p1: P1_EC,
p2: P2_DH,
data,
le: None,
};
(apdu, store, expected)
}

#[test]
fn test_ecdh_tag7_target_missing_returns_6985() {
// Applet 7.2 behavior: the Tag7 HMACKey object must be pre-created,
// otherwise SW_CONDITIONS_NOT_SATISFIED. This is the failure mode of
// wolfSSL's se050_ecc_shared_secret against middleware built for
// applet >= 07_02 (never creates the target object).
let (apdu, mut store, _) = ecdh_inobject_fixture();
let resp = handle_ecdh(&apdu, &mut store);
assert_eq!(resp.sw, SW_CONDITIONS_NOT_SATISFIED);
assert!(store.get(&[0, 0, 0, 0x66]).is_none());
}

#[test]
fn test_ecdh_tag7_wrong_size_target_returns_6985() {
let (apdu, mut store, _) = ecdh_inobject_fixture();
store.insert([0, 0, 0, 0x66], SecureObject::HMACKey { key: vec![0u8; 16] });
let resp = handle_ecdh(&apdu, &mut store);
assert_eq!(resp.sw, SW_CONDITIONS_NOT_SATISFIED);
}

#[test]
fn test_ecdh_tag7_wrong_type_target_returns_6985() {
let (apdu, mut store, _) = ecdh_inobject_fixture();
store.insert([0, 0, 0, 0x66], SecureObject::Binary { data: vec![0u8; 32] });
let resp = handle_ecdh(&apdu, &mut store);
assert_eq!(resp.sw, SW_CONDITIONS_NOT_SATISFIED);
}

#[test]
fn test_ecdh_tag7_precreated_hmackey_succeeds() {
let (apdu, mut store, expected) = ecdh_inobject_fixture();
store.insert([0, 0, 0, 0x66], SecureObject::HMACKey { key: vec![0u8; 32] });
let resp = handle_ecdh(&apdu, &mut store);
assert_eq!(resp.sw, 0x9000);
match store.get(&[0, 0, 0, 0x66]) {
Some(SecureObject::HMACKey { key }) => assert_eq!(key, &expected),
other => panic!("expected HMACKey with shared secret, got {:?}", other.is_some()),
}
}
}

#[cfg(test)]
Expand Down
Loading