diff --git a/ca-certificates-mozilla.changes b/ca-certificates-mozilla.changes index 30f7a6d..9b28d38 100644 --- a/ca-certificates-mozilla.changes +++ b/ca-certificates-mozilla.changes @@ -1,3 +1,9 @@ +------------------------------------------------------------------- +Tue Nov 12 09:58:01 UTC 2019 - Ludwig Nussel + +- export correct p11kit trust attributes so Firefox detects built in + certificates (boo#1154871). Courtesy of Fedora. + ------------------------------------------------------------------- Sun Aug 4 14:17:45 UTC 2019 - Andreas Stieger diff --git a/ca-certificates-mozilla.spec b/ca-certificates-mozilla.spec index 213c7ba..92475fb 100644 --- a/ca-certificates-mozilla.spec +++ b/ca-certificates-mozilla.spec @@ -38,8 +38,7 @@ URL: https://www.mozilla.org # accidentally included! Source: http://hg.mozilla.org/projects/nss/raw-file/default/lib/ckfw/builtins/certdata.txt Source1: http://hg.mozilla.org/projects/nss/raw-file/default/lib/ckfw/builtins/nssckbi.h -# from Fedora. Note: currently contains extra fix to remove quotes. Pending upstream approval. -Source10: certdata2pem.py +Source10: https://src.fedoraproject.org/rpms/ca-certificates/raw/master/f/certdata2pem.py Source11: %{name}.COPYING Source12: compareoldnew BuildRequires: ca-certificates @@ -61,7 +60,8 @@ from MozillaFirefox %prep %setup -qcT -/bin/cp %{SOURCE0} . +mkdir certs +ln -s %{SOURCE0} certs install -m 644 %{SOURCE11} COPYING ver=`sed -ne '/NSS_BUILTINS_LIBRARY_VERSION /s/.*"\(.*\)"/\1/p' < "%{SOURCE1}"` @@ -72,44 +72,29 @@ fi %build export LANG=en_US.UTF-8 +cd certs python3 %{SOURCE10} +cd .. +( + cat <<-EOF + # This is a bundle of X.509 certificates of public Certificate + # Authorities. It was generated from the Mozilla root CA list. + # These certificates and trust/distrust attributes use the file format accepted + # by the p11-kit-trust module. + # + # Source: nss/lib/ckfw/builtins/certdata.txt + # Source: nss/lib/ckfw/builtins/nssckbi.h + # + # Generated from: + EOF + awk '$2 = "NSS_BUILTINS_LIBRARY_VERSION" {print "# " $2 " " $3}'; + echo '#'; + ls -1 certs/*.tmp-p11-kit | sort | xargs cat +) > ca-certificates-mozila.trust.p11-kit %install -mkdir -p %{buildroot}/%{trustdir_static}/anchors -set +x -for i in *.crt; do - args=() - trust=`sed -n '/^# openssl-trust=/{s/^.*=//;p;q;}' "$i"` - distrust=`sed -n '/^# openssl-distrust=/{s/^.*=//;p;q;}' "$i"` - alias=`sed -n '/^# alias=/{s/^.*=//;p;q;}' "$i"` - args+=('-trustout') - for t in $trust; do - args+=("-addtrust" "$t") - done - for t in $distrust; do - args+=("-addreject" "$t") - done - [ -z "$alias" ] || args+=('-setalias' "$alias") - - echo "$i ${args[*]}" - fname="%{buildroot}/%{trustdir_static}$d/${i%%:*}.pem" - if [ -e "$fname" ]; then - fname="${fname%.pem}" - j=1 - while [ -e "$fname.$j.pem" ]; do - j=$((j+1)) - done - fname="$fname.$j.pem" - fi - { - grep '^#' "$i" - openssl x509 -in "$i" "${args[@]}" - } > "$fname" -done -for i in *.p11-kit ; do - install -m 644 "$i" "%{buildroot}/%{trustdir_static}" -done -set -x +mkdir -p %{buildroot}/%{trustdir_static} +install -m 644 ca-certificates-mozila.trust.p11-kit "%{buildroot}/%{trustdir_static}/ca-certificates-mozila.trust.p11-kit" %post update-ca-certificates || true diff --git a/certdata2pem.py b/certdata2pem.py index baad801..31712ac 100644 --- a/certdata2pem.py +++ b/certdata2pem.py @@ -1,4 +1,4 @@ -#!/usr/bin/python3 +#!/usr/bin/python # vim:set et sw=4: # # certdata2pem.py - splits certdata.txt into multiple files @@ -26,7 +26,8 @@ import os.path import re import sys import textwrap -import urllib.parse +import urllib.request, urllib.parse, urllib.error +import subprocess objects = [] @@ -35,7 +36,7 @@ def printable_serial(obj): # Dirty file parser. in_data, in_multiline, in_obj = False, False, False -field, vtype, value, obj = None, None, None, dict() +field, ftype, value, binval, obj = None, None, None, bytearray(), dict() for line in open('certdata.txt', 'r'): # Ignore the file header. if not in_data: @@ -55,33 +56,36 @@ for line in open('certdata.txt', 'r'): continue if in_multiline: if not line.startswith('END'): - if vtype == 'MULTILINE_OCTAL': + if ftype == 'MULTILINE_OCTAL': line = line.strip() - numbers = [int(i.group(1), 8) for i in re.finditer(r'\\([0-3][0-7][0-7])', line)] - value += bytes(numbers) + 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 - obj[field] = value in_multiline = False continue if line.startswith('CKA_CLASS'): in_obj = True line_parts = line.strip().split(' ', 2) if len(line_parts) > 2: - field, vtype = line_parts[0:2] + field, ftype = line_parts[0:2] value = ' '.join(line_parts[2:]) elif len(line_parts) == 2: - field, vtype = line_parts + field, ftype = line_parts value = None else: raise NotImplementedError('line_parts < 2 not supported.\n' + line) - if vtype == 'MULTILINE_OCTAL': + if ftype == 'MULTILINE_OCTAL': in_multiline = True - value = b"" + value = "" + binval = bytearray() continue obj[field] = value -if obj: +if len(list(obj.items())) > 0: objects.append(obj) # Build up trust database. @@ -109,15 +113,32 @@ def obj_to_filename(obj): .replace('(', '=')\ .replace(')', '=')\ .replace(',', '_') - # encode possible Unicode string to UTF8 bytes first - label = label.encode("utf8") - # decode hex escape sequences - label = re.sub(rb'\\x[0-9a-fA-F]{2}', lambda m:bytes([int(m.group(0)[2:], 16)]), label) - # read back UTF8 bytes - label = label.decode("utf8") + labelbytes = bytearray() + i = 0 + imax = len(label) + while i < imax: + if i < imax-3 and label[i] == '\\' and label[i+1] == 'x': + labelbytes.extend(bytes.fromhex(label[i+2:i+4])) + i += 4 + continue + labelbytes.extend(str.encode(label[i])) + i = i+1 + continue + label = labelbytes.decode('utf-8') serial = printable_serial(obj) return label + ":" + serial +def write_cert_ext_to_file(f, oid, value, public_key): + f.write("[p11-kit-object-v1]\n") + f.write("label: "); + f.write(tobj['CKA_LABEL']) + f.write("\n") + f.write("class: x-certificate-extension\n"); + f.write("object-id: " + oid + "\n") + f.write("value: \"" + value + "\"\n") + f.write("modifiable: false\n"); + f.write(public_key) + trust_types = { "CKA_TRUST_DIGITAL_SIGNATURE": "digital-signature", "CKA_TRUST_NON_REPUDIATION": "non-repudiation", @@ -137,6 +158,18 @@ trust_types = { "CKA_TRUST_STEP_UP_APPROVED": "step-up-approved", } +legacy_trust_types = { + "LEGACY_CKA_TRUST_SERVER_AUTH": "server-auth", + "LEGACY_CKA_TRUST_CODE_SIGNING": "code-signing", + "LEGACY_CKA_TRUST_EMAIL_PROTECTION": "email-protection", +} + +legacy_to_real_trust_types = { + "LEGACY_CKA_TRUST_SERVER_AUTH": "CKA_TRUST_SERVER_AUTH", + "LEGACY_CKA_TRUST_CODE_SIGNING": "CKA_TRUST_CODE_SIGNING", + "LEGACY_CKA_TRUST_EMAIL_PROTECTION": "CKA_TRUST_EMAIL_PROTECTION", +} + openssl_trust = { "CKA_TRUST_SERVER_AUTH": "serverAuth", "CKA_TRUST_CLIENT_AUTH": "clientAuth", @@ -152,7 +185,9 @@ for tobj in objects: distrustbits = [] openssl_trustflags = [] openssl_distrustflags = [] - for t in sorted(trust_types.keys()): + legacy_trustbits = [] + legacy_openssl_trustflags = [] + for t in list(trust_types.keys()): if t in tobj and tobj[t] == 'CKT_NSS_TRUSTED_DELEGATOR': trustbits.append(t) if t in openssl_trust: @@ -162,29 +197,186 @@ for tobj in objects: if t in openssl_trust: openssl_distrustflags.append(openssl_trust[t]) + for t in list(legacy_trust_types.keys()): + if t in tobj and tobj[t] == 'CKT_NSS_TRUSTED_DELEGATOR': + real_t = legacy_to_real_trust_types[t] + legacy_trustbits.append(real_t) + if real_t in openssl_trust: + legacy_openssl_trustflags.append(openssl_trust[real_t]) + if t in tobj and tobj[t] == 'CKT_NSS_NOT_TRUSTED': + raise NotImplementedError('legacy distrust not supported.\n' + line) + fname = obj_to_filename(tobj) try: obj = certmap[key] except: obj = None - if obj != None: - fname += ".crt" - else: - fname += ".p11-kit" + # optional debug code, that dumps the parsed input to files + #fulldump = "dump-" + fname + #dumpf = open(fulldump, 'w') + #dumpf.write(str(obj)); + #dumpf.write(str(tobj)); + #dumpf.close(); - f = open(fname, 'w') - if obj != None: - f.write("# alias=%s\n"%tobj['CKA_LABEL'][1:-1]) - f.write("# trust=" + " ".join(trustbits) + "\n") - f.write("# distrust=" + " ".join(distrustbits) + "\n") - if openssl_trustflags: - f.write("# openssl-trust=" + " ".join(openssl_trustflags) + "\n") - if openssl_distrustflags: - f.write("# openssl-distrust=" + " ".join(openssl_distrustflags) + "\n") + is_legacy = 0 + if 'LEGACY_CKA_TRUST_SERVER_AUTH' in tobj or 'LEGACY_CKA_TRUST_EMAIL_PROTECTION' in tobj or 'LEGACY_CKA_TRUST_CODE_SIGNING' in tobj: + is_legacy = 1 + if obj == None: + raise NotImplementedError('found legacy trust without certificate.\n' + line) + + legacy_fname = "legacy-default/" + fname + ".crt" + f = open(legacy_fname, 'w') + f.write("# alias=%s\n"%tobj['CKA_LABEL']) + f.write("# trust=" + " ".join(legacy_trustbits) + "\n") + if legacy_openssl_trustflags: + f.write("# openssl-trust=" + " ".join(legacy_openssl_trustflags) + "\n") f.write("-----BEGIN CERTIFICATE-----\n") - f.write("\n".join(textwrap.wrap(base64.b64encode(obj['CKA_VALUE']).decode("ascii"), 64))) + temp_encoded_b64 = base64.b64encode(obj['CKA_VALUE']) + temp_wrapped = textwrap.wrap(temp_encoded_b64.decode(), 64) + f.write("\n".join(temp_wrapped)) f.write("\n-----END CERTIFICATE-----\n") + f.close() + + if 'CKA_TRUST_SERVER_AUTH' in tobj or 'CKA_TRUST_EMAIL_PROTECTION' in tobj or 'CKA_TRUST_CODE_SIGNING' in tobj: + legacy_fname = "legacy-disable/" + fname + ".crt" + f = open(legacy_fname, 'w') + f.write("# alias=%s\n"%tobj['CKA_LABEL']) + f.write("# trust=" + " ".join(trustbits) + "\n") + if openssl_trustflags: + f.write("# openssl-trust=" + " ".join(openssl_trustflags) + "\n") + f.write("-----BEGIN CERTIFICATE-----\n") + f.write("\n".join(textwrap.wrap(base64.b64encode(obj['CKA_VALUE']), 64))) + f.write("\n-----END CERTIFICATE-----\n") + f.close() + + # don't produce p11-kit output for legacy certificates + continue + + pk = '' + cert_comment = '' + if obj != None: + # must extract the public key from the cert, let's use openssl + cert_fname = "cert-" + fname + fc = open(cert_fname, 'w') + fc.write("-----BEGIN CERTIFICATE-----\n") + temp_encoded_b64 = base64.b64encode(obj['CKA_VALUE']) + temp_wrapped = textwrap.wrap(temp_encoded_b64.decode(), 64) + fc.write("\n".join(temp_wrapped)) + fc.write("\n-----END CERTIFICATE-----\n") + fc.close(); + pk_fname = "pubkey-" + fname + fpkout = open(pk_fname, "w") + dump_pk_command = ["openssl", "x509", "-in", cert_fname, "-noout", "-pubkey"] + subprocess.call(dump_pk_command, stdout=fpkout) + fpkout.close() + with open (pk_fname, "r") as myfile: + pk=myfile.read() + # obtain certificate information suitable as a comment + comment_fname = "comment-" + fname + fcout = open(comment_fname, "w") + comment_command = ["openssl", "x509", "-in", cert_fname, "-noout", "-text"] + subprocess.call(comment_command, stdout=fcout) + fcout.close() + sed_command = ["sed", "--in-place", "s/^/#/", comment_fname] + subprocess.call(sed_command) + with open (comment_fname, "r", errors = 'replace') as myfile: + cert_comment=myfile.read() + + fname += ".tmp-p11-kit" + f = open(fname, 'w') + + if obj != None: + is_distrusted = False + has_server_trust = False + has_email_trust = False + has_code_trust = False + + if 'CKA_TRUST_SERVER_AUTH' in tobj: + if tobj['CKA_TRUST_SERVER_AUTH'] == 'CKT_NSS_NOT_TRUSTED': + is_distrusted = True + elif tobj['CKA_TRUST_SERVER_AUTH'] == 'CKT_NSS_TRUSTED_DELEGATOR': + has_server_trust = True + + if 'CKA_TRUST_EMAIL_PROTECTION' in tobj: + if tobj['CKA_TRUST_EMAIL_PROTECTION'] == 'CKT_NSS_NOT_TRUSTED': + is_distrusted = True + elif tobj['CKA_TRUST_EMAIL_PROTECTION'] == 'CKT_NSS_TRUSTED_DELEGATOR': + has_email_trust = True + + if 'CKA_TRUST_CODE_SIGNING' in tobj: + if tobj['CKA_TRUST_CODE_SIGNING'] == 'CKT_NSS_NOT_TRUSTED': + is_distrusted = True + elif tobj['CKA_TRUST_CODE_SIGNING'] == 'CKT_NSS_TRUSTED_DELEGATOR': + has_code_trust = True + + if is_distrusted: + trust_ext_oid = "1.3.6.1.4.1.3319.6.10.1" + trust_ext_value = "0.%06%0a%2b%06%01%04%01%99w%06%0a%01%04 0%1e%06%08%2b%06%01%05%05%07%03%04%06%08%2b%06%01%05%05%07%03%01%06%08%2b%06%01%05%05%07%03%03" + write_cert_ext_to_file(f, trust_ext_oid, trust_ext_value, pk) + + trust_ext_oid = "2.5.29.37" + if has_server_trust: + if has_email_trust: + if has_code_trust: + # server + email + code + trust_ext_value = "0%2a%06%03U%1d%25%01%01%ff%04 0%1e%06%08%2b%06%01%05%05%07%03%04%06%08%2b%06%01%05%05%07%03%01%06%08%2b%06%01%05%05%07%03%03" + else: + # server + email + trust_ext_value = "0 %06%03U%1d%25%01%01%ff%04%160%14%06%08%2b%06%01%05%05%07%03%04%06%08%2b%06%01%05%05%07%03%01" + else: + if has_code_trust: + # server + code + trust_ext_value = "0 %06%03U%1d%25%01%01%ff%04%160%14%06%08%2b%06%01%05%05%07%03%01%06%08%2b%06%01%05%05%07%03%03" + else: + # server + trust_ext_value = "0%16%06%03U%1d%25%01%01%ff%04%0c0%0a%06%08%2b%06%01%05%05%07%03%01" + else: + if has_email_trust: + if has_code_trust: + # email + code + trust_ext_value = "0 %06%03U%1d%25%01%01%ff%04%160%14%06%08%2b%06%01%05%05%07%03%04%06%08%2b%06%01%05%05%07%03%03" + else: + # email + trust_ext_value = "0%16%06%03U%1d%25%01%01%ff%04%0c0%0a%06%08%2b%06%01%05%05%07%03%04" + else: + if has_code_trust: + # code + trust_ext_value = "0%16%06%03U%1d%25%01%01%ff%04%0c0%0a%06%08%2b%06%01%05%05%07%03%03" + else: + # none + trust_ext_value = "0%18%06%03U%1d%25%01%01%ff%04%0e0%0c%06%0a%2b%06%01%04%01%99w%06%0a%10" + + # no 2.5.29.37 for neutral certificates + if (is_distrusted or has_server_trust or has_email_trust or has_code_trust): + write_cert_ext_to_file(f, trust_ext_oid, trust_ext_value, pk) + + pk = '' + f.write("\n") + + f.write("[p11-kit-object-v1]\n") + f.write("label: "); + f.write(tobj['CKA_LABEL']) + f.write("\n") + if is_distrusted: + f.write("x-distrusted: true\n") + elif has_server_trust or has_email_trust or has_code_trust: + f.write("trusted: true\n") + else: + f.write("trusted: false\n") + + # requires p11-kit >= 0.23.4 + f.write("nss-mozilla-ca-policy: true\n") + f.write("modifiable: false\n"); + + f.write("-----BEGIN CERTIFICATE-----\n") + temp_encoded_b64 = base64.b64encode(obj['CKA_VALUE']) + temp_wrapped = textwrap.wrap(temp_encoded_b64.decode(), 64) + f.write("\n".join(temp_wrapped)) + f.write("\n-----END CERTIFICATE-----\n") + f.write(cert_comment) + f.write("\n") + else: f.write("[p11-kit-object-v1]\n") f.write("label: "); @@ -192,6 +384,7 @@ for tobj in objects: f.write("\n") f.write("class: certificate\n") f.write("certificate-type: x-509\n") + f.write("modifiable: false\n"); f.write("issuer: \""); f.write(urllib.parse.quote(tobj['CKA_ISSUER'])); f.write("\"\n") @@ -201,4 +394,5 @@ for tobj in objects: if (tobj['CKA_TRUST_SERVER_AUTH'] == 'CKT_NSS_NOT_TRUSTED') or (tobj['CKA_TRUST_EMAIL_PROTECTION'] == 'CKT_NSS_NOT_TRUSTED') or (tobj['CKA_TRUST_CODE_SIGNING'] == 'CKT_NSS_NOT_TRUSTED'): f.write("x-distrusted: true\n") f.write("\n\n") + f.close() print(" -> written as '%s', trust = %s, openssl-trust = %s, distrust = %s, openssl-distrust = %s" % (fname, trustbits, openssl_trustflags, distrustbits, openssl_distrustflags)) diff --git a/compareoldnew b/compareoldnew index ec30489..1772e95 100644 --- a/compareoldnew +++ b/compareoldnew @@ -18,13 +18,13 @@ cd old echo old... ln -s ../.osc/certdata.txt python3 ../certdata2pem.py > stdout 2> stderr -ls -1 *.crt | sort > ../old.files +ls -1 cert-* | sort > ../old.files cd .. cd new echo new... ln -s ../certdata.txt python3 ../certdata2pem.py > stdout 2> stderr -ls -1 *.crt | sort > ../new.files +ls -1 cert-* | sort > ../new.files cd .. echo '----------------------------' while read line; do