321 lines
10 KiB
Python
321 lines
10 KiB
Python
|
#!/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
|