Seeking guidance: se05x_Minimal fails in OP-TEE environment Hello NXP Community, I am attempting to run se05x_Minimal on our target board running Linux with OP-TEE. Following the solution recommended in this community post (How to integrate Plug and Trust MW into OP-TEE), we built the environment accordingly. In particular, for Step 2, we configured the setup without enabling "keep CAAM enabled". As a result of this configuration, the I2C bus connected to SE05x is managed by OP-TEE (Secure World), and the standard Linux I2C device node /dev/i2c-1 is not visible/available in the Normal World (Linux). When executing `./se05x_Minimal` directly from the Linux user-space console, we encounter the following error: App :INFO :Running ./se05x_Minimal
App :INFO :If you want to over-ride the selection, use ENV=EX_SSS_BOOT_SSS_PORT or pass in command line arguments.
App :INFO :PlugAndTrust_v04.07.01_20250519
App :INFO :Using default PlatfSCP03 keys. You can use keys from file using ENV=EX_SSS_BOOT_SCP03_PATH
smCom :ERROR:opening failed...
Failed to open the i2c bus: No such file or directory
smCom :INFO :Pass i2c device address in the format : .
smCom :INFO :Example ./example /dev/i2c-1:0x48 OR ./example /dev/i2c-1
smCom :ERROR:phPalEse_i2c_open_and_configure Failed retry
smCom :ERROR:I2C init Failed: retval d
smCom :ERROR:phPalEse_Init Failed
smCom :ERROR: Failed to create physical connection with ESE
sss :ERROR:SM_I2CConnect Failed. Status 7012
App :ERROR:sss_session_open failed
App :ERROR:ex_sss_session_open Failed
App :ERROR:!ERROR! ret != 0. Environment & Hardware Setup: Evaluation Board: MCIMX8M-WEVK (i.MX 8M Dual/Quad) Secure Element Board: OM-SE051ARD Secure Element: SE05x (Plug & Trust MW v04.07.01) OS: Linux (Normal World) + OP-TEE (Secure World) Middleware Options (CMake): -DPTMW_Host=iMXLinux, -DPTMW_SMCOM=T1oI2C Note: se05x_Minimal works fine if Linux kernel-space direct I2C (/dev/i2c-1) is enabled. It appears smCom is still attempting to open the physical Linux I2C device (/dev/i2c-X), which no longer exists in our Normal World environment. Could you please provide instructions on what we need to do or modify so that se05x_Minimal can run successfully in this setup? SE050 Re: Seeking guidance: se05x_Minimal fails in OP-TEE environment @Kan_Li Thank you very much for your detailed explanation and the C code samples. Following your guidance, we implemented a C application using the standard PKCS#11 API (libckteec.so.0) under Option 1 (OP-TEE exclusive I2C setup). All operations -- including key generation, AES encrypt/decrypt, RSA sign/verify, and RSA encrypt/decrypt -- are now working completely as expected. We appreciate your support in resolving this issue. Re: Seeking guidance: se05x_Minimal fails in OP-TEE environment Hi @Uc_S ,
This is an excellent and important question. The short answer is:
In Option 1 (OP-TEE exclusive I2C), the Plug & Trust MW SSS APIs cannot be used from Linux userspace directly. You must use the standard PKCS#11 C API via libckteec.so .
Below is a detailed explanation and complete C code samples for all the operations you need.
Why SSS APIs Cannot Be Used in This Setup
The Plug & Trust MW SSS APIs ( sss_session_open , sss_key_store_set_key , sss_asymmetric_sign_digest , etc.) rely on a transport layer to communicate with the SE051. All supported transports ( T1oI2C , JRCP_V1_AM , etc.) ultimately require either Linux I2C access or a proxy server — neither of which is available when OP-TEE exclusively owns the I2C bus.
The correct path in the OP-TEE exclusive setup is:
Your C App → PKCS#11 C API (cryptoki.h) → libckteec.so → OP-TEE PKCS#11 TA → SE051
libckteec is provided by optee-client and implements the Cryptoki interface with the OP-TEE PKCS#11 TA as its backend. The TA in turn routes crypto operations to SE051 via OP-TEE's native I2C driver.
Required Headers and Linking
#include /* Standard Cryptoki header — from optee-client or OpenSC */
Compile and link:
gcc -o my_app my_app.c -ldl
# Or link directly:
gcc -o my_app my_app.c /usr/lib/libckteec.so.0
At runtime, set the module path if using dynamic loading:
#define PKCS11_MODULE "/usr/lib/libckteec.so.0"
Initialization and Token Setup (call once at startup)
#include
#include
#include
#define CHECK_RV(rv, msg) \
if ((rv) != CKR_OK) { fprintf(stderr, "%s failed: 0x%lX\n", (msg), (rv)); goto cleanup; }
/* User PIN — must match what was set with pkcs11-tool --init-pin */
static CK_UTF8CHAR user_pin[] = "1234";
static CK_ULONG user_pin_len = 4;
CK_FUNCTION_LIST *p11 = NULL; /* Global function list pointer */
CK_SESSION_HANDLE session = CK_INVALID_HANDLE;
int pkcs11_init(void)
{
CK_RV rv;
CK_ULONG slot_count = 0;
CK_SLOT_ID slot_id;
CK_SLOT_ID slot_list[8];
/* Load function list — if using dynamic linking, use C_GetFunctionList() */
rv = C_Initialize(NULL_PTR);
CHECK_RV(rv, "C_Initialize");
/* Get available slots */
rv = C_GetSlotList(CK_TRUE, NULL_PTR, &slot_count);
CHECK_RV(rv, "C_GetSlotList (count)");
rv = C_GetSlotList(CK_TRUE, slot_list, &slot_count);
CHECK_RV(rv, "C_GetSlotList");
slot_id = slot_list[0]; /* Use first slot — OP-TEE PKCS#11 TA */
/* Open a read-write session */
rv = C_OpenSession(slot_id, CKF_SERIAL_SESSION | CKF_RW_SESSION,
NULL_PTR, NULL_PTR, &session);
CHECK_RV(rv, "C_OpenSession");
/* Login as normal user */
rv = C_Login(session, CKU_USER, user_pin, user_pin_len);
CHECK_RV(rv, "C_Login");
return 0;
cleanup:
return -1;
}
void pkcs11_cleanup(void)
{
C_Logout(session);
C_CloseSession(session);
C_Finalize(NULL_PTR);
}
Operation 1: Generate an RSA Key Pair and Store in SE051
int generate_rsa_keypair(CK_OBJECT_HANDLE *pub_key, CK_OBJECT_HANDLE *priv_key)
{
CK_RV rv;
CK_MECHANISM mech = { CKM_RSA_PKCS_KEY_PAIR_GEN, NULL_PTR, 0 };
CK_ULONG key_bits = 2048;
CK_BYTE pub_exponent[] = { 0x01, 0x00, 0x01 }; /* 65537 */
CK_BBOOL ck_true = CK_TRUE;
CK_BBOOL ck_false = CK_FALSE;
/* Key ID stored in SE051 NVM — choose a unique 4-byte ID */
CK_BYTE key_id[] = { 0x10, 0x10, 0x10, 0x10 };
CK_ATTRIBUTE pub_tmpl[] = {
{ CKA_MODULUS_BITS, &key_bits, sizeof(key_bits) },
{ CKA_PUBLIC_EXPONENT, pub_exponent, sizeof(pub_exponent) },
{ CKA_VERIFY, &ck_true, sizeof(ck_true) },
{ CKA_ENCRYPT, &ck_true, sizeof(ck_true) },
{ CKA_TOKEN, &ck_true, sizeof(ck_true) },
{ CKA_ID, key_id, sizeof(key_id) },
};
CK_ATTRIBUTE priv_tmpl[] = {
{ CKA_SIGN, &ck_true, sizeof(ck_true) },
{ CKA_DECRYPT, &ck_true, sizeof(ck_true) },
{ CKA_TOKEN, &ck_true, sizeof(ck_true) },
{ CKA_SENSITIVE, &ck_true, sizeof(ck_true) },
{ CKA_EXTRACTABLE, &ck_false, sizeof(ck_false) },
{ CKA_ID, key_id, sizeof(key_id) },
};
rv = C_GenerateKeyPair(session, &mech,
pub_tmpl, sizeof(pub_tmpl) / sizeof(pub_tmpl[0]),
priv_tmpl, sizeof(priv_tmpl) / sizeof(priv_tmpl[0]),
pub_key, priv_key);
CHECK_RV(rv, "C_GenerateKeyPair");
printf("RSA-2048 key pair generated. Private key stays in SE051 NVM.\n");
return 0;
cleanup:
return -1;
}
Operation 2: Generate an AES Key and Store in SE051
int generate_aes_key(CK_OBJECT_HANDLE *aes_key)
{
CK_RV rv;
CK_MECHANISM mech = { CKM_AES_KEY_GEN, NULL_PTR, 0 };
CK_ULONG key_len = 32; /* 256-bit AES */
CK_BBOOL ck_true = CK_TRUE;
CK_BBOOL ck_false = CK_FALSE;
CK_BYTE key_id[] = { 0x20, 0x00, 0x00, 0x01 };
CK_ATTRIBUTE aes_tmpl[] = {
{ CKA_VALUE_LEN, &key_len, sizeof(key_len) },
{ CKA_ENCRYPT, &ck_true, sizeof(ck_true) },
{ CKA_DECRYPT, &ck_true, sizeof(ck_true) },
{ CKA_TOKEN, &ck_true, sizeof(ck_true) },
{ CKA_SENSITIVE, &ck_true, sizeof(ck_true) },
{ CKA_EXTRACTABLE, &ck_false, sizeof(ck_false) },
{ CKA_ID, key_id, sizeof(key_id) },
};
rv = C_GenerateKey(session, &mech,
aes_tmpl, sizeof(aes_tmpl) / sizeof(aes_tmpl[0]),
aes_key);
CHECK_RV(rv, "C_GenerateKey (AES)");
printf("AES-256 key generated and stored in SE051.\n");
return 0;
cleanup:
return -1;
}
Operations 3 & 4: AES-CBC Encrypt / Decrypt
int aes_encrypt(CK_OBJECT_HANDLE aes_key,
const CK_BYTE *plaintext, CK_ULONG plaintext_len,
CK_BYTE *ciphertext, CK_ULONG *ciphertext_len)
{
CK_RV rv;
CK_BYTE iv[16] = { 0 }; /* All-zero IV for example; use a random IV in production */
CK_MECHANISM mech = { CKM_AES_CBC_PAD, iv, sizeof(iv) };
rv = C_EncryptInit(session, &mech, aes_key);
CHECK_RV(rv, "C_EncryptInit");
rv = C_Encrypt(session, (CK_BYTE *)plaintext, plaintext_len,
ciphertext, ciphertext_len);
CHECK_RV(rv, "C_Encrypt");
return 0;
cleanup:
return -1;
}
int aes_decrypt(CK_OBJECT_HANDLE aes_key,
const CK_BYTE *ciphertext, CK_ULONG ciphertext_len,
CK_BYTE *plaintext, CK_ULONG *plaintext_len)
{
CK_RV rv;
CK_BYTE iv[16] = { 0 }; /* Must match the IV used for encryption */
CK_MECHANISM mech = { CKM_AES_CBC_PAD, iv, sizeof(iv) };
rv = C_DecryptInit(session, &mech, aes_key);
CHECK_RV(rv, "C_DecryptInit");
rv = C_Decrypt(session, (CK_BYTE *)ciphertext, ciphertext_len,
plaintext, plaintext_len);
CHECK_RV(rv, "C_Decrypt");
return 0;
cleanup:
return -1;
}
Operations 5 & 6: RSA Sign (private key stays in SE051) and Verify
int rsa_sign(CK_OBJECT_HANDLE priv_key,
const CK_BYTE *data, CK_ULONG data_len,
CK_BYTE *signature, CK_ULONG *sig_len)
{
CK_RV rv;
/* SHA256-PKCS1v1.5 — SE051 computes SHA-256 digest internally then signs */
CK_MECHANISM mech = { CKM_SHA256_RSA_PKCS, NULL_PTR, 0 };
rv = C_SignInit(session, &mech, priv_key);
CHECK_RV(rv, "C_SignInit");
rv = C_Sign(session, (CK_BYTE *)data, data_len, signature, sig_len);
CHECK_RV(rv, "C_Sign");
printf("RSA signature generated (%lu bytes). Private key never left SE051.\n", *sig_len);
return 0;
cleanup:
return -1;
}
int rsa_verify(CK_OBJECT_HANDLE pub_key,
const CK_BYTE *data, CK_ULONG data_len,
const CK_BYTE *signature, CK_ULONG sig_len)
{
CK_RV rv;
CK_MECHANISM mech = { CKM_SHA256_RSA_PKCS, NULL_PTR, 0 };
rv = C_VerifyInit(session, &mech, pub_key);
CHECK_RV(rv, "C_VerifyInit");
rv = C_Verify(session, (CK_BYTE *)data, data_len,
(CK_BYTE *)signature, sig_len);
if (rv == CKR_OK) {
printf("Signature verification: SUCCESS\n");
return 0;
} else if (rv == CKR_SIGNATURE_INVALID) {
printf("Signature verification: INVALID\n");
return 1;
}
CHECK_RV(rv, "C_Verify");
cleanup:
return -1;
}
Operations 7 & 8: RSA Encrypt / Decrypt
int rsa_encrypt(CK_OBJECT_HANDLE pub_key,
const CK_BYTE *plaintext, CK_ULONG plaintext_len,
CK_BYTE *ciphertext, CK_ULONG *ciphertext_len)
{
CK_RV rv;
/* RSA-OAEP with SHA-256 — recommended over PKCS1 v1.5 for new designs */
CK_RSA_PKCS_OAEP_PARAMS oaep_params = {
.hashAlg = CKM_SHA256,
.mgf = CKG_MGF1_SHA256,
.source = CKZ_DATA_SPECIFIED,
.pSourceData = NULL,
.ulSourceDataLen = 0
};
CK_MECHANISM mech = { CKM_RSA_PKCS_OAEP, &oaep_params, sizeof(oaep_params) };
rv = C_EncryptInit(session, &mech, pub_key);
CHECK_RV(rv, "C_EncryptInit (RSA-OAEP)");
rv = C_Encrypt(session, (CK_BYTE *)plaintext, plaintext_len,
ciphertext, ciphertext_len);
CHECK_RV(rv, "C_Encrypt (RSA-OAEP)");
return 0;
cleanup:
return -1;
}
int rsa_decrypt(CK_OBJECT_HANDLE priv_key,
const CK_BYTE *ciphertext, CK_ULONG ciphertext_len,
CK_BYTE *plaintext, CK_ULONG *plaintext_len)
{
CK_RV rv;
CK_RSA_PKCS_OAEP_PARAMS oaep_params = {
.hashAlg = CKM_SHA256,
.mgf = CKG_MGF1_SHA256,
.source = CKZ_DATA_SPECIFIED,
.pSourceData = NULL,
.ulSourceDataLen = 0
};
CK_MECHANISM mech = { CKM_RSA_PKCS_OAEP, &oaep_params, sizeof(oaep_params) };
rv = C_DecryptInit(session, &mech, priv_key);
CHECK_RV(rv, "C_DecryptInit (RSA-OAEP)");
rv = C_Decrypt(session, (CK_BYTE *)ciphertext, ciphertext_len,
plaintext, plaintext_len);
CHECK_RV(rv, "C_Decrypt (RSA-OAEP)");
printf("RSA decryption completed. Private key never left SE051.\n");
return 0;
cleanup:
return -1;
}
Accessing Existing Keys (without regenerating)
If a key was previously generated and stored in SE051, retrieve it by CKA_ID without calling C_GenerateKey again:
int find_key_by_id(CK_BYTE *key_id, CK_ULONG key_id_len,
CK_OBJECT_CLASS obj_class,
CK_OBJECT_HANDLE *handle)
{
CK_RV rv;
CK_ULONG obj_count = 0;
CK_ATTRIBUTE search_tmpl[] = {
{ CKA_CLASS, &obj_class, sizeof(obj_class) },
{ CKA_ID, key_id, key_id_len },
};
rv = C_FindObjectsInit(session, search_tmpl,
sizeof(search_tmpl) / sizeof(search_tmpl[0]));
CHECK_RV(rv, "C_FindObjectsInit");
rv = C_FindObjects(session, handle, 1, &obj_count);
CHECK_RV(rv, "C_FindObjects");
C_FindObjectsFinal(session);
if (obj_count == 0) {
fprintf(stderr, "Key not found in SE051\n");
return -1;
}
return 0;
cleanup:
C_FindObjectsFinal(session);
return -1;
}
Usage example:
CK_OBJECT_HANDLE priv_key;
CK_BYTE key_id[] = { 0x10, 0x10, 0x10, 0x10 };
CK_OBJECT_CLASS priv_class = CKO_PRIVATE_KEY;
find_key_by_id(key_id, sizeof(key_id), priv_class, &priv_key);
Supported Mechanisms (verified on OP-TEE PKCS#11 TA + SE051)
Operation
Mechanism Constant
Notes
RSA key generation
CKM_RSA_PKCS_KEY_PAIR_GEN
256–4096 bits
AES key generation
CKM_AES_KEY_GEN
16 or 32 bytes
RSA sign/verify
CKM_SHA256_RSA_PKCS
PKCS#1 v1.5
RSA sign/verify (PSS)
CKM_SHA256_RSA_PKCS_PSS
PSS padding
RSA encrypt/decrypt
CKM_RSA_PKCS_OAEP
OAEP recommended
RSA encrypt/decrypt
CKM_RSA_PKCS
PKCS#1 v1.5
AES encrypt/decrypt
CKM_AES_CBC_PAD
CBC with PKCS#7
AES encrypt/decrypt
CKM_AES_CBC
CBC without padding
AES encrypt/decrypt
CKM_AES_CTR
CTR mode
ECC sign/verify
CKM_ECDSA_SHA256
160–521 bits
ECDH key agreement
CKM_ECDH1_DERIVE
Can the SSS APIs Still Be Used at All?
Yes — but only in the co-existence setup (Option 2) where Linux still has access to the I2C bus (i.e., the lf-6.12.y-i2c-disabled-se050 DTS patch has NOT been applied). In that case, you can use the SSS APIs as described in AN13030 Section 3.3 directly from Linux.
If you choose Option 1 (OP-TEE exclusive), the Cryptoki/PKCS#11 C API shown above is the correct and only supported path from Linux userspace.
Reference
AN13030 Rev. 2.4, Section 3.3 — Full SSS API reference (for co-existence / non-OP-TEE builds)
OP-TEE PKCS#11 TA test suite (pkcs11_1000.c): optee-test/host/xtest/pkcs11_1000.c — comprehensive C examples for all Cryptoki operations
NXP GitHub: se05x-pkcs11 — NXP's PKCS#11 standalone library (alternative to libckteec.so for non-OP-TEE builds)
Hope that helps,
Have a great day, Kan
------------------------------------------------------------------------------- Note: - If this post answers your question, please click the "Mark Correct" button. Thank you! - We are following threads for 7 weeks after the last post, later replies are ignored Please open a new thread and refer to the closed one, if you have a related question at a later point in time. ------------------------------------------------------------------------------- Re: Seeking guidance: se05x_Minimal fails in OP-TEE environment @Kan_Li Thank you very much for the clear and detailed explanation. We are currently considering using Option 1 or Option 2 depending on the stage of our future development (e.g., using Option 2 for initial testing/evaluation and Option 1 for production). Regarding Option 1 (OP-TEE PKCS#11 TA), we have a question about application development in C. If we would like to implement cryptographic operations such as key storage, signing / signature generation, signature verification, and encryption/decryption in a C program under Option 1, which approach should we take? Should we write a C program that directly calls standard PKCS#11 APIs (e.g., C_Initialize, C_CreateObject, C_SignInit, C_Sign, C_VerifyInit, C_Verify, etc.) via the OP-TEE PKCS#11 library (libckteec.so)? Or is it still possible/recommended to use the Plug & Trust MW SSS APIs (such as sss_key_store_set_key, sss_asymmetric_sign_digest, sss_asymmetric_verify_digest, sss_cipher_update, etc.) in this setup? If PKCS#11 APIs are required, could you please provide a simple sample code or reference guide for calling libckteec.so in C? Re: Seeking guidance: se05x_Minimal fails in OP-TEE environment Hi @Uc_S ,
Thank you for the detailed report. The root cause is clear, and I can explain exactly why this happens and what your options are.
Root Cause
se05x_Minimal was built with -DPTMW_SMCOM=T1oI2C . This tells the smCom layer to open a physical Linux I2C device node ( /dev/i2c-X ) at session open time. Since you applied the lf-6.12.y-i2c-disabled-se050 DTS patch (Step 1 of the integration guide), that I2C controller is disabled in Linux Normal World — the device node simply does not exist. OP-TEE exclusively owns the I2C bus via CFG_IMX_I2C=y , and Linux cannot see or open it.
This is by design — the DTS patch is what gives OP-TEE exclusive, uncontended access to SE051. The se05x_Minimal binary built with T1oI2C is fundamentally incompatible with this configuration.
Your Two Options
✅ Option 1 — Use OP-TEE PKCS#11 TA (Recommended for Production)
This is the intended Linux userspace path in the OP-TEE exclusive setup. Instead of running se05x_Minimal , use pkcs11-tool or OpenSSL with libckteec.so (the OP-TEE PKCS#11 TA library). The TA internally uses SE051 as its crypto backend via OP-TEE's native I2C driver.
Quick verification that SE051 is reachable via PKCS#11:
# List available PKCS#11 slots — SE051 should appear
pkcs11-tool --module /usr/lib/libckteec.so.0 --list-slots
# Get a random number from SE051 via OP-TEE
pkcs11-tool --module /usr/lib/libckteec.so.0 --generate-random 16 | xxd
If you see a slot and random bytes, SE051 is fully accessible through OP-TEE. There is no need to run se05x_Minimal — it duplicates what the PKCS#11 TA already provides.
✅ Option 2 — Co-existence Setup (Recommended for Development/Testing)
If you specifically need to run se05x_Minimal and other Plug & Trust MW demos from Linux userspace, use the co-existence setup where Linux DTS still has I2C enabled (i.e., do not apply the I2C-disabled DTS patch).
Steps:
Step 1 — Revert the Linux DTS to keep I2C enabled in Normal World
Build your imx8mq-evk.dtb (or equivalent) from the unmodified Linux DTS (without the lf-6.12.y-i2c-disabled-se050 patch). The I2C controller node for SE051 must remain enabled in Linux.
Step 2 — Keep your existing cmake flags unchanged
Your current cmake configuration is correct for co-existence:
cmake -S . -B ./build/ -DPTMW_Applet=SE05X_C -DPTMW_SE05X_Ver=07_02 -DPTMW_Host=iMXLinux -DPTMW_SMCOM=T1oI2C -DPTMW_HostCrypto=OPENSSL -DPTMW_RTOS=Default -DPTMW_mbedTLS_ALT=None -DPTMW_SCP=SCP03_SSS -DPTMW_SE05X_Auth=PlatfSCP03 -DPTMW_Log=Silent -DCMAKE_BUILD_TYPE=Release -DPTMW_OpenSSL=3_0 -DPTMW_SE_RESET_LOGIC=1
Step 3 — Set the I2C port before running
export EX_SSS_BOOT_SSS_PORT=/dev/i2c-1
./se05x_Minimal
Trade-off: In this mode, both OP-TEE and Linux share the I2C bus to SE051. OP-TEE uses it for RSA/ECC offload; Linux uses it for MW demos. Concurrent access is not arbitrated, which can cause APDU collisions under load. Acceptable for development, but not recommended for production.
Summary
Option
I2C DTS
se05x_Minimal
OP-TEE exclusive
Recommended For
PKCS#11 TA ( libckteec.so )
Disabled
❌ Not needed
✅ Yes
Production
Co-existence (T1oI2C)
Enabled
✅ Works
⚠️ Shared I2C
Development/Testing
Clarification on the Integration Guide
The community post you referenced (Step 2 — without CAAM) configures OP-TEE to exclusively own I2C. The Linux-side MW compilation shown in that guide (with T1oI2C ) was intended for the initial verification step before switching to OP-TEE mode — not for use alongside the OP-TEE exclusive I2C setup.
Once OP-TEE owns I2C exclusively, the correct Linux userspace interface is the OP-TEE PKCS#11 TA, not the Plug & Trust MW SSS API demos directly.
Please let us know which option fits your use case and we can provide further guidance.
Have a great day, Kan
------------------------------------------------------------------------------- Note: - If this post answers your question, please click the "Mark Correct" button. Thank you! - We are following threads for 7 weeks after the last post, later replies are ignored Please open a new thread and refer to the closed one, if you have a related question at a later point in time. -------------------------------------------------------------------------------
View full article