Skip to content

Commit

Permalink
PROTON-2594: [C++] add test for newly added PKCS#11 support
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
a3f committed Nov 4, 2024
1 parent a98d93a commit 3913599
Show file tree
Hide file tree
Showing 8 changed files with 325 additions and 3 deletions.
16 changes: 13 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
fail-fast: false
matrix:
os:
- ubuntu-latest
- ubuntu-24.04
- macOS-13
- windows-latest
buildType:
Expand Down Expand Up @@ -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: |
Expand All @@ -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}}
Expand All @@ -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')
Expand Down
38 changes: 38 additions & 0 deletions ci/pkcs11-provider.sh
Original file line number Diff line number Diff line change
@@ -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 ..
113 changes: 113 additions & 0 deletions cpp/src/pkcs11_test.cpp
Original file line number Diff line number Diff line change
@@ -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;
}
28 changes: 28 additions & 0 deletions cpp/testdata/certs/server-private-key-lh-no-password.pem
Original file line number Diff line number Diff line change
@@ -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-----
10 changes: 10 additions & 0 deletions cpp/tests.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
22 changes: 22 additions & 0 deletions scripts/openssl-pkcs11.cnf
Original file line number Diff line number Diff line change
@@ -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
85 changes: 85 additions & 0 deletions scripts/prep-pkcs11_test.sh
Original file line number Diff line number Diff line change
@@ -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"
16 changes: 16 additions & 0 deletions scripts/softhsm2.conf.in
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 3913599

Please sign in to comment.