blob: 2e4cb9d98b0a1ba0ca2a970d2e7d962a326dee82 [file] [log] [blame]
#!/usr/bin/env python3
#
# Copyright (C) 2023 Savoir-faire Linux Inc.
#
# Author: Xavier Jouslin de Noray <xavier.jouslindenoray@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.
#
import os
from typing import List, Tuple, Optional
from OpenSSL import crypto
from zipfile import ZipFile
from utils import read, save, save_archive, read_archive
from abstactCertificate import AbstractCertificate
from certificate import Certificate
from certificateSigningRequestSignature import CertificateSigningRequestSignature
class CertificateSigningRequest(AbstractCertificate):
"""
A class representing a certificate signing request.
"""
def __init__(self, key: Optional[crypto.PKey], cert: Optional[crypto.X509Req]):
self._cert = cert
self._key = key
@property
def cert(self) -> Tuple[crypto.PKey, crypto.X509Req]:
"""
Get the certificate.
:return: A tuple containing the certificate and the key.
"""
return self._key, self._cert
@staticmethod
def load(file_path: str) -> 'CertificateSigningRequest':
"""
Load a certificate request from a file.
:param file_path: The path to the certificate request file.
:return: A Certificate object.
"""
cert = crypto.load_certificate_request(crypto.FILETYPE_PEM, read(f'{file_path}.csr', 'rb'))
if not cert.verify(cert.get_pubkey()):
raise ValueError
return CertificateSigningRequest(None, cert)
@staticmethod
def create(subject: List[Tuple[str, str]], issuer: Optional['AbstractCertificate'] = None) -> 'CertificateSigningRequest':
"""
Create a new certificate.
:param subject: A list of tuples containing the subject attributes.
:param issuer: The issuer certificate.
:return: A Certificate signing object.
"""
if issuer is not None:
raise ValueError
key = crypto.PKey()
key.generate_key(crypto.TYPE_RSA, 4096)
req = crypto.X509Req()
subj = req.get_subject()
for attr, info in subject:
if info is None:
continue
if attr == 'crlDistributionPoints':
# add CRL distribution points extension
ext = crypto.X509Extension(b'crlDistributionPoints', False, info.encode())
req.add_extensions([ext])
continue
setattr(subj, attr, info)
req.set_pubkey(key)
req.sign(key, 'sha512')
return CertificateSigningRequest(key, req)
def save(self, path_2_file: str) -> None:
"""
Save the certificate request to a file.
:param path_2_file: The path to the file.
:return: None
"""
if self._cert is None:
raise ValueError
save(f'{path_2_file}.csr', crypto.dump_certificate_request(crypto.FILETYPE_PEM, self._cert), 'wb')
if self._key is not None:
save(f'{path_2_file}.key', crypto.dump_privatekey(crypto.FILETYPE_PEM, self._key),'wb')
def verify(self) -> bool:
"""
Verify the certificate signing request.
:return: True if the certificate is valid, False otherwise.
"""
if self._cert is None:
return False
try:
verified = self._cert.verify(self._cert.get_pubkey())
except Exception:
return False
return verified is None
def save_archive(self, path_2_file: str):
"""
Save the certificate request archive.
:param path_2_file: The path to the file.
:return: None
"""
if self._cert is None:
raise ValueError
self.save(path_2_file)
save_archive(f'{path_2_file}.csr.gz', crypto.dump_certificate_request(crypto.FILETYPE_PEM, self._cert), 'wb')
def verify_from_archive(self, path_2_archive: str) -> bool:
data = read_archive(path_2_archive)
path_2_archive = path_2_archive[0:-4]
save(path_2_archive, data)
with ZipFile(path_2_archive, 'r') as zip_file:
filename = os.path.splitext(os.path.basename(path_2_archive))[0]
self._cert = crypto.load_certificate(crypto.FILETYPE_PEM, read(zip_file.extract(filename + '.crt')))
return self.verify()
def sign(self, issuer: 'Certificate') -> 'CertificateSigningRequestSignature':
"""
Sign the certificate signing request.
:param issuer: The issuer certificate.
:param serial: The serial number.
:param validity: The validity of the certificate.
:return: A Certificate object.
"""
return CertificateSigningRequestSignature(issuer).create(self)