2019-05-06 12:13:04 +02:00
|
|
|
#! /usr/bin/env python
|
|
|
|
'''
|
2020-09-09 15:30:49 +02:00
|
|
|
Sort X509/private key material
|
2019-05-06 12:13:04 +02:00
|
|
|
'''
|
|
|
|
|
|
|
|
from __future__ import print_function
|
|
|
|
|
|
|
|
import logging
|
|
|
|
import re
|
|
|
|
import fileinput
|
|
|
|
from argparse import ArgumentParser
|
2019-08-20 14:20:46 +02:00
|
|
|
from datetime import datetime
|
2019-05-06 12:13:04 +02:00
|
|
|
from OpenSSL import crypto
|
|
|
|
from cryptography.hazmat.primitives import serialization
|
2020-03-20 11:27:15 +01:00
|
|
|
import certifi.core
|
2019-05-06 12:13:04 +02:00
|
|
|
|
2020-03-20 11:27:41 +01:00
|
|
|
VALID_FQDN_RE = r'^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])'\
|
|
|
|
r'(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}'\
|
|
|
|
r'[a-zA-Z0-9]))*$'
|
2019-05-06 12:13:04 +02:00
|
|
|
|
2019-08-20 14:20:46 +02:00
|
|
|
CERTINFO_TEMPLATE = '''
|
|
|
|
subject= /{subject}
|
|
|
|
issuer= /{issuer}
|
|
|
|
notBefore={notbefore!s}
|
|
|
|
notAfter={notafter}
|
|
|
|
SHA1 Fingerprint={sha1fingerprint}
|
|
|
|
'''.strip()
|
|
|
|
|
|
|
|
ASN1TIME_FMT = str('%Y%m%d%H%M%SZ'.encode('utf8'))
|
|
|
|
OPENSSLTIME_FMT = '%b %e %T %Y GMT'
|
|
|
|
|
2020-08-17 12:19:21 +02:00
|
|
|
|
2020-09-10 16:43:32 +02:00
|
|
|
class PkDecorator(object):
|
|
|
|
'''
|
|
|
|
Provide some information on the private key object
|
|
|
|
'''
|
|
|
|
pk = None
|
|
|
|
|
|
|
|
def __init__(self, pk):
|
|
|
|
self.pk = pk
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
return "Private key"
|
|
|
|
|
|
|
|
|
|
|
|
class PkDecoratorEC(PkDecorator):
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
pk_crypto = self.pk.to_cryptography_key()
|
|
|
|
return "EC Private key curve %s (%d bits)" % (
|
|
|
|
pk_crypto.curve.name, pk_crypto.key_size)
|
|
|
|
|
|
|
|
|
|
|
|
class PkDecoratorRSA(PkDecorator):
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
pk_crypto = self.pk.to_cryptography_key()
|
2020-09-10 17:44:59 +02:00
|
|
|
return "RSA Private key %d bits (exponent %d)" % (
|
|
|
|
pk_crypto.key_size,
|
|
|
|
pk_crypto.private_numbers().public_numbers.e)
|
2020-09-10 16:43:32 +02:00
|
|
|
|
|
|
|
|
|
|
|
class PkDecoratorDSA(PkDecorator):
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
pk_crypto = self.pk.to_cryptography_key()
|
|
|
|
return "DSA Private key %d bits" % pk_crypto.key_size
|
|
|
|
|
|
|
|
|
|
|
|
class PkDecoratorDH(PkDecorator):
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
pk_crypto = self.pk.to_cryptography_key()
|
|
|
|
return "DH Private key %d bits" % pk_crypto.key_size
|
|
|
|
|
|
|
|
|
|
|
|
class PkDecoratorFactory(object):
|
|
|
|
'''
|
|
|
|
Provide some information on the private key object
|
|
|
|
'''
|
|
|
|
|
|
|
|
def create(pk):
|
|
|
|
'''
|
|
|
|
Create the appropriate decorater object
|
|
|
|
'''
|
|
|
|
decorators = {
|
|
|
|
crypto.TYPE_DH: PkDecoratorDH,
|
|
|
|
crypto.TYPE_EC: PkDecoratorEC,
|
|
|
|
crypto.TYPE_DSA: PkDecoratorDSA,
|
|
|
|
crypto.TYPE_RSA: PkDecoratorRSA,
|
|
|
|
}
|
|
|
|
if pk.type() in decorators:
|
|
|
|
return decorators[pk.type()](pk)
|
|
|
|
else:
|
|
|
|
raise UnsupportedPkEncryption("Unsupported private key type %d"
|
|
|
|
% pk.type())
|
|
|
|
|
|
|
|
|
2020-09-10 11:15:26 +02:00
|
|
|
class UnsupportedPkEncryption(Exception):
|
2020-03-20 14:26:38 +01:00
|
|
|
'''
|
2020-09-10 11:15:26 +02:00
|
|
|
When we encounter unsupported encryption algorithms
|
2020-03-20 14:26:38 +01:00
|
|
|
'''
|
|
|
|
pass
|
|
|
|
|
2020-08-17 12:19:21 +02:00
|
|
|
|
2020-03-20 14:26:38 +01:00
|
|
|
class CertificateComponentException(Exception):
|
|
|
|
'''
|
2020-08-17 12:19:21 +02:00
|
|
|
When something is not right with the whole cert+intermediates+private key
|
|
|
|
bundle
|
2020-03-20 14:26:38 +01:00
|
|
|
'''
|
|
|
|
pass
|
|
|
|
|
2019-05-06 12:13:04 +02:00
|
|
|
|
|
|
|
def load_data(filenames):
|
|
|
|
'''
|
|
|
|
Read all data till EOF.
|
|
|
|
Might come from full chain (+ privkey) bundles
|
|
|
|
Might come from stdin (e.g. as vim filter action)
|
|
|
|
Might not be a good idea if it is being used to read thousands of files
|
|
|
|
'''
|
|
|
|
datas = {}
|
|
|
|
current_file = None
|
|
|
|
|
|
|
|
# iterate over all inputs
|
|
|
|
for line in fileinput.input(filenames):
|
|
|
|
# switch files if the filename changes, automatically switches slots
|
|
|
|
if current_file is not fileinput.filename():
|
|
|
|
current_file = fileinput.filename()
|
|
|
|
logging.debug('Reading from %s', current_file)
|
|
|
|
if current_file in datas:
|
|
|
|
datas[current_file] += line.lstrip()
|
|
|
|
else:
|
|
|
|
datas[current_file] = line.lstrip()
|
|
|
|
return datas
|
|
|
|
|
|
|
|
|
2020-08-11 13:25:55 +02:00
|
|
|
def get_cert_pubkey(cert):
|
2019-05-06 12:13:04 +02:00
|
|
|
'''
|
2020-08-11 13:25:55 +02:00
|
|
|
Get the pubkey of a certificate
|
2019-05-06 12:13:04 +02:00
|
|
|
'''
|
|
|
|
|
2020-08-11 13:25:55 +02:00
|
|
|
cert_crypto = cert.to_cryptography()
|
2020-08-17 12:19:21 +02:00
|
|
|
pubkey = cert_crypto.public_key()
|
|
|
|
pub_bytes = pubkey.public_bytes(
|
|
|
|
serialization.Encoding.PEM,
|
|
|
|
serialization.PublicFormat.SubjectPublicKeyInfo)
|
2019-05-06 12:13:04 +02:00
|
|
|
|
2020-08-17 12:19:21 +02:00
|
|
|
return pub_bytes
|
2019-05-06 12:13:04 +02:00
|
|
|
|
|
|
|
|
2020-08-11 13:25:55 +02:00
|
|
|
def get_priv_pubkey(priv):
|
2019-05-06 12:13:04 +02:00
|
|
|
'''
|
2020-09-10 11:15:26 +02:00
|
|
|
Get the pubkey of a private key
|
2019-05-06 12:13:04 +02:00
|
|
|
'''
|
|
|
|
|
2020-08-11 13:25:55 +02:00
|
|
|
priv_crypto = priv.to_cryptography_key()
|
2019-05-06 12:13:04 +02:00
|
|
|
|
2020-08-17 12:19:21 +02:00
|
|
|
pubkey = priv_crypto.public_key()
|
|
|
|
pub_bytes = pubkey.public_bytes(
|
|
|
|
serialization.Encoding.PEM,
|
|
|
|
serialization.PublicFormat.SubjectPublicKeyInfo)
|
2019-05-06 12:13:04 +02:00
|
|
|
|
2020-08-17 12:19:21 +02:00
|
|
|
return pub_bytes
|
2019-05-06 12:13:04 +02:00
|
|
|
|
|
|
|
|
|
|
|
def match_cert_privkey(cert, priv):
|
|
|
|
'''
|
2020-03-20 11:27:41 +01:00
|
|
|
Copied from https://stackoverflow.com/questions/19922790/how-to-check-for-python-the-key-associated-with-the-certificate-or-not # noqa pylint: disable=line-too-long
|
2019-05-06 12:13:04 +02:00
|
|
|
and reworked
|
|
|
|
'''
|
|
|
|
|
2020-08-11 13:25:55 +02:00
|
|
|
return get_cert_pubkey(cert) == get_priv_pubkey(priv)
|
2019-05-06 12:13:04 +02:00
|
|
|
|
|
|
|
|
2020-03-30 11:16:07 +02:00
|
|
|
def find_root(x509_objects, root_issuers):
|
|
|
|
'''
|
|
|
|
Find a suitable anchor by finding the intermediate that was signed by root
|
|
|
|
'''
|
|
|
|
root_cert = root_issuers[str(x509_objects[-1].get_issuer())]
|
|
|
|
logging.debug('Retrieved root certificate %s', root_cert.get_subject())
|
|
|
|
return root_cert
|
|
|
|
|
2020-08-17 12:19:21 +02:00
|
|
|
|
2019-05-06 12:13:04 +02:00
|
|
|
def find_intermediate_root(x509_objects, root_issuers):
|
|
|
|
'''
|
|
|
|
Find a suitable anchor by finding the intermediate that was signed by root
|
|
|
|
'''
|
|
|
|
|
|
|
|
# Some intermediates have the *same* subject as some root certificates.
|
2022-07-27 11:19:35 +02:00
|
|
|
# blacklist them if their issuer and subject name is present in the root
|
|
|
|
# bundle
|
2019-05-06 12:13:04 +02:00
|
|
|
excluded_issuers = [str(x.get_subject()) for x in x509_objects
|
2022-07-27 11:19:35 +02:00
|
|
|
if x.get_subject() != x.get_issuer()
|
|
|
|
and str(x.get_issuer()) in root_issuers
|
|
|
|
and str(x.get_subject()) in root_issuers]
|
2019-05-06 12:13:04 +02:00
|
|
|
|
|
|
|
logging.debug('Known root issuers\n\t%s', '\n\t'.join(root_issuers))
|
|
|
|
logging.debug('Excluding issuers because of potential intermediates\n\t%s',
|
|
|
|
'\n\t'.join(excluded_issuers))
|
2022-07-27 11:21:04 +02:00
|
|
|
logging.debug('Certificates seen in data\n\t%s',
|
|
|
|
'\n\t'.join([f'Subject: {x.get_subject()},'
|
|
|
|
f' Issuer: {x.get_issuer()}'
|
|
|
|
for x in x509_objects]))
|
|
|
|
|
2019-05-06 12:13:04 +02:00
|
|
|
return [x for x in x509_objects
|
|
|
|
if str(x.get_issuer()) in root_issuers
|
|
|
|
and str(x.get_issuer()) not in excluded_issuers]
|
|
|
|
|
|
|
|
|
|
|
|
def order_x509(x509_objects, root_issuers):
|
|
|
|
'''
|
|
|
|
order x509 objects to ensure proper chain order
|
|
|
|
'''
|
|
|
|
bundle = []
|
|
|
|
root_crt = [x for x in x509_objects if x.get_subject() == x.get_issuer()]
|
|
|
|
if root_crt:
|
|
|
|
root_crt = x509_objects.pop(x509_objects.index(root_crt[0]))
|
2020-03-30 11:16:07 +02:00
|
|
|
logging.warning('Found self signed (root) certificate %s in input',
|
2019-08-20 12:09:45 +02:00
|
|
|
str(root_crt.get_subject()))
|
|
|
|
# Double check if our self signed root certificate is not also present
|
|
|
|
# as an intermediate:
|
|
|
|
# - It is probably invalid input, and doesn't make sense
|
|
|
|
# - It confuses the ordering process
|
|
|
|
if next((x for x in x509_objects
|
|
|
|
if x.get_subject() != x.get_issuer()
|
|
|
|
and x.get_subject() == root_crt.get_subject()), None):
|
2020-03-20 14:26:38 +01:00
|
|
|
raise CertificateComponentException('Both present as intermediate '
|
|
|
|
'and root certificate: %s' %
|
|
|
|
str(root_crt.get_subject()))
|
2019-05-06 12:13:04 +02:00
|
|
|
else:
|
|
|
|
# Get intermediate cert signed by any root from bundle as anchor, and
|
|
|
|
# make that our root
|
|
|
|
logging.debug('No root certificate in input,'
|
|
|
|
' obtain intermediate through known root issuers')
|
|
|
|
root_crt = find_intermediate_root(x509_objects, root_issuers)
|
|
|
|
logging.debug('intermediates seen in data signed by root \n\t%s',
|
|
|
|
'\n\t'.join([str(x.get_issuer()) for x in root_crt]))
|
|
|
|
|
|
|
|
if root_crt and len(root_crt) == 1:
|
|
|
|
logging.debug('Found subject=%s,issuer=%s',
|
|
|
|
root_crt[0].get_subject(), root_crt[0].get_issuer())
|
|
|
|
root_crt = x509_objects.pop(x509_objects.index(root_crt[0]))
|
|
|
|
else:
|
2020-03-20 14:26:38 +01:00
|
|
|
raise CertificateComponentException('No intermediate found')
|
2019-05-06 12:13:04 +02:00
|
|
|
|
|
|
|
# Insert our anchor.
|
|
|
|
bundle.insert(0, root_crt)
|
|
|
|
|
|
|
|
# now work our way up by going through the list,
|
|
|
|
# inserting the certificate where the issuer matches the current topmost
|
|
|
|
# subject until we are empty
|
|
|
|
while x509_objects:
|
|
|
|
sibling = [x for x in x509_objects
|
|
|
|
if x.get_issuer() == bundle[0].get_subject()]
|
|
|
|
if sibling and len(sibling) == 1:
|
|
|
|
# insert sibling at beginning of list
|
|
|
|
bundle.insert(0, x509_objects.pop(x509_objects.index(sibling[0])))
|
|
|
|
else:
|
|
|
|
# Lets complain
|
2022-07-27 11:51:58 +02:00
|
|
|
logging.error('Certificates remaining data\n\t%s',
|
|
|
|
'\n\t'.join([f'Subject: {x.get_subject()},'
|
|
|
|
f' Issuer: {x.get_issuer()}'
|
|
|
|
for x in x509_objects]))
|
|
|
|
logging.error('Certificates placed in bundle \n\t%s',
|
|
|
|
'\n\t'.join([f'Subject: {x.get_subject()},'
|
|
|
|
f' Issuer: {x.get_issuer()}'
|
|
|
|
for x in bundle]))
|
2020-08-17 12:19:21 +02:00
|
|
|
raise CertificateComponentException('Non matching certificates in '
|
|
|
|
'input:'
|
2020-03-20 14:26:38 +01:00
|
|
|
' No sibling found for %s'
|
|
|
|
% bundle[0].get_subject())
|
2019-05-06 12:13:04 +02:00
|
|
|
return bundle
|
|
|
|
|
2020-08-17 12:19:21 +02:00
|
|
|
|
2020-03-20 17:34:04 +01:00
|
|
|
def load_root_issuers():
|
|
|
|
'''
|
2020-09-10 11:15:26 +02:00
|
|
|
Return the list of CA roots
|
2020-03-20 17:34:04 +01:00
|
|
|
'''
|
|
|
|
root_issuers = None
|
|
|
|
|
|
|
|
mozrootbundle_location = certifi.core.where()
|
|
|
|
|
|
|
|
with open(mozrootbundle_location, 'r') as fname_fh:
|
|
|
|
logging.info('Using %s for root ca bundle', mozrootbundle_location)
|
|
|
|
data = fname_fh.read()
|
|
|
|
matches = re.finditer(r'(-----BEGIN CERTIFICATE-----'
|
|
|
|
'.*?'
|
|
|
|
'-----END CERTIFICATE-----)',
|
|
|
|
data, re.DOTALL)
|
|
|
|
root_certs = [crypto.load_certificate(crypto.FILETYPE_PEM,
|
|
|
|
match.group(1))
|
|
|
|
for match in matches]
|
|
|
|
|
|
|
|
logging.debug('Loaded root certificates from bundle')
|
|
|
|
|
|
|
|
for root_cert in root_certs:
|
|
|
|
try:
|
|
|
|
logging.debug('subject=%s\n\tissuer%s\n\t'
|
2020-08-11 13:25:55 +02:00
|
|
|
'expired=%s\n\tpubkey=%s',
|
2020-03-20 17:34:04 +01:00
|
|
|
root_cert.get_subject(),
|
|
|
|
root_cert.get_issuer(),
|
|
|
|
root_cert.has_expired(),
|
2020-08-11 13:25:55 +02:00
|
|
|
get_cert_pubkey(root_cert))
|
2020-09-10 11:15:26 +02:00
|
|
|
except UnsupportedPkEncryption as unsupported_crypto_exception:
|
|
|
|
logging.debug(unsupported_crypto_exception)
|
2020-03-20 17:34:04 +01:00
|
|
|
continue
|
|
|
|
|
2020-03-30 11:16:07 +02:00
|
|
|
root_issuers = {str(root_cert.get_subject()): root_cert
|
|
|
|
for root_cert in root_certs}
|
2020-03-20 17:34:04 +01:00
|
|
|
return root_issuers
|
|
|
|
|
|
|
|
|
2020-03-30 11:21:24 +02:00
|
|
|
def handle_args():
|
2019-05-06 12:13:04 +02:00
|
|
|
'''
|
2020-03-30 11:21:24 +02:00
|
|
|
Handle tool arguments
|
2019-05-06 12:13:04 +02:00
|
|
|
'''
|
2020-09-10 11:15:26 +02:00
|
|
|
parser = ArgumentParser(description='Reorder X509/Private key data for'
|
2019-05-06 12:13:04 +02:00
|
|
|
' hosting use')
|
|
|
|
|
|
|
|
loggrp = parser.add_mutually_exclusive_group()
|
|
|
|
loggrp.add_argument('-v', '--verbose',
|
|
|
|
action='store_true',
|
|
|
|
help='Show verbose logging')
|
|
|
|
loggrp.add_argument('-d', '--debug',
|
|
|
|
action='store_true',
|
|
|
|
help='Show debug logging')
|
|
|
|
loggrp.add_argument('-q', '--quiet',
|
|
|
|
action='store_true',
|
|
|
|
help='Show only error logging')
|
|
|
|
|
|
|
|
outputgrp = parser.add_mutually_exclusive_group()
|
|
|
|
|
2020-09-09 15:38:54 +02:00
|
|
|
outputgrp.add_argument('-c', '--check',
|
|
|
|
action='store_true',
|
|
|
|
help='Only check, output nothing')
|
2020-08-17 12:19:21 +02:00
|
|
|
outputgrp.add_argument('--just-certificate',
|
|
|
|
dest='print_cert',
|
|
|
|
action='store_true',
|
|
|
|
help='Just print certificate')
|
|
|
|
outputgrp.add_argument('--no-certificate',
|
|
|
|
dest='print_cert',
|
2019-05-06 12:13:04 +02:00
|
|
|
action='store_false',
|
|
|
|
help='Omit certificate from output')
|
|
|
|
outputgrp.set_defaults(print_cert=True)
|
|
|
|
|
2020-08-17 12:19:21 +02:00
|
|
|
outputgrp.add_argument('--just-chain',
|
|
|
|
dest='print_chain',
|
|
|
|
action='store_true',
|
|
|
|
help='Just print chain')
|
|
|
|
outputgrp.add_argument('--no-chain',
|
|
|
|
dest='print_chain',
|
|
|
|
action='store_false',
|
|
|
|
help='Omit chain from output')
|
|
|
|
outputgrp.add_argument('--include-root',
|
|
|
|
dest='include_root',
|
|
|
|
action='store_true',
|
|
|
|
help='Also include the root certificate')
|
2019-05-06 12:13:04 +02:00
|
|
|
outputgrp.set_defaults(print_chain=True)
|
|
|
|
|
2020-08-17 12:19:21 +02:00
|
|
|
outputgrp.add_argument('--key',
|
|
|
|
dest='print_key',
|
2019-05-06 12:13:04 +02:00
|
|
|
action='store_true', default=True,
|
|
|
|
help='Just print key')
|
2020-08-17 12:19:21 +02:00
|
|
|
outputgrp.add_argument('--no-key',
|
|
|
|
dest='print_key',
|
|
|
|
action='store_false',
|
|
|
|
help='Omit key from output')
|
2019-05-06 12:13:04 +02:00
|
|
|
outputgrp.set_defaults(print_key=True)
|
|
|
|
|
2020-09-09 15:39:45 +02:00
|
|
|
parser.add_argument('-i', '--informational',
|
|
|
|
action='store_true',
|
|
|
|
help='Show some information about the PEM blocks')
|
|
|
|
|
2020-08-17 12:19:21 +02:00
|
|
|
parser.add_argument('x509files',
|
|
|
|
metavar='x509 file',
|
|
|
|
nargs='*',
|
2019-05-06 12:13:04 +02:00
|
|
|
help='x509 fullchain (+ rsa privkey)'
|
|
|
|
' bundles to be checked')
|
|
|
|
|
2020-03-30 11:21:24 +02:00
|
|
|
return parser.parse_args()
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
'''
|
|
|
|
main program start and argument parsing
|
|
|
|
'''
|
|
|
|
|
2019-05-06 12:13:04 +02:00
|
|
|
root_issuers = None
|
|
|
|
|
2020-03-30 11:21:24 +02:00
|
|
|
args = handle_args()
|
|
|
|
|
2020-09-09 15:31:31 +02:00
|
|
|
if args.verbose or args.check:
|
2019-05-06 12:13:04 +02:00
|
|
|
logging.basicConfig(level=logging.INFO)
|
|
|
|
elif args.debug:
|
|
|
|
logging.basicConfig(level=logging.DEBUG)
|
|
|
|
elif args.quiet:
|
|
|
|
logging.basicConfig(level=logging.ERROR)
|
|
|
|
else:
|
|
|
|
logging.basicConfig(level=logging.WARNING)
|
|
|
|
|
2020-03-20 17:34:04 +01:00
|
|
|
root_issuers = load_root_issuers()
|
2019-05-06 12:13:04 +02:00
|
|
|
|
|
|
|
for fname, data in list(load_data(args.x509files).items()):
|
|
|
|
logging.debug('Processing %s', fname)
|
|
|
|
x509_objects_components = None
|
|
|
|
x509matches = re.finditer(r'(-----BEGIN CERTIFICATE-----'
|
|
|
|
'.*?'
|
|
|
|
'-----END CERTIFICATE-----)',
|
|
|
|
data, re.DOTALL)
|
|
|
|
|
|
|
|
rsamatches = re.finditer(r'(-----BEGIN RSA PRIVATE KEY-----'
|
|
|
|
'.*?'
|
|
|
|
'-----END RSA PRIVATE KEY-----)',
|
|
|
|
data, re.DOTALL)
|
|
|
|
|
|
|
|
pkmatches = re.finditer(r'(-----BEGIN PRIVATE KEY-----'
|
|
|
|
'.*?'
|
|
|
|
'-----END PRIVATE KEY-----)',
|
|
|
|
data, re.DOTALL)
|
|
|
|
|
|
|
|
x509_objects = [crypto.load_certificate(crypto.FILETYPE_PEM,
|
|
|
|
x509match.group(1))
|
|
|
|
for x509match in x509matches]
|
|
|
|
|
|
|
|
rsa_objects = [crypto.load_privatekey(crypto.FILETYPE_PEM,
|
|
|
|
rsamatch.group(1))
|
|
|
|
for rsamatch in rsamatches]
|
|
|
|
|
|
|
|
pk_objects = [crypto.load_privatekey(crypto.FILETYPE_PEM,
|
|
|
|
pkmatch.group(1))
|
|
|
|
for pkmatch in pkmatches]
|
|
|
|
|
|
|
|
x509_objects = order_x509(x509_objects, root_issuers)
|
|
|
|
x509_objects_components = x509_objects[0].get_subject().\
|
|
|
|
get_components()
|
|
|
|
|
|
|
|
if len(rsa_objects) > 1:
|
2020-08-17 12:19:21 +02:00
|
|
|
raise CertificateComponentException('More than one RSA private key'
|
|
|
|
' found in input.'
|
2020-03-20 14:26:38 +01:00
|
|
|
' Aborting')
|
2019-05-06 12:13:04 +02:00
|
|
|
elif rsa_objects:
|
|
|
|
if not match_cert_privkey(x509_objects[0], rsa_objects[0]):
|
2020-03-20 14:26:38 +01:00
|
|
|
raise CertificateComponentException('Provided certificate'
|
2020-08-17 12:19:21 +02:00
|
|
|
' and RSA private key'
|
|
|
|
' do not match')
|
2019-05-06 12:13:04 +02:00
|
|
|
else:
|
2020-08-11 13:25:55 +02:00
|
|
|
logging.info('OK: Public key of provided certificate'
|
2019-05-06 12:13:04 +02:00
|
|
|
' and RSA private key match')
|
|
|
|
elif len(pk_objects) > 1:
|
2020-09-10 11:15:26 +02:00
|
|
|
raise CertificateComponentException('More than one private key'
|
2020-08-17 12:19:21 +02:00
|
|
|
' found in input.'
|
2020-03-20 14:26:38 +01:00
|
|
|
' Aborting')
|
2019-05-06 12:13:04 +02:00
|
|
|
elif pk_objects:
|
|
|
|
if not match_cert_privkey(x509_objects[0], pk_objects[0]):
|
2020-03-20 14:26:38 +01:00
|
|
|
raise CertificateComponentException('Provided certificate'
|
2020-08-17 12:19:21 +02:00
|
|
|
' and private key'
|
|
|
|
' do not match')
|
2019-05-06 12:13:04 +02:00
|
|
|
else:
|
2020-08-11 13:25:55 +02:00
|
|
|
logging.info('OK: Public key of provided certificate'
|
2019-05-06 12:13:04 +02:00
|
|
|
' and private key match')
|
|
|
|
|
2020-03-30 11:16:07 +02:00
|
|
|
if args.include_root:
|
|
|
|
logging.debug('root certificate in output requested')
|
|
|
|
x509_objects.append(find_root(x509_objects, root_issuers))
|
|
|
|
|
2019-05-06 12:13:04 +02:00
|
|
|
logging.debug("Print certificates in order")
|
2020-03-20 11:27:41 +01:00
|
|
|
# Need to do b'CN' to have this python3 compatible
|
|
|
|
logging.info('Writing bundle for Subject: %s',
|
|
|
|
[x[1].decode('utf-8')
|
|
|
|
for x in x509_objects_components
|
|
|
|
if x[0] == b'CN'][0])
|
2019-05-06 12:13:04 +02:00
|
|
|
|
|
|
|
for x509_object in [x for x in x509_objects
|
2020-03-30 11:16:07 +02:00
|
|
|
if x.get_subject() != x.get_issuer()
|
|
|
|
or args.include_root]:
|
2019-08-20 14:20:46 +02:00
|
|
|
|
|
|
|
# Stringify subject like openssl x509 -subject
|
2020-03-20 11:27:41 +01:00
|
|
|
x509_subject = \
|
|
|
|
'/'.join(['{0}={1}'.format(component[0].decode(),
|
|
|
|
component[1].decode())
|
|
|
|
for component in
|
|
|
|
x509_object.get_subject().get_components()])
|
2019-08-20 14:20:46 +02:00
|
|
|
|
|
|
|
# Stringify issuer like openssl x509 -issuer
|
2020-03-20 11:27:41 +01:00
|
|
|
x509_issuer = \
|
|
|
|
'/'.join(['{0}={1}'.format(component[0].decode(),
|
|
|
|
component[1].decode())
|
|
|
|
for component in
|
|
|
|
x509_object.get_issuer().get_components()])
|
2019-08-20 14:20:46 +02:00
|
|
|
|
2020-03-20 11:27:41 +01:00
|
|
|
x509_not_after = \
|
|
|
|
datetime.strptime(str(x509_object.get_notAfter()),
|
|
|
|
ASN1TIME_FMT)
|
2019-08-20 14:20:46 +02:00
|
|
|
|
2020-03-20 11:27:41 +01:00
|
|
|
x509_not_before = \
|
|
|
|
datetime.strptime(str(x509_object.get_notBefore()),
|
|
|
|
ASN1TIME_FMT)
|
2019-08-20 14:20:46 +02:00
|
|
|
|
|
|
|
logging.info('Subject: %s', x509_subject)
|
|
|
|
logging.info('Issuer: %s', x509_issuer)
|
|
|
|
|
2020-09-09 15:39:45 +02:00
|
|
|
if args.informational:
|
|
|
|
print(CERTINFO_TEMPLATE.format(
|
|
|
|
subject=x509_subject,
|
|
|
|
issuer=x509_issuer,
|
|
|
|
notbefore=x509_not_before.strftime(OPENSSLTIME_FMT),
|
|
|
|
notafter=x509_not_after.strftime(OPENSSLTIME_FMT),
|
|
|
|
sha1fingerprint=x509_object.digest('sha1').decode()))
|
2019-08-20 14:20:46 +02:00
|
|
|
|
2020-09-09 15:31:31 +02:00
|
|
|
if not args.check:
|
|
|
|
print(crypto.dump_certificate(crypto.FILETYPE_PEM,
|
|
|
|
x509_object).decode('ascii'),
|
|
|
|
end='')
|
2019-05-06 12:13:04 +02:00
|
|
|
|
|
|
|
if rsa_objects:
|
2020-09-09 15:31:31 +02:00
|
|
|
if not args.check:
|
|
|
|
logging.info('Print RSA private keys')
|
|
|
|
for rsa_object in rsa_objects:
|
2020-09-10 16:43:32 +02:00
|
|
|
if args.informational:
|
|
|
|
print(PkDecoratorFactory.create(rsa_object))
|
2020-09-09 15:31:31 +02:00
|
|
|
print(rsa_object.to_cryptography_key().private_bytes(
|
|
|
|
encoding=serialization.Encoding.PEM,
|
|
|
|
format=serialization.PrivateFormat.TraditionalOpenSSL,
|
|
|
|
encryption_algorithm=serialization.NoEncryption()).decode(
|
|
|
|
'ascii'),
|
|
|
|
end='')
|
2019-05-06 12:13:04 +02:00
|
|
|
elif pk_objects:
|
2020-09-09 15:31:31 +02:00
|
|
|
if not args.check:
|
|
|
|
logging.info('Print private keys')
|
|
|
|
for pk_object in pk_objects:
|
2020-09-10 16:43:32 +02:00
|
|
|
if args.informational:
|
|
|
|
print(PkDecoratorFactory.create(pk_object))
|
2020-09-09 15:31:31 +02:00
|
|
|
print(crypto.dump_privatekey(crypto.FILETYPE_PEM,
|
|
|
|
pk_object).decode('ascii'),
|
|
|
|
end='')
|
2019-05-06 12:13:04 +02:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
2020-04-06 12:31:42 +02:00
|
|
|
try:
|
|
|
|
exit(main())
|
|
|
|
except CertificateComponentException as certcomponent_error:
|
|
|
|
logging.error(certcomponent_error)
|
|
|
|
exit(1)
|