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. 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?
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.
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.
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.
#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"
#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);
}
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;
}
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;
}
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;
}
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;
}
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;
}
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);
|
Operation |
Mechanism Constant |
Notes |
|---|---|---|
|
RSA key generation |
|
256–4096 bits |
|
AES key generation |
|
16 or 32 bytes |
|
RSA sign/verify |
|
PKCS#1 v1.5 |
|
RSA sign/verify (PSS) |
|
PSS padding |
|
RSA encrypt/decrypt |
|
OAEP recommended |
|
RSA encrypt/decrypt |
|
PKCS#1 v1.5 |
|
AES encrypt/decrypt |
|
CBC with PKCS#7 |
|
AES encrypt/decrypt |
|
CBC without padding |
|
AES encrypt/decrypt |
|
CTR mode |
|
ECC sign/verify |
|
160–521 bits |
|
ECDH key agreement |
|
|
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.
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.
-------------------------------------------------------------------------------
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?
If PKCS#11 APIs are required, could you please provide a simple sample code or reference guide for calling libckteec.so in C?
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.
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.
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.
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.
| Option | I2C DTS | se05x_Minimal | OP-TEE exclusive | Recommended For |
|---|---|---|---|---|
PKCS#11 TA (libckteec.so) |
Disabled | Production | ||
| Co-existence (T1oI2C) | Enabled | Development/Testing |
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.
-------------------------------------------------------------------------------