From 2b8465654eb96474eb9f31404933d2a5520739ec Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Thu, 23 May 2024 19:23:46 +0200 Subject: [PATCH 1/3] PROTON-2594: Add OpenSSL PKCS#11 PROVIDER support to enable HSM use Hardware Security Modules (HSMs) are physical computing devices that safeguard secrets and allow performing cryptographic operations like encryption and signing without necessarily divulging the private key material. PKCS#11 is a platform-independent API for cryptographic tokens like HSMs. It defines a scheme for pkcs11: URIs that describe objects on the token as well as an API to interact with them. This commit adds support for transparent use of PKCS#11 URIs: Whenever a certificate or private key path is prefixed with pkcs11: it will be interpreted as PKCS#11 URI instead of a file path and OpenSSL will do all necessary communication with the HSM behind the scenes. For programs like proton that use OpenSSL, this could have been realized in two ways: - OpenSSL ENGINE: Introduced in OpenSSL 0.9.6 and deprecated in 3.0 - OpenSSL PROVIDER: Introduced in OpenSSL 3.0 to replace ENGINE While both are supported in recent OpenSSL versions, it's more future proof to use the PROVIDER API, even if this comes at the cost of having to add an #ifdef to the code. This has been tested on Linux/x86_64 with softhsm and Linux/arm32 with OP-TEE, both with v0.5 of https://github.com/latchset/pkcs11-provider. As everything is loaded dynamically, we do not link against any PKCS#11-related shared libraries. It's expected that PKCS#11 provider will already be loaded via OPENSSL_CONF if it's to be used. --- c/src/ssl/openssl.c | 121 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 113 insertions(+), 8 deletions(-) diff --git a/c/src/ssl/openssl.c b/c/src/ssl/openssl.c index 4a1fc12b0..db7fd9902 100644 --- a/c/src/ssl/openssl.c +++ b/c/src/ssl/openssl.c @@ -50,6 +50,13 @@ #include #include #include + +#if (OPENSSL_VERSION_NUMBER >= 0x30000000L) +#include +#include +#include +#endif + #include #include #include @@ -607,27 +614,125 @@ void pn_ssl_domain_free( pn_ssl_domain_t *domain ) } } +#if (OPENSSL_VERSION_NUMBER >= 0x30000000L) +static int ui_read_string(UI *ui, UI_STRING *uis) +{ + return UI_set_result(ui, uis, UI_get0_user_data(ui)); +} + +static OSSL_STORE_INFO *pkcs11_provider_get_info( const char *uri, const char *pin, int type ) +{ + UI_METHOD *ui_method = NULL; + + if (pin) { + ui_method = UI_create_method("Pin Reader"); + if (!ui_method) { + ssl_log_error("Failed to set up UI_METHOD"); + return NULL; + } + UI_method_set_reader(ui_method, ui_read_string); + } + + OSSL_STORE_CTX *store = OSSL_STORE_open_ex(uri, NULL, "provider=pkcs11", + ui_method, (void *)pin, NULL, NULL, NULL); + if (!store) { + ssl_log_error("Failed to open store for provider=pkcs11\n"); + UI_destroy_method(ui_method); + return NULL; + } + + for (OSSL_STORE_INFO *info = OSSL_STORE_load(store); info; + info = OSSL_STORE_load(store)) { + if (type == OSSL_STORE_INFO_get_type(info)) { + OSSL_STORE_close(store); + UI_destroy_method(ui_method); + return info; + } + OSSL_STORE_INFO_free(info); + } + + OSSL_STORE_close(store); + UI_destroy_method(ui_method); + return NULL; +} + +static X509 *read_certificate_pkcs11( const char *uri, const char *key_pass ) +{ + OSSL_STORE_INFO *info = pkcs11_provider_get_info(uri, key_pass, OSSL_STORE_INFO_CERT); + if (!info) return NULL; + + X509 *cert = OSSL_STORE_INFO_get1_CERT(info); + OSSL_STORE_INFO_free(info); + return cert; +} + +static EVP_PKEY *read_private_key_pkcs11( const char *uri, const char *key_pass ) +{ + OSSL_STORE_INFO *info = pkcs11_provider_get_info(uri, key_pass, OSSL_STORE_INFO_PKEY); + if (!info) return NULL; + + EVP_PKEY *key = OSSL_STORE_INFO_get1_PKEY(info); + OSSL_STORE_INFO_free(info); + return key; +} +#else /* < OpenSSL 3.0 */ +static X509 *read_certificate_pkcs11( const char *uri, const char *key_pass ) { + ssl_log_error("pkcs11: URI support requires >= OpenSSL 3.0\n"); + return NULL; +} +static EVP_PKEY *read_private_key_pkcs11( const char *uri, const char *key_pass ) { + ssl_log_error("pkcs11: URI support requires >= OpenSSL 3.0\n"); + return NULL; +} +#endif + +static bool is_pkcs11_uri( const char *file_path ) +{ + return strncmp(file_path, "pkcs11:", sizeof("pkcs11:") - 1) == 0; +} int pn_ssl_domain_set_credentials( pn_ssl_domain_t *domain, const char *certificate_file, const char *private_key_file, const char *password) { + int rc; + if (!domain || !domain->ctx) return -1; - if (SSL_CTX_use_certificate_chain_file(domain->ctx, certificate_file) != 1) { - ssl_log_error("SSL_CTX_use_certificate_chain_file( %s ) failed", certificate_file); + if (is_pkcs11_uri(certificate_file)) { + X509 *cert = read_certificate_pkcs11(certificate_file, password); + if (cert) + rc = SSL_CTX_use_certificate(domain->ctx, cert); + else + rc = -1; + } else { + rc = SSL_CTX_use_certificate_chain_file(domain->ctx, certificate_file); + } + + if (rc != 1) { + ssl_log_error("Failed to load certificate %s", certificate_file); return -3; } - if (password) { - domain->keyfile_pw = pn_strdup(password); // @todo: obfuscate me!!! - SSL_CTX_set_default_passwd_cb(domain->ctx, keyfile_pw_cb); - SSL_CTX_set_default_passwd_cb_userdata(domain->ctx, domain->keyfile_pw); + if (is_pkcs11_uri(private_key_file)) { + EVP_PKEY *pkey = read_private_key_pkcs11(private_key_file, password); + if (pkey) + rc = SSL_CTX_use_PrivateKey(domain->ctx, pkey); + else + rc = -1; + } else { + if (password) { + domain->keyfile_pw = pn_strdup(password); // @todo: obfuscate me!!! + SSL_CTX_set_default_passwd_cb(domain->ctx, keyfile_pw_cb); + SSL_CTX_set_default_passwd_cb_userdata(domain->ctx, domain->keyfile_pw); + } + + rc = SSL_CTX_use_PrivateKey_file(domain->ctx, private_key_file, SSL_FILETYPE_PEM); } - if (SSL_CTX_use_PrivateKey_file(domain->ctx, private_key_file, SSL_FILETYPE_PEM) != 1) { - ssl_log_error("SSL_CTX_use_PrivateKey_file( %s ) failed", private_key_file); + if (rc != 1) { + ssl_log_error("Failed to load private key %s", private_key_file); return -4; } From 2dce8998e3e7f07f62268cb93742f4686f55b058 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Sun, 27 Oct 2024 21:09:34 +0100 Subject: [PATCH 2/3] PROTON-2594: [C++] connect_config_test: factor out some definitions into header The test_handler implementation in connect_config_test can be useful for further tests as well such as the incoming pkcs11_test. We don't want to add the PKCS#11 test here, because the test is not applicable to all platforms and it would overly complicate the existing tests, so let's share code via a common header. --- cpp/src/connect_config_test.cpp | 100 +----------------------- cpp/src/test_handler.hpp | 133 ++++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+), 98 deletions(-) create mode 100644 cpp/src/test_handler.hpp diff --git a/cpp/src/connect_config_test.cpp b/cpp/src/connect_config_test.cpp index 80c0372d2..0a9625f50 100644 --- a/cpp/src/connect_config_test.cpp +++ b/cpp/src/connect_config_test.cpp @@ -24,9 +24,6 @@ #include "proton/connection_options.hpp" #include "proton/container.hpp" #include "proton/error_condition.hpp" -#include "proton/listener.hpp" -#include "proton/listen_handler.hpp" -#include "proton/messaging_handler.hpp" #include "proton/transport.hpp" #include "proton/ssl.hpp" #include "proton/sasl.hpp" @@ -35,6 +32,8 @@ #include "proton/sasl.h" #include "proton/ssl.h" +#include "test_handler.hpp" + #include #include #include @@ -78,12 +77,6 @@ namespace { using namespace std; using namespace proton; -using proton::error_condition; - -string configure(connection_options& opts, const string& config) { - istringstream is(config); - return connect_config::parse(is, opts); -} void test_default_file() { // Default file locations in order of preference. @@ -146,86 +139,6 @@ void test_invalid_json() { } } -// Extra classes to resolve clash of on_error in both messaging_handler and listen_handler -class messaging_handler : public proton::messaging_handler { - virtual void on_messaging_error(const error_condition&) = 0; - - void on_error(const error_condition& c) override { - on_messaging_error(c); - } -}; - -class listen_handler : public proton::listen_handler { - virtual void on_listen_error(listener& , const string&) = 0; - - void on_error(listener& l, const string& s) override { - on_listen_error(l, s); - } -}; - -class test_handler : public messaging_handler, public listen_handler { - bool opened_; - connection_options connection_options_; - listener listener_; - - void on_open(listener& l) override { - on_listener_start(l.container()); - } - - connection_options on_accept(listener& l) override { - return connection_options_; - } - - void on_container_start(container& c) override { - listener_ = c.listen("//:0", *this); - } - - void on_connection_open(connection& c) override { - if (!c.active()) { // Server side - opened_ = true; - check_connection(c); - listener_.stop(); - c.close(); - } - } - - void on_messaging_error(const error_condition& e) override { - FAIL("unexpected error " << e); - } - - void on_listen_error(listener&, const string& s) override { - FAIL("unexpected listen error " << s); - } - - virtual void check_connection(connection& c) {} - virtual void on_listener_start(container& c) = 0; - - protected: - string config_with_port(const string& bare_config) { - ostringstream ss; - ss << "{" << "\"port\":" << listener_.port() << ", " << bare_config << "}"; - return ss.str(); - } - - void connect(container& c, const string& bare_config) { - connection_options opts; - c.connect(configure(opts, config_with_port(bare_config)), opts); - } - - void stop_listener() { - listener_.stop(); - } - - public: - test_handler(const connection_options& listen_opts = connection_options()) : - opened_(false), connection_options_(listen_opts) {} - - void run() { - container(*this).run(); - ASSERT(opened_); - } -}; - class test_almost_default_connect : public test_handler { public: @@ -241,15 +154,6 @@ class test_almost_default_connect : public test_handler { } }; -// Hack to use protected pn_object member of proton::object -template -class internal_access_of: public P { - -public: - internal_access_of(const P& t) : P(t) {} - T* pn_object() { return proton::internal::object::pn_object(); } -}; - class test_default_connect : public test_handler { public: diff --git a/cpp/src/test_handler.hpp b/cpp/src/test_handler.hpp new file mode 100644 index 000000000..8e6781b78 --- /dev/null +++ b/cpp/src/test_handler.hpp @@ -0,0 +1,133 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "test_bits.hpp" + +#include "proton/connect_config.hpp" +#include "proton/connection.hpp" +#include "proton/connection_options.hpp" +#include "proton/container.hpp" +#include "proton/error_condition.hpp" +#include "proton/listener.hpp" +#include "proton/listen_handler.hpp" +#include "proton/messaging_handler.hpp" + +#include + +namespace { + +using namespace std; +using namespace proton; +using proton::error_condition; + +static inline string configure(connection_options& opts, const string& config) { + istringstream is(config); + return connect_config::parse(is, opts); +} + +// Extra classes to resolve clash of on_error in both messaging_handler and listen_handler +class messaging_handler : public proton::messaging_handler { + virtual void on_messaging_error(const error_condition&) = 0; + + void on_error(const error_condition& c) override { + on_messaging_error(c); + } +}; + +class listen_handler : public proton::listen_handler { + virtual void on_listen_error(listener& , const string&) = 0; + + void on_error(listener& l, const string& s) override { + on_listen_error(l, s); + } +}; + +class test_handler : public messaging_handler, public listen_handler { + bool opened_; + connection_options connection_options_; + listener listener_; + + void on_open(listener& l) override { + on_listener_start(l.container()); + } + + connection_options on_accept(listener& l) override { + return connection_options_; + } + + void on_container_start(container& c) override { + listener_ = c.listen("//:0", *this); + } + + void on_connection_open(connection& c) override { + if (!c.active()) { // Server side + opened_ = true; + check_connection(c); + listener_.stop(); + c.close(); + } + } + + void on_messaging_error(const error_condition& e) override { + FAIL("unexpected error " << e); + } + + void on_listen_error(listener&, const string& s) override { + FAIL("unexpected listen error " << s); + } + + virtual void check_connection(connection& c) {} + virtual void on_listener_start(container& c) = 0; + + protected: + string config_with_port(const string& bare_config) { + ostringstream ss; + ss << "{" << "\"port\":" << listener_.port() << ", " << bare_config << "}"; + return ss.str(); + } + + void connect(container& c, const string& bare_config) { + connection_options opts; + c.connect(configure(opts, config_with_port(bare_config)), opts); + } + + void stop_listener() { + listener_.stop(); + } + + public: + test_handler(const connection_options& listen_opts = connection_options()) : + opened_(false), connection_options_(listen_opts) {} + + void run() { + container(*this).run(); + ASSERT(opened_); + } +}; + +// Hack to use protected pn_object member of proton::object +template +class internal_access_of: public P { + +public: + internal_access_of(const P& t) : P(t) {} + T* pn_object() { return proton::internal::object::pn_object(); } +}; + +} From 369965abc6e77263668e348428fc0842db5428f3 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Thu, 24 Oct 2024 20:41:50 +0200 Subject: [PATCH 3/3] PROTON-2594: [C++] add test for newly added PKCS#11 support Existing tests hardcode paths to PEM files. For easily testing PKCS#11 usage for client certificates on the target, we want to pass in dynamically PKCS#11 URIs identifying the certificates and keys to use without requiring recompilation. Enable doing that by consulting a set of new environment variables: PKCS11_CLIENT_CERT: URI of client certificate PKCS11_CLIENT_KEY: URI of client private key PKCS11_SERVER_CERT: URI of server certificate PKCS11_SERVER_KEY: URI of server private key PKCS11_CA_CERT: URI of CA certificate These variables are populated and exported by sourcing the new scripts/prep-pkcs11_test.sh script prior to executing the test. The script uses SoftHSM, which is an implementation of a cryptographic store accessible through a PKCS #11 interface without requiring an actual Hardware Security Module (HSM). We load into the SoftHSM both client and server keys and certificates. As the server key exists only in encrypted form, we decrypt server-private-key-lh.pem, so we need not handle passphrase input when the PEM file is processed by pkcs11-tool. When the script is not sourced, none of the environment variables will be set and the test will be skipped without being marked as error. --- .github/workflows/build.yml | 16 ++- ci/pkcs11-provider.sh | 38 ++++++ cpp/src/pkcs11_test.cpp | 113 ++++++++++++++++++ cpp/testdata/certs/make_certs.sh | 1 + .../server-private-key-lh-no-password.pem | 28 +++++ cpp/tests.cmake | 10 ++ scripts/openssl-pkcs11.cnf | 22 ++++ scripts/prep-pkcs11_test.sh | 85 +++++++++++++ scripts/softhsm2.conf.in | 16 +++ 9 files changed, 326 insertions(+), 3 deletions(-) create mode 100755 ci/pkcs11-provider.sh create mode 100644 cpp/src/pkcs11_test.cpp create mode 100644 cpp/testdata/certs/server-private-key-lh-no-password.pem create mode 100644 scripts/openssl-pkcs11.cnf create mode 100644 scripts/prep-pkcs11_test.sh create mode 100644 scripts/softhsm2.conf.in diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c8e7ba3b3..6c2477b1b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,7 +9,7 @@ jobs: fail-fast: false matrix: os: - - ubuntu-latest + - ubuntu-24.04 - macOS-13 - windows-latest buildType: @@ -47,7 +47,7 @@ jobs: - name: Install Linux dependencies if: runner.os == 'Linux' run: | - sudo apt install -y swig libpython3-dev libsasl2-dev libjsoncpp-dev + sudo apt install -y swig libpython3-dev libsasl2-dev libjsoncpp-dev softhsm2 opensc - name: Install Windows dependencies if: runner.os == 'Windows' run: | @@ -63,6 +63,10 @@ jobs: working-directory: ${{github.workspace}} run: sudo sh ./ci/otel.sh shell: bash + - name: pkcs11-provider build/install + if: runner.os == 'Linux' + run: sudo sh ./ci/pkcs11-provider.sh + shell: bash - name: cmake configure working-directory: ${{env.BuildDir}} run: cmake "${{github.workspace}}" "-DCMAKE_BUILD_TYPE=${BuildType}" "-DCMAKE_INSTALL_PREFIX=${InstallPrefix}" ${{matrix.cmake_extra}} @@ -88,7 +92,13 @@ jobs: - id: ctest name: ctest working-directory: ${{env.BuildDir}} - run: ctest -C ${BuildType} -V -T Test --no-compress-output ${{matrix.ctest_extra}} + run: | + if [ "$RUNNER_OS" = "Linux" ]; then + pushd ${{github.workspace}} + . scripts/prep-pkcs11_test.sh + popd + fi + ctest -C ${BuildType} -V -T Test --no-compress-output ${{matrix.ctest_extra}} shell: bash - name: Upload Test results if: always() && (steps.ctest.outcome == 'failure' || steps.ctest.outcome == 'success') diff --git a/ci/pkcs11-provider.sh b/ci/pkcs11-provider.sh new file mode 100755 index 000000000..0354e5862 --- /dev/null +++ b/ci/pkcs11-provider.sh @@ -0,0 +1,38 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +set -e + +# pkcs11-provider dependencies + +sudo apt-get install meson + +# Clone pkcs11-provider + +git clone -b v0.5 https://github.com/latchset/pkcs11-provider + +# Build/Install pkcs11-provider + +cd pkcs11-provider +mkdir build + +meson setup build . +meson compile -C build +meson install -C build +cd .. diff --git a/cpp/src/pkcs11_test.cpp b/cpp/src/pkcs11_test.cpp new file mode 100644 index 000000000..4677de535 --- /dev/null +++ b/cpp/src/pkcs11_test.cpp @@ -0,0 +1,113 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "test_bits.hpp" + +#include "proton/connection_options.hpp" +#include "proton/container.hpp" +#include "proton/ssl.hpp" + +// The C++ API lacks a way to test for presence of extended SSL support. +#include "proton/ssl.h" + +#include +#include + +#include "test_handler.hpp" + +#define SKIP_RETURN_CODE 127 + +namespace { + +using namespace std; +using namespace proton; + +// Hack to write strings with embedded '"' and newlines +#define RAW_STRING(...) #__VA_ARGS__ + +static const char *client_cert, *client_key, + *server_cert, *server_key, + *ca_cert; + +class test_tls_external : public test_handler { + + static connection_options make_opts() { + ssl_certificate cert(server_cert, server_key); + connection_options opts; + opts.ssl_server_options(ssl_server_options(cert, ca_cert, + ca_cert, ssl::VERIFY_PEER)); + return opts; + } + + public: + + test_tls_external() : test_handler(make_opts()) {} + + void on_listener_start(container& c) override { + static char buf[1024]; + + snprintf(buf, sizeof(buf), RAW_STRING( + "scheme":"amqps", + "sasl":{ "mechanisms": "EXTERNAL" }, + "tls": { + "cert":"%s", + "key":"%s", + "ca":"%s", + "verify":true }), + client_cert, client_key, ca_cert); + + connect(c, buf); + } +}; + +} // namespace + +int main(int argc, char** argv) { + client_cert = getenv("PKCS11_CLIENT_CERT"); + client_key = getenv("PKCS11_CLIENT_KEY"); + + server_cert = getenv("PKCS11_SERVER_CERT"); + server_key = getenv("PKCS11_SERVER_KEY"); + + ca_cert = getenv("PKCS11_CA_CERT"); + + if (!client_key || !client_cert || !server_key || !server_cert || !ca_cert) { + std::cout << argv[0] << ": Environment variable configuration missing:" << std::endl; + std::cout << "\tPKCS11_CLIENT_CERT: URI of client certificate" << std::endl; + std::cout << "\tPKCS11_CLIENT_KEY: URI of client private key" << std::endl; + std::cout << "\tPKCS11_SERVER_CERT: URI of server certificate" << std::endl; + std::cout << "\tPKCS11_SERVER_KEY: URI of server private key" << std::endl; + std::cout << "\tPKCS11_CA_CERT: URI of CA certificate" << std::endl; + return SKIP_RETURN_CODE; + } + + int failed = 0; + + pn_ssl_domain_t *have_ssl = pn_ssl_domain(PN_SSL_MODE_SERVER); + + if (!have_ssl) { + std::cout << "SKIP: TLS tests, not available" << std::endl; + return SKIP_RETURN_CODE; + } + + pn_ssl_domain_free(have_ssl); + RUN_TEST(failed, test_tls_external().run()); + + return failed; +} diff --git a/cpp/testdata/certs/make_certs.sh b/cpp/testdata/certs/make_certs.sh index 0b180b605..9c80d7f89 100755 --- a/cpp/testdata/certs/make_certs.sh +++ b/cpp/testdata/certs/make_certs.sh @@ -18,6 +18,7 @@ keytool -storetype pkcs12 -keystore server-lh.pkcs12 -storepass server-password keytool -storetype pkcs12 -keystore server-lh.pkcs12 -storepass server-password -alias server-certificate -keypass server-password -certreq -file server-request-lh.pem keytool -storetype pkcs12 -keystore ca.pkcs12 -storepass ca-password -alias ca -keypass ca-password -gencert -rfc -validity 99999 -infile server-request-lh.pem -outfile server-certificate-lh.pem openssl pkcs12 -nocerts -passin pass:server-password -in server-lh.pkcs12 -passout pass:server-password -out server-private-key-lh.pem +openssl pkcs12 -nocerts -passin pass:server-password -in server-lh.pkcs12 -nodes -out server-private-key-lh-no-password.pem # Create a certificate request for the client certificate. Use the CA's certificate to sign it: keytool -storetype pkcs12 -keystore client.pkcs12 -storepass client-password -alias client-certificate -keypass client-password -keyalg RSA -genkey -dname "O=Client,CN=127.0.0.1" -validity 99999 diff --git a/cpp/testdata/certs/server-private-key-lh-no-password.pem b/cpp/testdata/certs/server-private-key-lh-no-password.pem new file mode 100644 index 000000000..07ff58a92 --- /dev/null +++ b/cpp/testdata/certs/server-private-key-lh-no-password.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCVOfIlORCE9+96 +/GjwQgaNine138F/EI4QM0NkvmWfxCxUL+OQn//LCZOtsA/OpizubPrp1vThqYuy +9lHD4Mx3quNNODke6oWrQ63V4E5XYH9tn9xeUAJoiNzrdSTLkfXgJJEgqjzw55pS +S3f/6/MLrVkDqS6sGdKkcue6r5imPIZ15EUKrMvf0im5UqZDnbNVbLohrRXRSBHp +O1MI6dTbAZ+MdgbOLcC/lPZrW3hgz8LlLOmSBW+tqNTSLwQ0Hc2v7lJx78XVGZAF +swP7+03M3MubjFeEs9Ipn9HTnG2aC7p9FJ0M9MlJK5FFv2K3tFEwAId3rPcAReDq +X23/K1J7AgMBAAECggEBAJP9bXkgyJM111germtm71y7b9D84MaEwn6qeDGW6O2y +/UtYWtR4+JKBIuXjbym/f1vM1GHHff+1xwdqZNhfPieHX/iaw3s3leytJ96tnsPk +vTsYiNE3g8vrvzv7ZsxEKpVpbkv4yIsZBOCMW6uAcf6ooViSFekzisTv94Qa1MY2 +REPcpfzNQDP04szB7VeWGc1fO9bqqD28nPW4qpzJc7kSW2R7YbhqgLyvc/vAc0kt +Wps5dSs84Gc2d39IWt0Z6c/+0MUw505Pt3aYs4Q2xExjt1/oobGNyADwKPsxpmua +vqd8FcQmGOL1KQgAtJzbkw7tC+oQXuPk5DUXbVEXm9ECgYEA4kmFztVnCSWrUZSM +zooFEGp5PmmTelMNXeB10lRJSBUYw+4xBrYXsR2ikNGDqRuTJQkdfG+LNHIcoq36 +nMAJvZXwN5u7wrmNMvd+rv73iucC34/ANSWAyiQcpKnqM0rQebXc8Ra5GjEIWXB9 +KxU3RQWeBnM4FBZ/YEltT4glRd0CgYEAqNIUY4hIRQjUVLDRno8NHiReAgpVNogU +tORAjuc7oJdYMN3q+04mTNxj8VR4gHK6SiZAcILuZYkFWfC3ivPHp2pxzwm3Zslf +W/+nXGnR1rCZKSuVbPDkAyqDWZxyi96nvjaOqOF537oL9pwBQ4AL8Mfn5ppsG18K +/4urcGoBkDcCgYEA4TvtRAKFnEUyUPFbdflLMRvJsqXDdW5VT6urmr7qciUNkXf0 +tIlq65Bjz2G7ewdHXwXDo6gjFwC+H+6sFHnRODOV9sO8EAZA1QojvmtqWYe3BG9B +EaVSm+F14TB/PK6q83phgFbtx3Qmq1+cNtXXPYxpzmHA373E60Iq247YCsECgYAo +9IYju10k+kZgoWDJIZUiGdqAjjcr+oljdPhActJhXDX17PBjtQrPnKvWURLGvo55 +DJyXbvwcv8f/kMlGOWvXLpibjJTkp7etnvDgF3/joIYXmc4vVqVKK1cgNzcGvaZe +G+gyCjlB0GW0lxYrZPYAnM6igBX38e++HQkjRWRJswKBgF6JbeE/kEAsy6hSf7ww +NN2734VzNguycLWEHzVlyg1vYQogXPoFDlxrJ9G4QdzHuYQ1Bj/Qh5aG3RV0egC3 +unPDSWiY3DTlx0wGeyYc/iyTMfKIQ3d81vfjNJF0uRvdhUKA0Ubn/qx25DTxmgUW +QnhRCvGHXLFPPv2gBrtXuge9 +-----END PRIVATE KEY----- diff --git a/cpp/tests.cmake b/cpp/tests.cmake index 8c63c24b6..b00d50129 100644 --- a/cpp/tests.cmake +++ b/cpp/tests.cmake @@ -69,6 +69,16 @@ if (ENABLE_JSONCPP) set_tests_properties(cpp-connect_config_test PROPERTIES WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}") # Test data and output directories for connect_config_test file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/testdata" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") + + if (NOT CMAKE_SYSTEM_NAME STREQUAL Windows) + add_executable (pkcs11_test src/pkcs11_test.cpp) + target_link_libraries (pkcs11_test qpid-proton-cpp qpid-proton-core ${PLATFORM_LIBS}) + # PKCS#11 URIs contain semicolons, which CMake would interpret as + # list sepearator, so we side step add_cpp_test and pass the env + # through as is. + add_test(NAME cpp-pkcs11_test COMMAND $) + set_tests_properties(cpp-pkcs11_test PROPERTIES SKIP_RETURN_CODE 127) + endif() endif() if (ENABLE_OPENTELEMETRYCPP) diff --git a/scripts/openssl-pkcs11.cnf b/scripts/openssl-pkcs11.cnf new file mode 100644 index 000000000..fa4a0ecec --- /dev/null +++ b/scripts/openssl-pkcs11.cnf @@ -0,0 +1,22 @@ +HOME = . + +# Use this in order to automatically load providers. +openssl_conf = openssl_init + +[openssl_init] +providers = provider_sect + +[provider_sect] +default = default_sect +pkcs11 = pkcs11_sect + +[default_sect] +activate = 1 + +[pkcs11_sect] +module = $ENV::PKCS11_PROVIDER +pkcs11-module-quirks = no-operation-state no-deinit +pkcs11-module-load-behavior = $ENV::PKCS11_MODULE_LOAD_BEHAVIOR +pkcs11-module-encode-provider-uri-to-pem = true +pkcs11-module-token-pin = tclientpw +activate = 1 diff --git a/scripts/prep-pkcs11_test.sh b/scripts/prep-pkcs11_test.sh new file mode 100644 index 000000000..f63a3abdd --- /dev/null +++ b/scripts/prep-pkcs11_test.sh @@ -0,0 +1,85 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# prep-pkcs11_test.sh - Source to set up environment for pkcs11_test to run +# against a SoftHSM + +set -x + +KEYDIR="$(readlink -f cpp/testdata/certs)" + +if [ -z "$PKCS11_PROVIDER" ]; then + export PKCS11_PROVIDER=$(openssl version -m | cut -d'"' -f2)/pkcs11.so +fi + +if [ -z "$PKCS11_PROVIDER_MODULE" ]; then + export PKCS11_PROVIDER_MODULE="/usr/lib/softhsm/libsofthsm2.so" +fi + +PKCS11_PROVIDER=$(readlink -f "$PKCS11_PROVIDER") +PKCS11_PROVIDER_MODULE=$(readlink -f "$PKCS11_PROVIDER_MODULE") + +if [ ! -r "$PKCS11_PROVIDER" ]; then + echo "PKCS11_PROVIDER=$PKCS11_PROVIDER not found" + return 1 +fi + +if [ ! -r "$PKCS11_PROVIDER_MODULE" ]; then + echo "PKCS11_PROVIDER_MODULE=$PKCS11_PROVIDER_MODULE not found" + return 1 +fi + +export OPENSSL_CONF="$(readlink -f scripts/openssl-pkcs11.cnf)" +export SOFTHSM2_CONF="${XDG_RUNTIME_DIR}/qpid-proton-build/softhsm2.conf" + +softhsmtokendir="${XDG_RUNTIME_DIR}/qpid-proton-build/softhsm2-tokens" +mkdir -p "${softhsmtokendir}" + +sed -r "s;@softhsmtokendir@;${softhsmtokendir};g" scripts/softhsm2.conf.in >$SOFTHSM2_CONF + +export PKCS11_MODULE_LOAD_BEHAVIOR=late + +set -x + +softhsm2-util --delete-token --token proton-test 2>/dev/null || true +softhsm2-util --init-token --free --label proton-test --pin tclientpw --so-pin tclientpw + +pkcs11_tool () { pkcs11-tool --module=$PKCS11_PROVIDER_MODULE --token-label proton-test --pin tclientpw "$@"; } + +pkcs11_tool --module=$PKCS11_PROVIDER_MODULE --token-label proton-test --pin tclientpw -l --label tclient --delete-object --type privkey 2>/dev/null || true + +pkcs11_tool --module=$PKCS11_PROVIDER_MODULE --token-label proton-test --pin tclientpw -l --label tclient --id 2222 \ + --write-object "$KEYDIR/client-certificate.pem" --type cert --usage-sign +pkcs11_tool --module=$PKCS11_PROVIDER_MODULE --token-label proton-test --pin tclientpw -l --label tclient --id 2222 \ + --write-object "$KEYDIR/client-private-key-no-password.pem" --type privkey --usage-sign + +pkcs11_tool --module=$PKCS11_PROVIDER_MODULE --token-label proton-test --pin tclientpw -l --label tserver --id 4444 \ + --write-object "$KEYDIR/server-certificate-lh.pem" --type cert --usage-sign +pkcs11_tool --module=$PKCS11_PROVIDER_MODULE --token-label proton-test --pin tclientpw -l --label tserver --id 4444 \ + --write-object "$KEYDIR/server-private-key-lh-no-password.pem" --type privkey --usage-sign + +set +x + +# Workaround for https://github.com/latchset/pkcs11-provider/issues/419 +export PKCS11_MODULE_LOAD_BEHAVIOR=early + +export PKCS11_CLIENT_CERT="pkcs11:token=proton-test;object=tclient;type=cert" +export PKCS11_CLIENT_KEY="pkcs11:token=proton-test;object=tclient;type=private" +export PKCS11_SERVER_CERT="pkcs11:token=proton-test;object=tserver;type=cert" +export PKCS11_SERVER_KEY="pkcs11:token=proton-test;object=tserver;type=private" +export PKCS11_CA_CERT="$KEYDIR/ca-certificate.pem" diff --git a/scripts/softhsm2.conf.in b/scripts/softhsm2.conf.in new file mode 100644 index 000000000..72a25bd3d --- /dev/null +++ b/scripts/softhsm2.conf.in @@ -0,0 +1,16 @@ +# SoftHSM v2 configuration file + +directories.tokendir = @softhsmtokendir@ +objectstore.backend = file + +# ERROR, WARNING, INFO, DEBUG +log.level = ERROR + +# If CKF_REMOVABLE_DEVICE flag should be set +slots.removable = false + +# Enable and disable PKCS#11 mechanisms using slots.mechanisms. +slots.mechanisms = ALL + +# If the library should reset the state on fork +library.reset_on_fork = false