source: mod_gnutls/test/mgstest/softhsm.py @ ff039b1

asynciomainproxy-ticket
Last change on this file since ff039b1 was ff039b1, checked in by Fiona Klute <fiona.klute@…>, 3 years ago

Test suite: Replace softhsm.bash with Python code

With this all test scripts only call runtest.py.

  • Property mode set to 100644
File size: 6.5 KB
Line 
1# Copyright 2020 Fiona Klute
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""SoftHSM support for testing mod_gnutls' PKCS#11 features."""
16
17import os
18import re
19import shutil
20import subprocess
21import tempfile
22from pathlib import Path
23
24softhsm_libname = 'libsofthsm2.so'
25# common install locations to search for the libsofthsm2 PKCS#11 module
26softhsm_searchpath = [
27    Path('/usr/lib64/pkcs11'),
28    Path('/usr/lib/softhsm'),
29    Path('/usr/lib64/softhsm'),
30    Path('/usr/lib/x86_64-linux-gnu/softhsm'),
31    Path('/usr/lib')
32]
33
34# token directory setting in token config file
35tokendir_re = re.compile(r'^directories\.tokendir\s*=\s*(.*)$')
36
37test_key_label = 'privkey'
38test_cert_label = 'certificate'
39
40class Token:
41    """Represents a PKCS#11 token."""
42    def __init__(self, config_file, label='mod_gnutls-test'):
43        """The config_file is what SoftHSM expects as SOFTHSM2_CONF."""
44        self.config = config_file
45        self.label = label
46        # Fixed defaults (for now?)
47        # SO -> security officer
48        self.so_pin = '123456'
49        # export as GNUTLS_PIN for use with GnuTLS tools
50        self.pin = '1234'
51
52        # get tokendir from config file
53        self.tokendir = None
54        with open(self.config) as fh:
55            for line in fh:
56                m = tokendir_re.fullmatch(line.strip())
57                if m:
58                    self.tokendir = Path(m.group(1))
59                    break
60
61        self.softhsm = find_softhsm_bin()
62        self.softhsm_lib = find_softhsm_lib()
63
64        # GnuTLS PKCS#11 tool, currently taken from PATH
65        self.p11tool = ['p11tool', '--provider', self.softhsm_lib]
66        # Lazy initialization
67        self._token_url = None
68        self._object_listing = None
69
70    def reset_db(self):
71        """Delete the SoftHSM database directory, and recreate it."""
72        if self.tokendir.exists():
73            shutil.rmtree(self.tokendir)
74        self.tokendir.mkdir()
75
76    def init_token(self):
77        """Initialize a token. The SoftHSM database directory must already
78        exist."""
79        subprocess.run([self.softhsm, '--init-token',
80                        '--free', '--label', self.label,
81                        '--so-pin', self.so_pin, '--pin', self.pin],
82                       check=True, env={'SOFTHSM2_CONF': self.config})
83
84    @property
85    def token_url(self):
86        if not self._token_url:
87            proc = subprocess.run(self.p11tool + ['--list-token-urls'],
88                                  stdout=subprocess.PIPE, check=True, text=True,
89                                  env={'SOFTHSM2_CONF': self.config})
90            url_re = re.compile(f'^pkcs11:.*token={self.label}\\b.*$')
91            for line in proc.stdout.splitlines():
92                if url_re.fullmatch(line):
93                    self._token_url = line
94                    break
95        return self._token_url
96
97    @property
98    def p11tool_env(self):
99        return {'SOFTHSM2_CONF': self.config, 'GNUTLS_PIN': self.pin}
100
101    def store_key(self, keyfile, label):
102        """Store a private key in this token."""
103        subprocess.run(self.p11tool +
104                       ['--login', '--write', '--label', label,
105                        '--load-privkey', keyfile, self.token_url],
106                       check=True, text=True, env=self.p11tool_env)
107        self._object_listing = None
108
109    def store_cert(self, certfile, label):
110        """Store a certificate in this token."""
111        subprocess.run(self.p11tool +
112                       ['--login', '--write', '--no-mark-private',
113                        '--label', label,
114                        '--load-certificate', certfile, self.token_url],
115                       check=True, text=True, env=self.p11tool_env)
116        self._object_listing = None
117
118    def get_object_url(self, label):
119        """Get the PKCS#11 URL for an object in this token, selected by
120        label."""
121        if not self._object_listing:
122            proc = subprocess.run(self.p11tool +
123                                  ['--login', '--list-all', self.token_url],
124                                  stdout=subprocess.PIPE,
125                                  check=True, text=True, env=self.p11tool_env)
126            self._object_listing = proc.stdout.splitlines()
127        object_re = re.compile(f'^\s*URL:\s+(.*object={label}.*)$')
128        for line in self._object_listing:
129            m = object_re.fullmatch(line)
130            if m:
131                return m.group(1)
132
133    @property
134    def test_env(self):
135        """The environment variables expected by the mod_gnutls test Apache
136        configuration to use this token."""
137        return {
138            'SOFTHSM2_CONF': str(Path(self.config).resolve()),
139            'SOFTHSM_LIB': str(Path(self.softhsm_lib).resolve()),
140            'P11_PIN': self.pin,
141            'P11_CERT_URL': self.get_object_url(test_cert_label),
142            'P11_KEY_URL': self.get_object_url(test_key_label)
143        }
144
145def find_softhsm_bin():
146    """Find the SoftHSM Util binary to use.
147
148    Returns the value selected by ./configure if available, otherwise
149    searches the PATH for 'softhsm2-util'.
150
151    """
152    softhsm = os.environ.get('SOFTHSM')
153    if softhsm and softhsm != 'no':
154        return softhsm
155    return shutil.which('softhsm2-util')
156
157def find_softhsm_lib(libname=softhsm_libname, searchpath=softhsm_searchpath):
158    """Get the path to the SoftHSM PKCS#11 module.
159
160    Return SOFTHSM_LIB if set, otherwise search a list of directories
161    for libsofthsm2.so.
162
163    """
164    lib = os.environ.get('SOFTHSM_LIB')
165    if lib:
166        return lib
167    for p in searchpath:
168        lib = p.joinpath(libname)
169        if lib.is_file():
170            return str(lib)
171
172def tmp_softhsm_conf(db):
173    """Create a temporary SOFTHSM2_CONF file, using an absolute path for
174    the database.
175
176    """
177    with tempfile.NamedTemporaryFile(
178            prefix='mod_gnutls_test-', suffix='.conf', delete=False) as conf:
179        try:
180            conf.write(b'objectstore.backend = file\n')
181            conf.write(f'directories.tokendir = {Path(db).resolve()!s}\n'
182                       .encode())
183        except:
184            Path(conf).unlink()
185            raise
186        return conf.name
Note: See TracBrowser for help on using the repository browser.