diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c8e7ba3b3e..6c2477b1b0 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/c/src/ssl/openssl.c b/c/src/ssl/openssl.c index 4a1fc12b01..db7fd99028 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; } diff --git a/ci/pkcs11-provider.sh b/ci/pkcs11-provider.sh new file mode 100755 index 0000000000..0354e5862e --- /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/connect_config_test.cpp b/cpp/src/connect_config_test.cpp index 80c0372d24..0a9625f50e 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/pkcs11_test.cpp b/cpp/src/pkcs11_test.cpp new file mode 100644 index 0000000000..4677de535a --- /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/src/test_handler.hpp b/cpp/src/test_handler.hpp new file mode 100644 index 0000000000..8e6781b786 --- /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(); } +}; + +} diff --git a/cpp/testdata/certs/make_certs.sh b/cpp/testdata/certs/make_certs.sh index 0b180b6057..9c80d7f89c 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 0000000000..07ff58a92d --- /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 8c63c24b65..b00d501295 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 0000000000..fa4a0ecec9 --- /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 0000000000..f63a3abddf --- /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 0000000000..72a25bd3d1 --- /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