From 6d164aedd7ed6f6d89778dceaecd29f69f94be55 Mon Sep 17 00:00:00 2001 From: Bob Relyea Date: Mon, 24 May 2021 10:49:58 -0700 Subject: [PATCH] Update tools to pick up code signing certs from the Common CA Database: https://www.ccadb.org/resources Our normal root certs come from mozilla, but mozilla does not evaluate code signing. Currently code signing is only used my Microsoft .net, so we need to get code signing certs from Microsoft's code signing list. The certs in this list will only show up in the code signing lists or in the general list with only code signing set. --- fetch.sh | 11 +- fetch_objsign.sh | 62 +++++++++ mergepem2certdata.py | 320 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 392 insertions(+), 1 deletion(-) create mode 100755 fetch_objsign.sh create mode 100644 mergepem2certdata.py diff --git a/fetch.sh b/fetch.sh index a6b6e7d..3b1af7a 100755 --- a/fetch.sh +++ b/fetch.sh @@ -5,8 +5,9 @@ # baseurl="https://hg.mozilla.org/releases/mozilla-release/raw-file/default/security/nss/lib" force=0 +skip_signed_obj=0 release_type="RTM" -release="3_43" +release="3_65" while [ -n "$1" ]; do case $1 in "-d") @@ -32,11 +33,15 @@ while [ -n "$1" ]; do "-f") force=1 ;; + "-s") + skip_signed_obj=1 + ;; *) echo "usage: $0 [-r] [-n release] [-f]" echo "-d use the development tip rather than the latest release" echo "-n release fetch a specific nss release" echo "-f skip the verify check" + echo "-s skip fetching signed objects" exit 1 ;; esac @@ -108,6 +113,10 @@ if [ $? -ne 0 ]; then exit 1; fi +if [ ${skip_signed_obj} -eq 0 ]; then + ./fetch_objsign.sh +fi + # Verify everything is good with the user echo -e "Upgrading ${current_version} -> ${version}:" echo -e "*${log_date} ${name} <$email> ${version}-${release}\n - Update to CKBI ${ckbi_version} from NSS ${nss_version}" diff --git a/fetch_objsign.sh b/fetch_objsign.sh new file mode 100755 index 0000000..a919f00 --- /dev/null +++ b/fetch_objsign.sh @@ -0,0 +1,62 @@ +#!/bin/sh +# +# This script fetches the object signing list from the Microsoft list. It then +# mergest that list into the fetched certdata.txt. +# +baseurl="https://ccadb-public.secure.force.com/microsoft/IncludedRootsPEMTxtForMSFT?TrustBitsInclude=Code%20Signing" +target="microsoft_code_siging.pem" +certdata="./certdata.txt" +merge=1 +diff=0 +while [ -n "$1" ]; do + case $1 in + "-u") + shift + baseurl=$1 + ;; + "-o") + shift + target=$1 + ;; + "-c") + shift + certdata=$1 + ;; + "-u") + merge=0 + ;; + "-d") + diff=1 + difffile=$1 + ;; + *) + echo "usage: $0 [-u URL] [-o target] [-c certdata] [-n]" + echo "-u URL base URL to fetch code signing list" + echo "-o target name of the codesigning target" + echo "-c certdata patch to certdata.txt to merge with" + echo "-d diff optional diff file" + echo "-n don't merge" + exit 1 + ;; + esac + shift +done + + +wget ${baseurl} -O ${target} + +if [ ${merge} -eq 0 ]; then + exit 0; +fi + +out=${certdata} +if [ ${diff} -eq 1]; then + out=${certdata}.out +fi + +python3 ./mergepem2certdata.py -c "${certdata}" -p "${target}" -o "${out}" -t "CKA_TRUST_CODE_SIGNING" -l "Microsoft Code Signing Only Certificate" + +if [ ${diff} -eq 1 ]; then + diff -u ${certdata} ${out} > ${difffile} + mv ${out} ${certdata} +fi diff --git a/mergepem2certdata.py b/mergepem2certdata.py new file mode 100644 index 0000000..694a9be --- /dev/null +++ b/mergepem2certdata.py @@ -0,0 +1,320 @@ +#!/usr/bin/python +# vim:set et sw=4: +# +# certdata2pem.py - splits certdata.txt into multiple files +# +# Copyright (C) 2009 Philipp Kern +# Copyright (C) 2013 Kai Engert +# +# 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