def demo_create_and_sign_key_mgr(): prikey_keymgr = cct_common.PrivateKey.from_hex(KEYMGR_PRIVATE_HEX) # pubkey_keymgr = cct_common.PublicKey.from_bytes(KEYMGR_PUBLIC_BYTES) # print('public test key for keymgr: ' + pubkey_keymgr.to_hex()) # print('private test key for keymgr: ' + prikey_keymgr.to_hex()) key_mgr = cct_metadata_construction.build_delegating_metadata( metadata_type='key_mgr', # 'root' or 'key_mgr' delegations={ 'pkg_mgr': { 'pubkeys': [PKGMGR_PUBLIC_HEX], 'threshold': 1 } }, version=1, #timestamp default: now #expiration default: now plus root expiration default duration ) key_mgr = cct_signing.wrap_as_signable(key_mgr) # sign dictionary in place cct_signing.sign_signable(key_mgr, prikey_keymgr) with open(KEYMGR_FNAME, 'wb') as fobj: fobj.write(cct_common.canonserialize(key_mgr)) return key_mgr
def test_root_gen_sign_verify(): # Integration test # Build a basic root metadata file with empty key_mgr delegation and one # root key, threshold 1, version 1. rmd = metadata_construction.build_root_metadata( root_version=1, root_pubkeys=[SAMPLE_KEYVAL], root_threshold=1, key_mgr_pubkeys=[], key_mgr_threshold=1) rmd = signing.wrap_as_signable(rmd) signed_portion = rmd['signed'] canonical_signed_portion = common.canonserialize(signed_portion) if not SSLIB_AVAILABLE: pytest.skip('--TEST SKIPPED⚠️ : Unable to perform GPG signing without ' 'securesystemslib and GPG.') return # gpg_key_obj = securesystemslib.gpg.functions.export_pubkey( # SAMPLE_FINGERPRINT) gpg_sig = root_signing.sign_via_gpg(canonical_signed_portion, SAMPLE_FINGERPRINT) signed_rmd = copy.deepcopy(rmd) signed_rmd['signatures'][SAMPLE_KEYVAL] = gpg_sig # # Dump working files # with open('T_gpg_sig.json', 'wb') as fobj: # fobj.write(common.canonserialize(gpg_sig)) # with open('T_gpg_key_obj.json', 'wb') as fobj: # fobj.write(common.canonserialize(gpg_key_obj)) # with open('T_canonical_sigless_md.json', 'wb') as fobj: # fobj.write(canonical_signed_portion) # with open('T_full_rmd.json', 'wb') as fobj: # fobj.write(common.canonserialize(signed_rmd)) # Verify using the SSL code and the expected pubkey object. # # (Purely as a test -- we wouldn't normally do this.) # verified = securesystemslib.gpg.functions.verify_signature( # gpg_sig, gpg_key_obj, canonical_signed_portion) # assert verified authentication.verify_gpg_signature(gpg_sig, SAMPLE_KEYVAL, canonical_signed_portion) print('--TEST SUCCESS✅: GPG signing (using GPG and securesystemslib) and ' 'GPG signature verification (using only cryptography)')
def create_key_mgr(self, keys): private_key_key_mgr = cct_common.PrivateKey.from_hex( keys["key_mgr"][0]["private"]) pkg_mgr_pub_keys = [k["public"] for k in keys["pkg_mgr"]] key_mgr = cct_metadata_construction.build_delegating_metadata( metadata_type="key_mgr", # 'root' or 'key_mgr' delegations={ "pkg_mgr": { "pubkeys": pkg_mgr_pub_keys, "threshold": 1 } }, version=1, # timestamp default: now # expiration default: now plus root expiration default duration ) key_mgr = cct_signing.wrap_as_signable(key_mgr) # sign dictionary in place cct_signing.sign_signable(key_mgr, private_key_key_mgr) key_mgr_serialized = cct_common.canonserialize(key_mgr) with open(self.folder / "key_mgr.json", "wb") as fobj: fobj.write(key_mgr_serialized) # let's run a verification root_metadata = cct_common.load_metadata_from_file(self.folder / "1.root.json") key_mgr_metadata = cct_common.load_metadata_from_file(self.folder / "key_mgr.json") cct_common.checkformat_signable(root_metadata) if "delegations" not in root_metadata["signed"]: raise ValueError('Expected "delegations" entry in root metadata.') root_delegations = root_metadata["signed"][ "delegations"] # for brevity cct_common.checkformat_delegations(root_delegations) if "key_mgr" not in root_delegations: raise ValueError( 'Missing expected delegation to "key_mgr" in root metadata.') cct_common.checkformat_delegation(root_delegations["key_mgr"]) # Doing delegation processing. cct_authentication.verify_delegation("key_mgr", key_mgr_metadata, root_metadata) console.print( "[green]Success: key mgr metadata verified based on root metadata." ) return key_mgr
def test_build_delegating_metadata(): # See also test_build_root_metadata. key_mgr = build_delegating_metadata( metadata_type='key_mgr', # 'root' or 'key_mgr' delegations={'pkg_mgr': { 'pubkeys': [PKGMGR_PUBLIC_HEX], 'threshold': 1}}, version=1, #timestamp default: now #expiration default: now plus root expiration default duration ) key_mgr = wrap_as_signable(key_mgr) checkformat_delegating_metadata(key_mgr)
def create_root(self, keys): root_keys = keys["root"] root_pubkeys = [k["public"] for k in root_keys] key_mgr_pubkeys = [k["public"] for k in keys["key_mgr"]] root_version = 1 root_md = cct_metadata_construction.build_root_metadata( root_pubkeys=root_pubkeys[0:1], root_threshold=1, root_version=root_version, key_mgr_pubkeys=key_mgr_pubkeys, key_mgr_threshold=1, ) # Wrap the metadata in a signing envelope. root_md = cct_signing.wrap_as_signable(root_md) root_md_serialized_unsigned = cct_common.canonserialize(root_md) root_filepath = self.folder / f"{root_version}.root.json" print("Writing out: ", root_filepath) # Write unsigned sample root metadata. with open(root_filepath, "wb") as fout: fout.write(root_md_serialized_unsigned) # This overwrites the file with a signed version of the file. cct_root_signing.sign_root_metadata_via_gpg( root_filepath, root_keys[0]["fingerprint"]) # Load untrusted signed root metadata. signed_root_md = cct_common.load_metadata_from_file(root_filepath) cct_authentication.verify_signable(signed_root_md, root_pubkeys, 1, gpg=True) console.print("[green]Root metadata signed & verified!")
def _process_raw_repodata_str(self, raw_repodata_str): json_obj = json.loads(raw_repodata_str or '{}') subdir = json_obj.get('info', {}).get('subdir') or self.channel.subdir assert subdir == self.channel.subdir add_pip = context.add_pip_as_python_dependency schannel = self.channel.canonical_name self._package_records = _package_records = [] self._names_index = _names_index = defaultdict(list) self._track_features_index = _track_features_index = defaultdict(list) signatures = json_obj.get("signatures", {}) _internal_state = { 'channel': self.channel, 'url_w_subdir': self.url_w_subdir, 'url_w_credentials': self.url_w_credentials, 'cache_path_base': self.cache_path_base, 'fn': self.repodata_fn, '_package_records': _package_records, '_names_index': _names_index, '_track_features_index': _track_features_index, '_etag': json_obj.get('_etag'), '_mod': json_obj.get('_mod'), '_cache_control': json_obj.get('_cache_control'), '_url': json_obj.get('_url'), '_add_pip': add_pip, '_pickle_version': REPODATA_PICKLE_VERSION, '_schannel': schannel, 'repodata_version': json_obj.get('repodata_version', 0), } if _internal_state["repodata_version"] > MAX_REPODATA_VERSION: raise CondaUpgradeError(dals(""" The current version of conda is too old to read repodata from %s (This version only supports repodata_version 1.) Please update conda to use this channel. """) % self.url_w_subdir) meta_in_common = { # just need to make this once, then apply with .update() 'arch': json_obj.get('info', {}).get('arch'), 'channel': self.channel, 'platform': json_obj.get('info', {}).get('platform'), 'schannel': schannel, 'subdir': subdir, } channel_url = self.url_w_credentials legacy_packages = json_obj.get("packages", {}) conda_packages = {} if context.use_only_tar_bz2 else json_obj.get("packages.conda", {}) _tar_bz2 = CONDA_PACKAGE_EXTENSION_V1 use_these_legacy_keys = set(iterkeys(legacy_packages)) - set( k[:-6] + _tar_bz2 for k in iterkeys(conda_packages) ) if context.extra_safety_checks: if cct is None: log.warn("metadata signature verification requested, " "but `conda-content-trust` is not installed.") verify_metadata_signatures = False elif not context.signing_metadata_url_base: log.info("no metadata URL base has not been specified") verify_metadata_signatures = False elif self._key_mgr is None: log.warn("could not find key_mgr data for metadata signature verification") verify_metadata_signatures = False else: verify_metadata_signatures = True else: verify_metadata_signatures = False for group, copy_legacy_md5 in ( (iteritems(conda_packages), True), (((k, legacy_packages[k]) for k in use_these_legacy_keys), False)): for fn, info in group: # Verify metadata signature before anything else so run-time # updates to the info dictionary performed below do not # invalidate the signatures provided in metadata.json. if verify_metadata_signatures: if fn in signatures: signable = wrap_as_signable(info) signable['signatures'].update(signatures[fn]) try: verify_trust_delegation('pkg_mgr', signable, self._key_mgr) info['metadata_signature_status'] = MetadataSignatureStatus.verified # TODO (AV): more granular signature errors (?) except SignatureError: log.warn(f"invalid signature for {fn}") info['metadata_signature_status'] = MetadataSignatureStatus.error else: info['metadata_signature_status'] = MetadataSignatureStatus.unsigned info['fn'] = fn info['url'] = join_url(channel_url, fn) if copy_legacy_md5: counterpart = fn.replace('.conda', '.tar.bz2') if counterpart in legacy_packages: info['legacy_bz2_md5'] = legacy_packages[counterpart].get('md5') info['legacy_bz2_size'] = legacy_packages[counterpart].get('size') if (add_pip and info['name'] == 'python' and info['version'].startswith(('2.', '3.'))): info['depends'].append('pip') info.update(meta_in_common) if info.get('record_version', 0) > 1: log.debug("Ignoring record_version %d from %s", info["record_version"], info['url']) continue package_record = PackageRecord(**info) _package_records.append(package_record) _names_index[package_record.name].append(package_record) for ftr_name in package_record.track_features: _track_features_index[ftr_name].append(package_record) self._internal_state = _internal_state return _internal_state
def demo_verify_pkg_sig_via_key_mgr(key_mgr): packages = { "pytorch-1.2.0-cuda92py27hd3e106c_0.tar.bz2": { "build": "cuda92py27hd3e106c_0", "build_number": 0, "depends": [ "_pytorch_select 0.2", "blas 1.0 mkl", "cffi", "cudatoolkit 9.2.*", "cudnn >=7.3.0,<=8.0a0", "future", "libgcc-ng >=7.3.0", "libstdcxx-ng >=7.3.0", "mkl >=2019.4,<2021.0a0", "mkl-service >=2,<3.0a0", "ninja", "numpy >=1.11.3,<2.0a0", "python >=2.7,<2.8.0a0" ], "license": "BSD 3-Clause", "license_family": "BSD", "md5": "793c6af90ed62c964e28b046e0b071c6", "name": "pytorch", "sha256": "a53f772a224485df7436d4b2aa2c5d44e249e2fb43eee98831eeaaa51a845697", "size": 282176733, "subdir": "linux-64", "timestamp": 1566783471689, "version": "1.2.0" } } print('\n\n\nHere is a sample package entry from repodata.json:') from pprint import pprint pprint(packages) junk = input_func('\n\nNext: sign it with the pkg_mgr key.') signable = cct_signing.wrap_as_signable( packages['pytorch-1.2.0-cuda92py27hd3e106c_0.tar.bz2']) # Sign in place. cct_signing.sign_signable( signable, cct_common.PrivateKey.from_hex( 'f3cdab14740066fb277651ec4f96b9f6c3e3eb3f812269797b9656074cd52133') ) print('Signed envelope around this pytorch package metadata:\n\n') pprint(signable) junk = input_func( '\n\nNext: verify the signature based on what the now-trusted ' 'key manager role told us to expect.\n') # Some argument validation for the key manager role. cct_common.checkformat_signable(key_mgr) if 'delegations' not in key_mgr['signed']: raise ValueError( 'Expected "delegations" entry in key manager metadata.') key_mgr_delegations = key_mgr['signed']['delegations'] # for brevity cct_common.checkformat_delegations(key_mgr_delegations) if 'pkg_mgr' not in key_mgr_delegations: raise ValueError( 'Missing expected delegation to "pkg_mgr" in key manager metadata.' ) cct_common.checkformat_delegation(key_mgr_delegations['pkg_mgr']) # Doing delegation processing. cct_authentication.verify_delegation('pkg_mgr', signable, key_mgr) print('\n\nSuccess: signature over package metadata verified based on ' 'trusted key manager metadata.') # Additional scenario: tampered metadata / incorrectly signed package # Man-in-the-middle adds false dependency signable['signed']['depends'] += ['django'] try: cct_authentication.verify_delegation('pkg_mgr', signable, key_mgr) except cct_common.SignatureError: print( 'Modified metadata results in verification failure: attack prevented' ) else: assert False, 'Demo test failed.'
def demo_root_signing_and_verifying_and_chaining(): # Build sample root metadata. ('metadata' -> 'md') root_md = cct_metadata_construction.build_root_metadata( root_pubkeys=[ROOT_PUBKEY_HEX], root_threshold=1, root_version=1, key_mgr_pubkeys=[KEYMGR_PUBLIC_HEX], key_mgr_threshold=1) # Wrap the metadata in a signing envelope. root_md = cct_signing.wrap_as_signable(root_md) root_md_serialized_unsigned = cct_common.canonserialize(root_md) print('\n-- Unsigned root metadata version 1 generated.\n') # # This is the part of the data over which signatures are constructed. # root_md_serialized_portion_to_sign = cct_common.canonserialize( # root_md['signed']) # TODO: ✅ Format-validate constructed root metadata using checkformat # function. if not os.path.exists('demo'): os.mkdir('demo') # Write unsigned sample root metadata. with open(ROOT_FNAME_V1, 'wb') as fobj: fobj.write(root_md_serialized_unsigned) print('\n-- Unsigned root metadata version 1 written.\n') # Sign sample root metadata. junk = input_func( 'Preparing to request root signature. Please plug in your ' 'YubiKey and prepare to put in your user PIN in a GPG dialog box. ' ' When the YubiKey is plugged in and you are READY TO ENTER your ' 'pin, hit enter to begin.') # This overwrites the file with a signed version of the file. cct_root_signing.sign_root_metadata_via_gpg(ROOT_FNAME_V1, ROOT_PUBKEY_GPG_FINGERPRINT) cct_root_signing.sign_root_metadata_via_gpg(ROOT_FNAME_V1, ROOT_PUBKEY_GPG_FINGERPRINT) junk = input_func( '\n-- Root metadata v1 signed. Next: load signed root v1.\n') # Load untrusted signed root metadata. signed_root_md = cct_common.load_metadata_from_file(ROOT_FNAME_V1) junk = input_func( '\n-- Signed root metadata v1 loaded. Next: verify signed root v1\n') # Verify untrusted signed root metadata. (Normally, one uses the prior # version of root, but here we're bootstrapping for the demo. We'll verify # with a prior version lower down in this demo.) cct_authentication.verify_signable(signed_root_md, [ROOT_PUBKEY_HEX], 1, gpg=True) junk = input_func( '\n-- Root metadata v1 fully verified. Next: build root metadata v2.\n' ) # Build sample second version of root metadata. In this case, let's try # adding another authorized key and requiring signatures from both keys. root_md2 = cct_metadata_construction.build_root_metadata( root_pubkeys=[ROOT_PUBKEY_HEX, ROOT_PUBKEY_2_HEX], root_threshold=2, root_version=2, key_mgr_pubkeys=[KEYMGR_PUBLIC_HEX], key_mgr_threshold=1) # Wrap the version 2 metadata in a signing envelope, canonicalize it, and # serialize it to write to disk. root_md2 = cct_signing.wrap_as_signable(root_md2) root_md2 = cct_common.canonserialize(root_md2) # Write unsigned sample root metadata. with open(ROOT_FNAME_V2, 'wb') as fobj: fobj.write(root_md2) junk = input_func( '\n-- Unsigned root metadata version 2 generated and written. Next: sign root v2\n' ) # This overwrites the file with a signed version of the file. # We'll sign with both keys specified. cct_root_signing.sign_root_metadata_via_gpg(ROOT_FNAME_V2, ROOT_PUBKEY_GPG_FINGERPRINT) cct_root_signing.sign_root_metadata_via_gpg(ROOT_FNAME_V2, ROOT_PUBKEY_2_GPG_FINGERPRINT) junk = input_func( '\n-- Root metadata v2 signed. Next: load and verify signed root v2 based on root v1 (root chaining).\n' ) # Load the now-signed version from disk. signed_root_md2 = cct_common.load_metadata_from_file(ROOT_FNAME_V2) # Test root chaining (verifying v2 using v1) cct_authentication.verify_root(signed_root_md, signed_root_md2) print('\n-- Root metadata v2 fully verified based directly on Root ' 'metadata v1 (root chaining success)\n') print('\n-- Success. :)\n') # Build sample third version of root metadata. In this case, let's reduce # the number of required keys to one. root_md3 = cct_metadata_construction.build_root_metadata( root_pubkeys=[ROOT_PUBKEY_HEX, ROOT_PUBKEY_2_HEX], root_threshold=1, root_version=3, key_mgr_pubkeys=[KEYMGR_PUBLIC_HEX], key_mgr_threshold=1) # Wrap the version 2 metadata in a signing envelope, canonicalize it, and # serialize it to write to disk. root_md3 = cct_signing.wrap_as_signable(root_md3) root_md3 = cct_common.canonserialize(root_md3) # Write unsigned sample root metadata. with open(ROOT_FNAME_V3, 'wb') as fobj: fobj.write(root_md3) junk = input_func( '\n-- Unsigned root metadata version 2 generated and written. Next: sign root v2\n' ) # This overwrites the file with a signed version of the file. # We'll sign with both keys specified. cct_root_signing.sign_root_metadata_via_gpg(ROOT_FNAME_V3, ROOT_PUBKEY_GPG_FINGERPRINT) cct_root_signing.sign_root_metadata_via_gpg(ROOT_FNAME_V3, ROOT_PUBKEY_2_GPG_FINGERPRINT) junk = input_func( '\n-- Root metadata v2 signed. Next: load and verify signed root v2 based on root v1 (root chaining).\n' ) # Load the now-signed version from disk. signed_root_md3 = cct_common.load_metadata_from_file(ROOT_FNAME_V3) # Test root chaining (verifying v2 using v1) cct_authentication.verify_root(signed_root_md2, signed_root_md3) print('\n-- Root metadata v3 fully verified based directly on Root ' 'metadata v2 (root chaining success)\n') print('\n-- Success. :)\n') return signed_root_md, signed_root_md2, signed_root_md3
def test_build_root_metadata(): # Test only construction of (unsigned) root metadata. # Regression root_md = build_root_metadata( root_pubkeys=[ROOT_PUBLIC_HEX], root_threshold=1, root_version=1, root_expiration=TEST_EXPIRY_DATE, key_mgr_pubkeys=[PUBLIC_HEX], key_mgr_threshold=1, root_timestamp=TEST_TIMESTAMP) assert root_md == EXPECTED_UNSIGNED_ROOT # This format check expects a signing envelope. checkformat_delegating_metadata(wrap_as_signable(root_md)) # # Bad-argument tests, expecting TypeErrors # bad_hashmap = copy.deepcopy(REPODATA_HASHMAP) # bad_hashmap['some_filename'] = 'this is not a hash' # Bad-argument tests, expecting TypeErrors or ValueErrors with pytest.raises(ValueError): root_md = build_root_metadata( root_pubkeys=[ROOT_PUBLIC_HEX[:-1]], # too short to be a key root_threshold=1, root_version=1, root_expiration=TEST_EXPIRY_DATE, key_mgr_pubkeys=[PUBLIC_HEX], key_mgr_threshold=1, root_timestamp=TEST_TIMESTAMP) with pytest.raises(TypeError): root_md = build_root_metadata( root_pubkeys=[ROOT_PUBLIC_HEX], root_threshold='this is not an integer', # <--- root_version=1, root_expiration=TEST_EXPIRY_DATE, key_mgr_pubkeys=[PUBLIC_HEX], key_mgr_threshold=1, root_timestamp=TEST_TIMESTAMP) with pytest.raises(ValueError): root_md = build_root_metadata( root_pubkeys=ROOT_PUBLIC_HEX, # not a list of keys root_threshold=1, root_version=1, root_expiration=TEST_EXPIRY_DATE, key_mgr_pubkeys=[PUBLIC_HEX], key_mgr_threshold=1, root_timestamp=TEST_TIMESTAMP) with pytest.raises(ValueError): root_md = build_root_metadata( root_pubkeys=[ROOT_PUBLIC_HEX], root_threshold=1, root_version='this is not a version number', root_expiration=TEST_EXPIRY_DATE, key_mgr_pubkeys=[PUBLIC_HEX], key_mgr_threshold=1, root_timestamp=TEST_TIMESTAMP) with pytest.raises(TypeError): root_md = build_root_metadata( root_pubkeys=[ROOT_PUBLIC_HEX], root_threshold=1, root_version=1, root_expiration=91, # <------ key_mgr_pubkeys=[PUBLIC_HEX], key_mgr_threshold=1, root_timestamp=TEST_TIMESTAMP) assert not is_a_signable(root_md) signable_root_md = wrap_as_signable(root_md) assert is_a_signable(signable_root_md)
def test_wrap_sign_verify_signable(): # Make a new keypair. Returns keys and writes keys to disk. # Then load it from disk and compare that to the return value. Exercise # some of the functions redundantly. generated_private, generated_public = gen_and_write_keys('keytest_new') loaded_new_private_bytes, loaded_new_public_bytes = keyfiles_to_bytes( 'keytest_new') loaded_new_private, loaded_new_public = keyfiles_to_keys('keytest_new') old_private = PrivateKey.from_bytes(REG__PRIVATE_BYTES) old_public = PublicKey.from_bytes(REG__PUBLIC_BYTES) assert generated_private.is_equivalent_to(loaded_new_private) assert generated_public.is_equivalent_to(loaded_new_public) assert loaded_new_private.is_equivalent_to( PrivateKey.from_bytes(loaded_new_private_bytes)) assert loaded_new_public.is_equivalent_to( PublicKey.from_bytes(loaded_new_public_bytes)) # Clean up a bit for the next tests. new_private = loaded_new_private new_public = loaded_new_public del (loaded_new_public, loaded_new_private, generated_private, generated_public, loaded_new_private_bytes, loaded_new_public_bytes) # Test wrapping, signing signables, and verifying signables. d = {'foo': 'bar', '1': 2} d_modified = {'foo': 'DOOM', '1': 2} signable_d = wrap_as_signable(d) assert is_a_signable(signable_d) sign_signable(signable_d, old_private) assert is_a_signable(signable_d) verify_signable(signable=signable_d, authorized_pub_keys=[old_public.to_hex()], threshold=1) # Expect failure this time due to bad format. try: verify_signable(signable=signable_d['signed'], authorized_pub_keys=[old_public.to_hex()], threshold=1) except TypeError: pass else: assert False, 'Failed to raise expected exception.' # Expect failure this time due to non-matching signature. try: modified_signable_d = copy.deepcopy(signable_d) modified_signable_d['signed'] = d_modified verify_signable(signable=modified_signable_d, authorized_pub_keys=[old_public.to_hex()], threshold=1) except SignatureError: pass else: assert False, 'Failed to raise expected exception.' # Clean up a bit. for fname in [ 'keytest_new.pub', 'keytest_new.pri', 'keytest_old.pri', 'keytest_old.pub' ]: if os.path.exists(fname): os.remove(fname)