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; }