source: mod_gnutls/test/mgstest/softhsm.py

asyncio
Last change on this file was 83b5bf6, checked in by Fiona Klute <fiona.klute@…>, 7 months ago

Fix flake8 warnings in mgstest.softhsm

  • Property mode set to 100644
File size: 7.1 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 enum import Enum
23from pathlib import Path
24
25softhsm_libname = 'libsofthsm2.so'
26# common install locations to search for the libsofthsm2 PKCS#11 module
27softhsm_searchpath = [
28    Path('/usr/lib64/pkcs11'),
29    Path('/usr/lib/softhsm'),
30    Path('/usr/lib64/softhsm'),
31    Path('/usr/lib/x86_64-linux-gnu/softhsm'),
32    Path('/usr/lib')
33]
34
35# token directory setting in token config file
36tokendir_re = re.compile(r'^directories\.tokendir\s*=\s*(.*)$')
37
38test_label = 'test_server'
39
40
41class ObjectType(Enum):
42    """Types that may occur in PKCS#11 URIs (type=...).
43
44    See: https://tools.ietf.org/html/rfc7512#section-2.3
45
46    """
47    CERT = 'cert'
48    DATA = 'data'
49    PRIVATE = 'private'
50    PUBLIC = 'public'
51    SECRET_KEY = 'secret-key'
52
53    def __init__(self, uri_type):
54        self.uri_type = uri_type
55
56    def __str__(self):
57        """
58        >>> str(ObjectType.CERT)
59        'type=cert'
60        """
61        return f'type={self.uri_type}'
62
63    def __repr__(self):
64        """
65        >>> repr(ObjectType.PRIVATE)
66        'ObjectType.PRIVATE'
67        """
68        return f'{self.__class__.__name__!s}.{self.name}'
69
70
71class Token:
72    """Represents a PKCS#11 token."""
73    def __init__(self, config_file, label='mod_gnutls-test'):
74        """The config_file is what SoftHSM expects as SOFTHSM2_CONF."""
75        self.config = config_file
76        self.label = label
77        # Fixed defaults (for now?)
78        # SO -> security officer
79        self.so_pin = '123456'
80        # export as GNUTLS_PIN for use with GnuTLS tools
81        self.pin = '1234'
82
83        # get tokendir from config file
84        self.tokendir = None
85        with open(self.config) as fh:
86            for line in fh:
87                m = tokendir_re.fullmatch(line.strip())
88                if m:
89                    self.tokendir = Path(m.group(1))
90                    break
91
92        self.softhsm = find_softhsm_bin()
93        self.softhsm_lib = find_softhsm_lib()
94
95        # GnuTLS PKCS#11 tool, currently taken from PATH
96        self.p11tool = ['p11tool', '--provider', self.softhsm_lib]
97        # Lazy initialization
98        self._token_url = None
99        self._object_listing = None
100
101    def reset_db(self):
102        """Delete the SoftHSM database directory, and recreate it."""
103        if self.tokendir.exists():
104            shutil.rmtree(self.tokendir)
105        self.tokendir.mkdir()
106
107    def init_token(self):
108        """Initialize a token. The SoftHSM database directory must already
109        exist."""
110        subprocess.run([self.softhsm, '--init-token',
111                        '--free', '--label', self.label,
112                        '--so-pin', self.so_pin, '--pin', self.pin],
113                       check=True, env={'SOFTHSM2_CONF': self.config})
114
115    @property
116    def token_url(self):
117        if not self._token_url:
118            proc = subprocess.run(
119                self.p11tool + ['--list-token-urls'],
120                stdout=subprocess.PIPE, check=True, text=True,
121                env={'SOFTHSM2_CONF': self.config})
122            url_re = re.compile(f'^pkcs11:.*token={self.label}\\b.*$')
123            for line in proc.stdout.splitlines():
124                if url_re.fullmatch(line):
125                    self._token_url = line
126                    break
127        return self._token_url
128
129    @property
130    def p11tool_env(self):
131        return {'SOFTHSM2_CONF': self.config, 'GNUTLS_PIN': self.pin}
132
133    def store_key(self, keyfile, label):
134        """Store a private key in this token."""
135        subprocess.run(
136            self.p11tool + ['--login', '--write', '--label', label,
137                            '--load-privkey', keyfile, self.token_url],
138            check=True, text=True, env=self.p11tool_env)
139        self._object_listing = None
140
141    def store_cert(self, certfile, label):
142        """Store a certificate in this token."""
143        subprocess.run(
144            self.p11tool + ['--login', '--write', '--no-mark-private',
145                            '--label', label,
146                            '--load-certificate', certfile, self.token_url],
147            check=True, text=True, env=self.p11tool_env)
148        self._object_listing = None
149
150    def get_object_url(self, label, type):
151        """Get the PKCS#11 URL for an object in this token, selected by
152        label."""
153        if not self._object_listing:
154            proc = subprocess.run(
155                self.p11tool + ['--login', '--list-all', self.token_url],
156                stdout=subprocess.PIPE,
157                check=True, text=True, env=self.p11tool_env)
158            self._object_listing = proc.stdout.splitlines()
159        object_re = re.compile(f'^\\s*URL:\\s+(.*object={label}.*)$')
160        for line in self._object_listing:
161            m = object_re.fullmatch(line)
162            if m and str(type) in m.group(1):
163                return m.group(1)
164
165    @property
166    def test_env(self):
167        """The environment variables expected by the mod_gnutls test Apache
168        configuration to use this token."""
169        return {
170            'SOFTHSM2_CONF': str(Path(self.config).resolve()),
171            'SOFTHSM_LIB': str(Path(self.softhsm_lib).resolve()),
172            'P11_PIN': self.pin,
173            'P11_CERT_URL': self.get_object_url(test_label, ObjectType.CERT),
174            'P11_KEY_URL': self.get_object_url(test_label, ObjectType.PRIVATE)
175        }
176
177
178def find_softhsm_bin():
179    """Find the SoftHSM Util binary to use.
180
181    Returns the value selected by ./configure if available, otherwise
182    searches the PATH for 'softhsm2-util'.
183
184    """
185    softhsm = os.environ.get('SOFTHSM')
186    if softhsm and softhsm != 'no':
187        return softhsm
188    return shutil.which('softhsm2-util')
189
190
191def find_softhsm_lib(libname=softhsm_libname, searchpath=softhsm_searchpath):
192    """Get the path to the SoftHSM PKCS#11 module.
193
194    Return SOFTHSM_LIB if set, otherwise search a list of directories
195    for libsofthsm2.so.
196
197    """
198    lib = os.environ.get('SOFTHSM_LIB')
199    if lib:
200        return lib
201    for p in searchpath:
202        lib = p.joinpath(libname)
203        if lib.is_file():
204            return str(lib)
205
206
207def tmp_softhsm_conf(db):
208    """Create a temporary SOFTHSM2_CONF file, using an absolute path for
209    the database.
210
211    """
212    with tempfile.NamedTemporaryFile(
213            prefix='mod_gnutls_test-', suffix='.conf', delete=False) as conf:
214        try:
215            conf.write(b'objectstore.backend = file\n')
216            conf.write(f'directories.tokendir = {Path(db).resolve()!s}\n'
217                       .encode())
218        except:
219            Path(conf).unlink()
220            raise
221        return conf.name
Note: See TracBrowser for help on using the repository browser.