ca-certificates/mergepem2certdata.py

321 lines
10 KiB
Python
Raw Normal View History

#!/usr/bin/python
# vim:set et sw=4:
#
# certdata2pem.py - splits certdata.txt into multiple files
#
# Copyright (C) 2009 Philipp Kern <pkern@debian.org>
# Copyright (C) 2013 Kai Engert <kaie@redhat.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 2 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, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301,
# USA.
import base64
import os.path
import re
import sys
import textwrap
import subprocess
import getopt
import asn1
from cryptography import x509
from cryptography.hazmat.primitives import hashes
objects = []
pemcerts = []
certdata='./certdata.txt'
pem='./cert.pem'
output='./certdata_out.txt'
trust='CKA_TRUST_CODE_SIGNING'
merge_label="Non-Mozilla Object Signing Only Certificate"
trust_types = {
"CKA_TRUST_SERVER_AUTH",
"CKA_TRUST_EMAIL_PROTECTION",
"CKA_TRUST_CODE_SIGNING"
}
attribute_types = {
"CKA_CLASS" : "CK_OBJECT_CLASS",
"CKA_TOKEN" : "CK_BBOOL",
"CKA_PRIVATE" : "CK_BBOOL",
"CKA_MODIFIABLE" : "CK_BBOOL",
"CKA_LABEL" : "UTF8",
"CKA_CERTIFICATE_TYPE" : "CK_CERTIFICATE_TYPE",
"CKA_SUBJECT" : "MULTILINE_OCTAL",
"CKA_ID" : "UTF8",
"CKA_CERT_SHA1_HASH" : "MULTILINE_OCTAL",
"CKA_CERT_MD5_HASH" : "MULTILINE_OCTAL",
"CKA_ISSUER" : "MULTILINE_OCTAL",
"CKA_SERIAL_NUMBER" : "MULTILINE_OCTAL",
"CKA_VALUE" : "MULTILINE_OCTAL",
"CKA_NSS_MOZILLA_CA_POLICY" : "CK_BBOOL",
"CKA_NSS_SERVER_DISTRUST_AFTER" : "Distrust",
"CKA_NSS_EMAIL_DISTRUST_AFTER" : "Distrust",
"CKA_TRUST_SERVER_AUTH" : "CK_TRUST",
"CKA_TRUST_EMAIL_PROTECTION" : "CK_TRUST",
"CKA_TRUST_CODE_SIGNING" : "CK_TRUST",
"CKA_TRUST_STEP_UP_APPROVED" : "CK_BBOOL"
}
def printable_serial(obj):
return ".".join([str(x) for x in obj['CKA_SERIAL_NUMBER']])
def getSerial(cert):
encoder = asn1.Encoder()
encoder.start()
encoder.write(cert.serial_number)
return encoder.output()
def dumpOctal(f,value):
for i in range(len(value)) :
if i % 16 == 0 :
f.write("\n")
f.write("\\%03o"%int.from_bytes(value[i:i+1],sys.byteorder))
f.write("\nEND\n")
# in python 3.8 this can be replaces with return byteval.hex(':',1)
def formatHex(byteval) :
string=byteval.hex()
string_out=""
for i in range(0,len(string)-2,2) :
string_out += string[i:i+2] + ':'
string_out += string[-2:]
return string_out
try:
opts, args = getopt.getopt(sys.argv[1:],"c:o:p:t:l:",)
except getopt.GetoptError as err:
print(err)
print(sys.argv[0] + ' [-c certdata] [-p pem] [-o certdata_target] [-t trustvalue] [-l merge_label]')
print('-c certdata certdata file to merge to (default="'+certdata+'")');
print('-p pem pem file with CAs to merge from (default="'+pem+'")');
print('-o certdata_target resulting output file (default="'+output+'")');
print('-t trustvalue what these CAs are trusted for (default="'+trust+'")');
print('-l merge_label what label CAs that aren\'t in certdata (default="'+merge_label+'")');
sys.exit(2)
for opt, arg in opts:
if opt == '-c' :
certdata = arg
elif opt == '-p' :
pem = arg
elif opt == '-o' :
output = arg
elif opt == '-t' :
trust = arg
elif opt == '-l' :
merge_label = arg
# read the pem file
in_cert, certvalue = False, ""
for line in open(pem, 'r'):
if not in_cert:
if line.find("BEGIN CERTIFICATE") != -1:
in_cert = True;
continue
# Ignore comment lines and blank lines.
if line.startswith('#'):
continue
if len(line.strip()) == 0:
continue
if line.find("END CERTIFICATE") != -1 :
pemcerts.append(certvalue);
certvalue = "";
in_cert = False;
continue
certvalue += line;
# read the certdata.txt file
in_data, in_multiline, in_obj = False, False, False
field, ftype, value, binval, obj = None, None, None, bytearray(), dict()
header, comment = "", ""
for line in open(certdata, 'r'):
# Ignore the file header.
if not in_data:
header += line
if line.startswith('BEGINDATA'):
in_data = True
continue
# Ignore comment lines.
if line.startswith('#'):
comment += line
continue
# Empty lines are significant if we are inside an object.
if in_obj and len(line.strip()) == 0:
# collect all the inline comments in this object
obj['Comment'] += comment
comment = ""
objects.append(obj)
obj = dict()
in_obj = False
continue
if len(line.strip()) == 0:
continue
if in_multiline:
if not line.startswith('END'):
if ftype == 'MULTILINE_OCTAL':
line = line.strip()
for i in re.finditer(r'\\([0-3][0-7][0-7])', line):
integ = int(i.group(1), 8)
binval.extend((integ).to_bytes(1, sys.byteorder))
obj[field] = binval
else:
value += line
obj[field] = value
continue
in_multiline = False
continue
if line.startswith('CKA_CLASS'):
in_obj = True
obj['Comment'] = comment
comment = ""
line_parts = line.strip().split(' ', 2)
if len(line_parts) > 2:
field, ftype = line_parts[0:2]
value = ' '.join(line_parts[2:])
elif len(line_parts) == 2:
field, ftype = line_parts
value = None
else:
raise NotImplementedError('line_parts < 2 not supported.\n' + line)
if ftype == 'MULTILINE_OCTAL':
in_multiline = True
value = ""
binval = bytearray()
continue
obj[field] = value
if len(list(obj.items())) > 0:
objects.append(obj)
# now merge the results
for certval in pemcerts:
certder = base64.b64decode(certval)
cert = x509.load_der_x509_certificate(certder)
certhashsha1 = cert.fingerprint(hashes.SHA1())
certhashmd5 = cert.fingerprint(hashes.MD5())
try:
label=cert.subject.get_attributes_for_oid(x509.oid.NameOID.COMMON_NAME)[0].value
except:
try:
label=cert.subject.get_attributes_for_oid(x509.oid.NameOID.ORGANIZATION_UNIT_NAME)[0].value
except:
try:
label=cert.subject.get_attributes_for_oid(x509.oid.NameOID.ORGANIZATION_NAME)[0].value
except:
label="Unknown Certificate"
found = False
# see if it exists in certdata.txt
for obj in objects:
# we only need to check the trust objects, because
# that is the object we would modify if it exists
if obj['CKA_CLASS'] != 'CKO_NSS_TRUST':
continue
# explicitly distrusted certs don't have a hash value
if not 'CKA_CERT_SHA1_HASH' in obj:
continue
if obj['CKA_CERT_SHA1_HASH'] != certhashsha1:
continue
obj[trust] = 'CKT_NSS_TRUSTED_DELEGATOR'
found = True
print('Found "'+label+'"');
break
if found :
continue
# append this certificate
obj=dict()
time='%a %b %d %H:%M:%S %Y'
comment = '# ' + merge_label + '\n# %s "'+label+'"\n'
comment += '# Issuer: ' + cert.issuer.rfc4514_string() + '\n'
comment += '# Serial Number:'
sn=cert.serial_number
if sn < 0x100000:
comment += ' %d (0x%x)\n'%(sn,sn)
else:
comment += formatHex(sn.to_bytes((sn.bit_length()+7)//8,"big")) + '\n'
comment += '# Subject: ' + cert.subject.rfc4514_string() + '\n'
comment += '# Not Valid Before: ' + cert.not_valid_before.strftime(time) + '\n'
comment += '# Not Valid After: ' + cert.not_valid_after.strftime(time) + '\n'
comment += '# Fingerprint (MD5): ' + formatHex(certhashmd5) + '\n'
comment += '# Fingerprint (SHA1): ' + formatHex(certhashsha1) + '\n'
obj['Comment']= comment%"Certificate"
obj['CKA_CLASS'] = 'CKO_CERTIFICATE'
obj['CKA_TOKEN'] = 'CK_TRUE'
obj['CKA_PRIVATE'] = 'CK_FALSE'
obj['CKA_MODIFIABLE'] = 'CK_FALSE'
obj['CKA_LABEL'] = '"' + label + '"'
obj['CKA_CERTIFICATE_TYPE'] = 'CKC_X_509'
obj['CKA_SUBJECT'] = cert.subject.public_bytes()
obj['CKA_ID'] = '"0"'
obj['CKA_ISSUER'] = cert.issuer.public_bytes()
obj['CKA_SERIAL_NUMBER'] = getSerial(cert)
obj['CKA_VALUE'] = certder
obj['CKA_NSS_MOZILLA_CA_POLICY'] = 'CK_FALSE'
obj['CKA_NSS_SERVER_DISTRUST_AFTER'] = 'CK_FALSE'
obj['CKA_NSS_EMAIL_DISTRUST_AFTER'] = 'CK_FALSE'
objects.append(obj)
# append the trust values
obj=dict()
obj['Comment']= comment%"Trust for"
obj['CKA_CLASS'] = 'CKO_TRUST'
obj['CKA_TOKEN'] = 'CK_TRUE'
obj['CKA_PRIVATE'] = 'CK_FALSE'
obj['CKA_MODIFIABLE'] = 'CK_FALSE'
obj['CKA_LABEL'] = '"' + label + '"'
obj['CKA_CERT_SHA1_HASH'] = certhashsha1
obj['CKA_CERT_MD5_HASH'] = certhashmd5
obj['CKA_ISSUER'] = cert.issuer.public_bytes()
obj['CKA_SERIAL_NUMBER'] = getSerial(cert)
for t in list(trust_types):
if t == trust:
obj[t] = 'CKT_NSS_TRUSTED_DELEGATOR'
else:
obj[t] = 'CKT_NSS_MUST_VERIFY_TRUST'
obj['CKA_TRUST_STEP_UP_APPROVED'] = 'CK_FALSE'
objects.append(obj)
print('Added "'+label+'"');
# now dump the results
f = open(output, 'w')
f.write(header)
for obj in objects:
if 'Comment' in obj:
f.write(obj['Comment'])
else:
print("Object with no comment!!")
print(obj)
for field in list(attribute_types.keys()):
if not field in obj:
continue
ftype = attribute_types[field];
if ftype == 'Distrust':
if obj[field] == 'CK_FALSE':
ftype = 'CK_BBOOL'
else:
ftype = 'MULTILINE_OCTAL'
f.write("%s %s"%(field,ftype));
if ftype == 'MULTILINE_OCTAL':
dumpOctal(f,obj[field])
else:
f.write(" %s\n"%obj[field])
f.write("\n")
f.close