Merge pull request #618 from dgarske/optee_pkcs11

Add PKCS11 OP-TEE token support with a PKCS#11 token initialization example
pull/619/head
Andrew Hutchings 2026-08-21 13:30:46 +01:00 committed by GitHub
commit 05daee9e89
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 506 additions and 3 deletions

View File

@ -2,7 +2,9 @@
CC = gcc
WOLFSSL_INSTALL_DIR = /usr/local
CFLAGS = -Wall -I$(WOLFSSL_INSTALL_DIR)/include
LIBS = -L$(WOLFSSL_INSTALL_DIR)/lib -lm
# -ldl for pkcs11_inittoken, which dlopen()s the PKCS#11 library. Harmless
# on glibc >= 2.34 where libdl is merged into libc, required on older ones.
LIBS = -L$(WOLFSSL_INSTALL_DIR)/lib -lm -ldl
# option variables
DYN_LIB = -lwolfssl
@ -11,9 +13,28 @@ DEBUG_FLAGS = -g -DDEBUG
DEBUG_INC_PATHS = -MD
OPTIMIZE = -Os
# WC_ECC_FLAG_DERIVE is an enum member, not a macro, so the preprocessor cannot
# test for it - and a version check cannot either, because wolfSSL master and
# v5.9.2-stable both report LIBWOLFSSL_VERSION_HEX 0x05009002. Probe by
# compiling against the installed headers instead, which is accurate on any
# release, snapshot or git build.
# Skipped for goals that never compile C, so "make clean" does not spawn a
# throwaway compile - which would also fail noisily where wolfSSL is absent.
ifeq ($(filter clean,$(MAKECMDGOALS)),)
HAVE_ECC_FLAG_DERIVE := $(shell printf '%s\n' \
'#include <wolfssl/options.h>' \
'#include <wolfssl/wolfcrypt/ecc.h>' \
'int main(void){return (int)WC_ECC_FLAG_DERIVE;}' \
| $(CC) -I$(WOLFSSL_INSTALL_DIR)/include -x c - -o /dev/null 2>/dev/null \
&& echo yes)
endif
# Options
#CFLAGS+=$(DEBUG_FLAGS)
CFLAGS+=$(OPTIMIZE)
ifeq ($(HAVE_ECC_FLAG_DERIVE),yes)
CFLAGS+=-DHAVE_WC_ECC_FLAG_DERIVE
endif
#LIBS+=$(STATIC_LIB) -ldl -lm
LIBS+=$(DYN_LIB)

View File

