added domain to description, fixed the "convert" command
This commit is contained in:
parent
ac951f1b30
commit
99920111ff
2 changed files with 67 additions and 36 deletions
|
@ -108,6 +108,7 @@ CROOTCA_TEMPLATE = """\
|
||||||
# - cert_label: The certificate's name field (Usually CN, in the subject)
|
# - cert_label: The certificate's name field (Usually CN, in the subject)
|
||||||
# - cert_issue: The certificate's issuer string
|
# - cert_issue: The certificate's issuer string
|
||||||
# - cert_subject: The certificate's subject string
|
# - cert_subject: The certificate's subject string
|
||||||
|
# - cert_domain: The domains polled by this tool that returned this certificate
|
||||||
CCERT_DESC_TEMPLATE = """\
|
CCERT_DESC_TEMPLATE = """\
|
||||||
* Index: {cert_num}
|
* Index: {cert_num}
|
||||||
* Label: {cert_label}
|
* Label: {cert_label}
|
||||||
|
@ -171,18 +172,6 @@ def get_server_root_cert(address, port, certDict):
|
||||||
return None
|
return None
|
||||||
return certDict[cn_hash]
|
return certDict[cn_hash]
|
||||||
|
|
||||||
def filter_duplicate_x509(certs):
|
|
||||||
serial_numbers = set()
|
|
||||||
out_certs = list()
|
|
||||||
# filter duplicate certs
|
|
||||||
for cert in certs:
|
|
||||||
# Skip duplicate certs where required.
|
|
||||||
if cert.get_serial_number() in serial_numbers:
|
|
||||||
continue
|
|
||||||
out_certs.append(cert)
|
|
||||||
serial_numbers.add(cert.get_serial_number())
|
|
||||||
return out_certs
|
|
||||||
|
|
||||||
def bytes_to_c_data(mah_bytes, length=None):
|
def bytes_to_c_data(mah_bytes, length=None):
|
||||||
"""Converts a byte array to a CSV C array data format, with endlines!
|
"""Converts a byte array to a CSV C array data format, with endlines!
|
||||||
e.g: 0x12, 0xA4, etc.
|
e.g: 0x12, 0xA4, etc.
|
||||||
|
@ -199,7 +188,13 @@ def bytes_to_c_data(mah_bytes, length=None):
|
||||||
# join, wrap, and return
|
# join, wrap, and return
|
||||||
return textwrap.fill(''.join(ret), width=6*12 + 5, initial_indent=' ', subsequent_indent=' ', break_long_words=False)
|
return textwrap.fill(''.join(ret), width=6*12 + 5, initial_indent=' ', subsequent_indent=' ', break_long_words=False)
|
||||||
|
|
||||||
def decribe_cert_object(cert, cert_num):
|
def decribe_cert_object(cert, cert_num, domain=None):
|
||||||
|
"""
|
||||||
|
Formats a string describing a certificate object, including the domain
|
||||||
|
being used and the index in the trust anchor array. Cert should be a
|
||||||
|
x509 object, domain should be a string name, and cert_num should be
|
||||||
|
an integer.
|
||||||
|
"""
|
||||||
# get the label from the subject feild on the certificate
|
# get the label from the subject feild on the certificate
|
||||||
label = ""
|
label = ""
|
||||||
com = dict(cert.get_subject().get_components())
|
com = dict(cert.get_subject().get_components())
|
||||||
|
@ -211,16 +206,20 @@ def decribe_cert_object(cert, cert_num):
|
||||||
label = com[b'O'].decode("utf-8")
|
label = com[b'O'].decode("utf-8")
|
||||||
# return the formated string
|
# return the formated string
|
||||||
crypto = cert.to_cryptography()
|
crypto = cert.to_cryptography()
|
||||||
return CCERT_DESC_TEMPLATE.format(
|
out_str = CCERT_DESC_TEMPLATE.format(
|
||||||
cert_num=cert_num,
|
cert_num=cert_num,
|
||||||
cert_label=label,
|
cert_label=label,
|
||||||
cert_subject=crypto.subject.rfc4514_string(),
|
cert_subject=crypto.subject.rfc4514_string(),
|
||||||
)
|
)
|
||||||
|
# if domain, then add domain entry
|
||||||
|
if domain is not None:
|
||||||
|
out_str += "\n * Domain(s): " + domain
|
||||||
|
return out_str
|
||||||
|
|
||||||
|
def x509_to_header(x509Certs, cert_var, cert_length_var, output_file, keep_dupes, domains=None):
|
||||||
def x509_to_header(x509Certs, cert_var, cert_length_var, output_file, keep_dupes):
|
|
||||||
"""Combine a collection of PEM format certificates into a single C header with the
|
"""Combine a collection of PEM format certificates into a single C header with the
|
||||||
combined cert data in BearSSL format. x509Certs should be a list of pyOpenSSL x590 objects,
|
combined cert data in BearSSL format. x509Certs should be a list of pyOpenSSL x590 objects,
|
||||||
|
domains should be a list of respective domain strings (in same order as x509Certs),
|
||||||
cert_var controls the name of the cert data variable in the output header, cert_length_var
|
cert_var controls the name of the cert data variable in the output header, cert_length_var
|
||||||
controls the name of the cert data length variable/define, output is the output file
|
controls the name of the cert data length variable/define, output is the output file
|
||||||
(which must be open for writing). Keep_dupes is a boolean to indicate if duplicate
|
(which must be open for writing). Keep_dupes is a boolean to indicate if duplicate
|
||||||
|
@ -228,24 +227,41 @@ def x509_to_header(x509Certs, cert_var, cert_length_var, output_file, keep_dupes
|
||||||
"""
|
"""
|
||||||
cert_description = ''
|
cert_description = ''
|
||||||
certs = x509Certs
|
certs = x509Certs
|
||||||
if not keep_dupes:
|
|
||||||
certs = filter_duplicate_x509(x509Certs)
|
|
||||||
# Save cert data as a C style header.
|
# Save cert data as a C style header.
|
||||||
# start by building each component
|
# start by building each component
|
||||||
cert_data = ""
|
cert_data = ""
|
||||||
|
# hold an array of static array strings (TA_RSA_N)
|
||||||
static_arrays = list()
|
static_arrays = list()
|
||||||
|
# same with CA entries
|
||||||
CAs = list()
|
CAs = list()
|
||||||
|
# descriptions
|
||||||
cert_desc = list()
|
cert_desc = list()
|
||||||
|
# track the serial numbers so we can find duplicates
|
||||||
|
cert_ser = list()
|
||||||
for i, cert in enumerate(certs):
|
for i, cert in enumerate(certs):
|
||||||
|
# calculate the index shifted from duplicates (if any)
|
||||||
|
cert_index = len(CAs)
|
||||||
|
# deduplicate certificates
|
||||||
|
if not keep_dupes and cert.get_serial_number() in cert_ser:
|
||||||
|
# append the domain we used it for into the cert description
|
||||||
|
if domains is not None:
|
||||||
|
cert_desc[cert_ser.index(cert.get_serial_number())] += ", " + domains[i]
|
||||||
|
# we don't need to generate stuff for this certificate
|
||||||
|
continue
|
||||||
|
# record the serial number for later
|
||||||
|
cert_ser.append(cert.get_serial_number())
|
||||||
# add a description of the certificate to the array
|
# add a description of the certificate to the array
|
||||||
cert_desc.append(decribe_cert_object(cert, i))
|
if domains is None:
|
||||||
|
cert_desc.append(decribe_cert_object(cert, cert_index))
|
||||||
|
else:
|
||||||
|
cert_desc.append(decribe_cert_object(cert, cert_index, domain=domains[i]))
|
||||||
# build static arrays containing all the keys of the certificate
|
# build static arrays containing all the keys of the certificate
|
||||||
# start with distinguished name
|
# start with distinguished name
|
||||||
# get the distinguished name in bytes
|
# get the distinguished name in bytes
|
||||||
dn_bytes_str = bytes_to_c_data(cert.get_subject().der())
|
dn_bytes_str = bytes_to_c_data(cert.get_subject().der())
|
||||||
static_arrays.append(CRAY_TEMPLATE.format(
|
static_arrays.append(CRAY_TEMPLATE.format(
|
||||||
ray_type="unsigned char",
|
ray_type="unsigned char",
|
||||||
ray_name=DN_PRE + str(i),
|
ray_name=DN_PRE + str(cert_index),
|
||||||
ray_data=dn_bytes_str))
|
ray_data=dn_bytes_str))
|
||||||
# next, the RSA public numbers
|
# next, the RSA public numbers
|
||||||
pubkey = cert.get_pubkey()
|
pubkey = cert.get_pubkey()
|
||||||
|
@ -254,19 +270,19 @@ def x509_to_header(x509Certs, cert_var, cert_length_var, output_file, keep_dupes
|
||||||
n_bytes_str = bytes_to_c_data(numbers.n.to_bytes(pubkey.bits() // 8, byteorder="big"))
|
n_bytes_str = bytes_to_c_data(numbers.n.to_bytes(pubkey.bits() // 8, byteorder="big"))
|
||||||
static_arrays.append(CRAY_TEMPLATE.format(
|
static_arrays.append(CRAY_TEMPLATE.format(
|
||||||
ray_type="unsigned char",
|
ray_type="unsigned char",
|
||||||
ray_name=RSA_N_PRE + str(i),
|
ray_name=RSA_N_PRE + str(cert_index),
|
||||||
ray_data=n_bytes_str))
|
ray_data=n_bytes_str))
|
||||||
# and then the exponent
|
# and then the exponent
|
||||||
e_bytes_str = bytes_to_c_data(numbers.e.to_bytes(math.ceil(numbers.e.bit_length() / 8), byteorder="big"))
|
e_bytes_str = bytes_to_c_data(numbers.e.to_bytes(math.ceil(numbers.e.bit_length() / 8), byteorder="big"))
|
||||||
static_arrays.append(CRAY_TEMPLATE.format(
|
static_arrays.append(CRAY_TEMPLATE.format(
|
||||||
ray_type="unsigned char",
|
ray_type="unsigned char",
|
||||||
ray_name=RSA_E_PRE + str(i),
|
ray_name=RSA_E_PRE + str(cert_index),
|
||||||
ray_data=e_bytes_str))
|
ray_data=e_bytes_str))
|
||||||
# format the root certificate entry
|
# format the root certificate entry
|
||||||
CAs.append(CROOTCA_TEMPLATE.format(
|
CAs.append(CROOTCA_TEMPLATE.format(
|
||||||
ta_dn_name=DN_PRE + str(i),
|
ta_dn_name=DN_PRE + str(cert_index),
|
||||||
rsa_number_name=RSA_N_PRE + str(i),
|
rsa_number_name=RSA_N_PRE + str(cert_index),
|
||||||
rsa_exp_name=RSA_E_PRE + str(i)))
|
rsa_exp_name=RSA_E_PRE + str(cert_index)))
|
||||||
# concatonate it all into the big header file template
|
# concatonate it all into the big header file template
|
||||||
# cert descriptions
|
# cert descriptions
|
||||||
cert_desc_out = '\n * \n'.join(cert_desc)
|
cert_desc_out = '\n * \n'.join(cert_desc)
|
||||||
|
@ -281,6 +297,6 @@ def x509_to_header(x509Certs, cert_var, cert_length_var, output_file, keep_dupes
|
||||||
guard_name=os.path.splitext(output_file.name)[0].upper(),
|
guard_name=os.path.splitext(output_file.name)[0].upper(),
|
||||||
cert_description=cert_desc_out,
|
cert_description=cert_desc_out,
|
||||||
cert_length_var=cert_length_var,
|
cert_length_var=cert_length_var,
|
||||||
cert_length=str(len(certs)),
|
cert_length=str(len(CAs)),
|
||||||
cert_data=cert_data_out,
|
cert_data=cert_data_out,
|
||||||
))
|
))
|
|
@ -18,6 +18,7 @@
|
||||||
import cert_util
|
import cert_util
|
||||||
import click
|
import click
|
||||||
import certifi
|
import certifi
|
||||||
|
from OpenSSL import crypto
|
||||||
|
|
||||||
# Default name for the cert length varible
|
# Default name for the cert length varible
|
||||||
CERT_LENGTH_NAME = "TAs_NUM"
|
CERT_LENGTH_NAME = "TAs_NUM"
|
||||||
|
@ -82,7 +83,7 @@ def download(port, cert_var, cert_length_var, output, use_store, keep_dupes, dom
|
||||||
# append cert to array
|
# append cert to array
|
||||||
down_certs.append(cert)
|
down_certs.append(cert)
|
||||||
# Combine PEMs and write output header.
|
# Combine PEMs and write output header.
|
||||||
cert_util.x509_to_header(down_certs, cert_var, cert_length_var, output, keep_dupes)
|
cert_util.x509_to_header(down_certs, cert_var, cert_length_var, output, keep_dupes, domains=domain)
|
||||||
|
|
||||||
|
|
||||||
@pycert_bearssl.command(short_help='Convert PEM certs into a C header.')
|
@pycert_bearssl.command(short_help='Convert PEM certs into a C header.')
|
||||||
|
@ -92,33 +93,47 @@ def download(port, cert_var, cert_length_var, output, use_store, keep_dupes, dom
|
||||||
help='name of the define in the header which will contain the length of the certificate data (default: {0})'.format(CERT_LENGTH_NAME))
|
help='name of the define in the header which will contain the length of the certificate data (default: {0})'.format(CERT_LENGTH_NAME))
|
||||||
@click.option('--output', '-o', type=click.File('w'), default='certificates.h',
|
@click.option('--output', '-o', type=click.File('w'), default='certificates.h',
|
||||||
help='name of the output file (default: certificates.h)')
|
help='name of the output file (default: certificates.h)')
|
||||||
@click.option('--full-chain', '-f', is_flag=True, default=False,
|
@click.option('--use-store', '-s', type=click.File('r'), default=certifi.where(),
|
||||||
help='use the full certificate chain and not just the root/last cert (default: false, root cert only)')
|
help='the location of the .pem file containing a list of trusted root certificates (default: use certifi.where())')
|
||||||
@click.option('--keep-dupes', '-d', is_flag=True, default=False,
|
@click.option('--keep-dupes', '-d', is_flag=True, default=False,
|
||||||
help='write all certs including any duplicates (default: remove duplicates)')
|
help='write all certs including any duplicates (default: remove duplicates)')
|
||||||
@click.argument('cert', type=click.File('r'), nargs=-1)
|
@click.argument('cert', type=click.File('r'), nargs=-1)
|
||||||
def convert(cert_var, cert_length_var, output, full_chain, keep_dupes, cert):
|
def convert(cert_var, cert_length_var, output, use_store, keep_dupes, cert):
|
||||||
"""Convert PEM certificates into a C header that can be imported into a
|
"""Convert PEM certificates into a C header that can be imported into a
|
||||||
sketch. Specify each certificate to encode as a separate argument (each
|
sketch. Specify each certificate to encode as a separate argument (each
|
||||||
must be in PEM format) and they will be merged into a single file.
|
must be in PEM format) and they will be merged into a single file.
|
||||||
By default the file 'certificates.h' will be created, however you can change
|
By default the file 'certificates.h' will be created, however you can change
|
||||||
the name of the file with the --output option.
|
the name of the file with the --output option.
|
||||||
If a chain of certificates is found then only the root certificate (i.e.
|
If a chain of certificates is found then only the root certificate (i.e.
|
||||||
the last in the chain) will be saved. However you can override this and
|
the last in the chain) will be saved.
|
||||||
force the full chain to be saved with the --full-chain option.
|
|
||||||
Example of converting a foo.pem certificate into a certificates.h header:
|
Example of converting a foo.pem certificate into a certificates.h header:
|
||||||
pycert convert foo.pem
|
pycert convert foo.pem
|
||||||
Example of converting foo.pem and bar.pem certificates into data.h:
|
Example of converting foo.pem and bar.pem certificates into data.h:
|
||||||
pycert convert foo.pem bar.pem
|
pycert convert foo.pem bar.pem
|
||||||
"""
|
"""
|
||||||
|
# prepare root certificate store
|
||||||
|
cert_obj_store = cert_util.parse_root_certificate_store(use_store)
|
||||||
|
cert_dict = dict([(cert.get_subject().hash(), cert) for cert in cert_obj_store])
|
||||||
# Load all the provided PEM files.
|
# Load all the provided PEM files.
|
||||||
pems = []
|
cert_objs = []
|
||||||
for c in cert:
|
for c in cert:
|
||||||
cert_pem = c.read()
|
cert_pem = c.read()
|
||||||
click.echo('Loaded certificate {0}'.format(c.name))
|
cert_parsed = crypto.load_certificate(crypto.FILETYPE_PEM, cert_pem)
|
||||||
pems.append(cert_pem)
|
if cert_parsed is None:
|
||||||
|
click.echo('Failed to load certificate {0}'.format(c.name))
|
||||||
|
else:
|
||||||
|
click.echo('Loaded certificate {0}'.format(c.name))
|
||||||
|
cert_objs.append(cert_parsed)
|
||||||
|
# find a root certificate for each
|
||||||
|
root_certs = []
|
||||||
|
for i, c in enumerate(cert_objs):
|
||||||
|
cn_hash = c.get_issuer().hash()
|
||||||
|
if cn_hash not in cert_dict:
|
||||||
|
click.echo('Could not find a root certificate for {0}'.format(cert[i].name))
|
||||||
|
else:
|
||||||
|
root_certs.append(cert_dict[cn_hash])
|
||||||
# Combine PEMs and write output header.
|
# Combine PEMs and write output header.
|
||||||
PEM_to_header(pems, cert_var, cert_length_var, output, full_chain, keep_dupes)
|
cert_util.x509_to_header(root_certs, cert_var, cert_length_var, output, keep_dupes)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
Loading…
Reference in a new issue