Skip to content

Commit 1d9a56d

Browse files
authored
Enable signing with PKCS #11 devices (HSMs) (#411)
* Implement a class for handling of PKCS #11 URIs Implement a class for handling of PKCS #11 URIs that will be used for parsing the URIs and accessing its components. This will enable signing with PKCS #11 enabled devices, such as HSMs. Signed-off-by: Stefan Berger <[email protected]> * cli: Add support for signing with PKCS #11 URIs Add support for signing with PKCS #11 keys that are described as URIs from RFC 7512. Extend the existing signing with private key ('key' and 'certificate') to also accept PKCS #11 URIs, which are detected by their prefix 'pkcs11:'. Add documentation for PKCS #11 support. Signed-off-by: Stefan Berger <[email protected]> * test: Add a tests for PKCS #11 with SoftHSM2 used for signing Extend the python tests with a test case for pkcs11 using SoftHSM2 for signing. Add a script that runs the SoftHSM test. Install softhsm2 and gnutls-bin on ubuntu-latest to be able to run the python integration test. Signed-off-by: Stefan Berger <[email protected]> * test: Skip softhsm setup if user already set it up To speed up the integration test, skip running 'softhsm_setup setup' if the key URI can be gotten from the softhsm_setup script. Setup softhsm before running integration tests on github action. Signed-off-by: Stefan Berger <[email protected]> --------- Signed-off-by: Stefan Berger <[email protected]>
1 parent 38fc9e5 commit 1d9a56d

File tree

10 files changed

+1617
-9
lines changed

10 files changed

+1617
-9
lines changed

.github/workflows/integration.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,8 @@ jobs:
4848
- name: Run integration tests
4949
run: |
5050
set -euxo pipefail
51+
if [[ "${{ matrix.os }}" == "ubuntu-latest" ]]; then
52+
sudo apt install softhsm2 gnutls-bin
53+
./scripts/pkcs11-tests/softhsm_setup setup
54+
fi
5155
hatch test -c -py ${{ matrix.python-version }} -m integration