@ -159,6 +159,114 @@ See [PKCS11.md](./PKCS11.md) in this folder.
It should be noted WOLFSSL_PKCS11_RW_TOKENS is only needed for adding the keys and certs to the store. Once already in the store this is no longer needed.
## Setting up and testing OP-TEE
[OP-TEE](https://optee.readthedocs.io/) provides a PKCS #11 trusted
application, so private keys can be generated and used inside the TrustZone
secure world and never appear in normal-world memory. This has been tested on
an NXP i.MX95 running OP-TEE 4.4, but nothing here is board specific.
1. Build OP-TEE with the PKCS #11 trusted application
The TA is not in every OP-TEE build. Enable it with `CFG_PKCS11_TA=y` and
install the resulting `fd02c9da-306c-48c7-a49c-bbd827ae86ee.ta` where
`tee-supplicant` looks for TAs. You also need the `optee_client` userspace:
`tee-supplicant`, `libteec.so.2` and `libckteec.so.0`.
2. Make sure `tee-supplicant` is running
Every PKCS #11 call fails at `C_Initialize` without it, because the TA
cannot be loaded. If the libraries are not in the default library path,
point the loader at them:
```
export LD_LIBRARY_PATH=/path/to/optee/lib
tee-supplicant -l /path/to/ta/dir &
```
3. Change to wolfssl directory
```
./autogen.sh
./configure --enable-pkcs11 --enable-cryptocb-rsa-pad
make
sudo make install
```
`--enable-cryptocb-rsa-pad` matters. Without it wolfSSL asks the token for
raw RSA (`CKM_RSA_X_509`), which OP-TEE's TA does not implement, and RSA
private key operations fail with `RSA_BUFFER_E` (-131). With it wolfSSL
uses `CKM_RSA_PKCS` and the RSA examples pass. The same applies to any
token that declines raw RSA.
4. Change to wolfssl-examples/pkcs11 directory and build
```
make
```
5. Initialize the token and run the examples
OP-TEE tokens come up uninitialized and OP-TEE ships no equivalent of
`softhsm2-util`, so `pkcs11_inittoken` does it through the PKCS #11 API:
```
./optee-init.sh
```
That initializes slot 0 and then runs the examples. To use a different
slot, label or PIN:
```
OPTEE_TOKEN=myToken OPTEE_PIN=1234 ./optee-init.sh 1
```
Once the token is initialized, run the examples on their own with:
```
./optee.sh
```
Or a single example directly, with the usual argument order:
```
./pkcs11_genecc libckteec.so.0 0 wolfSSL cryptoki
```
### EC keys that both derive and sign
`pkcs11_test` generates a single EC key and then uses it for both ECDH and
ECDSA. PKCS #11 leaves the defaults for `CKA_DERIVE` and `CKA_SIGN` up to the
token: SoftHSM grants both regardless of the template, while OP-TEE grants only
what was asked for.
The examples therefore request both explicitly:
```c
wc_ecc_make_key_ex2(&rng, 32, key, ECC_CURVE_DEF, EC_KEYGEN_FLAGS);
```
`EC_KEYGEN_FLAGS` is `WC_ECC_FLAG_DEC_SIGN | WC_ECC_FLAG_DERIVE` when the
installed wolfSSL has `WC_ECC_FLAG_DERIVE`, and `WC_ECC_FLAG_DEC_SIGN` alone
when it does not. No edit is needed either way: the Makefile probes for the
flag by compiling against the installed headers and defines
`HAVE_WC_ECC_FLAG_DERIVE` when it is present.
The probe exists because neither of the usual tests works here.
`WC_ECC_FLAG_DERIVE` is an enum member rather than a macro, so `#ifdef` cannot
see it, and a version test cannot distinguish the two cases either, because
wolfSSL master and v5.9.2-stable both report `LIBWOLFSSL_VERSION_HEX`
`0x05009002`.
Against a wolfSSL without the flag the key is generated sign-only, which is all
that library can request. `pkcs11_test` then fails on a strict token - the
OP-TEE TA among them - at the first ECDH operation with
`CKR_KEY_FUNCTION_NOT_PERMITTED`, surfacing as `WC_HW_E` (-248). Tokens that
enable `CKA_DERIVE` by default are unaffected.
All the examples pass, including RSA key generation, ECDSA, ECDH, AES-CBC,
AES-GCM, HMAC and RNG.
## TLS Server Example with SoftHSM (RSA)
The example `server-tls-pkcs11` is a server that uses a private key that has been stored on the PKCS #11 device.

View File

@ -0,0 +1,48 @@
#!/bin/sh
# Initialize an OP-TEE PKCS#11 token and then run the examples against it.
#
# OP-TEE tokens come up uninitialized and OP-TEE ships no equivalent of
# softhsm2-util, so pkcs11_inittoken does it through the PKCS#11 API. Re-running
# this is safe: an already-initialized token is left alone.
set -e
cd "$(dirname "$0")"
# Same argument convention as optee.sh: an optional slot id first, then any
# specific examples to run.
# Only treat the first argument as a slot id if it is numeric, so that
# "./optee-init.sh pkcs11_rsa" runs one example against the default slot instead of
# silently consuming the example name as a slot id.
case "${1:-}" in
'' | *[!0-9]* ) ;;
* ) OPTEE_SLOTID=$1; shift ;;
esac
if [ -z "$OPTEE_LIB" ]
then
OPTEE_LIB=libckteec.so.0
fi
if [ -z "$OPTEE_SLOTID" ]
then
OPTEE_SLOTID=0
fi
if [ -z "$OPTEE_TOKEN" ]
then
OPTEE_TOKEN=wolfSSL
fi
if [ -z "$OPTEE_SOPIN" ]
then
OPTEE_SOPIN=cryptoki
fi
if [ -z "$OPTEE_PIN" ]
then
OPTEE_PIN=cryptoki
fi
./pkcs11_inittoken "$OPTEE_LIB" "$OPTEE_SLOTID" "$OPTEE_TOKEN" \
"$OPTEE_SOPIN" "$OPTEE_PIN"
OPTEE_LIB="$OPTEE_LIB" OPTEE_TOKEN="$OPTEE_TOKEN" OPTEE_PIN="$OPTEE_PIN" \
exec ./optee.sh "$OPTEE_SLOTID" "$@"

