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/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/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/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