README.md

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ This project demonstrates how to protect the integrity of a model by signing it.
3535
We support generating signatures via [Sigstore](https://www.sigstore.dev/), a
3636
tool for making code signatures transparent without requiring management of
3737
cryptographic key material. But we also support traditional signing methods, so
38-
models can be signed with public keys or signing certificates.
38+
models can be signed with public keys or signing certificates as well as
39+
PKCS #11 enabled devices.
3940

4041
The signing part creates a
4142
[sigstore bundle](https://github.com/sigstore/protobuf-specs/blob/main/protos/sigstore_bundle.proto)
@@ -178,6 +179,48 @@ Similarly, for key verification, we can use
178179
--signature resnet.sig --public_key key.pub
179180
```
180181
182+
#### Signing with PKCS #11 URIs
183+
184+
Signing with PKCS #11 enabled crypto devices is supported through RFC 7512
185+
compliant PKCS #11 URIs. The URI can be used in place of the private key
186+
when siging with a private key or certificate.
187+
188+
The following features are supported/required:
189+
190+
- The PKCS #11 URI must either provide the module through the query
191+
parameter 'module-path', or the query parameter 'module-name' must
192+
describe the name of a module that can be found in well-known
193+
directories of Linux distributions.
194+
- A token can be selected based on a provided 'slot-id' path parameter.
195+
The first token that matches the given slot-id will be used. If a
196+
'token' path parameter is also provided, then it will be used for
197+
selecting the appropriate token by its label.
198+
- When no 'slot-id' is given then all slots are searched for by the
199+
name of the given 'token'.
200+
- A PIN may be provided as 'pin-value' query parameter or may be read
201+
from a file described by the 'pin-source' query parameter.
202+
- An 'id' path parameter and/or key label (path parameter 'object') must
203+
be provided to select the signing key.
204+
- The public key on the PKCS #11 device will also be accessed during
205+
signing.
206+
- The signing key must be of type NIST P256/384/521 (secp256/384/512r1).
207+
208+
With a PKCS #11 URI describing the private key, we can use the following
209+
for signing:
210+
211+
```bash
212+
[...]$ model_signing sign pkcs11-key --signature model.sig \
213+
--private_key "pkcs11:..." /path/to/your/model
214+
```
215+
216+
For signature verification it is necessary to retrieve the public key from
217+
the PKCS #11 device and store it in a file in PEM format. With can then use:
218+
219+
```bash
220+
[...]$ model_signing verify key --signature model.sig\
221+
--public_key key.pub /path/to/your/model
222+
```
223+
181224
### Model Signing API
182225
183226
We offer an API which can be used in integrations with ML frameworks, ML

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,11 @@ classifiers = [
2828
"Typing :: Typed",
2929
]
3030
dependencies = [
31+
"asn1crypto",
3132
"click",
3233
"cryptography",
3334
"in-toto-attestation",
35+
"PyKCS11",
3436
"sigstore",
3537
"typing_extensions",
3638
]

scripts/pkcs11-tests/softhsm_setup

Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
#!/usr/bin/env bash
2+
3+
# For the license, see the LICENSE file in the root directory.
4+
5+
# This script may not work with softhsm2 2.0.0 but with >= 2.2.0
6+
7+
if [ -z "$(type -P p11tool)" ]; then
8+
echo "Need p11tool from gnutls"
9+
exit 77
10+
fi
11+
12+
if [ -z "$(type -P softhsm2-util)" ]; then
13+
echo "Need softhsm2-util from softhsm2 package"
14+
exit 77
15+
fi
16+
17+
NAME=model-signing-test
18+
PIN=${PIN:-1234}
19+
SO_PIN=${SO_PIN:-1234}
20+
SOFTHSM_SETUP_CONFIGDIR=${SOFTHSM_SETUP_CONFIGDIR:-~/.config/softhsm2}
21+
export SOFTHSM2_CONF=${SOFTHSM_SETUP_CONFIGDIR}/softhsm2.conf
22+
23+
UNAME_S="$(uname -s)"
24+
25+
case "${UNAME_S}" in
26+
Darwin)
27+
if ! msg=$(sudo -v -n); then
28+
echo "Need password-less sudo rights on OS X to change /etc/gnutls/pkcs11.conf"
29+
exit 1
30+
fi
31+
;;
32+
esac
33+
34+
teardown_softhsm() {
35+
local configdir=${SOFTHSM_SETUP_CONFIGDIR}
36+
local configfile=${SOFTHSM2_CONF}
37+
local bakconfigfile=${configfile}.bak
38+
local tokendir=${configdir}/tokens
39+
40+
softhsm2-util --token "${NAME}" --delete-token &>/dev/null
41+
42+
case "${UNAME_S}" in
43+
Darwin*)
44+
if [ -f /etc/gnutls/pkcs11.conf.bak ]; then
45+
sudo rm -f /etc/gnutls/pkcs11.conf
46+
sudo mv /etc/gnutls/pkcs11.conf.bak \
47+
/etc/gnutls/pkcs11.conf &>/dev/null
48+
fi
49+
;;
50+
esac
51+
52+
if [ -f "$bakconfigfile" ]; then
53+
mv "$bakconfigfile" "$configfile"
54+
else
55+
rm -f "$configfile"
56+
fi
57+
if [ -d "$tokendir" ]; then
58+
rm -rf "${tokendir}"
59+
fi
60+
return 0
61+
}
62+
63+
setup_softhsm() {
64+
local msg tokenuri keyuri
65+
local configdir=${SOFTHSM_SETUP_CONFIGDIR}
66+
local configfile=${SOFTHSM2_CONF}
67+
local bakconfigfile=${configfile}.bak
68+
local tokendir=${configdir}/tokens
69+
local rc
70+
71+
case "${UNAME_S}" in
72+
Darwin*)
73+
if [ -f /etc/gnutls/pkcs11.conf.bak ]; then
74+
echo "/etc/gnutls/pkcs11.conf.bak already exists; need to 'teardown' first"
75+
return 1
76+
fi
77+
sudo mv /etc/gnutls/pkcs11.conf \
78+
/etc/gnutls/pkcs11.conf.bak &>/dev/null
79+
if [ "$(id -u)" -eq 0 ]; then
80+
SONAME="$(sudo -u nobody brew ls --verbose softhsm | \
81+
grep -E "\.so$")"
82+
else
83+
SONAME="$(brew ls --verbose softhsm | \
84+
grep -E "\.so$")"
85+
fi
86+
sudo mkdir -p /etc/gnutls &>/dev/null
87+
sudo bash -c "echo 'load=${SONAME}' > /etc/gnutls/pkcs11.conf"
88+
;;
89+
esac
90+
91+
if ! [ -d "$configdir" ]; then
92+
mkdir -p "$configdir"
93+
fi
94+
mkdir -p "${tokendir}"
95+
96+
if [ -f "$configfile" ]; then
97+
mv "$configfile" "$bakconfigfile"
98+
fi
99+
100+
if ! [ -f "$configfile" ]; then
101+
cat <<_EOF_ > "$configfile"
102+
directories.tokendir = ${tokendir}
103+
objectstore.backend = file
104+
log.level = DEBUG
105+
slots.removable = false
106+
_EOF_
107+
fi
108+
109+
if ! msg=$(p11tool --list-tokens 2>&1 | grep "token=${NAME}" | tail -n1); then
110+
echo "Could not list existing tokens"
111+
echo "$msg"
112+
fi
113+
tokenuri=$(echo "$msg" | sed -n 's/.*URL: \([[:print:]*]\)/\1/p')
114+
115+
if [ -z "$tokenuri" ]; then
116+
if ! msg=$(softhsm2-util \
117+
--init-token --pin "${PIN}" --so-pin "${SO_PIN}" \
118+
--free --label "${NAME}" 2>&1); then
119+
echo "Could not initialize token"
120+
echo "$msg"
121+
return 2
122+
fi
123+
124+
slot=$(echo "$msg" | \
125+
sed -n 's/.* reassigned to slot \([0-9]*\)$/\1/p')
126+
if [ -z "$slot" ]; then
127+
slot=$(softhsm2-util --show-slots | \
128+
grep -E "^Slot " | head -n1 |
129+
sed -n 's/Slot \([0-9]*\)/\1/p')
130+
if [ -z "$slot" ]; then
131+
echo "Could not parse slot number from output."
132+
echo "$msg"
133+
return 3
134+
fi
135+
fi
136+
137+
if ! msg=$(p11tool --list-tokens 2>&1 | \
138+
grep "token=${NAME}" | tail -n1); then
139+
echo "Could not list existing tokens"
140+
echo "$msg"
141+
fi
142+
tokenuri=$(echo "$msg" | sed -n 's/.*URL: \([[:print:]*]\)/\1/p')
143+
if [ -z "${tokenuri}" ]; then
144+
echo "Could not get tokenuri!"
145+
return 4
146+
fi
147+
148+
# more recent versions of p11tool have --generate-privkey ...
149+
if ! msg=$(GNUTLS_PIN=$PIN p11tool \
150+
--generate-privkey=ecdsa --curve secp384r1 --label mykey --login \
151+
"${tokenuri}" 2>&1);
152+
then
153+
echo "Could not create secp384r1 key!"
154+
echo "$msg"
155+
return 5
156+
fi
157+
fi
158+
159+
getkeyuri_softhsm "$slot"
160+
rc=$?
161+
if [ $rc -ne 0 ]; then
162+
teardown_softhsm
163+
fi
164+
165+
return $rc
166+
}
167+
168+
_getkeyuri_softhsm() {
169+
local msg tokenuri keyuri
170+
171+
if ! msg=$(p11tool --list-tokens 2>&1 | grep "token=${NAME}"); then
172+
echo "Could not list existing tokens"
173+
echo "$msg"
174+
return 5
175+
fi
176+
tokenuri=$(echo "$msg" | sed -n 's/.*URL: \([[:print:]*]\)/\1/p')
177+
if [ -z "$tokenuri" ]; then
178+
echo "Could not get token URL"
179+
echo "$msg"
180+
return 6
181+
fi
182+
if ! msg=$(p11tool --list-all "${tokenuri}" 2>&1); then
183+
echo "Could not list object under token $tokenuri"
184+
echo "$msg"
185+
softhsm2-util --show-slots
186+
return 7
187+
fi
188+
189+
keyuri=$(echo "$msg" | sed -n 's/.*URL: \([[:print:]*]\)/\1/p')
190+
if [ -z "$keyuri" ]; then
191+
echo "Could not get key URL"
192+
echo "$msg"
193+
return 8
194+
fi
195+
echo "$keyuri"
196+
return 0
197+
}
198+
199+
getkeyuri_softhsm() {
200+
local keyuri rc
201+
202+
keyuri=$(_getkeyuri_softhsm)
203+
rc=$?
204+
if [ $rc -ne 0 ]; then
205+
return $rc
206+
fi
207+
echo "keyuri: $keyuri?pin-value=${PIN}&module-name=softhsm2"
208+
return 0
209+
}
210+
211+
getpubkey_softhsm() {
212+
local keyuri rc
213+
214+
keyuri=$(_getkeyuri_softhsm)
215+
rc=$?
216+
if [ $rc -ne 0 ]; then
217+
return $rc
218+
fi
219+
GNUTLS_PIN=${PIN} p11tool --export-pubkey "${keyuri}" --login 2>/dev/null
220+
return $?
221+
}
222+
223+
usage() {
224+
cat <<_EOF_
225+
Usage: $0 [command]
226+
227+
Supported commands are:
228+
229+
setup : Setup the user's account for softhsm and create a
230+
token and key with a test configuration
231+
232+
getkeyuri : Get the key's URI; may only be called after setup
233+
234+
getpubkey : Get the public key in PEM format; may only be called after setup
235+
236+
teardown : Remove the temporary softhsm test configuration
237+
238+
_EOF_
239+
}
240+
241+
main() {
242+
local ret
243+
244+
if [ $# -lt 1 ]; then
245+
usage "$0"
246+
echo -e "Missing command.\n\n"
247+
return 1
248+
fi
249+
case "$1" in
250+
setup)
251+
setup_softhsm
252+
ret=$?
253+
;;
254+
getkeyuri)
255+
getkeyuri_softhsm
256+
ret=$?
257+
;;
258+
getpubkey)
259+
getpubkey_softhsm
260+
ret=$?
261+
;;
262+
teardown)
263+
teardown_softhsm
264+
ret=$?
265+
;;
266+
*)
267+
echo -e "Unsupported command: $1\n\n"
268+
usage "$0"
269+
ret=1
270+
esac
271+
return $ret
272+
}
273+
274+
main "$@"
275+
exit $?

0 commit comments

Comments
 (0)