From d741ddbee4b4a0b5ad66c776d305a153eeed7cbe Mon Sep 17 00:00:00 2001 From: Greg Hudson Date: Mon, 26 Feb 2024 19:03:38 -0500 Subject: [PATCH] Use SoftHSMv2 for PKCS11 PKINIT tests Instead of softpkcs11, use SoftHSMv2 to mock the PKCS11 token for PKINIT tests. Use pkcs11-tool from OpenSC to initialize the token and import a certificate and key. SoftHSM does not support PIN-less tokens (see https://github.com/opendnssec/SoftHSMv2/issues/480) so remove that test for now. (cherry picked from commit 8ab61608236883fdc5c2d43f4bd1ff2094401d19) --- .github/workflows/build.yml | 2 +- src/tests/t_pkinit.py | 82 ++++++++++++++++++++----------------- 2 files changed, 45 insertions(+), 39 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 68a4788adb..d7ae86b150 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -33,7 +33,7 @@ jobs: if: startsWith(matrix.os, 'ubuntu') run: | sudo apt-get update -qq - sudo apt-get install -y bison gettext keyutils ldap-utils libcmocka-dev libldap2-dev libkeyutils-dev libsasl2-dev libssl-dev python3-kdcproxy python3-pip slapd tcsh + sudo apt-get install -y bison gettext keyutils ldap-utils libcmocka-dev libldap2-dev libkeyutils-dev libsasl2-dev libssl-dev python3-kdcproxy python3-pip slapd tcsh softhsm2 opensc pip3 install pyrad - name: Build env: diff --git a/src/tests/t_pkinit.py b/src/tests/t_pkinit.py index f8f2debc1b..4435746429 100755 --- a/src/tests/t_pkinit.py +++ b/src/tests/t_pkinit.py @@ -1,11 +1,10 @@ from k5test import * +import re # Skip this test if pkinit wasn't built. if not pkinit_enabled: skip_rest('PKINIT tests', 'PKINIT module not built') -soft_pkcs11 = os.path.join(buildtop, 'tests', 'softpkcs11', 'softpkcs11.so') - # Construct a krb5.conf fragment configuring pkinit. user_pem = os.path.join(pkinit_certs, 'user.pem') privkey_pem = os.path.join(pkinit_certs, 'privkey.pem') @@ -55,9 +54,6 @@ p12_upn2_identity = 'PKCS12:%s' % user_upn2_p12 p12_upn3_identity = 'PKCS12:%s' % user_upn3_p12 p12_generic_identity = 'PKCS12:%s' % generic_p12 p12_enc_identity = 'PKCS12:%s' % user_enc_p12 -p11_identity = 'PKCS11:' + soft_pkcs11 -p11_token_identity = ('PKCS11:module_name=' + soft_pkcs11 + - ':slotid=1:token=SoftToken (token)') # Start a realm with the test kdb module for the following UPN SAN tests. realm = K5Realm(kdc_conf=alias_kdc_conf, create_kdb=False, pkinit=True) @@ -389,53 +385,63 @@ realm.klist(realm.user_princ) realm.kinit(realm.user_princ, flags=['-X', 'X509_user_identity=,'], expected_code=1, expected_msg='Preauthentication failed while') -softpkcs11rc = os.path.join(os.getcwd(), 'testdir', 'soft-pkcs11.rc') -realm.env['SOFTPKCS11RC'] = softpkcs11rc +softhsm2 = '/usr/lib/softhsm/libsofthsm2.so' +if not os.path.exists(softhsm2): + skip_rest('PKCS11 tests', 'SoftHSMv2 required') +pkcs11_tool = which('pkcs11-tool') +if not pkcs11_tool: + skip_rest('PKCS11 tests', 'pkcs11-tool from OpenSC required') +tool_cmd = [pkcs11_tool, '--module', softhsm2] + +# Prepare a SoftHSM token. +softhsm2_conf = os.path.join(realm.testdir, 'softhsm2.conf') +softhsm2_tokens = os.path.join(realm.testdir, 'tokens') +os.mkdir(softhsm2_tokens) +realm.env['SOFTHSM2_CONF'] = softhsm2_conf +with open(softhsm2_conf, 'w') as f: + f.write('directories.tokendir = %s\n' % softhsm2_tokens) +realm.run(tool_cmd + ['--init-token', '--label', 'user', + '--so-pin', 'sopin', '--init-pin', '--pin', 'userpin']) +realm.run(tool_cmd + ['-w', user_pem, '-y', 'cert']) +realm.run(tool_cmd + ['-w', privkey_pem, '-y', 'privkey', + '-l', '--pin', 'userpin']) + +# Extract the slot ID generated by SoftHSM. +out = realm.run(tool_cmd + ['-L']) +m = re.search(r'slot ID 0x([0-9a-f]+)\n', out) +if not m: + fail('could not extract slot ID from SoftHSM token') +slot_id = int(m.group(1), 16) + +p11_attr = 'X509_user_identity=PKCS11:' + softhsm2 +p11_token_identity = ('PKCS11:module_name=%s:slotid=%d:token=user' % + (softhsm2, slot_id)) -# PKINIT with PKCS11: identity, with no need for a PIN. -mark('PKCS11 identity, no PIN') -conf = open(softpkcs11rc, 'w') -conf.write("%s\t%s\t%s\t%s\n" % ('user', 'user token', user_pem, privkey_pem)) -conf.close() -# Expect to succeed without having to supply any more information. -realm.kinit(realm.user_princ, - flags=['-X', 'X509_user_identity=%s' % p11_identity]) +mark('PKCS11 identity, with PIN (prompter)') +realm.kinit(realm.user_princ, flags=['-X', p11_attr], password='userpin') realm.klist(realm.user_princ) realm.run([kvno, realm.host_princ]) -# PKINIT with PKCS11: identity, with a PIN supplied by the prompter. -mark('PKCS11 identity, with PIN (prompter)') -os.remove(softpkcs11rc) -conf = open(softpkcs11rc, 'w') -conf.write("%s\t%s\t%s\t%s\n" % ('user', 'user token', user_pem, - privkey_enc_pem)) -conf.close() -# Expect failure if the responder does nothing, and there's no prompter +mark('PKCS11 identity, unavailable PIN') realm.run(['./responder', '-x', 'pkinit={"%s": 0}' % p11_token_identity, - '-X', 'X509_user_identity=%s' % p11_identity, realm.user_princ], - expected_code=2) -realm.kinit(realm.user_princ, - flags=['-X', 'X509_user_identity=%s' % p11_identity], - password='encrypted') -realm.klist(realm.user_princ) -realm.run([kvno, realm.host_princ]) + '-X', p11_attr, realm.user_princ], expected_code=2) -# Supply the wrong PIN. mark('PKCS11 identity, wrong PIN') expected_trace = ('PKINIT client has no configured identity; giving up',) realm.kinit(realm.user_princ, - flags=['-X', 'X509_user_identity=%s' % p11_identity], + flags=['-X', p11_attr], password='wrong', expected_code=1, expected_trace=expected_trace) # PKINIT with PKCS11: identity, with a PIN supplied by the responder. -# Supply the response in raw form. +# Supply the response in raw form. Expect the PIN_COUNT_LOW flag (1) +# to be set due to the previous test. mark('PKCS11 identity, with PIN (responder)') -realm.run(['./responder', '-x', 'pkinit={"%s": 0}' % p11_token_identity, - '-r', 'pkinit={"%s": "encrypted"}' % p11_token_identity, - '-X', 'X509_user_identity=%s' % p11_identity, realm.user_princ]) +realm.run(['./responder', '-x', 'pkinit={"%s": 1}' % p11_token_identity, + '-r', 'pkinit={"%s": "userpin"}' % p11_token_identity, + '-X', p11_attr, realm.user_princ]) # Supply the response through the convenience API. -realm.run(['./responder', '-X', 'X509_user_identity=%s' % p11_identity, - '-p', '%s=%s' % (p11_token_identity, 'encrypted'), +realm.run(['./responder', '-X', p11_attr, + '-p', '%s=%s' % (p11_token_identity, 'userpin'), realm.user_princ]) realm.klist(realm.user_princ) realm.run([kvno, realm.host_princ]) -- 2.47.1