blob: 54f18b8c6683b02ec635b60b20451ab427a54bb3 [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.
#
from os import makedirs, path
import argparse
from typing import Optional, Union, Tuple, Dict, List
import utils
from certificateSigningRequest import CertificateSigningRequest
from pluginSignature import PluginSignature
from certificate import Certificate
from certificateRevocationList import CertificateRevocationList
from pluginCertificate import PluginCertificate
from abstactCertificate import AbstractCertificate
from signature import Signature
def parse() ->argparse.Namespace:
parser = argparse.ArgumentParser(description='Create a certificate.')
sub_parser = parser.add_subparsers(dest='subcommand')
parser.add_argument('save', help='Path to save file', type=str)
parser.add_argument('--plugin',
action='store_true',
help='Specified if it\'s a plugin certificate.',
)
parser.add_argument('--archive',
help='Specified if a gz copy is create.',
action='store_true'
)
parser.add_argument('--req',
help='Specified if it\'s a certificate request.',
action='store_true'
)
revoke_arg_parser = sub_parser.add_parser("revoke")
revoke_arg_group = revoke_arg_parser.add_argument_group('revoke')
revoke_arg_group.add_argument('--crl',
help='Specified the path to the CRL.',
required=True,
type=str
)
revoke_arg_group.add_argument('--subject',
help='Specified the certificate to be revoked.',
required=True,
type=str
)
revoke_arg_group.add_argument('--issuer',
help='Specified issuer path to certificate and private key.',
required=True,
type=str
)
revoke_arg_group.add_argument('--reason',
help='Specified reason to revoke the certificate.',
required=False,
type=str
)
verify_arg_parser = sub_parser.add_parser("verify")
verify_arg_group = verify_arg_parser.add_argument_group('verify')
verify_arg_group.add_argument('--path',
help='Specified the path to the file to verify',
required=True,
type=str
)
verify_arg_group.add_argument('--issuer',
help='Specified issuer path to certificate and private key.',
type=str
)
create_arg_parser = sub_parser.add_parser("create")
create_arg_group = create_arg_parser.add_argument_group('create')
create_arg_group.add_argument('--subject',
help='Specified subject of a new certificate.',
type=str
)
create_arg_group.add_argument('--issuer',
help='Specified issuer path to certificate and private key.',
type=str
)
create_arg_group.add_argument('--crl',
help='Specified if it\'s crl.',
action='store_true'
)
for arg in ['country', 'state', 'city', 'organization', 'division', 'CRL']:
create_arg_group.add_argument(f'--{arg}',
help=f'Specified the subject {arg.lower()}.',
type=str
)
sign_arg_parser = sub_parser.add_parser("sign")
sign_arg_group = sign_arg_parser.add_argument_group('sign')
sign_arg_group.add_argument('--path',
help='Specified the path to the file to sign',
required=True,
type=str
)
sign_arg_group.add_argument('--issuer',
help='Specified issuer path to certificate and private key.',
type=str
)
return parser.parse_args()
def create(subject: List[Tuple[str, str]], issuer: Optional[str], is_plugin: bool, is_req: bool, is_crl: bool) -> Optional['Certificate']:
cert = None
if (len(subject) == 0 and not is_crl) or (is_plugin and utils.isEmpty(issuer)):
raise ValueError
if utils.isEmpty(issuer) and not is_req:
cert = Certificate.create(subject)
elif issuer is not None:
ca = Certificate.load(issuer)
cert = Certificate.create(subject, ca)
elif is_req:
cert = CertificateSigningRequest.create(subject, None)
if is_crl:
if issuer is None:
raise ValueError
cert = CertificateRevocationList.create(Certificate.load(issuer), [])
return cert
def sign(path_to_file: str, issuer: str, plugin: bool, req: bool) -> Union[bytes,Dict[str, Dict[str, bytes]]]:
cert = Certificate.load(issuer)
if plugin:
signature = PluginCertificate.load(cert).sign(path_to_file)
elif req:
signature = CertificateSigningRequest.load(path_to_file).sign(cert)
else:
signature = cert.sign(path_to_file)
return signature
def verify(path_to_file: str, issuer: str, plugin: bool, req: bool, is_archive: bool) -> bool:
if plugin:
return PluginSignature(None).verify_from_archive(
path_to_file
) if is_archive else PluginSignature(
None
).verify()
if req:
return CertificateSigningRequest(None, None).verify_from_archive(path_to_file) if is_archive else CertificateSigningRequest.load(path_to_file).verify()
if not req:
cert = None
if is_archive:
cert = Certificate.load_archive(path_to_file) if issuer is None else Certificate.load(issuer)
else:
cert = Certificate.load(path_to_file) if issuer is None else Certificate.load(issuer)
return cert.verify_from_archive(path_to_file) if is_archive else cert.verify(Certificate.load(path_to_file).cert[1])
return False
def save(cert: Optional['AbstractCertificate'], signature: Optional['Signature'], save_file: Optional[str], is_plugin: bool, is_csr: bool, is_archive: bool, is_crl) -> None:
if save_file is None:
raise ValueError
path_to_file = path.abspath(path.dirname(save_file))
if not path.exists(path_to_file):
makedirs(path_to_file)
if cert is not None:
if path.isdir(save_file):
if is_csr:
save_file += 'certificate_request'
elif is_archive:
save_file += 'archive'
elif is_crl:
save_file += 'list'
else:
save_file += 'certificate'
if is_archive:
cert.save_archive(save_file)
else:
cert.save(save_file)
if signature is not None:
if path.isdir(save_file):
if is_plugin:
save_file += 'plugin.jpl'
elif is_csr:
save_file += 'certificate'
else:
save_file += 'signature'
if is_archive:
signature.save_archive(save_file)
else:
signature.save(save_file)
def revoke(crl: str, issuer: str, subject: str, reason: str) -> 'CertificateRevocationList':
cert = CertificateRevocationList.load(crl, issuer)
subject = Certificate.load(subject)
cert.revoke(subject, 'unspecified' if utils.isEmpty(reason) else reason)
return cert
def main():
cert = None
signature = None
parsed_args = parse()
if parsed_args.subcommand == 'create':
subject = [('commonName', parsed_args.subject)]
for arg in [('country', 'countryName'),
('state', 'stateOrProvinceName'),
('city', 'localityName'),
('organization', 'organizationName'),
('division', 'organizationalUnitName'),
('CRL', 'crlDistributionPoints')]:
subject.append((arg[1], getattr(parsed_args, arg[0])))
cert = create(subject, parsed_args.issuer, parsed_args.plugin, parsed_args.req, parsed_args.crl)
elif parsed_args.subcommand == 'revoke':
all_reason = ['unspecified', 'keyCompromise', 'CACompromise', 'affiliationChanged', 'superseded', 'cessationOfOperation', 'certificateHold']
if not utils.isEmpty(parsed_args.reason) and not parsed_args.reason in all_reason:
ValueError
cert = revoke(parsed_args.crl, parsed_args.issuer, parsed_args.subject, parsed_args.reason)
elif parsed_args.subcommand == 'sign':
signature = sign(parsed_args.path, parsed_args.issuer, parsed_args.plugin, parsed_args.req)
if parsed_args.subcommand == 'verify':
print("OK" if verify(parsed_args.path, parsed_args.issuer, parsed_args.plugin, parsed_args.req, parsed_args.archive) else "FAIL")
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':
cert = Certificate.load(parsed_args.issuer)
cert = PluginCertificate.load(cert)
save(cert,
signature,
parsed_args.save,
parsed_args.subcommand == 'sign' and parsed_args.plugin,
parsed_args.subcommand == 'sign' and parsed_args.req,
parsed_args.archive,
parsed_args.subcommand == 'revoke' or parsed_args.subcommand == 'create' and parsed_args.crl
)
if __name__ == '__main__':
main()