diff options
Diffstat (limited to 'tools')
-rw-r--r-- | tools/ca/ca/__main__.py | 48 |
1 files changed, 45 insertions, 3 deletions
diff --git a/tools/ca/ca/__main__.py b/tools/ca/ca/__main__.py index e3e4bbe6..118b3763 100644 --- a/tools/ca/ca/__main__.py +++ b/tools/ca/ca/__main__.py | |||
@@ -30,6 +30,7 @@ from tempfile import TemporaryFile | |||
30 | import subprocess | 30 | import subprocess |
31 | import json | 31 | import json |
32 | from leapseconddata import LeapSecondData | 32 | from leapseconddata import LeapSecondData |
33 | from collections.abc import Iterable | ||
33 | 34 | ||
34 | 35 | ||
35 | class KeyType(Enum): | 36 | class KeyType(Enum): |
@@ -76,6 +77,28 @@ class KeyType(Enum): | |||
76 | except KeyError: | 77 | except KeyError: |
77 | raise ValueError() | 78 | raise ValueError() |
78 | 79 | ||
80 | class SupportedKeyUsage(Enum): | ||
81 | SERVER_AUTH = 'server' | ||
82 | CLIENT_AUTH = 'client' | ||
83 | |||
84 | @property | ||
85 | def oid(self): | ||
86 | match self: | ||
87 | case SupportedKeyUsage.SERVER_AUTH: | ||
88 | return ExtendedKeyUsageOID.SERVER_AUTH | ||
89 | case SupportedKeyUsage.CLIENT_AUTH: | ||
90 | return ExtendedKeyUsageOID.CLIENT_AUTH | ||
91 | |||
92 | def __str__(self): | ||
93 | return self.value | ||
94 | |||
95 | @classmethod | ||
96 | def from_string(cls, s): | ||
97 | try: | ||
98 | return cls(s) | ||
99 | except KeyError: | ||
100 | raise ValueError() | ||
101 | |||
79 | class ValidFQDN(FQDN): | 102 | class ValidFQDN(FQDN): |
80 | def __init__(self, *args, **kwds): | 103 | def __init__(self, *args, **kwds): |
81 | super().__init__(*args, **kwds) | 104 | super().__init__(*args, **kwds) |
@@ -133,6 +156,19 @@ class BooleanAction(argparse.Action): | |||
133 | def __call__(self, parser, namespace, values, option_string=None): | 156 | def __call__(self, parser, namespace, values, option_string=None): |
134 | setattr(namespace, self.dest, False if option_string.startswith('--no') else True) | 157 | setattr(namespace, self.dest, False if option_string.startswith('--no') else True) |
135 | 158 | ||
159 | class ExtendAction(argparse.Action): | ||
160 | def __init__(self, *args, **kwargs): | ||
161 | super().__init__(*args, **kwargs) | ||
162 | self.reset_dest = False | ||
163 | def __call__(self, parser, namespace, values, option_string=None): | ||
164 | if not self.reset_dest: | ||
165 | setattr(namespace, self.dest, []) | ||
166 | self.reset_dest = True | ||
167 | if isinstance(values, Iterable): | ||
168 | getattr(namespace, self.dest).extend(values) | ||
169 | else: | ||
170 | getattr(namespace, self.dest).append(values) | ||
171 | |||
136 | 172 | ||
137 | def load_key(keyfile, prompt='CA private key password: '): | 173 | def load_key(keyfile, prompt='CA private key password: '): |
138 | key = None | 174 | key = None |
@@ -294,7 +330,10 @@ def initca(ca_cert, ca_key, key_type, subject, clock_skew, validity, sops): | |||
294 | logger.debug('Adjusting permissions for ‘%s’...', ca_cert) | 330 | logger.debug('Adjusting permissions for ‘%s’...', ca_cert) |
295 | os.chmod(ca_cert, 0o0444) | 331 | os.chmod(ca_cert, 0o0444) |
296 | 332 | ||
297 | def signcsr(ca_cert, ca_key, clock_skew, validity, subject, alternative_name, ignore_alternative_names, csr, output): | 333 | def signcsr(ca_cert, ca_key, clock_skew, validity, subject, alternative_name, key_usage, ignore_alternative_names, csr, output): |
334 | if not key_usage: | ||
335 | raise InvalidParamsError('No extended key usages specified') | ||
336 | |||
298 | csr_bytes = None | 337 | csr_bytes = None |
299 | try: | 338 | try: |
300 | csr_bytes = csr.read() | 339 | csr_bytes = csr.read() |
@@ -348,7 +387,7 @@ def signcsr(ca_cert, ca_key, clock_skew, validity, subject, alternative_name, ig | |||
348 | x509.BasicConstraints(ca=False, path_length=None), | 387 | x509.BasicConstraints(ca=False, path_length=None), |
349 | True | 388 | True |
350 | ).add_extension( | 389 | ).add_extension( |
351 | x509.ExtendedKeyUsage([ExtendedKeyUsageOID.CLIENT_AUTH]), | 390 | x509.ExtendedKeyUsage(list(map(lambda ku: ku.oid, key_usage))), |
352 | False | 391 | False |
353 | ) | 392 | ) |
354 | 393 | ||
@@ -374,7 +413,7 @@ def signcsr(ca_cert, ca_key, clock_skew, validity, subject, alternative_name, ig | |||
374 | logger.debug('Adjusting permissions for ‘%s’...', output) | 413 | logger.debug('Adjusting permissions for ‘%s’...', output) |
375 | os.chmod(output, 0o0444) | 414 | os.chmod(output, 0o0444) |
376 | 415 | ||
377 | def new_client(ca_cert, ca_key, key_type, clock_skew, validity, subject, alternative_name, sops, output): | 416 | def new_client(ca_cert, ca_key, key_type, clock_skew, validity, subject, alternative_name, key_usage, sops, output): |
378 | key_file = output.with_suffix('.key') | 417 | key_file = output.with_suffix('.key') |
379 | cert_file = output.with_suffix('.crt') | 418 | cert_file = output.with_suffix('.crt') |
380 | 419 | ||
@@ -417,6 +456,7 @@ def new_client(ca_cert, ca_key, key_type, clock_skew, validity, subject, alterna | |||
417 | validity=validity, | 456 | validity=validity, |
418 | subject=None, | 457 | subject=None, |
419 | alternative_name=[], | 458 | alternative_name=[], |
459 | key_usage=key_usage, | ||
420 | ignore_alternative_names=False, | 460 | ignore_alternative_names=False, |
421 | output=cert_file, | 461 | output=cert_file, |
422 | csr=csr.sign( | 462 | csr=csr.sign( |
@@ -524,6 +564,7 @@ def main(): | |||
524 | subparser.add_argument('--validity', metavar='DURATION', type=duration, default=timedelta(days=ceil(365.2425*10))) | 564 | subparser.add_argument('--validity', metavar='DURATION', type=duration, default=timedelta(days=ceil(365.2425*10))) |
525 | subparser.add_argument('--subject', metavar='CN', type=str, required=False) | 565 | subparser.add_argument('--subject', metavar='CN', type=str, required=False) |
526 | subparser.add_argument('--ignore-alternative-names', '--no-ignore-alternative-names', action=BooleanAction, default=True) | 566 | subparser.add_argument('--ignore-alternative-names', '--no-ignore-alternative-names', action=BooleanAction, default=True) |
567 | subparser.add_argument('--key-usage', metavar='KEY_USAGE', type=SupportedKeyUsage, action=ExtendAction, default=[SupportedKeyUsage.CLIENT_AUTH]) | ||
527 | subparser.add_argument('--alternative-name', metavar='CN', type=str, action='append') | 568 | subparser.add_argument('--alternative-name', metavar='CN', type=str, action='append') |
528 | subparser.add_argument('--output', type=Path, required=True) | 569 | subparser.add_argument('--output', type=Path, required=True) |
529 | subparser.add_argument('csr', metavar='FILE', type=argparse.FileType(mode='rb')) | 570 | subparser.add_argument('csr', metavar='FILE', type=argparse.FileType(mode='rb')) |
@@ -537,6 +578,7 @@ def main(): | |||
537 | subparser.add_argument('--validity', metavar='DURATION', type=duration, default=timedelta(days=ceil(365.2425*10))) | 578 | subparser.add_argument('--validity', metavar='DURATION', type=duration, default=timedelta(days=ceil(365.2425*10))) |
538 | subparser.add_argument('--sops', '--no-sops', action=BooleanAction, default=True) | 579 | subparser.add_argument('--sops', '--no-sops', action=BooleanAction, default=True) |
539 | subparser.add_argument('--subject', metavar='CN', type=str, required=True) | 580 | subparser.add_argument('--subject', metavar='CN', type=str, required=True) |
581 | subparser.add_argument('--key-usage', metavar='KEY_USAGE', type=SupportedKeyUsage, action=ExtendAction, default=[SupportedKeyUsage.CLIENT_AUTH]) | ||
540 | subparser.add_argument('--alternative-name', metavar='CN', type=str, action='append') | 582 | subparser.add_argument('--alternative-name', metavar='CN', type=str, action='append') |
541 | subparser.add_argument('--output', type=Path, required=True) | 583 | subparser.add_argument('--output', type=Path, required=True) |
542 | subparser.set_defaults(cmd=new_client) | 584 | subparser.set_defaults(cmd=new_client) |