83
pkcs11/optee.sh 100755
View File

@ -0,0 +1,83 @@
#!/bin/sh
# Run the PKCS#11 examples against OP-TEE's PKCS#11 trusted application.
#
# The token must already be initialized - use ./optee-init.sh for that.
#
# Requires tee-supplicant to be running; without it every call fails at
# C_Initialize because the TA cannot be loaded.
# Only treat the first argument as a slot id if it is numeric, so that
# "./optee.sh pkcs11_rsa" runs one example against the default slot instead of
# silently consuming the example name as a slot id.
case "${1:-}" in
'' | *[!0-9]* ) ;;
* ) OPTEE_SLOTID=$1; shift ;;
esac
# OP-TEE's PKCS#11 client library. It is usually installed as a normal shared
# library, but on an embedded rootfs it is often staged elsewhere, in which
# case set OPTEE_LIB (and LD_LIBRARY_PATH) to point at it.
if [ -z "$OPTEE_LIB" ]
then
OPTEE_LIB=libckteec.so.0
fi
if [ -z "$OPTEE_SLOTID" ]
then
OPTEE_SLOTID=0
fi
if [ -z "$OPTEE_TOKEN" ]
then
OPTEE_TOKEN=wolfSSL
fi
if [ -z "$OPTEE_PIN" ]
then
OPTEE_PIN=cryptoki
fi
rc=0
run_example()
{
name=$1
shift
echo
echo "# $name"
if ! "$@" "$OPTEE_LIB" "$OPTEE_SLOTID" "$OPTEE_TOKEN" "$OPTEE_PIN"
then
echo "# FAILED: $name"
rc=1
fi
}
echo "# Using slot ID: $OPTEE_SLOTID"
echo "# Using library: $OPTEE_LIB"
echo "# Using token: $OPTEE_TOKEN"
if [ $# -gt 0 ]
then
for example in "$@"
do
run_example "$example" "./$example"
done
else
run_example "RSA example" ./pkcs11_rsa
run_example "ECC example" ./pkcs11_ecc
run_example "Generate ECC example" ./pkcs11_genecc
run_example "AES-GCM example" ./pkcs11_aesgcm
run_example "AES-CBC example" ./pkcs11_aescbc
run_example "HMAC example" ./pkcs11_hmac
run_example "Random Number Generation example" ./pkcs11_rand
run_example "PKCS#11 test" ./pkcs11_test
fi
echo
if [ $rc -eq 0 ]
then
echo "# All PKCS#11 examples passed"
else
echo "# One or more PKCS#11 examples FAILED"
fi
exit $rc

View File

@ -0,0 +1,215 @@
/* pkcs11_inittoken.c
*
* Copyright (C) 2006-2026 wolfSSL Inc.
*
* This file is part of wolfSSL.
*
* wolfSSL is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* wolfSSL is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
*/
/* Initialize a PKCS#11 token: set its label and SO PIN, then set the user PIN.
*
* The other examples in this directory need a token that is already
* initialized. SoftHSM has softhsm2-util for that, but many PKCS#11
* implementations ship no such utility - OP-TEE's PKCS#11 trusted application
* is one, and its tokens come up uninitialized. This example does the job
* through the PKCS#11 API itself, so it works against any implementation and
* needs nothing installed on the target beyond the PKCS#11 library.
*
* It is safe to re-run: a fully initialized token is left untouched, and one
* left half-initialized by an interrupted run is completed rather than wiped.
*
* Note this deliberately talks to the PKCS#11 library directly rather than
* going through wolfSSL. Token initialization is administrative, not
* cryptographic, so wolfSSL does not wrap C_InitToken/C_InitPIN.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dlfcn.h>
#include <wolfssl/options.h>
#include <wolfssl/wolfcrypt/settings.h>
#include <wolfssl/wolfcrypt/pkcs11.h>
/* Not declared in wolfssl/wolfcrypt/pkcs11.h - only the flags wolfSSL itself
* uses are. From PKCS#11 v2.40, CK_TOKEN_INFO flags. */
#ifndef CKF_TOKEN_INITIALIZED
#define CKF_TOKEN_INITIALIZED 0x00000400UL
#endif
#ifndef CKF_USER_PIN_INITIALIZED
#define CKF_USER_PIN_INITIALIZED 0x00000008UL
#endif
/* PKCS#11 labels are a fixed-width, space-padded field - not a C string. */
#define LABEL_SZ 32
static int init_token(CK_FUNCTION_LIST* func, CK_SLOT_ID slotId,
const char* label, const char* soPin,
const char* userPin)
{
CK_SESSION_HANDLE session = 0;
CK_TOKEN_INFO tokenInfo;
CK_UTF8CHAR padded[LABEL_SZ];
CK_RV rv;
int ret = 0;
size_t labelSz;
labelSz = strlen(label);
if (labelSz > LABEL_SZ) {
fprintf(stderr, "Label too long: %d bytes maximum\n", LABEL_SZ);
return 1;
}
memset(&tokenInfo, 0, sizeof(tokenInfo));
rv = func->C_GetTokenInfo(slotId, &tokenInfo);
if (rv != CKR_OK) {
fprintf(stderr, "Failed to get token info: 0x%lx\n", (unsigned long)rv);
return 1;
}
/* Initialization is two steps that can be interrupted between: C_InitToken
* sets the SO PIN and marks the token initialized, and only a later SO
* login can set the user PIN. Treat the token as done only when both have
* happened, so a run that died in between can be completed by re-running
* rather than needing the token wiped. */
if ((tokenInfo.flags & CKF_TOKEN_INITIALIZED) != 0 &&
(tokenInfo.flags & CKF_USER_PIN_INITIALIZED) != 0) {
printf("Token in slot %lu is already initialized - nothing to do\n",
(unsigned long)slotId);
return 0;
}
if ((tokenInfo.flags & CKF_TOKEN_INITIALIZED) == 0) {
memset(padded, ' ', sizeof(padded));
memcpy(padded, label, labelSz);
/* Sets the SO PIN and the label, and puts the token in a state where
* the SO can log in to set the user PIN. */
rv = func->C_InitToken(slotId, (CK_UTF8CHAR_PTR)soPin,
(CK_ULONG)strlen(soPin), padded);
if (rv != CKR_OK) {
fprintf(stderr, "Failed to initialize token: 0x%lx\n",
(unsigned long)rv);
return 1;
}
printf("Initialized token in slot %lu with label \"%s\"\n",
(unsigned long)slotId, label);
}
else {
/* Resuming: re-running C_InitToken here would destroy every object
* already on the token, so pick up at the user PIN instead. The SO PIN
* must match the one the earlier run set. */
printf("Token in slot %lu is initialized but has no user PIN"
" - setting it\n", (unsigned long)slotId);
}
/* The user PIN can only be set by the SO, over a read/write session. */
rv = func->C_OpenSession(slotId, CKF_SERIAL_SESSION | CKF_RW_SESSION,
NULL, NULL, &session);
if (rv != CKR_OK) {
fprintf(stderr, "Failed to open session: 0x%lx\n", (unsigned long)rv);
return 1;
}
rv = func->C_Login(session, CKU_SO, (CK_UTF8CHAR_PTR)soPin,
(CK_ULONG)strlen(soPin));
if (rv != CKR_OK) {
fprintf(stderr, "Failed to login as SO: 0x%lx\n", (unsigned long)rv);
ret = 1;
}
if (ret == 0) {
rv = func->C_InitPIN(session, (CK_UTF8CHAR_PTR)userPin,
(CK_ULONG)strlen(userPin));
if (rv != CKR_OK) {
fprintf(stderr, "Failed to set user PIN: 0x%lx\n",
(unsigned long)rv);
ret = 1;
}
else {
printf("User PIN set - token is ready for the other examples\n");
}
}
func->C_CloseSession(session);
return ret;
}
int main(int argc, char* argv[])
{
void* dlib = NULL;
CK_C_GetFunctionList getFuncList;
CK_FUNCTION_LIST* func = NULL;
CK_SLOT_ID slotId;
CK_RV rv;
int ret;
unsigned long slotVal;
char* slotEnd;
if (argc != 6) {
fprintf(stderr, "Usage: pkcs11_inittoken <libname> <slot> <tokenname>"
" <sopin> <userpin>\n");
return 1;
}
/* strtoul rather than atoi: atoi returns 0 for non-numeric input, which
* would silently initialize slot 0 - the wrong token, destructively. */
slotEnd = NULL;
slotVal = strtoul(argv[2], &slotEnd, 10);
if (slotEnd == argv[2] || *slotEnd != '\0') {
fprintf(stderr, "Slot must be a number: %s\n", argv[2]);
return 1;
}
slotId = (CK_SLOT_ID)slotVal;
dlib = dlopen(argv[1], RTLD_NOW);
if (dlib == NULL) {
fprintf(stderr, "Failed to open PKCS#11 library: %s\n", dlerror());
return 2;
}
getFuncList = (CK_C_GetFunctionList)dlsym(dlib, "C_GetFunctionList");
if (getFuncList == NULL) {
fprintf(stderr, "Library has no C_GetFunctionList\n");
dlclose(dlib);
return 2;
}
rv = getFuncList(&func);
if (rv != CKR_OK || func == NULL) {
fprintf(stderr, "Failed to get function list: 0x%lx\n",
(unsigned long)rv);
dlclose(dlib);
return 2;
}
rv = func->C_Initialize(NULL);
if (rv != CKR_OK) {
fprintf(stderr, "Failed to initialize PKCS#11 library: 0x%lx\n",
(unsigned long)rv);
dlclose(dlib);
return 2;
}
ret = init_token(func, slotId, argv[3], argv[4], argv[5]);
func->C_Finalize(NULL);
dlclose(dlib);
return ret;
}

View File

@ -27,6 +27,24 @@
#include <wolfssl/wolfcrypt/error-crypt.h>
#include <wolfssl/wolfcrypt/logging.h>
/* Ask the token for derive as well as sign when generating the EC key.
*
* WC_ECC_FLAG_DERIVE is an enum member added to wolfSSL after 5.9.2, so the
* preprocessor cannot test for it directly, and a version test does not help
* either: 5.9.2-stable and current master both report LIBWOLFSSL_VERSION_HEX
* 0x05009002. The Makefile probes for it by compiling against the installed
* headers and defines HAVE_WC_ECC_FLAG_DERIVE when it is present.
*
* Without it the key is generated sign-only, which is all a wolfSSL that
* predates the flag can request. The ECDH test then fails on a token that
* grants only what was asked for, such as the OP-TEE PKCS#11 TA; tokens that
* enable CKA_DERIVE by default are unaffected. */
#ifdef HAVE_WC_ECC_FLAG_DERIVE
#define EC_KEYGEN_FLAGS (WC_ECC_FLAG_DEC_SIGN | WC_ECC_FLAG_DERIVE)
#else
#define EC_KEYGEN_FLAGS (WC_ECC_FLAG_DEC_SIGN)
#endif
#ifndef NO_RSA
static const unsigned char client_key_der_2048[] =
{
@ -555,7 +573,12 @@ int gen_ec_keys_label(Pkcs11Token* token, ecc_key* key, char* label, int devId)
if (ret != 0)
fprintf(stderr, "Failed to initialize EC key: %d\n", ret);
if (ret == 0) {
ret = wc_ecc_make_key_ex(&rng, 32, key, ECC_CURVE_DEF);
/* Request derive as well as sign: this key is used for both ECDH
* and ECDSA below. PKCS#11 leaves the CKA_DERIVE and CKA_SIGN
* defaults up to the token, so a token that grants only what was
* asked for refuses the other operation. */
ret = wc_ecc_make_key_ex2(&rng, 32, key, ECC_CURVE_DEF,
EC_KEYGEN_FLAGS);
if (ret != 0)
fprintf(stderr, "Failed to generate EC key: %d\n", ret);
}
@ -571,7 +594,12 @@ int gen_ec_keys(Pkcs11Token* token, ecc_key* key, unsigned char* id, int idLen,
if (ret != 0)
fprintf(stderr, "Failed to initialize EC key: %d\n", ret);
if (ret == 0) {
ret = wc_ecc_make_key_ex(&rng, 32, key, ECC_CURVE_DEF);
/* Request derive as well as sign: this key is used for both ECDH
* and ECDSA below. PKCS#11 leaves the CKA_DERIVE and CKA_SIGN
* defaults up to the token, so a token that grants only what was
* asked for refuses the other operation. */
ret = wc_ecc_make_key_ex2(&rng, 32, key, ECC_CURVE_DEF,
EC_KEYGEN_FLAGS);
if (ret != 0)
fprintf(stderr, "Failed to generate EC key: %d\n", ret);
}