14db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
24db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert# Use of this source code is governed by a BSD-style license that can be
34db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert# found in the LICENSE file.
44db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert
5f2c929f74efbc152acc33669c15210dc5a129b64Jim Hebertimport glob, json, logging, os, re, stat
64db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert
74db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebertfrom autotest_lib.client.bin import test, utils
84db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebertfrom autotest_lib.client.common_lib import error
94db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebertfrom autotest_lib.client.common_lib import pexpect
104db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert
114db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert
1212afba228d5806e0ec35711e06fa808dd2f704e1Jim HebertDEFAULT_BASELINE = 'baseline'
1312afba228d5806e0ec35711e06fa808dd2f704e1Jim Hebert
144db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim HebertFINGERPRINT_RE = re.compile(r'Fingerprint \(SHA1\):\n\s+(\b[:\w]+)\b')
15f2c929f74efbc152acc33669c15210dc5a129b64Jim HebertNSS_ISSUER_RE = re.compile(r'Object Token:(.+?)\s+C,.?,.?')
164db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert
174bcdd2ba40de41d306d0ece671f076660582a145Mike FrysingerNSSCERTUTIL = '/usr/local/bin/certutil'
184bcdd2ba40de41d306d0ece671f076660582a145Mike FrysingerNSSMODUTIL = '/usr/local/bin/modutil'
194db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim HebertOPENSSL = '/usr/bin/openssl'
204db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert
21bb8b2c38cfc14a7ba2bd0ba735a61640c71e4c67J. Richard Barnette# This glob pattern is coupled to the snprintf() format in
22386924ea4ceaead84d537e4e371f523e2100d0a5Jorge Lucangeli Obes# get_cert_by_subject() in crypto/x509/by_dir.c in the OpenSSL
23bb8b2c38cfc14a7ba2bd0ba735a61640c71e4c67J. Richard Barnette# sources.  In theory the glob can catch files not created by that
24bb8b2c38cfc14a7ba2bd0ba735a61640c71e4c67J. Richard Barnette# snprintf(); such file names probably shouldn't be allowed to exist
25bb8b2c38cfc14a7ba2bd0ba735a61640c71e4c67J. Richard Barnette# anyway.
26bb8b2c38cfc14a7ba2bd0ba735a61640c71e4c67J. Richard BarnetteOPENSSL_CERT_GLOB = '/etc/ssl/certs/' + '[0-9a-f]' * 8 + '.*'
274db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert
284db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert
294db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebertclass security_RootCA(test.test):
30386924ea4ceaead84d537e4e371f523e2100d0a5Jorge Lucangeli Obes    """Verifies that the root CAs trusted by both NSS and OpenSSL
31f2c929f74efbc152acc33669c15210dc5a129b64Jim Hebert       match the expected set."""
324db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert    version = 1
334db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert
3412afba228d5806e0ec35711e06fa808dd2f704e1Jim Hebert    def get_baseline_sets(self, baseline_file):
354db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert        """Returns a dictionary of sets. The keys are the names of
364db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert           the ssl components and the values are the sets of fingerprints
374db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert           we expect to find in that component's Root CA list.
38f2c929f74efbc152acc33669c15210dc5a129b64Jim Hebert
39f2c929f74efbc152acc33669c15210dc5a129b64Jim Hebert           @param baseline_file: name of JSON file containing baseline.
404db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert        """
41f2c929f74efbc152acc33669c15210dc5a129b64Jim Hebert        baselines = {'nss': {}, 'openssl': {}}
4212afba228d5806e0ec35711e06fa808dd2f704e1Jim Hebert        baseline_file = open(os.path.join(self.bindir, baseline_file))
43f2c929f74efbc152acc33669c15210dc5a129b64Jim Hebert        raw_baselines = json.load(baseline_file)
44f2c929f74efbc152acc33669c15210dc5a129b64Jim Hebert        for i in ['nss', 'openssl']:
45f2c929f74efbc152acc33669c15210dc5a129b64Jim Hebert            baselines[i].update(raw_baselines[i])
46f2c929f74efbc152acc33669c15210dc5a129b64Jim Hebert            baselines[i].update(raw_baselines['both'])
474db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert        return baselines
484db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert
494db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert    def get_nss_certs(self):
50386924ea4ceaead84d537e4e371f523e2100d0a5Jorge Lucangeli Obes        """
51386924ea4ceaead84d537e4e371f523e2100d0a5Jorge Lucangeli Obes        Returns the dict of certificate fingerprints observed in NSS,
52386924ea4ceaead84d537e4e371f523e2100d0a5Jorge Lucangeli Obes        or None if NSS is not available.
53386924ea4ceaead84d537e4e371f523e2100d0a5Jorge Lucangeli Obes        """
544db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert        tmpdir = self.tmpdir
554db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert
56386924ea4ceaead84d537e4e371f523e2100d0a5Jorge Lucangeli Obes        nss_shlib_glob = glob.glob('/usr/lib*/libnssckbi.so')
57386924ea4ceaead84d537e4e371f523e2100d0a5Jorge Lucangeli Obes        if len(nss_shlib_glob) == 0:
58386924ea4ceaead84d537e4e371f523e2100d0a5Jorge Lucangeli Obes            return None
59386924ea4ceaead84d537e4e371f523e2100d0a5Jorge Lucangeli Obes        elif len(nss_shlib_glob) > 1:
60386924ea4ceaead84d537e4e371f523e2100d0a5Jorge Lucangeli Obes            logging.warn("Found more than one copy of libnssckbi.so")
61386924ea4ceaead84d537e4e371f523e2100d0a5Jorge Lucangeli Obes
624db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert        # Create new empty cert DB.
634db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert        child = pexpect.spawn('"%s" -N -d %s' % (NSSCERTUTIL, tmpdir))
644db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert        child.expect('Enter new password:')
654db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert        child.sendline('foo')
664db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert        child.expect('Re-enter password:')
674db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert        child.sendline('foo')
684db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert        child.close()
694db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert
704db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert        # Add the certs found in the compiled NSS shlib to a new module in DB.
717ba20285a2a9b6a66099b6525ea9164a089c3d78Jim Hebert        cmd = ('"%s" -add testroots -libfile %s -dbdir %s' %
72386924ea4ceaead84d537e4e371f523e2100d0a5Jorge Lucangeli Obes               (NSSMODUTIL, nss_shlib_glob[0], tmpdir))
734db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert        nssmodutil = pexpect.spawn(cmd)
744db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert        nssmodutil.expect('\'q <enter>\' to abort, or <enter> to continue:')
754db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert        nssmodutil.sendline('\n')
764db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert        ret = utils.system_output(NSSMODUTIL + ' -list '
774db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert                                  '-dbdir %s' % tmpdir)
784db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert        self.assert_('2. testroots' in ret)
794db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert
804db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert        # Dump out the list of root certs.
814db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert        all_certs = utils.system_output(NSSCERTUTIL +
82f2c929f74efbc152acc33669c15210dc5a129b64Jim Hebert                                        ' -L -d %s -h all' % tmpdir,
83f2c929f74efbc152acc33669c15210dc5a129b64Jim Hebert                                        retain_output=True)
844db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert        certdict = {}  # A map of {SHA1_Fingerprint : CA_Nickname}.
85f2c929f74efbc152acc33669c15210dc5a129b64Jim Hebert        cert_matches = NSS_ISSUER_RE.findall(all_certs)
86f2c929f74efbc152acc33669c15210dc5a129b64Jim Hebert        logging.debug('NSS_ISSUER_RE.findall returned: %s', cert_matches)
87f2c929f74efbc152acc33669c15210dc5a129b64Jim Hebert        for cert in cert_matches:
884db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert            cert_dump = utils.system_output(NSSCERTUTIL +
894db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert                                            ' -L -d %s -n '
904db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert                                            '\"Builtin Object Token:%s\"' %
91f2c929f74efbc152acc33669c15210dc5a129b64Jim Hebert                                            (tmpdir, cert), retain_output=True)
92f2c929f74efbc152acc33669c15210dc5a129b64Jim Hebert            matches = FINGERPRINT_RE.findall(cert_dump)
93f2c929f74efbc152acc33669c15210dc5a129b64Jim Hebert            for match in matches:
94f2c929f74efbc152acc33669c15210dc5a129b64Jim Hebert                certdict[match] = cert
95f2c929f74efbc152acc33669c15210dc5a129b64Jim Hebert        return certdict
964db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert
974db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert
984db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert    def get_openssl_certs(self):
99386924ea4ceaead84d537e4e371f523e2100d0a5Jorge Lucangeli Obes        """Returns the dict of certificate fingerprints observed in OpenSSL."""
1004db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert        fingerprint_cmd = ' '.join([OPENSSL, 'x509', '-fingerprint',
1014db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert                                    '-issuer', '-noout',
1022d012fdbed42b9d001b1c963d7acc2ca8413b47bJ. Richard Barnette                                    '-in %s'])
1034db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert        certdict = {}  # A map of {SHA1_Fingerprint : CA_Nickname}.
1044db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert
1052d012fdbed42b9d001b1c963d7acc2ca8413b47bJ. Richard Barnette        for certfile in glob.glob(OPENSSL_CERT_GLOB):
106f2c929f74efbc152acc33669c15210dc5a129b64Jim Hebert            f, i = utils.system_output(fingerprint_cmd % certfile,
107f2c929f74efbc152acc33669c15210dc5a129b64Jim Hebert                                       retain_output=True).splitlines()
1084db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert            fingerprint = f.split('=')[1]
1094db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert            for field in i.split('/'):
1104db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert                items = field.split('=')
1114db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert                # Compensate for stupidly malformed issuer fields.
1124db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert                if len(items) > 1:
1134db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert                    if items[0] == 'CN':
1144db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert                        certdict[fingerprint] = items[1]
1154db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert                        break
1164db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert                    elif items[0] == 'O':
1174db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert                        certdict[fingerprint] = items[1]
1184db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert                        break
1194db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert                else:
120f2c929f74efbc152acc33669c15210dc5a129b64Jim Hebert                    logging.warning('Malformed issuer string %s', i)
1214db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert            # Check that we found a name for this fingerprint.
1224db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert            if not fingerprint in certdict:
1234db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert                raise error.TestFail('Couldn\'t find issuer string for %s' %
1244db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert                                     fingerprint)
125f2c929f74efbc152acc33669c15210dc5a129b64Jim Hebert        return certdict
1264db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert
1274db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert
128654451e32abf7199a8c144950cc9f667f9b9088aJim Hebert    def cert_perms_errors(self):
129654451e32abf7199a8c144950cc9f667f9b9088aJim Hebert        """Returns True if certificate files have bad permissions."""
130654451e32abf7199a8c144950cc9f667f9b9088aJim Hebert        # Acts as a regression check for crosbug.com/19848
131654451e32abf7199a8c144950cc9f667f9b9088aJim Hebert        has_errors = False
132654451e32abf7199a8c144950cc9f667f9b9088aJim Hebert        for certfile in glob.glob(OPENSSL_CERT_GLOB):
133654451e32abf7199a8c144950cc9f667f9b9088aJim Hebert            s = os.stat(certfile)
134654451e32abf7199a8c144950cc9f667f9b9088aJim Hebert            if s.st_uid != 0 or stat.S_IMODE(s.st_mode) != 0644:
135f2c929f74efbc152acc33669c15210dc5a129b64Jim Hebert                logging.error("Bad permissions: %s",
136654451e32abf7199a8c144950cc9f667f9b9088aJim Hebert                              utils.system_output("ls -lH %s" % certfile))
137654451e32abf7199a8c144950cc9f667f9b9088aJim Hebert                has_errors = True
138654451e32abf7199a8c144950cc9f667f9b9088aJim Hebert
139654451e32abf7199a8c144950cc9f667f9b9088aJim Hebert        return has_errors
140654451e32abf7199a8c144950cc9f667f9b9088aJim Hebert
141654451e32abf7199a8c144950cc9f667f9b9088aJim Hebert
14212afba228d5806e0ec35711e06fa808dd2f704e1Jim Hebert    def run_once(self, opts=None):
1436a4ff93a9316d479f24d948927d886caff0cd9d4Christopher Wiley        """Test entry point.
1446a4ff93a9316d479f24d948927d886caff0cd9d4Christopher Wiley
1456a4ff93a9316d479f24d948927d886caff0cd9d4Christopher Wiley            Accepts 2 optional args, e.g. test_that --args="relaxed
1466a4ff93a9316d479f24d948927d886caff0cd9d4Christopher Wiley            baseline=foo".  Parses the args array and invokes the main test
1476a4ff93a9316d479f24d948927d886caff0cd9d4Christopher Wiley            method.
148f2c929f74efbc152acc33669c15210dc5a129b64Jim Hebert
149f2c929f74efbc152acc33669c15210dc5a129b64Jim Hebert           @param opts: string containing command line arguments.
15012afba228d5806e0ec35711e06fa808dd2f704e1Jim Hebert        """
15112afba228d5806e0ec35711e06fa808dd2f704e1Jim Hebert        args = {'baseline': DEFAULT_BASELINE}
15212afba228d5806e0ec35711e06fa808dd2f704e1Jim Hebert        if opts:
15312afba228d5806e0ec35711e06fa808dd2f704e1Jim Hebert            args.update(dict([[k, v] for (k, e, v) in
15412afba228d5806e0ec35711e06fa808dd2f704e1Jim Hebert                              [x.partition('=') for x in opts]]))
15512afba228d5806e0ec35711e06fa808dd2f704e1Jim Hebert
15612afba228d5806e0ec35711e06fa808dd2f704e1Jim Hebert        self.verify_rootcas(baseline_file=args['baseline'],
15712afba228d5806e0ec35711e06fa808dd2f704e1Jim Hebert                            exact_match=('relaxed' not in args))
15812afba228d5806e0ec35711e06fa808dd2f704e1Jim Hebert
15912afba228d5806e0ec35711e06fa808dd2f704e1Jim Hebert
16012afba228d5806e0ec35711e06fa808dd2f704e1Jim Hebert    def verify_rootcas(self, baseline_file=DEFAULT_BASELINE, exact_match=True):
16112afba228d5806e0ec35711e06fa808dd2f704e1Jim Hebert        """Verify installed Root CA's all appear on a specified whitelist.
162386924ea4ceaead84d537e4e371f523e2100d0a5Jorge Lucangeli Obes           Covers both NSS and OpenSSL.
163f2c929f74efbc152acc33669c15210dc5a129b64Jim Hebert
164f2c929f74efbc152acc33669c15210dc5a129b64Jim Hebert           @param baseline_file: name of baseline file to use in verification.
165f2c929f74efbc152acc33669c15210dc5a129b64Jim Hebert           @param exact_match: boolean indicating if expected-but-missing CAs
166f2c929f74efbc152acc33669c15210dc5a129b64Jim Hebert                               should cause test failure. Defaults to True.
1674db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert        """
1684db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert        testfail = False
1694db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert
1704db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert        # Dump certificate info and run comparisons.
1714db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert        seen = {}
172386924ea4ceaead84d537e4e371f523e2100d0a5Jorge Lucangeli Obes        nss_store = self.get_nss_certs()
173386924ea4ceaead84d537e4e371f523e2100d0a5Jorge Lucangeli Obes        openssl_store = self.get_openssl_certs()
174386924ea4ceaead84d537e4e371f523e2100d0a5Jorge Lucangeli Obes        if nss_store is not None:
175386924ea4ceaead84d537e4e371f523e2100d0a5Jorge Lucangeli Obes            seen['nss'] = nss_store
176386924ea4ceaead84d537e4e371f523e2100d0a5Jorge Lucangeli Obes        if openssl_store is not None:
177386924ea4ceaead84d537e4e371f523e2100d0a5Jorge Lucangeli Obes            seen['openssl'] = openssl_store
178386924ea4ceaead84d537e4e371f523e2100d0a5Jorge Lucangeli Obes
179f2c929f74efbc152acc33669c15210dc5a129b64Jim Hebert        # Merge all 4 dictionaries (seen-nss, seen-openssl, expected-nss,
180f2c929f74efbc152acc33669c15210dc5a129b64Jim Hebert        # and expected-openssl) into 1 so we have 1 place to lookup
181f2c929f74efbc152acc33669c15210dc5a129b64Jim Hebert        # fingerprint -> comment for logging purposes.
18212afba228d5806e0ec35711e06fa808dd2f704e1Jim Hebert        expected = self.get_baseline_sets(baseline_file)
183f2c929f74efbc152acc33669c15210dc5a129b64Jim Hebert        cert_details = {}
184386924ea4ceaead84d537e4e371f523e2100d0a5Jorge Lucangeli Obes        for store in seen.keys():
185386924ea4ceaead84d537e4e371f523e2100d0a5Jorge Lucangeli Obes            for certdict in [expected, seen]:
186386924ea4ceaead84d537e4e371f523e2100d0a5Jorge Lucangeli Obes                cert_details.update(certdict[store])
187386924ea4ceaead84d537e4e371f523e2100d0a5Jorge Lucangeli Obes                certdict[store] = set(certdict[store])
188386924ea4ceaead84d537e4e371f523e2100d0a5Jorge Lucangeli Obes
189386924ea4ceaead84d537e4e371f523e2100d0a5Jorge Lucangeli Obes        for store in seen.keys():
190386924ea4ceaead84d537e4e371f523e2100d0a5Jorge Lucangeli Obes            missing = expected[store].difference(seen[store])
191386924ea4ceaead84d537e4e371f523e2100d0a5Jorge Lucangeli Obes            unexpected = seen[store].difference(expected[store])
19212afba228d5806e0ec35711e06fa808dd2f704e1Jim Hebert            if unexpected or (missing and exact_match):
1934db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert                testfail = True
194386924ea4ceaead84d537e4e371f523e2100d0a5Jorge Lucangeli Obes                logging.error('Results for %s', store)
1954db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert                logging.error('Unexpected')
1964db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert                for i in unexpected:
1978ab2707c0052ef30c6bb523c3e4efdd67b981a66Jorge Lucangeli Obes                    logging.error('"%s": "%s"', i, cert_details[i])
19812afba228d5806e0ec35711e06fa808dd2f704e1Jim Hebert                if exact_match:
19912afba228d5806e0ec35711e06fa808dd2f704e1Jim Hebert                    logging.error('Missing')
20012afba228d5806e0ec35711e06fa808dd2f704e1Jim Hebert                    for i in missing:
2018ab2707c0052ef30c6bb523c3e4efdd67b981a66Jorge Lucangeli Obes                        logging.error('"%s": "%s"', i, cert_details[i])
2024db70188ba021eb305bd6ebe0efb269fca9dbfb0Jim Hebert
203654451e32abf7199a8c144950cc9f667f9b9088aJim Hebert        # cert_perms_errors() call first to avoid short-circuiting.
204654451e32abf7199a8c144950cc9f667f9b9088aJim Hebert        # Short circuiting could mask additional failures that would
205654451e32abf7199a8c144950cc9f667f9b9088aJim Hebert        # require a second build/test iteration to uncover.
206654451e32abf7199a8c144950cc9f667f9b9088aJim Hebert        if self.cert_perms_errors() or testfail:
207654451e32abf7199a8c144950cc9f667f9b9088aJim Hebert            raise error.TestFail('Unexpected Root CA findings')
208