diff --git a/pkcs11/Makefile b/pkcs11/Makefile index 0827fc88..3e3215b2 100644 --- a/pkcs11/Makefile +++ b/pkcs11/Makefile @@ -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 ' \ + '#include ' \ + '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) diff --git a/pkcs11/README.md b/pkcs11/README.md index 96b0e940..4dc74e19 100644 --- a/pkcs11/README.md +++ b/pkcs11/README.md @@ -243,14 +243,26 @@ what was asked for. The examples therefore request both explicitly: ```c -wc_ecc_make_key_ex2(&rng, 32, key, ECC_CURVE_DEF, - WC_ECC_FLAG_DEC_SIGN | WC_ECC_FLAG_DERIVE); +wc_ecc_make_key_ex2(&rng, 32, key, ECC_CURVE_DEF, EC_KEYGEN_FLAGS); ``` -`WC_ECC_FLAG_DERIVE` requires wolfSSL with PKCS #11 derive+sign key generation -support. Against an older wolfSSL, drop that flag; `pkcs11_test` will then fail -on a strict token at the first ECDSA operation with -`CKR_KEY_FUNCTION_NOT_PERMITTED`, surfacing as `WC_HW_E` (-248). +`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. diff --git a/pkcs11/optee-init.sh b/pkcs11/optee-init.sh index 18f08001..2f809570 100755 --- a/pkcs11/optee-init.sh +++ b/pkcs11/optee-init.sh @@ -12,11 +12,13 @@ cd "$(dirname "$0")" # Same argument convention as optee.sh: an optional slot id first, then any # specific examples to run. -if [ $# -gt 0 ] -then - OPTEE_SLOTID=$1 - shift -fi +# 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 diff --git a/pkcs11/optee.sh b/pkcs11/optee.sh index 119a9781..7dd17b19 100755 --- a/pkcs11/optee.sh +++ b/pkcs11/optee.sh @@ -7,11 +7,13 @@ # Requires tee-supplicant to be running; without it every call fails at # C_Initialize because the TA cannot be loaded. -if [ $# -gt 0 ] -then - OPTEE_SLOTID=$1 - shift -fi +# 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 diff --git a/pkcs11/pkcs11_inittoken.c b/pkcs11/pkcs11_inittoken.c index 1a9877c0..fd02daa3 100644 --- a/pkcs11/pkcs11_inittoken.c +++ b/pkcs11/pkcs11_inittoken.c @@ -28,7 +28,8 @@ * 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: an already-initialized token is left untouched. + * 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 @@ -49,6 +50,9 @@ #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 @@ -77,26 +81,41 @@ static int init_token(CK_FUNCTION_LIST* func, CK_SLOT_ID slotId, return 1; } - if ((tokenInfo.flags & CKF_TOKEN_INITIALIZED) != 0) { + /* 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; } - memset(padded, ' ', sizeof(padded)); - memcpy(padded, label, labelSz); + 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; + /* 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); } - printf("Initialized token in slot %lu with label \"%s\"\n", - (unsigned long)slotId, label); /* 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, @@ -139,6 +158,8 @@ int main(int argc, char* argv[]) CK_SLOT_ID slotId; CK_RV rv; int ret; + unsigned long slotVal; + char* slotEnd; if (argc != 6) { fprintf(stderr, "Usage: pkcs11_inittoken " @@ -146,7 +167,15 @@ int main(int argc, char* argv[]) return 1; } - slotId = (CK_SLOT_ID)atoi(argv[2]); + /* 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) { diff --git a/pkcs11/pkcs11_test.c b/pkcs11/pkcs11_test.c index fa761326..0ec08243 100644 --- a/pkcs11/pkcs11_test.c +++ b/pkcs11/pkcs11_test.c @@ -27,6 +27,24 @@ #include #include +/* 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[] = { @@ -560,7 +578,7 @@ int gen_ec_keys_label(Pkcs11Token* token, ecc_key* key, char* label, int devId) * 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, - WC_ECC_FLAG_DEC_SIGN | WC_ECC_FLAG_DERIVE); + EC_KEYGEN_FLAGS); if (ret != 0) fprintf(stderr, "Failed to generate EC key: %d\n", ret); } @@ -581,7 +599,7 @@ int gen_ec_keys(Pkcs11Token* token, ecc_key* key, unsigned char* id, int idLen, * 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, - WC_ECC_FLAG_DEC_SIGN | WC_ECC_FLAG_DERIVE); + EC_KEYGEN_FLAGS); if (ret != 0) fprintf(stderr, "Failed to generate EC key: %d\n", ret); }