def verify_identity_with_file(identity, keydb, filename): # type: (str, str) -> Result log.debug("verify_identity_with_file({!r}, {!r}, {!r})".format( identity, keydb, filename)) # ensure tempfile exists if not os.path.exists(filename): return Failure("File {!r} doesn't exist!".format(filename)) # ensure signing identity exists and is valid res_find = identity_installed(identity, keydb) if not res_find: log.debug(res_find) # use identity to codesign the tempfile cmd_sign = run(['codesign', '-fs', identity, filename]) if not cmd_sign: return Failure(cmd_sign) # check signing authority of file signature cmd_check = run(['codesign', '-dvv', filename]) if not cmd_check: return Failure(cmd_check) # ensure signing authority matches the identity name re_auth = r"^Authority=(?P<authority>{})$".format(identity) m = re.search(re_auth, cmd_check.stderr, re.MULTILINE) if not m: log.debug(cmd_check) return Failure('Identity not found in signature') return Success(m.group('authority'))
def derive_keychain_extension(): # type: () -> str log.debug("Determining keychain file extension") ext = "keychain-db" # used by macOS 10.12+ log.debug("Starting with default extension: '%s'", ext) sw_vers = run('sw_vers -productVersion'.split()) if sw_vers: version_string = sw_vers.stdout version = list(map(int, version_string.decode().split('.'))) if version < [10, 12]: # pragma: no cover ext = 'keychain' log.debug("Pre-Sierra OS detected (%s). Using: '%s'", version_string, ext) else: # pragma: no cover log.warn("Failed to query OS version: %s", sw_vers) sec_default = run("security default-keychain".split()) if sec_default: # strip security garbage to get filename keydb = strip_security_noise(sec_default.stdout) ext = os.path.splitext(keydb)[1].lstrip('.') log.debug("Derived extension '%s' from login keychain '%s'", ext, keydb) log.debug("Using keychain file extension: %s", ext) return ext
def trust_identity(identity, keydb): # type: (str) -> Result fd, tmpfile = tempfile.mkstemp() fh = os.fdopen(fd, 'w') log.debug("Temp filename: %s", tmpfile) sec_extract = run(['security', 'find-certificate', '-p', '-c', identity, keydb]) if not sec_extract: log.debug(sec_extract) return Failure(sec_extract) fh.write(sec_extract.stdout) fh.close() sec_add = sudo_run(['security', 'add-trusted-cert', '-d', '-p', 'basic', '-p', 'codeSign', tmpfile]) if not sec_add: os.unlink(tmpfile) return Failure("Failed to add cert:\n{}".format(sec_extract.stdout)) res_valid = find_identity(identity, keydb, valid=True) if not res_valid: return Failure("Invalid identity.") sec_sign = run(['codesign', '--keychain', keydb, '-s', identity, tmpfile]) os.unlink(tmpfile) if not sec_sign: return Failure("Codesigning failed: {}".format(sec_sign.stderr)) return Success(identity)
def verify_identity_with_file(identity, keydb, filename): # type: (str, str) -> Result log.debug("verify_identity_with_file({!r}, {!r}, {!r})". format(identity, keydb, filename)) # ensure tempfile exists if not os.path.exists(filename): return Failure("File {!r} doesn't exist!".format(filename)) # ensure signing identity exists and is valid res_find = identity_installed(identity, keydb) if not res_find: log.debug(res_find) # use identity to codesign the tempfile cmd_sign = run(['codesign', '-fs', identity, filename]) if not cmd_sign: return Failure(cmd_sign) # check signing authority of file signature cmd_check = run(['codesign', '-dvv', filename]) if not cmd_check: return Failure(cmd_check) # ensure signing authority matches the identity name re_auth = r"^Authority=(?P<authority>{})$".format(identity) m = re.search(re_auth, cmd_check.stderr, re.MULTILINE) if not m: log.debug(cmd_check) return Failure('Identity not found in signature') return Success(m.group('authority'))
def trust_identity(identity, keydb): # type: (str) -> Result fd, tmpfile = tempfile.mkstemp() fh = os.fdopen(fd, 'w') log.debug("Temp filename: %s", tmpfile) sec_extract = run( ['security', 'find-certificate', '-p', '-c', identity, keydb]) if not sec_extract: log.debug(sec_extract) return Failure(sec_extract) fh.write(sec_extract.stdout) fh.close() sec_add = sudo_run([ 'security', 'add-trusted-cert', '-d', '-p', 'basic', '-p', 'codeSign', tmpfile ]) if not sec_add: os.unlink(tmpfile) return Failure("Failed to add cert:\n{}".format(sec_extract.stdout)) res_valid = find_identity(identity, keydb, valid=True) if not res_valid: return Failure("Invalid identity.") sec_sign = run(['codesign', '--keychain', keydb, '-s', identity, tmpfile]) os.unlink(tmpfile) if not sec_sign: return Failure("Codesigning failed: {}".format(sec_sign.stderr)) return Success(identity)
def test_run_with_sudo(self): """ - run should raise if asked to perform su/sudo operations - attempt to execute several illegal commands - assert that each raises a RuntimeError """ illegal_cmds = [ ['sudo', 'ls'], ['su', os.getenv('USER'), '-c', 'ls'], ] for cmd in illegal_cmds: with self.subTest(cmd=cmd): with self.assertRaisesRegexp(RuntimeError, 'Unauthorized'): sh.run(cmd)
def test_run_simple(self): """ - ensure true returns 0 - ensure false returns non-zero """ cmd_true = sh.run(['/usr/bin/true']) log.debug(cmd_true) self.assertTrue(cmd_true) self.assertEqual(0, cmd_true.code) cmd_false = sh.run(['/usr/bin/false']) log.debug(cmd_false) self.assertFalse(cmd_false) self.assertNotEqual(0, cmd_false.code)
def get_search_list(): # type: () -> Result sec_list = run(['security', 'list-keychains']) if not sec_list: # pragma: no cover return Failure("Failed to get search list: {}".format(sec_list)) list_lines = map(strip_security_noise, sec_list.stdout.splitlines()) return Success(list_lines)
def test_run_compound(self): """ - describe expected output (stdout, stderr, return code) - construct python program to emit expected output - run python code (using the current python interpreter) - ensure program executed successfully - ensure expected output was received """ sub_stdout = r"Some out text." sub_stderr = r"Some err text." sub_code = 7 sub_string = ('import sys;' ' sys.stdout.write("{0}");' ' sys.stderr.write("{1}");' ' sys.exit({2})').format(sub_stdout, sub_stderr, sub_code) # run above python code in current executable sub_cmd = sh.run([sys.executable, '-c', sub_string]) log.debug(sub_cmd) self.assertEqual(sub_code, sub_cmd.code) self.assertEqual(sub_stderr, sub_cmd.stderr) self.assertEqual(sub_stdout, sub_cmd.stdout) if sub_code == 0: self.assertTrue(sub_cmd) else: self.assertFalse(sub_cmd)
def test_run_compound(self): """ - describe expected output (stdout, stderr, return code) - construct python program to emit expected output - run python code (using the current python interpreter) - ensure program executed successfully - ensure expected output was received """ sub_stdout = r"Some out text." sub_stderr = r"Some err text." sub_code = 7 sub_string = ('import sys;' ' sys.stdout.write("{0}");' ' sys.stderr.write("{1}");' ' sys.exit({2})').format( sub_stdout, sub_stderr, sub_code) # run above python code in current executable sub_cmd = sh.run([sys.executable, '-c', sub_string]) log.debug(sub_cmd) self.assertEqual(sub_code, sub_cmd.code) self.assertEqual(sub_stderr, sub_cmd.stderr) self.assertEqual(sub_stdout, sub_cmd.stdout) if sub_code == 0: self.assertTrue(sub_cmd) else: self.assertFalse(sub_cmd)
def import_identity(keydb, key_pass, identity, id_file, id_pass): # type: (str, str, str, str, str) -> Result log.debug('import_identity({!r}, {!r}, {!r}, {!r})'.format( keydb, identity, id_file, '********')) # ensure keychain exists if not os.path.exists(id_file): return Failure('Identity file {!r} not found'.format(id_file)) sec_add = add_to_search_list(keydb) if not sec_add: return Failure(sec_add) res_find_id = identity_installed(identity, keydb) if res_find_id: return Failure("Identity exists: {!r}".format(res_find_id.value)) # import identity to keychain cmd_import = run(['security', 'import', id_file, '-k', keydb, '-f', 'pkcs12', '-P', id_pass, '-T', '/usr/bin/codesign']) if not cmd_import: return Failure(cmd_import.stderr) sec_part = sudo_run(['security', 'set-key-partition-list', '-S', 'apple', '-k', key_pass, keydb]) if not sec_part: return Failure("Failed to authorize identity: {}". format(sec_part.stderr)) return Success("Imported identity {} from {}".format(identity, id_file))
def import_identity(keydb, key_pass, identity, id_file, id_pass): # type: (str, str, str, str, str) -> Result log.debug('import_identity({!r}, {!r}, {!r}, {!r})'.format( keydb, identity, id_file, '********')) # ensure keychain exists if not os.path.exists(id_file): return Failure('Identity file {!r} not found'.format(id_file)) sec_add = add_to_search_list(keydb) if not sec_add: return Failure(sec_add) res_find_id = identity_installed(identity, keydb) if res_find_id: return Failure("Identity exists: {!r}".format(res_find_id.value)) # import identity to keychain cmd_import = run([ 'security', 'import', id_file, '-k', keydb, '-f', 'pkcs12', '-P', id_pass, '-T', '/usr/bin/codesign' ]) if not cmd_import: return Failure(cmd_import.stderr) sec_part = sudo_run([ 'security', 'set-key-partition-list', '-S', 'apple', '-k', key_pass, keydb ]) if not sec_part: return Failure("Failed to authorize identity: {}".format( sec_part.stderr)) return Success("Imported identity {} from {}".format(identity, id_file))
def unlock_keychain(keydb, password): # type: (str, str) -> Result log.debug('unlock_keychain({!r}, {!r})'.format(keydb, '********')) sec_unlock = run(['security', 'unlock-keychain', '-p', password, keydb]) if not sec_unlock: return Failure(sec_unlock) return Success(keydb)
def test_keychain_operations(self): """ - assert keychain does not exist - create keychain and assert exists - lock keychain - unlock keychain and assert exists """ keyfile = self.keyfile password = self.password # some assorted negatives self.assertFalse(S.create_keychain('/tmp', password)) # assert keychain does not exist self.assertFalse(S.keychain_exists(keyfile)) # create and assert success res_create = S.create_keychain(keyfile, password) self.assertTrue(res_create) self.assertTrue(S.keychain_exists(keyfile)) # assert second creation succeeds self.assertTrue(S.create_keychain(keyfile, password)) # keychain is unlocked at creation; lock it to test unlocking cmd_lock = sh.run(['security', 'lock-keychain', keyfile]) self.assertTrue(cmd_lock) self.assertFalse(S.unlock_keychain(keyfile, '')) self.assertTrue(S.unlock_keychain(keyfile, password)) # ensure keychain settings were set correctly cmd_info = sh.run(['security', 'show-keychain-info', keyfile]) self.assertTrue(cmd_info) self.assertRegexpMatches(cmd_info.stderr, r"\bno-timeout\b", cmd_info) # delete with backup res_delete = S.delete_keychain(keyfile, backup=True) self.assertTrue(res_delete) backup_file = res_delete.value # assert backup was made self.assertTrue(os.path.exists(backup_file)) # assert keychain is gone self.assertFalse(S.keychain_exists(keyfile)) self.assertFalse(os.path.exists(keyfile)) # cleanup backup file os.unlink(backup_file)
def delete_identity(identity, keydb): # type: (str, str) -> Result log.debug('delete_identity({!r}'.format(identity)) sec_delete = run(['security', 'delete-identity', '-tc', identity, keydb]) if not sec_delete: return Failure(sec_delete.stderr) return Success(identity)
def _run_linter(): # type: () -> list(str) report_file = 'flake8_report.pep8.txt' fmt = 'lint: %(path)s:%(row)d:%(col)d: %(code)s %(text)s' lint_paths = ['./debugsign', './dbsign/', './unittests/'] cmd_flake = sh.run(['flake8', '--tee', report_file, '--format={}'.format(fmt)] + lint_paths) return cmd_flake.stdout.splitlines()
def create_keychain(keydb, password): # type: (str, str) -> Result log.debug("Creating keychain: %s", keydb) if not keychain_exists(keydb): sec_make = run(['security', 'create-keychain', '-p', password, keydb]) if not sec_make: # pragma: no cover return Failure(sec_make) sec_add = add_to_search_list(keydb) if not sec_add: # pragma: no cover return Failure(sec_add) # Invoking without arguments sets timeout=infinite. # Prevents keychain from locking after timeout. sec_settings = run(['security', 'set-keychain-settings', keydb]) if not sec_settings: # pragma: no cover return Failure(sec_settings) return Success(sec_settings)
def authdb_privilege_read(privilege): # type: (str) -> Result log.debug('authdb_privilege_read("%s")', privilege) res = run(["security", "authorizationdb", "read", privilege]) if not res: err_msg = 'Failed to read privilege {}: {}'.format( privilege, res.stdout) log.warn(err_msg) return Failure(err_msg) return Success(res.stdout)
def test_privilege_read(self): # assert read of valid privilege succeeds and matches priv = self.test_priv res_read = S.authdb_privilege_read(priv) self.assertTrue(res_read) rules = S.rules_from(res_read.value) cmd_read = sh.run(['security', 'authorizationdb', 'read', priv]) self.assertTrue(cmd_read) for rule in rules: self.assertIn(rule, cmd_read.stdout)
def find_identity(identity, keydb, valid=False): # type: (str, str) -> Result log.debug('find_identity({!r}, {!r}, valid={!r})'. format(identity, keydb, valid)) if valid: flags = '-vp' else: flags = '-p' sec = run(['security', 'find-identity', flags, 'codesigning', keydb]) if not sec: log.debug(sec) return Failure(sec) m = re.search(r'\b{}\b'.format(identity), sec.stdout) if not m: log.debug("{}: {!r}".format(identity, sec.stdout)) return Failure('{!r} not found in {!r}'.format(identity, sec.stdout)) return Success(identity)
def find_identity(identity, keydb, valid=False): # type: (str, str) -> Result log.debug('find_identity({!r}, {!r}, valid={!r})'.format( identity, keydb, valid)) if valid: flags = '-vp' else: flags = '-p' sec = run(['security', 'find-identity', flags, 'codesigning', keydb]) if not sec: log.debug(sec) return Failure(sec) m = re.search(r'\b{}\b'.format(identity), sec.stdout) if not m: log.debug("{}: {!r}".format(identity, sec.stdout)) return Failure('{!r} not found in {!r}'.format(identity, sec.stdout)) return Success(identity)
def delete_keychain(keydb, backup=True): # type: (str, bool) -> Result log.debug('delete_keychain({!r}'.format(keydb)) if backup: new_keydb = "{}.bak-{}".format(keydb, str(os.getpid())) try: os.rename(keydb, new_keydb) log.info("Backed up {!r} to {!r}".format(keydb, new_keydb)) except OSError as ose: # pragma: no cover log.debug(ose) return Failure(ose) sec_delete = run(['security', 'delete-keychain', keydb]) if not (sec_delete or backup): # pragma: no cover # If backed up, security WILL return non-zero. # However, it will also remove the keychain from the search list. log.debug(sec_delete) if os.path.exists(keydb): # pragma: no cover os.unlink(keydb) # if it's still around, brute force rm it return Success(new_keydb)
def add_to_search_list(keydb): # type: (str) -> Result """ Adds keychain to security search list. codesign will only search keychains on this list. """ res_list = get_search_list() if not res_list: return res_list.renew() keylist = res_list.value if keydb in keylist: # keychain already present; noop log.debug(keylist) log.debug("{} already present in search list: {}".format( keydb, keylist)) return Success(keydb) keylist.append(keydb) cmd_params = ['security', 'list-keychains', '-d', 'user', '-s'] sec_add = run(cmd_params + keylist) if not sec_add: # pragma: no cover return Failure(sec_add) return Success(keydb)
def add_to_search_list(keydb): # type: (str) -> Result """ Adds keychain to security search list. codesign will only search keychains on this list. """ res_list = get_search_list() if not res_list: return res_list.renew() keylist = res_list.value if keydb in keylist: # keychain already present; noop log.debug(keylist) log.debug("{} already present in search list: {}". format(keydb, keylist)) return Success(keydb) keylist.append(keydb) cmd_params = ['security', 'list-keychains', '-d', 'user', '-s'] sec_add = run(cmd_params + keylist) if not sec_add: # pragma: no cover return Failure(sec_add) return Success(keydb)
def test_run_invalid_executable(self): """ - run() should raise OSError if execution is impossible """ with self.assertRaises(OSError): sh.run(['/'])