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