This is an automated email from the ASF dual-hosted git repository.

cliffjansen pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/qpid-proton.git

commit af0124ef969a474d7a8c43bd68f3fdad2a3465ef
Author: Ahmad Fatoum <a.fat...@pengutronix.de>
AuthorDate: Thu Oct 24 20:41:50 2024 +0200

    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 +
 .../certs/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(-)

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 <cstdio>
+#include <string>
+
+#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 $<TARGET_FILE:pkcs11_test>)
+    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


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@qpid.apache.org
For additional commands, e-mail: commits-h...@qpid.apache.org

Reply via email to