From 2539d72b7c8d03d54538533db5b346dad52d6db3 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Mon, 31 Oct 2022 15:09:26 +0100 Subject: [PATCH] Add support for PKCS#11 provider in OpenSSL 3.0 The engine API in OpenSSL 3.0 is deprecated so we are in the progress of working on a PKCS#11 provider for OpenSSL. This commit introduces a conditional build with the pkcs11-provider support (instead of engines) with all the changes required for the provider to work with existing code and tests. The CI modification is only temporary before we will have the real package in Fedora or somewhere to use. Signed-off-by: Jakub Jelen Reviewed-by: Anderson Toshiyuki Sasaki Reviewed-by: Norbert Pocs --- .gitlab-ci.yml | 18 ++++++ CMakeLists.txt | 8 +-- ConfigureChecks.cmake | 16 ++++- DefineOptions.cmake | 1 + config.h.cmake | 3 + doc/pkcs11.dox | 30 ++++++++-- include/libssh/crypto.h | 2 +- src/libcrypto.c | 5 +- src/pki_crypto.c | 78 +++++++++++++++++++++++-- tests/CMakeLists.txt | 16 ++++- tests/client/torture_auth_pkcs11.c | 7 +-- tests/pkcs11/setup-softhsm-tokens.sh | 78 +++++++++++++++++++++---- tests/tests_config.h.cmake | 1 + tests/torture.c | 57 +++++++++++++++--- tests/torture.h | 1 + tests/unittests/CMakeLists.txt | 1 + tests/unittests/torture_pki_ecdsa_uri.c | 6 +- tests/unittests/torture_pki_rsa_uri.c | 10 +--- 18 files changed, 276 insertions(+), 62 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7fec19ea..2bbb4647 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -147,6 +147,24 @@ fedora/coverage: fedora/openssl_3.0.x/x86_64: extends: .fedora +fedora/openssl_3.0.x/x86_64/pkcs11-provider: + variables: + CMAKE_ADDITIONAL_OPTIONS: -DWITH_PKCS11_URI=ON -DWITH_PKCS11_PROVIDER=ON + extends: .fedora + before_script: + - dnf -y install automake libtool autoconf-archive rpm-build gnutls-utils + nss-devel nss-tools opensc openssl p11-kit-devel p11-kit-server expect + - git clone https://github.com/latchset/pkcs11-provider.git + - pushd pkcs11-provider && + autoreconf -fiv && + ./configure && + make dist && + mkdir -p rpmbuild/SOURCES && + cp pkcs11-provider*tar.gz rpmbuild/SOURCES && + rpmbuild --define "_topdir $PWD/rpmbuild" -ba packaging/pkcs11-provider.spec && + dnf install -y rpmbuild/RPMS/x86_64/*.rpm + - popd && mkdir -p obj && cd obj + fedora/openssl_3.0.x/x86_64/minimal: extends: .fedora variables: diff --git a/CMakeLists.txt b/CMakeLists.txt index d165c91f..5e4246c8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -89,13 +89,6 @@ if (WITH_GSSAPI) find_package(GSSAPI) endif (WITH_GSSAPI) -if (WITH_PKCS11_URI) - find_package(softhsm) - if (NOT SOFTHSM_FOUND) - message(SEND_ERROR "Could not find softhsm module!") - endif (NOT SOFTHSM_FOUND) -endif (WITH_PKCS11_URI) - if (WITH_NACL) find_package(NaCl) if (NOT NACL_FOUND) @@ -256,6 +249,7 @@ message(STATUS "Unit testing: ${UNIT_TESTING}") message(STATUS "Client code testing: ${CLIENT_TESTING}") message(STATUS "Blowfish cipher support: ${WITH_BLOWFISH_CIPHER}") message(STATUS "PKCS #11 URI support: ${WITH_PKCS11_URI}") +message(STATUS "With PKCS #11 provider support: ${WITH_PKCS11_PROVIDER}") message(STATUS "DSA support: ${WITH_DSA}") set(_SERVER_TESTING OFF) if (WITH_SERVER) diff --git a/ConfigureChecks.cmake b/ConfigureChecks.cmake index 65dbdbdc..334695f1 100644 --- a/ConfigureChecks.cmake +++ b/ConfigureChecks.cmake @@ -458,11 +458,21 @@ if (WITH_PKCS11_URI) if (WITH_GCRYPT) message(FATAL_ERROR "PKCS #11 is not supported for gcrypt.") set(WITH_PKCS11_URI 0) - endif() - if (WITH_MBEDTLS) + elseif (WITH_MBEDTLS) message(FATAL_ERROR "PKCS #11 is not supported for mbedcrypto") set(WITH_PKCS11_URI 0) - endif() + elseif (OPENSSL_FOUND AND OPENSSL_VERSION VERSION_GREATER_EQUAL "3.0.0") + find_library(PKCS11_PROVIDER + NAMES + pkcs11.so + PATH_SUFFIXES + ossl-modules + ) + if (NOT PKCS11_PROVIDER) + set(WITH_PKCS11_PROVIDER 0) + message(WARNING "Could not find pkcs11 provider! Falling back to engines") + endif (NOT PKCS11_PROVIDER) + endif () endif() # ENDIAN diff --git a/DefineOptions.cmake b/DefineOptions.cmake index 0608ade4..7401e86d 100644 --- a/DefineOptions.cmake +++ b/DefineOptions.cmake @@ -12,6 +12,7 @@ option(WITH_PCAP "Compile with Pcap generation support" ON) option(WITH_INTERNAL_DOC "Compile doxygen internal documentation" OFF) option(BUILD_SHARED_LIBS "Build shared libraries" ON) option(WITH_PKCS11_URI "Build with PKCS#11 URI support" OFF) +option(WITH_PKCS11_PROVIDER "Use the PKCS#11 provider for accessing pkcs11 objects" OFF) option(UNIT_TESTING "Build with unit tests" OFF) option(CLIENT_TESTING "Build with client tests; requires openssh" OFF) option(SERVER_TESTING "Build with server tests; requires openssh and dropbear" OFF) diff --git a/config.h.cmake b/config.h.cmake index a1cd3cc7..5d0afdd7 100644 --- a/config.h.cmake +++ b/config.h.cmake @@ -255,6 +255,9 @@ /* Define to 1 if you want to enable PKCS #11 URI support */ #cmakedefine WITH_PKCS11_URI 1 +/* Define to 1 if we want to build a support for PKCS #11 provider. */ +#cmakedefine WITH_PKCS11_PROVIDER 1 + /*************************** ENDIAN *****************************/ /* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most diff --git a/doc/pkcs11.dox b/doc/pkcs11.dox index 0bdfc6dc..c2732a81 100644 --- a/doc/pkcs11.dox +++ b/doc/pkcs11.dox @@ -9,11 +9,11 @@ objects stored on the tokens can be uniquely identified is called PKCS #11 URI (Uniform Resource Identifier) and is defined in RFC 7512 (https://tools.ietf.org/html/rfc7512). -Pre-requisites: +# Pre-requisites (OpenSSL < 3.0): -OpenSSL defines an abstract layer called the "engine" to achieve cryptographic -acceleration. The engine_pkcs11 module acts like an interface between the PKCS #11 -modules and the OpenSSL engine. +OpenSSL 1.x defines an abstract layer called the "engine" to achieve +cryptographic acceleration. The engine_pkcs11 module acts like an interface +between the PKCS #11 modules and the OpenSSL application. To build and use libssh with PKCS #11 support: 1. Enable the cmake option: $ cmake -DWITH_PKCS11_URI=ON @@ -21,6 +21,20 @@ To build and use libssh with PKCS #11 support: 3. Install and configure engine_pkcs11 (https://github.com/OpenSC/libp11). 4. Plug in a working smart card or configure softhsm (https://www.opendnssec.org/softhsm). +# Pre-requisites (OpenSSL 3.0.8+) + +The OpenSSL 3.0 is deprecating usage of low-level engines in favor of high-level +"providers" to provide alternative implementation of cryptographic operations +or acceleration. + +To build and use libssh with PKCS #11 support using OpenSSL providers: +1. Install and configure pkcs11 provider (https://github.com/latchset/pkcs11-provider). +2. Enable the cmake options: $ cmake -DWITH_PKCS11_URI=ON -DWITH_PKCS11_PROVIDER=ON +3. Build with OpenSSL. +4. Plug in a working smart card or configure softhsm (https://www.opendnssec.org/softhsm). + +# New API functions + The functions ssh_pki_import_pubkey_file() and ssh_pki_import_privkey_file() that import the public and private keys from files respectively are now modified to support PKCS #11 URIs. These functions automatically detect if the provided filename is a file path @@ -31,7 +45,7 @@ corresponding to the PKCS #11 URI are loaded from the PKCS #11 device. If you wish to authenticate using public keys on your own, follow the steps mentioned under "Authentication with public keys" in Chapter 2 - A deeper insight into authentication. -The function pki_uri_import() is used to populate the public/private ssh_key from the +The function pki_uri_import() is used to populate the public/private ssh_key from the engine with PKCS #11 URIs as the look up. Here is a minimalistic example of public key authentication using PKCS #11 URIs: @@ -64,4 +78,10 @@ We recommend the users to provide a specific PKCS #11 URI so that it matches onl If the engine discovers multiple slots that could potentially contain the private keys referenced by the provided PKCS #11 URI, the engine will not try to authenticate. +For testing, the SoftHSM PKCS#11 library is used. But it has some issues with +OpenSSL initialization/cleanup when used with OpenSSL 3.0 so we are using it +indirectly through a p11-kit remoting as described in the following article: + +https://p11-glue.github.io/p11-glue/p11-kit/manual/remoting.html + */ diff --git a/include/libssh/crypto.h b/include/libssh/crypto.h index 7085b404..32016827 100644 --- a/include/libssh/crypto.h +++ b/include/libssh/crypto.h @@ -223,7 +223,7 @@ int sshkdf_derive_key(struct ssh_crypto_struct *crypto, size_t requested_len); int secure_memcmp(const void *s1, const void *s2, size_t n); -#ifdef HAVE_LIBCRYPTO +#if defined(HAVE_LIBCRYPTO) && !defined(WITH_PKCS11_PROVIDER) ENGINE *pki_get_engine(void); #endif /* HAVE_LIBCRYPTO */ diff --git a/src/libcrypto.c b/src/libcrypto.c index 216fc5ef..2e635e66 100644 --- a/src/libcrypto.c +++ b/src/libcrypto.c @@ -84,7 +84,6 @@ static int libcrypto_initialized = 0; -static ENGINE *engine = NULL; void ssh_reseed(void){ #ifndef _WIN32 @@ -94,6 +93,9 @@ void ssh_reseed(void){ #endif } +#ifndef WITH_PKCS11_PROVIDER +static ENGINE *engine = NULL; + ENGINE *pki_get_engine(void) { int ok; @@ -123,6 +125,7 @@ ENGINE *pki_get_engine(void) } return engine; } +#endif /* WITH_PKCS11_PROVIDER */ #ifdef HAVE_OPENSSL_ECC static const EVP_MD *nid_to_evpmd(int nid) diff --git a/src/pki_crypto.c b/src/pki_crypto.c index 59a8648c..aca19ce2 100644 --- a/src/pki_crypto.c +++ b/src/pki_crypto.c @@ -42,6 +42,10 @@ #include #include #include +#if defined(WITH_PKCS11_URI) && defined(WITH_PKCS11_PROVIDER) +#include +#include +#endif #endif /* OPENSSL_VERSION_NUMBER */ #ifdef HAVE_OPENSSL_EC_H @@ -102,6 +106,9 @@ static int pki_key_ecdsa_to_nid(EC_KEY *k) const EC_GROUP *g = EC_KEY_get0_group(k); int nid; + if (g == NULL) { + return -1; + } nid = EC_GROUP_get_curve_name(g); if (nid) { return nid; @@ -2416,15 +2423,19 @@ error: } #ifdef WITH_PKCS11_URI +#ifdef WITH_PKCS11_PROVIDER +static bool pkcs11_provider_failed = false; +#endif + /** * @internal * - * @brief Populate the public/private ssh_key from the engine with + * @brief Populate the public/private ssh_key from the engine/provider with * PKCS#11 URIs as the look up. * * @param[in] uri_name The PKCS#11 URI * @param[in] nkey The ssh-key context for - * the key loaded from the engine. + * the key loaded from the engine/provider. * @param[in] key_type The type of the key used. Public/Private. * * @return SSH_OK if ssh-key is valid; SSH_ERROR otherwise. @@ -2433,13 +2444,14 @@ int pki_uri_import(const char *uri_name, ssh_key *nkey, enum ssh_key_e key_type) { - ENGINE *engine = NULL; EVP_PKEY *pkey = NULL; ssh_key key = NULL; enum ssh_keytypes_e type = SSH_KEYTYPE_UNKNOWN; #if OPENSSL_VERSION_NUMBER < 0x30000000L && HAVE_OPENSSL_ECC EC_KEY *ecdsa = NULL; #endif +#ifndef WITH_PKCS11_PROVIDER + ENGINE *engine = NULL; /* Do the init only once */ engine = pki_get_engine(); @@ -2454,7 +2466,7 @@ int pki_uri_import(const char *uri_name, if (pkey == NULL) { SSH_LOG(SSH_LOG_TRACE, "Could not load key: %s", - ERR_error_string(ERR_get_error(),NULL)); + ERR_error_string(ERR_get_error(), NULL)); goto fail; } break; @@ -2463,7 +2475,7 @@ int pki_uri_import(const char *uri_name, if (pkey == NULL) { SSH_LOG(SSH_LOG_TRACE, "Could not load key: %s", - ERR_error_string(ERR_get_error(),NULL)); + ERR_error_string(ERR_get_error(), NULL)); goto fail; } break; @@ -2472,6 +2484,62 @@ int pki_uri_import(const char *uri_name, "Invalid key type: %d", key_type); goto fail; } +#else /* WITH_PKCS11_PROVIDER */ + OSSL_STORE_CTX *store = NULL; + OSSL_STORE_INFO *info = NULL; + + /* The provider can be either configured in openssl.cnf or dynamically + * loaded, assuming it does not need any special configuration */ + if (OSSL_PROVIDER_available(NULL, "pkcs11") == 0 && + !pkcs11_provider_failed) { + OSSL_PROVIDER *pkcs11_provider = NULL; + + pkcs11_provider = OSSL_PROVIDER_try_load(NULL, "pkcs11", 1); + if (pkcs11_provider == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to initialize provider: %s", + ERR_error_string(ERR_get_error(), NULL)); + /* Do not attempt to load it again */ + pkcs11_provider_failed = true; + goto fail; + } + } + + store = OSSL_STORE_open(uri_name, NULL, NULL, NULL, NULL); + if (store == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to open OpenSSL store: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto fail; + } + + for (info = OSSL_STORE_load(store); + info != NULL; + info = OSSL_STORE_load(store)) { + int ossl_type = OSSL_STORE_INFO_get_type(info); + + if (ossl_type == OSSL_STORE_INFO_PUBKEY && key_type == SSH_KEY_PUBLIC) { + pkey = OSSL_STORE_INFO_get1_PUBKEY(info); + break; + } else if (ossl_type == OSSL_STORE_INFO_PKEY && + key_type == SSH_KEY_PRIVATE) { + pkey = OSSL_STORE_INFO_get1_PKEY(info); + break; + } else { + SSH_LOG(SSH_LOG_TRACE, + "Ignoring object not matching our type: %d", + ossl_type); + } + } + OSSL_STORE_close(store); + if (pkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "No key found in the pkcs11 store: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto fail; + } + +#endif /* WITH_PKCS11_PROVIDER */ key = ssh_key_new(); if (key == NULL) { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1eab175c..50bbe9b4 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -183,6 +183,16 @@ if (CLIENT_TESTING OR SERVER_TESTING) if (NOT SOFTHSM_FOUND) message(SEND_ERROR "Could not find softhsm module!") endif (NOT SOFTHSM_FOUND) + if (WITH_PKCS11_PROVIDER) + find_package(PkgConfig) + if (PKG_CONFIG_FOUND) + pkg_check_modules(P11_KIT p11-kit-1) + if (P11_KIT_FOUND) + pkg_get_variable(P11_MODULE_PATH p11-kit-1 p11_module_path) + set(P11_KIT_CLIENT ${P11_MODULE_PATH}/p11-kit-client.so) + endif (P11_KIT_FOUND) + endif (PKG_CONFIG_FOUND) + endif (WITH_PKCS11_PROVIDER) endif (WITH_PKCS11_URI) find_program(SSH_EXECUTABLE NAMES ssh) @@ -297,12 +307,14 @@ if (CLIENT_TESTING OR SERVER_TESTING) file(COPY keys/certauth/id_rsa DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh_cert/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) file(COPY keys/certauth/id_rsa.pub DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh_cert/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) file(COPY keys/certauth/id_rsa-cert.pub DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh_cert/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) +endif () +if (WITH_PKCS11_URI) #Copy the script to setup PKCS11 tokens file(COPY pkcs11/setup-softhsm-tokens.sh DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/pkcs11 FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE) +endif (WITH_PKCS11_URI) - message(STATUS "TORTURE_ENVIRONMENT=${TORTURE_ENVIRONMENT}") -endif () +message(STATUS "TORTURE_ENVIRONMENT=${TORTURE_ENVIRONMENT}") configure_file(tests_config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/tests_config.h) diff --git a/tests/client/torture_auth_pkcs11.c b/tests/client/torture_auth_pkcs11.c index e75fea0e..f0658484 100644 --- a/tests/client/torture_auth_pkcs11.c +++ b/tests/client/torture_auth_pkcs11.c @@ -39,7 +39,6 @@ #define LIBSSH_ECDSA_256_TESTKEY "id_pkcs11_ecdsa_256" #define LIBSSH_ECDSA_384_TESTKEY "id_pkcs11_ecdsa_384" #define LIBSSH_ECDSA_521_TESTKEY "id_pkcs11_ecdsa_521" -#define SOFTHSM_CONF "softhsm.conf" const char template[] = "temp_dir_XXXXXX"; @@ -109,7 +108,6 @@ static int setup_session(void **state) struct torture_state *s = *state; struct pki_st *test_state = NULL; int rc; - char conf_path[1024] = {0}; char keys_dir[1024] = {0}; char *temp_dir; @@ -134,9 +132,6 @@ static int setup_session(void **state) test_state->keys_dir = strdup(keys_dir); - snprintf(conf_path, sizeof(conf_path), "%s/softhsm.conf", test_state->temp_dir); - setenv("SOFTHSM2_CONF", conf_path, 1); - setup_tokens(state, LIBSSH_RSA_TESTKEY, "rsa"); setup_tokens(state, LIBSSH_ECDSA_256_TESTKEY, "ecdsa256"); setup_tokens(state, LIBSSH_ECDSA_384_TESTKEY, "ecdsa384"); @@ -160,7 +155,7 @@ static int sshd_teardown(void **state) { struct pki_st *test_state = s->private_data; int rc; - unsetenv("SOFTHSM2_CONF"); + torture_cleanup_tokens(test_state->temp_dir); rc = torture_change_dir(test_state->orig_dir); assert_int_equal(rc, 0); diff --git a/tests/pkcs11/setup-softhsm-tokens.sh b/tests/pkcs11/setup-softhsm-tokens.sh index ae316c7a..bd8e0944 100755 --- a/tests/pkcs11/setup-softhsm-tokens.sh +++ b/tests/pkcs11/setup-softhsm-tokens.sh @@ -5,8 +5,10 @@ TESTDIR=$1 PRIVKEY=$2 OBJNAME=$3 +TOKENLABEL=$3 # yeah. The same as object label LOADPUBLIC=$4 LIBSOFTHSM_PATH=$5 +P11_KIT_CLIENT=$6 shift 5 PUBKEY="$PRIVKEY.pub" @@ -15,24 +17,27 @@ echo "TESTDIR: $TESTDIR" echo "PRIVKEY: $PRIVKEY" echo "PUBKEY: $PUBKEY" echo "OBJNAME: $OBJNAME" +echo "TOKENLABEL: $TOKENLABEL" echo "LOADPUBLIC: $LOADPUBLIC" -# Create temporary directory for tokens -install -d -m 0755 "$TESTDIR/db" +if [ ! -d "$TESTDIR/db" ]; then + # Create temporary directory for tokens + install -d -m 0755 "$TESTDIR/db" -# Create SoftHSM configuration file -cat >"$TESTDIR/softhsm.conf" <"$TESTDIR/softhsm.conf" <temp_dir); - setenv("SOFTHSM2_CONF", conf_path, 1); - setup_tokens_ecdsa(state, 256, "ecdsa256", "1"); setup_tokens_ecdsa(state, 384, "ecdsa384", "1"); setup_tokens_ecdsa(state, 521, "ecdsa521", "1"); @@ -114,7 +110,7 @@ static int teardown_directory_structure(void **state) struct pki_st *test_state = *state; int rc; - unsetenv("SOFTHSM2_CONF"); + torture_cleanup_tokens(test_state->temp_dir); rc = torture_change_dir(test_state->orig_dir); assert_int_equal(rc, 0); diff --git a/tests/unittests/torture_pki_rsa_uri.c b/tests/unittests/torture_pki_rsa_uri.c index f322cf02..11c6a3d8 100644 --- a/tests/unittests/torture_pki_rsa_uri.c +++ b/tests/unittests/torture_pki_rsa_uri.c @@ -13,7 +13,6 @@ #define LIBSSH_RSA_TESTKEY "libssh_testkey.id_rsa" #define LIBSSH_RSA_TESTKEY_PASSPHRASE "libssh_testkey_passphrase.id_rsa" -#define SOFTHSM_CONF "softhsm.conf" #define PUB_URI_FMT "pkcs11:token=%s;object=%s;type=public" #define PRIV_URI_FMT "pkcs11:token=%s;object=%s;type=private?pin-value=%s" @@ -33,7 +32,6 @@ struct pki_st { static int setup_tokens(void **state) { - char conf_path[1024] = {0}; char keys_path[1024] = {0}; char keys_path_pub[1024] = {0}; char *cwd = NULL; @@ -85,10 +83,6 @@ static int setup_tokens(void **state) torture_setup_tokens(cwd, keys_path, obj_tempname, "1"); - snprintf(conf_path, sizeof(conf_path), "%s/softhsm.conf", cwd); - - setenv("SOFTHSM2_CONF", conf_path, 1); - return 0; } @@ -126,6 +120,8 @@ static int teardown_directory_structure(void **state) struct pki_st *test_state = *state; int rc; + torture_cleanup_tokens(test_state->temp_dir); + rc = torture_change_dir(test_state->orig_dir); assert_int_equal(rc, 0); @@ -142,8 +138,6 @@ static int teardown_directory_structure(void **state) SAFE_FREE(test_state->pub_uri_invalid_token); SAFE_FREE(test_state); - unsetenv("SOFTHSM2_CONF"); - return 0; }