def test_generate_and_write_rsa_keypair(self): # Test normal case. temporary_directory = tempfile.mkdtemp(dir=self.temporary_directory) test_keypath = os.path.join(temporary_directory, 'rsa_key') test_keypath_unencrypted = os.path.join(temporary_directory, 'rsa_key_unencrypted') interface.generate_and_write_rsa_keypair(test_keypath, password='******') self.assertTrue(os.path.exists(test_keypath)) self.assertTrue(os.path.exists(test_keypath + '.pub')) # If an empty string is given for 'password', the private key file # is written to disk unencrypted. interface.generate_and_write_rsa_keypair(test_keypath_unencrypted, password='') self.assertTrue(os.path.exists(test_keypath_unencrypted)) self.assertTrue(os.path.exists(test_keypath_unencrypted + '.pub')) # Ensure the generated key files are importable. scheme = 'rsassa-pss-sha256' imported_pubkey = \ interface.import_rsa_publickey_from_file(test_keypath + '.pub') self.assertTrue(securesystemslib.formats.RSAKEY_SCHEMA.matches(imported_pubkey)) imported_privkey = interface.import_rsa_privatekey_from_file(test_keypath, 'pw') self.assertTrue(securesystemslib.formats.RSAKEY_SCHEMA.matches(imported_privkey)) # Try to import the unencrypted key file. imported_privkey = interface.import_rsa_privatekey_from_file(test_keypath_unencrypted, '') self.assertTrue(securesystemslib.formats.RSAKEY_SCHEMA.matches(imported_privkey)) # Custom 'bits' argument. os.remove(test_keypath) os.remove(test_keypath + '.pub') interface.generate_and_write_rsa_keypair(test_keypath, bits=2048, password='******') self.assertTrue(os.path.exists(test_keypath)) self.assertTrue(os.path.exists(test_keypath + '.pub')) # Test improperly formatted arguments. self.assertRaises(securesystemslib.exceptions.FormatError, interface.generate_and_write_rsa_keypair, 3, bits=2048, password='******') self.assertRaises(securesystemslib.exceptions.FormatError, interface.generate_and_write_rsa_keypair, test_keypath, bits='bad', password='******') self.assertRaises(securesystemslib.exceptions.FormatError, interface.generate_and_write_rsa_keypair, test_keypath, bits=2048, password=3) # Test invalid 'bits' argument. self.assertRaises(securesystemslib.exceptions.FormatError, interface.generate_and_write_rsa_keypair, test_keypath, bits=1024, password='******')
def test_import_rsa_privatekey_from_file(self): # Test normal case. temporary_directory = tempfile.mkdtemp(dir=self.temporary_directory) # Load one of the pre-generated key files from # 'securesystemslib/tests/repository_data'. 'password' unlocks the # pre-generated key files. key_filepath = os.path.join( os.path.dirname(os.path.realpath(__file__)), 'data', 'keystore', 'rsa_key') self.assertTrue(os.path.exists(key_filepath)) imported_rsa_key = interface.import_rsa_privatekey_from_file( key_filepath, 'password') self.assertTrue( securesystemslib.formats.RSAKEY_SCHEMA.matches(imported_rsa_key)) # Test load encrypted key prompt for password with mock.patch('securesystemslib.interface.get_password', return_value='password'): imported_rsa_key = interface.import_rsa_privatekey_from_file( key_filepath, prompt=True) self.assertTrue( securesystemslib.formats.RSAKEY_SCHEMA.matches( imported_rsa_key)) # Test improperly formatted 'filepath' argument. self.assertRaises(securesystemslib.exceptions.FormatError, interface.import_rsa_privatekey_from_file, 3, 'pw') # Test improperly formatted 'password' argument. with self.assertRaises(securesystemslib.exceptions.FormatError): interface.import_rsa_privatekey_from_file(key_filepath, 123) # Test unallowed empty 'password' with self.assertRaises(ValueError): interface.import_rsa_privatekey_from_file(key_filepath, '') # Test unallowed passing 'prompt' and 'password' with self.assertRaises(ValueError): interface.import_rsa_privatekey_from_file(key_filepath, password='******', prompt=True) # Test invalid argument. # Non-existent key file. nonexistent_keypath = os.path.join(temporary_directory, 'nonexistent_keypath') self.assertRaises(securesystemslib.exceptions.StorageError, interface.import_rsa_privatekey_from_file, nonexistent_keypath, 'pw') # Invalid key file argument. invalid_keyfile = os.path.join(temporary_directory, 'invalid_keyfile') with open(invalid_keyfile, 'wb') as file_object: file_object.write(b'bad keyfile') self.assertRaises(securesystemslib.exceptions.CryptoError, interface.import_rsa_privatekey_from_file, invalid_keyfile, 'pw')
def test_import_rsa_privatekey_from_file(self): # Test normal case. temporary_directory = tempfile.mkdtemp(dir=self.temporary_directory) # Load one of the pre-generated key files from # 'securesystemslib/tests/repository_data'. 'password' unlocks the # pre-generated key files. key_filepath = os.path.join('data', 'keystore', 'rsa_key') self.assertTrue(os.path.exists(key_filepath)) imported_rsa_key = interface.import_rsa_privatekey_from_file( key_filepath, 'password') self.assertTrue(securesystemslib.formats.RSAKEY_SCHEMA.matches(imported_rsa_key)) # Test improperly formatted argument. self.assertRaises(securesystemslib.exceptions.FormatError, interface.import_rsa_privatekey_from_file, 3, 'pw') # Test invalid argument. # Non-existent key file. nonexistent_keypath = os.path.join(temporary_directory, 'nonexistent_keypath') self.assertRaises(IOError, interface.import_rsa_privatekey_from_file, nonexistent_keypath, 'pw') # Invalid key file argument. invalid_keyfile = os.path.join(temporary_directory, 'invalid_keyfile') with open(invalid_keyfile, 'wb') as file_object: file_object.write(b'bad keyfile') self.assertRaises(securesystemslib.exceptions.CryptoError, interface.import_rsa_privatekey_from_file, invalid_keyfile, 'pw')
def _modify_repository_root( self, modification_func: Callable[[Metadata], None], bump_version: bool = False, ) -> None: """Apply 'modification_func' to root and persist it.""" role_path = os.path.join(self.repository_directory, "metadata", "root.json") root = Metadata.from_file(role_path) modification_func(root) if bump_version: root.signed.version += 1 root_key_path = os.path.join(self.keystore_directory, "root_key") root_key_dict = import_rsa_privatekey_from_file(root_key_path, password="******") signer = SSlibSigner(root_key_dict) root.sign(signer) root.to_file( os.path.join(self.repository_directory, "metadata", "root.json")) root.to_file( os.path.join( self.repository_directory, "metadata", f"{root.signed.version}.root.json", ))
def setUpClass(self): # Create layout with one inspection self.layout = Layout.read({ "_type": "layout", "inspect": [], "steps": [{ "name": "run-command", "expected_command": ["{EDITOR}"], }] }) # Find demo files demo_files = os.path.join(os.path.dirname(os.path.realpath(__file__)), "demo_files") self.set_up_test_dir() # Copy demo files to temp dir for fn in os.listdir(demo_files): shutil.copy(os.path.join(demo_files, fn), self.test_dir) # load alice's key self.alice = import_rsa_privatekey_from_file("alice") self.alice_pub_dict = import_publickeys_from_file(["alice.pub"])
def setUpClass(cls): cls.repo_dir = os.path.join(os.getcwd(), 'repository_data', 'repository', 'metadata') cls.metadata = {} for md in [ "root", "timestamp", "snapshot", "targets", "role1", "role2" ]: with open(os.path.join(cls.repo_dir, f"{md}.json"), "rb") as f: cls.metadata[md] = f.read() keystore_dir = os.path.join(os.getcwd(), 'repository_data', 'keystore') cls.keystore = {} root_key_dict = import_rsa_privatekey_from_file(os.path.join( keystore_dir, "root" + '_key'), password="******") cls.keystore["root"] = SSlibSigner(root_key_dict) for role in ["delegation", "snapshot", "targets", "timestamp"]: key_dict = import_ed25519_privatekey_from_file(os.path.join( keystore_dir, role + '_key'), password="******") cls.keystore[role] = SSlibSigner(key_dict) def hashes_length_modifier(timestamp: Timestamp) -> None: timestamp.meta["snapshot.json"].hashes = None timestamp.meta["snapshot.json"].length = None cls.metadata["timestamp"] = cls.modify_metadata( cls, "timestamp", hashes_length_modifier)
def main(): key_owner = interface.import_rsa_privatekey_from_file("./keys/owner") key_clone = interface.import_rsa_publickey_from_file("./keys/clone.pub") key_build = interface.import_rsa_publickey_from_file("./keys/build.pub") key_build_image = interface.import_rsa_publickey_from_file( "./keys/build-image.pub") layout = Layout.read({ "_type": "layout", "keys": { key_clone["keyid"]: key_clone, key_build["keyid"]: key_build, key_build_image["keyid"]: key_build_image, }, "steps": [{ "name": "clone", "expected_materials": [["DISALLOW", "*"]], "expected_products": [["CREATE", "*"]], "pubkeys": [key_clone["keyid"]], "expected_command": [ "git", "clone", "https://gitlab.com/boxboat/demos/intoto-spire/go-hello-world" ], "threshold": 1, }, { "name": "build", "expected_materials": [["MATCH", "*", "WITH", "PRODUCTS", "FROM", "clone"], ["DISALLOW", "*"]], "expected_products": [["CREATE", "go-hello-world"], ["DISALLOW", "*"]], "pubkeys": [key_build["keyid"]], "expected_command": ["go", "build", "./..."], "threshold": 1, }, { "name": "build-image", "expected_materials": [["MATCH", "*", "WITH", "PRODUCTS", "FROM", "clone"], ["DISALLOW", "*"]], "expected_products": [["CREATE", "image-id"], ["CREATE", "go-hello-world.tar"], ["DISALLOW", "*"]], "pubkeys": [key_build_image["keyid"]], "threshold": 1, }], "inspect": [] }) metadata = Metablock(signed=layout) # Sign and dump layout to "root.layout" metadata.sign(key_owner) metadata.dump("root.layout")
def setUpClass(self): """Creates and changes into temporary directory. Copies demo files to temp dir... - owner/functionary key pairs - *.link metadata files - layout template (not signed, no expiration date) - final product ...and dumps various layouts for different test scenarios """ # Find demo files demo_files = os.path.join( os.path.dirname(os.path.realpath(__file__)), "demo_files") # find where the scripts directory is located. scripts_directory = os.path.join( os.path.dirname(os.path.realpath(__file__)), "scripts") self.set_up_test_dir() # Copy demo files to temp dir for fn in os.listdir(demo_files): shutil.copy(os.path.join(demo_files, fn), self.test_dir) shutil.copytree(scripts_directory, 'scripts') # Load layout template layout_template = Metablock.load("demo.layout.template") # Store layout paths to be used in tests self.layout_single_signed_path = "single-signed.layout" self.layout_double_signed_path = "double-signed.layout" # Import layout signing keys alice = import_rsa_privatekey_from_file("alice") bob = import_rsa_privatekey_from_file("bob") self.alice_path = "alice.pub" self.bob_path = "bob.pub" # dump a single signed layout layout_template.sign(alice) layout_template.dump(self.layout_single_signed_path) # dump a double signed layout layout_template.sign(bob) layout_template.dump(self.layout_double_signed_path)
def setUpClass(self): """Create and change into temporary directory, generate two key pairs and dummy product. """ self.set_up_test_dir() self.key_path = "test-key" self.key_path2 = "test-key2" generate_and_write_unencrypted_rsa_keypair(self.key_path) generate_and_write_unencrypted_rsa_keypair(self.key_path2) self.key = import_rsa_privatekey_from_file(self.key_path) self.key2 = import_rsa_privatekey_from_file(self.key_path2) self.step_name = "test-step" self.link_name = "{}.{:.8}.link".format(self.step_name, self.key["keyid"]) self.link_name_unfinished = UNFINISHED_FILENAME_FORMAT.format( step_name=self.step_name, keyid=self.key["keyid"]) self.test_product = "test_product" Path(self.test_product).touch()
def setUpClass(self): """Create and change into temporary directory, generate key pair and dummy material, read key pair. """ self.set_up_test_dir() self.step_name = "test_step" self.key_path = "test_key" generate_and_write_unencrypted_rsa_keypair(self.key_path) self.key = import_rsa_privatekey_from_file(self.key_path) self.key_pub = import_rsa_publickey_from_file(self.key_path + ".pub") self.test_artifact = "test_artifact" open(self.test_artifact, "w").close()
def setUpClass(self): """Create and change into temporary directory, generate key pair and dummy material, read key pair. """ self.set_up_test_dir() self.key_path = "test_key" generate_and_write_unencrypted_rsa_keypair(self.key_path) self.key = import_rsa_privatekey_from_file(self.key_path) self.step_name = "test_step" self.link_name_unfinished = UNFINISHED_FILENAME_FORMAT.format(step_name=self.step_name, keyid=self.key["keyid"]) self.test_material = "test_material" Path(self.test_material).touch()
def setUpClass(cls) -> None: cls.repo_dir = os.path.join(utils.TESTS_DIR, "repository_data", "repository", "metadata") cls.metadata = {} for md in [ Root.type, Timestamp.type, Snapshot.type, Targets.type, "role1", "role2", ]: with open(os.path.join(cls.repo_dir, f"{md}.json"), "rb") as f: cls.metadata[md] = f.read() keystore_dir = os.path.join(utils.TESTS_DIR, "repository_data", "keystore") cls.keystore = {} root_key_dict = import_rsa_privatekey_from_file(os.path.join( keystore_dir, Root.type + "_key"), password="******") cls.keystore[Root.type] = SSlibSigner(root_key_dict) for role in [ "delegation", Snapshot.type, Targets.type, Timestamp.type ]: key_dict = import_ed25519_privatekey_from_file(os.path.join( keystore_dir, role + "_key"), password="******") cls.keystore[role] = SSlibSigner(key_dict) def hashes_length_modifier(timestamp: Timestamp) -> None: timestamp.snapshot_meta.hashes = None timestamp.snapshot_meta.length = None cls.metadata[Timestamp.type] = cls.modify_metadata( Timestamp.type, hashes_length_modifier)
def main(): # Load Alice's private key to later sign the layout key_alice = interface.import_rsa_privatekey_from_file("alice") # Fetch and load Bob's and Carl's public keys # to specify that they are authorized to perform certain step in the layout key_bob = interface.import_rsa_publickey_from_file( "../functionary_bob/bob.pub") key_carl = interface.import_rsa_publickey_from_file( "../functionary_carl/carl.pub") layout = Layout.read({ "_type": "layout", "keys": { key_bob["keyid"]: key_bob, key_carl["keyid"]: key_carl, }, "steps": [{ "name": "clone", "expected_materials": [], "expected_products": [["CREATE", "demo-project/foo.py"], ["DISALLOW", "*"]], "pubkeys": [key_bob["keyid"]], "expected_command": ["git", "clone", "https://github.com/in-toto/demo-project.git"], "threshold": 1, }, { "name": "update-version", "expected_materials": [["MATCH", "demo-project/*", "WITH", "PRODUCTS", "FROM", "clone"], ["DISALLOW", "*"]], "expected_products": [["ALLOW", "demo-project/foo.py"], ["DISALLOW", "*"]], "pubkeys": [key_bob["keyid"]], "expected_command": [], "threshold": 1, }, { "name": "package", "expected_materials": [ [ "MATCH", "demo-project/*", "WITH", "PRODUCTS", "FROM", "update-version" ], ["DISALLOW", "*"], ], "expected_products": [ ["CREATE", "demo-project.tar.gz"], ["DISALLOW", "*"], ], "pubkeys": [key_carl["keyid"]], "expected_command": [ "tar", "--exclude", ".git", "-zcvf", "demo-project.tar.gz", "demo-project", ], "threshold": 1, }], "inspect": [{ "name": "untar", "expected_materials": [ [ "MATCH", "demo-project.tar.gz", "WITH", "PRODUCTS", "FROM", "package" ], # FIXME: If the routine running inspections would gather the # materials/products to record from the rules we wouldn't have to # ALLOW other files that we aren't interested in. ["ALLOW", ".keep"], ["ALLOW", "alice.pub"], ["ALLOW", "root.layout"], ["DISALLOW", "*"] ], "expected_products": [ [ "MATCH", "demo-project/foo.py", "WITH", "PRODUCTS", "FROM", "update-version" ], # FIXME: See expected_materials above ["ALLOW", "demo-project/.git/*"], ["ALLOW", "demo-project.tar.gz"], ["ALLOW", ".keep"], ["ALLOW", "alice.pub"], ["ALLOW", "root.layout"], ["DISALLOW", "*"] ], "run": [ "tar", "xzf", "demo-project.tar.gz", ] }], }) metadata = DbomMetablock(signed=layout) # Sign and dump layout to "root.layout" metadata.sign(key_alice) metadata.save_owner_key(key_alice, assetID) metadata.dump("root.layout") metadata.save_layout("root.layout", assetID)
def main(): # Load Niels's private key to later sign the layout key_niels = interface.import_rsa_privatekey_from_file("niels") # Fetch and load aimee's and noud's public keys # to specify that they are authorized to perform certain step in the layout key_aimee = interface.import_rsa_publickey_from_file( "../functionary_aimee/aimee.pub") key_noud = interface.import_rsa_publickey_from_file( "../functionary_noud/noud.pub") layout = Layout.read({ "_type": "layout", "keys": { key_aimee["keyid"]: key_aimee, key_noud["keyid"]: key_noud, }, "steps": [{ "name": "create", "expected_materials": [], "expected_products": [["CREATE", "app/Program.cs"], ["DISALLOW", "*"]], "pubkeys": [key_aimee["keyid"]], "expected_command": ["dotnet", "new", "console", "-n", "app"], "threshold": 1, }, { "name": "publish", "expected_materials": [ [ "MATCH", "app/Program.cs", "WITH", "PRODUCTS", "FROM", "create" ], ["DISALLOW", "*"], ], "expected_products": [["CREATE", "published/app"], ["CREATE", "published/app.*"], ["DISALLOW", "*"]], "pubkeys": [key_noud["keyid"]], "expected_command": ["dotnet", "publish", "-o", "published", "app"], "threshold": 1, }, { "name": "package", "expected_materials": [ [ "MATCH", "published/*", "WITH", "PRODUCTS", "FROM", "publish" ], ["DISALLOW", "*"], ], "expected_products": [ ["CREATE", "published.tar.gz"], ["DISALLOW", "*"], ], "pubkeys": [key_noud["keyid"]], "expected_command": [ "tar", "-zcvf", "published.tar.gz", "published", ], "threshold": 1, }], "inspect": [{ "name": "untar", "expected_materials": [ [ "MATCH", "published.tar.gz", "WITH", "PRODUCTS", "FROM", "package" ], # FIXME: If the routine running inspections would gather the # materials/products to record from the rules we wouldn't have to # ALLOW other files that we aren't interested in. ["ALLOW", ".keep"], ["ALLOW", "niels.pub"], ["ALLOW", "root.layout"], ["DISALLOW", "*"] ], "expected_products": [ [ "MATCH", "published/*", "WITH", "PRODUCTS", "FROM", "publish" ], # FIXME: See expected_materials above ["ALLOW", "published/app.*"], ["ALLOW", "published.tar.gz"], ["ALLOW", ".keep"], ["ALLOW", "niels.pub"], ["ALLOW", "root.layout"], ["DISALLOW", "*"] ], "run": [ "tar", "xzf", "published.tar.gz", ] }], }) metadata = Metablock(signed=layout) # Sign and dump layout to "root.layout" metadata.sign(key_niels) metadata.dump("root.layout")
def update_link_metadata(checks, core_workflow=True): root = get_root() ensure_dir_exists(path_join(root, LINK_DIR)) # Sign only what affects each wheel products = [] for check in checks: products.append(path_join(check, 'datadog_checks')) products.append(path_join(check, 'setup.py')) if core_workflow: key_id = get_key_id(GPG_COMMAND) # Find this latest signed link metadata file on disk. # NOTE: in-toto currently uses the first 8 characters of the signing keyId. key_id_prefix = key_id[:8].lower() tag_link = f'{STEP_NAME}.{key_id_prefix}.link' # Normalize line endings, particularly on Windows, to deal with git. options = {'gpg_keyid': key_id, 'normalize_line_endings': True} else: signing_key_path = os.getenv('IN_TOTO_SIGNING_KEY_PATH', '') signing_key = import_rsa_privatekey_from_file( signing_key_path, os.getenv('IN_TOTO_SIGNING_KEY_PASSWORD')) # NOTE: in-toto currently uses the first 8 characters of the signing keyID, # the latter of which we assume is the key filename. key_id_prefix = signing_key['keyid'][:8].lower() tag_link = f'{STEP_NAME}.{key_id_prefix}.link' # Do not allow machine to normalize line endings, because it is not on Windows, # for which git might rewrite line endings in files. options = {'signing_key': signing_key, 'normalize_line_endings': False} # Final location of metadata file. metadata_file = path_join(LINK_DIR, tag_link) with chdir(root): # We should ignore products untracked and ignored by git. run_in_toto(products, **options) # Check whether each signed product is being tracked AND ignored by git. # NOTE: We have to check now *AFTER* signing the tag link file, so that # we can check against the actual complete list of products. with open(tag_link) as tag_json: tag = json.load(tag_json) products = tag['signed']['products'] for product in products: # If NOT tracked... if not tracked_by_git(product): # First, delete the tag link off disk so as not to pollute. os.remove(tag_link) # AND NOT ignored, then it most likely means the developer # forgot to add the file to git. if not ignored_by_git(product): raise NeitherTrackedNorIgnoredFileException(product) # AND ignored, then it most likely means that incorrectly # recorded with in-toto files ignored by git. else: raise UntrackedButIgnoredFileException(product) # Move it to the expected location. shutil.move(tag_link, metadata_file) return (metadata_file, )
def main(): # Load Jerry's private key to later sign the layout key_jerry = interface.import_rsa_privatekey_from_file("jerry") # Fetch and load Bob's and Alice's public keys # to specify that they are authorized to perform certain step in the layout key_alice = interface.import_rsa_publickey_from_file( "../functionary_alice/alice.pub") key_bob = interface.import_rsa_publickey_from_file( "../functionary_bob/bob.pub") layout = Layout.read({ "_type": "layout", "keys": { key_bob["keyid"]: key_bob, key_alice["keyid"]: key_alice, }, "steps": [{ "name": "clone", "expected_materials": [], "expected_products": [["CREATE", "inclavare-containers/rbi/kata-agent/Dockerfile"], [ "CREATE", "inclavare-containers/rbi/kata-agent/kata-build-docker.sh" ], [ "CREATE", "inclavare-containers/rbi/kata-agent/kata-source-code.sh" ], ["CREATE", "inclavare-containers/rbi/kata-agent/kata-test.sh"], [ "CREATE", "inclavare-containers/rbi/kata-agent/patch/Cargo.lock" ], ["CREATE", "inclavare-containers/rbi/kata-agent/patch/Makefile"], ["CREATE", "inclavare-containers/rbi/kata-agent/patch/protocols"], [ "CREATE", "inclavare-containers/rbi/kata-agent/patch/protocols/Cargo.toml" ], ["CREATE", "inclavare-containers/rbi/kata-agent/readme.md"], ["CREATE", "inclavare-containers/rbi/kata-agent/readme_cn.md"], [ "CREATE", "inclavare-containers/rbi/kata-agent/scripts/start.sh" ], ["CREATE", "inclavare-containers/rbi/misc/check-integrity.sh"]], "pubkeys": [key_alice["keyid"]], "expected_command": [ "git", "clone", "https://github.com/alibaba/inclavare-containers.git" ], "threshold": 1, }, { "name": "build", "expected_materials": [ [ "MATCH", "inclavare-containers/rbi/kata-agent/Dockerfile", "WITH", "PRODUCTS", "FROM", "clone" ], [ "MATCH", "inclavare-containers/rbi/kata-agent/kata-build-docker.sh", "WITH", "PRODUCTS", "FROM", "clone" ], [ "MATCH", "inclavare-containers/rbi/kata-agent/kata-source-code.sh", "WITH", "PRODUCTS", "FROM", "clone" ], [ "MATCH", "inclavare-containers/rbi/kata-agent/kata-test.sh", "WITH", "PRODUCTS", "FROM", "clone" ], [ "MATCH", "inclavare-containers/rbi/kata-agent/patch/Cargo.lock", "WITH", "PRODUCTS", "FROM", "clone" ], [ "MATCH", "inclavare-containers/rbi/kata-agent/patch/Makefile", "WITH", "PRODUCTS", "FROM", "clone" ], [ "MATCH", "inclavare-containers/rbi/kata-agent/patch/protocols", "WITH", "PRODUCTS", "FROM", "clone" ], [ "MATCH", "inclavare-containers/rbi/kata-agent/patch/protocols/Cargo.toml", "WITH", "PRODUCTS", "FROM", "clone" ], [ "MATCH", "inclavare-containers/rbi/kata-agent/readme.md", "WITH", "PRODUCTS", "FROM", "clone" ], [ "MATCH", "inclavare-containers/rbi/kata-agent/readme_cn.md", "WITH", "PRODUCTS", "FROM", "clone" ], [ "MATCH", "inclavare-containers/rbi/kata-agent/scripts/start.sh", "WITH", "PRODUCTS", "FROM", "clone" ] ], "expected_products": [ [ "CREATE", "inclavare-containers/rbi/result/kata-agent/kata-agent" ], ], "pubkeys": [key_bob["keyid"]], "expected_command": [ "bash", "inclavare-containers/rbi/rbi.sh", "agent", ], "threshold": 1, }], "inspect": [{ "name": "integrity", "expected_materials": [ [ "MATCH", "inclavare-containers/rbi/result/kata-agent/kata-agent", "WITH", "PRODUCTS", "FROM", "build" ], [ "MATCH", "inclavare-containers/rbi/misc/check-integrity.sh", "WITH", "PRODUCTS", "FROM", "clone" ], # FIXME: If the routine running inspections would gather the # materials/products to record from the rules we wouldn't have to # ALLOW other files that we aren't interested in. ["ALLOW", "jerry.pub"], ["ALLOW", "root.layout"], ["ALLOW", ".keep"] ], "expected_products": [ [ "CREATE", "inclavare-containers/rbi/result/kata-agent/.check_done" ], # FIXME: See expected_materials above ["ALLOW", "jerry.pub"], ["ALLOW", "root.layout"], ["ALLOW", ".keep"] ], "run": [ "bash", "inclavare-containers/rbi/misc/check-integrity.sh", "inclavare-containers/rbi/result/kata-agent/kata-agent", SHA256_VALUE ] }], }) metadata = Metablock(signed=layout) # Sign and dump layout to "root.layout" metadata.sign(key_jerry) metadata.dump("root.layout")
def test_generate_and_write_rsa_keypair(self): # Test normal case. temporary_directory = tempfile.mkdtemp(dir=self.temporary_directory) test_keypath = os.path.join(temporary_directory, 'rsa_key') test_keypath_unencrypted = os.path.join(temporary_directory, 'rsa_key_unencrypted') returned_path = interface.generate_and_write_rsa_keypair(test_keypath, password='******') self.assertTrue(os.path.exists(test_keypath)) self.assertTrue(os.path.exists(test_keypath + '.pub')) self.assertEqual(returned_path, test_keypath) # If an empty string is given for 'password', the private key file # is written to disk unencrypted. interface.generate_and_write_rsa_keypair(test_keypath_unencrypted, password='') self.assertTrue(os.path.exists(test_keypath_unencrypted)) self.assertTrue(os.path.exists(test_keypath_unencrypted + '.pub')) # Ensure the generated key files are importable. scheme = 'rsassa-pss-sha256' imported_pubkey = \ interface.import_rsa_publickey_from_file(test_keypath + '.pub') self.assertTrue(securesystemslib.formats.RSAKEY_SCHEMA.matches(imported_pubkey)) imported_privkey = interface.import_rsa_privatekey_from_file(test_keypath, 'pw') self.assertTrue(securesystemslib.formats.RSAKEY_SCHEMA.matches(imported_privkey)) # Try to import the unencrypted key file, by not passing a password imported_privkey = interface.import_rsa_privatekey_from_file(test_keypath_unencrypted) self.assertTrue(securesystemslib.formats.RSAKEY_SCHEMA.matches(imported_privkey)) # Try to import the unencrypted key file, by entering an empty password with mock.patch('securesystemslib.interface.get_password', return_value=''): imported_privkey = \ interface.import_rsa_privatekey_from_file(test_keypath_unencrypted, prompt=True) self.assertTrue( securesystemslib.formats.RSAKEY_SCHEMA.matches(imported_privkey)) # Fail importing unencrypted key passing a password with self.assertRaises(securesystemslib.exceptions.CryptoError): interface.import_rsa_privatekey_from_file(test_keypath_unencrypted, 'pw') # Fail importing encrypted key passing no password with self.assertRaises(securesystemslib.exceptions.CryptoError): interface.import_rsa_privatekey_from_file(test_keypath) # Custom 'bits' argument. os.remove(test_keypath) os.remove(test_keypath + '.pub') interface.generate_and_write_rsa_keypair(test_keypath, bits=2048, password='******') self.assertTrue(os.path.exists(test_keypath)) self.assertTrue(os.path.exists(test_keypath + '.pub')) # Test for a default filepath. If 'filepath' is not given, the key's # KEYID is used as the filename. The key is saved to the current working # directory. default_keypath = interface.generate_and_write_rsa_keypair(password='******') self.assertTrue(os.path.exists(default_keypath)) self.assertTrue(os.path.exists(default_keypath + '.pub')) written_key = interface.import_rsa_publickey_from_file(default_keypath + '.pub') self.assertEqual(written_key['keyid'], os.path.basename(default_keypath)) os.remove(default_keypath) os.remove(default_keypath + '.pub') # Test improperly formatted arguments. self.assertRaises(securesystemslib.exceptions.FormatError, interface.generate_and_write_rsa_keypair, 3, bits=2048, password='******') self.assertRaises(securesystemslib.exceptions.FormatError, interface.generate_and_write_rsa_keypair, test_keypath, bits='bad', password='******') self.assertRaises(securesystemslib.exceptions.FormatError, interface.generate_and_write_rsa_keypair, test_keypath, bits=2048, password=3) # Test invalid 'bits' argument. self.assertRaises(securesystemslib.exceptions.FormatError, interface.generate_and_write_rsa_keypair, test_keypath, bits=1024, password='******')
def main(): # Load Jerry's private key to later sign the layout key_jerry = interface.import_rsa_privatekey_from_file("jerry") # Fetch and load Bob's and Alice's public keys # to specify that they are authorized to perform certain step in the layout key_alice = interface.import_rsa_publickey_from_file( "../functionary_alice/alice.pub") key_bob = interface.import_rsa_publickey_from_file( "../functionary_bob/bob.pub") layout = Layout.read({ "_type": "layout", "keys": { key_bob["keyid"]: key_bob, key_alice["keyid"]: key_alice, }, "steps": [{ "name": "clone", "expected_materials": [], "expected_products": [["CREATE", "inclavare-containers/rbi/kernel/Dockerfile"], [ "CREATE", "inclavare-containers/rbi/kernel/build-docker-image.sh" ], ["CREATE", "inclavare-containers/rbi/kernel/build-kernel.sh"], ["CREATE", "inclavare-containers/rbi/kernel/check-integrity.sh"], [ "CREATE", "inclavare-containers/rbi/kernel/patch/build-kernel.sh" ], ["CREATE", "inclavare-containers/rbi/kernel/scripts/start.sh"], ["CREATE", "inclavare-containers/rbi/misc/check-integrity.sh"]], "pubkeys": [key_alice["keyid"]], "expected_command": [ "git", "clone", "https://github.com/alibaba/inclavare-containers.git" ], "threshold": 1, }, { "name": "build", "expected_materials": [[ "MATCH", "inclavare-containers/rbi/kernel/Dockerfile", "WITH", "PRODUCTS", "FROM", "clone" ], [ "MATCH", "inclavare-containers/rbi/kernel/build-docker-image.sh", "WITH", "PRODUCTS", "FROM", "clone" ], [ "MATCH", "inclavare-containers/rbi/kernel/build-kernel.sh", "WITH", "PRODUCTS", "FROM", "clone" ], [ "MATCH", "inclavare-containers/rbi/kernel/check-integrity.sh", "WITH", "PRODUCTS", "FROM", "clone" ], [ "MATCH", "inclavare-containers/rbi/kernel/patch/build-kernel.sh", "WITH", "PRODUCTS", "FROM", "clone" ], [ "MATCH", "inclavare-containers/rbi/kernel/readme.md", "WITH", "PRODUCTS", "FROM", "clone" ], [ "MATCH", "inclavare-containers/rbi/kernel/scripts/start.sh", "WITH", "PRODUCTS", "FROM", "clone" ]], "expected_products": [ ["CREATE", "inclavare-containers/rbi/result/kernel/vmlinux"], ], "pubkeys": [key_bob["keyid"]], "expected_command": [ "bash", "inclavare-containers/rbi/rbi.sh", "kernel", ], "threshold": 1, }], "inspect": [{ "name": "integrity", "expected_materials": [ [ "MATCH", "inclavare-containers/rbi/result/kernel/vmlinux", "WITH", "PRODUCTS", "FROM", "build" ], [ "MATCH", "inclavare-containers/rbi/misc/check-integrity.sh", "WITH", "PRODUCTS", "FROM", "clone" ], ["ALLOW", "jerry.pub"], ["ALLOW", "root.layout"], ["ALLOW", ".keep"] ], "expected_products": [ [ "CREATE", "inclavare-containers/rbi/result/kernel/.check_done" ], # FIXME: See expected_materials above ["ALLOW", "jerry.pub"], ["ALLOW", "root.layout"], ["ALLOW", ".keep"] ], "run": [ "bash", "inclavare-containers/rbi/misc/check-integrity.sh", "inclavare-containers/rbi/result/kernel/vmlinux", SHA256_VALUE ] }], }) metadata = Metablock(signed=layout) # Sign and dump layout to "root.layout" metadata.sign(key_jerry) metadata.dump("root.layout")
def test_rsa(self): """Test RSA key _generation and import interface functions. """ # TEST: Generate default keys and import # Assert location and format fn_default = "default" fn_default_ret = _generate_and_write_rsa_keypair(filepath=fn_default) pub = import_rsa_publickey_from_file(fn_default + ".pub") priv = import_rsa_privatekey_from_file(fn_default) self.assertEqual(fn_default, fn_default_ret) self.assertTrue(RSAKEY_SCHEMA.matches(pub)) self.assertTrue(PUBLIC_KEY_SCHEMA.matches(pub)) self.assertTrue(RSAKEY_SCHEMA.matches(priv)) # NOTE: There is no private key schema, at least check it has a value self.assertTrue(priv["keyval"]["private"]) # TEST: Generate unencrypted keys with empty prompt # Assert importable without password fn_empty_prompt = "empty_prompt" with mock.patch("securesystemslib.interface.get_password", return_value=""): _generate_and_write_rsa_keypair(filepath=fn_empty_prompt, prompt=True) import_rsa_privatekey_from_file(fn_empty_prompt) # TEST: Generate keys with auto-filename, i.e. keyid # Assert filename is keyid fn_keyid = _generate_and_write_rsa_keypair() pub = import_rsa_publickey_from_file(fn_keyid + ".pub") priv = import_rsa_privatekey_from_file(fn_keyid) self.assertTrue( os.path.basename(fn_keyid) == pub["keyid"] == priv["keyid"]) # TEST: Generate keys with custom bits # Assert length bits = 4096 fn_bits = "bits" _generate_and_write_rsa_keypair(filepath=fn_bits, bits=bits) priv = import_rsa_privatekey_from_file(fn_bits) # NOTE: Parse PEM with pyca/cryptography to get the key size property obj_bits = load_pem_private_key( priv["keyval"]["private"].encode("utf-8"), password=None, backend=default_backend()) self.assertEqual(obj_bits.key_size, bits) # TEST: Generate two keypairs with encrypted private keys using ... pw = "pw" fn_encrypted = "encrypted" fn_prompt = "prompt" # ... a passed pw ... _generate_and_write_rsa_keypair(filepath=fn_encrypted, password=pw) with mock.patch("securesystemslib.interface.get_password", return_value=pw): # ... and a prompted pw. _generate_and_write_rsa_keypair(filepath=fn_prompt, prompt=True) # Assert that both private keys are importable using the prompted pw ... import_rsa_privatekey_from_file(fn_prompt, prompt=True) import_rsa_privatekey_from_file(fn_encrypted, prompt=True) # ... and the passed pw. import_rsa_privatekey_from_file(fn_prompt, password=pw) import_rsa_privatekey_from_file(fn_encrypted, password=pw) # TEST: Import existing keys with encrypted private key (test regression) # Assert format pub = import_rsa_publickey_from_file(self.path_rsa + ".pub") priv = import_rsa_privatekey_from_file(self.path_rsa, "password") self.assertTrue(RSAKEY_SCHEMA.matches(pub)) self.assertTrue(PUBLIC_KEY_SCHEMA.matches(pub)) self.assertTrue(RSAKEY_SCHEMA.matches(priv)) # NOTE: There is no private key schema, at least check it has a value self.assertTrue(priv["keyval"]["private"]) # TEST: Generation errors for idx, (kwargs, err_msg) in enumerate([ # Error on empty password ({ "password": "" }, "encryption password must be 1 or more characters long"), # Error on 'password' and 'prompt=True' ({ "password": pw, "prompt": True }, "passing 'password' and 'prompt=True' is not allowed") ]): with self.assertRaises(ValueError, msg="(row {})".format(idx)) as ctx: _generate_and_write_rsa_keypair(**kwargs) self.assertEqual( err_msg, str(ctx.exception), "expected: '{}' got: '{}' (row {})".format( err_msg, ctx.exception, idx)) # Error on bad argument format for idx, kwargs in enumerate([ { "bits": 1024 }, # Too low { "bits": "not-an-int" }, { "filepath": 123456 }, # Not a string { "password": 123456 }, # Not a string { "prompt": "not-a-bool" } ]): with self.assertRaises(FormatError, msg="(row {})".format(idx)): _generate_and_write_rsa_keypair(**kwargs) # TEST: Import errors # Error public key import err_msg = "Invalid public pem" with self.assertRaises(Error) as ctx: import_rsa_publickey_from_file(fn_default) self.assertTrue( err_msg in str(ctx.exception), "expected: '{}' got: '{}'".format(err_msg, ctx.exception)) # Error on private key import... for idx, (args, kwargs, err, err_msg) in enumerate([ # Error on not a private key ([fn_default + ".pub"], {}, CryptoError, "Could not deserialize key data"), # Error on not encrypted ([fn_default], { "password": pw }, CryptoError, "Password was given but private key is not encrypted"), # Error on encrypted but no pw ([fn_encrypted], {}, CryptoError, "Password was not given but private key is encrypted"), # Error on encrypted but empty pw passed ([fn_encrypted], { "password": "" }, CryptoError, "Password was not given but private key is encrypted"), # Error on encrypted but bad pw passed ([fn_encrypted], { "password": "******" }, CryptoError, "Bad decrypt. Incorrect password?"), # Error on pw and prompt ([fn_default], { "password": pw, "prompt": True }, ValueError, "passing 'password' and 'prompt=True' is not allowed") ]): with self.assertRaises(err, msg="(row {})".format(idx)) as ctx: import_rsa_privatekey_from_file(*args, **kwargs) self.assertTrue( err_msg in str(ctx.exception), "expected: '{}' got: '{}' (row {})".format( err_msg, ctx.exception, idx)) # Error on encrypted but bad pw prompted err_msg = "Password was not given but private key is encrypted" with self.assertRaises(CryptoError) as ctx, mock.patch( "securesystemslib.interface.get_password", return_value="bad_pw"): import_rsa_privatekey_from_file(fn_encrypted) self.assertTrue( err_msg in str(ctx.exception), "expected: '{}' got: '{}'".format(err_msg, ctx.exception)) # Error on bad argument format for idx, (args, kwargs) in enumerate([ ([123456], {}), # bad path ([fn_default], { "scheme": 123456 }), # bad scheme ([fn_default], { "scheme": "bad scheme" }) # bad scheme ]): with self.assertRaises(FormatError, msg="(row {})".format(idx)): import_rsa_publickey_from_file(*args, **kwargs) with self.assertRaises(FormatError, msg="(row {})".format(idx)): import_rsa_privatekey_from_file(*args, **kwargs) # bad password with self.assertRaises(FormatError): import_rsa_privatekey_from_file(fn_default, password=123456) # bad prompt with self.assertRaises(FormatError): import_rsa_privatekey_from_file(fn_default, prompt="not-a-bool")
def setUp(self): """Imports keys, generates layout and layout_key_dict for the clone, update-version, and package step.""" self.key_alice = interface.import_rsa_privatekey_from_file( "keys/alice") # Fetch and load Bob's and Carl's public keys # to specify that they are authorized to perform certain step in the layout self.key_bob = interface.import_rsa_publickey_from_file("keys/bob.pub") self.key_carl = interface.import_rsa_publickey_from_file( "keys/carl.pub") # Create layout self.layout = Layout.read({ "_type": "layout", "keys": { self.key_bob["keyid"]: self.key_bob, self.key_carl["keyid"]: self.key_carl, }, "steps": [{ "name": "clone", "expected_materials": [], "expected_products": [["CREATE", "demo-project/foo.py"], ["DISALLOW", "*"]], "pubkeys": [self.key_bob["keyid"]], "expected_command": [ "git", "clone", "https://github.com/in-toto/demo-project.git" ], "threshold": 1, }, { "name": "update-version", "expected_materials": [[ "MATCH", "demo-project/*", "WITH", "PRODUCTS", "FROM", "clone" ], ["DISALLOW", "*"]], "expected_products": [["ALLOW", "demo-project/foo.py"], ["DISALLOW", "*"]], "pubkeys": [self.key_bob["keyid"]], "expected_command": [], "threshold": 1, }, { "name": "package", "expected_materials": [ [ "MATCH", "demo-project/*", "WITH", "PRODUCTS", "FROM", "update-version" ], ["DISALLOW", "*"], ], "expected_products": [ ["CREATE", "demo-project.tar.gz"], ["DISALLOW", "*"], ], "pubkeys": [self.key_carl["keyid"]], "expected_command": [ "tar", "--exclude", ".git", "-zcvf", "demo-project.tar.gz", "demo-project", ], "threshold": 1, }], "inspect": [], }) self.metadata = Metablock(signed=self.layout) self.metadata.sign(self.key_alice) # Create the public key dict self.layout_key_dict = {} self.layout_key_dict.update( interface.import_publickeys_from_file(["keys/alice.pub"]))