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