Skip to content
Snippets Groups Projects
Commit aef4eaaf authored by Xavier Jouslin de Noray's avatar Xavier Jouslin de Noray
Browse files

CRLManager: add manager for crl

Change-Id: I2387cd889f8d7b79b42fc4b0d8edabfb46b7b79a
parent 6be92339
Branches
No related tags found
No related merge requests found
...@@ -69,7 +69,7 @@ def parse() ->argparse.Namespace: ...@@ -69,7 +69,7 @@ def parse() ->argparse.Namespace:
help='Specified issuer path to certificate and private key.', help='Specified issuer path to certificate and private key.',
type=str type=str
) )
for arg in ['country', 'state', 'city', 'organization', 'division']: for arg in ['country', 'state', 'city', 'organization', 'division', 'crl']:
create_arg_group.add_argument(f'--{arg}', create_arg_group.add_argument(f'--{arg}',
help=f'Specified the subject {arg.lower()}.', help=f'Specified the subject {arg.lower()}.',
type=str type=str
...@@ -160,7 +160,7 @@ def main(): ...@@ -160,7 +160,7 @@ def main():
parsed_args = parse() parsed_args = parse()
if parsed_args.subcommand == 'create': if parsed_args.subcommand == 'create':
subject = [('commonName', parsed_args.subject)] subject = [('commonName', parsed_args.subject)]
for arg in [('country', 'countryName'), ('state', 'stateOrProvinceName'), ('city', 'localityName'), ('organization', 'organizationName'), ('division', 'organizationalUnitName')]: for arg in [('country', 'countryName'), ('state', 'stateOrProvinceName'), ('city', 'localityName'), ('organization', 'organizationName'), ('division', 'organizationalUnitName'), ('crl', 'crlDistributionPoints'))]:
subject.append((arg[1], getattr(parsed_args, arg[0]))) subject.append((arg[1], getattr(parsed_args, arg[0])))
cert = create(subject, parsed_args.issuer, parsed_args.plugin, parsed_args.req) cert = create(subject, parsed_args.issuer, parsed_args.plugin, parsed_args.req)
elif parsed_args.subcommand == 'sign': elif parsed_args.subcommand == 'sign':
...@@ -170,7 +170,7 @@ def main(): ...@@ -170,7 +170,7 @@ def main():
if not utils.isEmpty(parsed_args.save) and (not signature is None or not cert is None): if not utils.isEmpty(parsed_args.save) and (not signature is None or not cert is None):
if parsed_args.plugin and parsed_args.subcommand == 'sign': if parsed_args.plugin and parsed_args.subcommand == 'sign':
cert = Certificate.load(parsed_args.issuer) cert = Certificate.load(parsed_args.issuer)
cert = PluginCertificate.load(cert, parsed_args.path) cert = PluginCertificate.load(cert)
save(cert, save(cert,
signature, signature,
parsed_args.save, parsed_args.save,
......
...@@ -83,6 +83,11 @@ class Certificate(AbstractCertificate): ...@@ -83,6 +83,11 @@ class Certificate(AbstractCertificate):
for attr, info in subject: for attr, info in subject:
if info is None: if info is None:
continue continue
if attr == 'crlDistributionPoints':
# add CRL distribution points extension
ext = crypto.X509Extension(b'crlDistributionPoints', False, info.encode())
cert.add_extensions([ext])
continue
setattr(subj, attr, info) setattr(subj, attr, info)
issuer_crt = issuer.cert[1] if issuer is not None else cert issuer_crt = issuer.cert[1] if issuer is not None else cert
issuer_key = issuer.cert[0] if issuer is not None else key issuer_key = issuer.cert[0] if issuer is not None else key
......
#
# Copyright (C) 2023 Savoir-faire Linux Inc.
#
# Author: Xavier Jouslin de Noray <xavier.joulindenoray@savoirfairelinux.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# Creates packaging targets for a distribution and architecture.
# This helps reduce the length of the top Makefile.
#
from typing import Optional, List, Tuple
from datetime import datetime
from OpenSSL import crypto
from certificate import Certificate
from abstactCertificate import AbstractCertificate
from utils import read, save, save_archive
class CertificateRevocationList(AbstractCertificate):
"""
A class representing a certificate revocation list.
"""
def __init__(self, key: Optional[crypto.PKey], cert: Optional[crypto.CRL], issuer: 'Certificate'):
self._cert = cert
self._key = key
self._issuer = issuer
@property
def cert(self) -> Tuple[crypto.PKey, crypto.CRL]:
"""
Get the certificate.
:return: A tuple containing the certificate and the key.
"""
return self._key, self._cert
@staticmethod
def create(issuer: 'Certificate', revoked: List[Tuple[int, datetime]]) -> 'CertificateRevocationList':
"""
Create a certificate revocation list.
:param issuer: The issuer of the certificate revocation list.
:param revoked: The list of revoked certificates.
:return: A Certificate object.
"""
crl = crypto.CRL()
crl.set_lastUpdate(datetime.utcnow().strftime('%Y%m%d%H%M%SZ').encode('ascii'))
for serial, date in revoked:
revoked_cert = crypto.Revoked()
revoked_cert.set_serial(str(serial).encode('ascii'))
revoked_cert.set_rev_date(date.strftime('%Y%m%d%H%M%SZ').encode('ascii'))
crl.add_revoked(revoked)
return CertificateRevocationList(None, crl, issuer).sign(issuer)
@staticmethod
def load(file_path: str, issuer: str) -> 'CertificateRevocationList':
"""
Load a certificate from a file.
:param file_path: The path to the certificate.
:return: A Certificate object.
"""
crl = crypto.load_crl(crypto.FILETYPE_PEM, read(f'{file_path}.crl', 'rb'))
issuer = crypto.load_certificate(crypto.FILETYPE_PEM, read(f'{issuer}.crt', 'rb'))
return CertificateRevocationList(None, crl, Certificate(None, issuer, None))
def revoke(self, subject: 'Certificate', reason: str = 'unspecified') -> 'CertificateRevocationList':
"""
Revoke a certificate.
:param subject: The certificate to revoke.
:param reason: The reason for revocation.
"""
if self._cert is None:
raise ValueError
revoked = crypto.Revoked()
revoked.set_serial(hex(subject.cert[1].get_serial_number())[2:].encode('ascii'))
revoked.set_reason(reason.encode('ascii'))
now = datetime.utcnow()
revoked.set_rev_date(now.strftime("%Y%m%d%H%M%SZ").encode('ascii'))
self._cert.add_revoked(revoked)
return CertificateRevocationList(None, self._cert, self._issuer).sign(self._issuer)
def sign(self, issuer: 'Certificate') -> 'CertificateRevocationList':
"""
Sign the certificate.
:param issuer: The issuer certificate.
"""
if self._cert is None:
raise ValueError
self._cert.sign(issuer.cert[1], issuer.cert[0], b'sha512')
return CertificateRevocationList(None, self._cert, self._issuer)
def save(self, file_path: str) -> None:
"""
Save the certificate to a file.
:param file_path: The path to the file.
"""
if self._cert is None:
raise ValueError
save(f'{file_path}.crl', crypto.dump_crl(crypto.FILETYPE_PEM, self._cert), 'wb')
def save_archive(self, path_2_file: str) -> None:
"""
Save the CRL to a archive file.
:param path_2_file: The path to the file.
"""
if self._cert is None:
raise ValueError
save_archive(path_2_file, crypto.dump_crl(crypto.FILETYPE_PEM, self._cert), 'wb')
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment