diff --git a/datatrails_scitt_samples/scripts/verify_receipt.py b/datatrails_scitt_samples/scripts/verify_receipt.py index d2660e8..4289573 100644 --- a/datatrails_scitt_samples/scripts/verify_receipt.py +++ b/datatrails_scitt_samples/scripts/verify_receipt.py @@ -4,6 +4,8 @@ import sys import json +from requests import HTTPError + from pycose.messages import Sign1Message from datatrails_scitt_samples.cose_receipt_verification import verify_receipt_mmriver @@ -50,7 +52,7 @@ def verify_transparent_statement( return verify_receipt_mmriver(receipt_bytes, leaf) -def main(): +def main(args=None) -> bool: """Verifies a counter signed receipt signature""" parser = argparse.ArgumentParser( @@ -91,7 +93,7 @@ def main(): default="transparent-statement.cbor", ) - args = parser.parse_args() + args = parser.parse_args(args or sys.argv[1:]) # Note: the context is only used if --entryid is # used to obtain the leaf hash directly from datatrails @@ -101,25 +103,36 @@ def main(): ctx = ServiceContext.from_env("verify-receipt", **cfg_overrides) if not (args.leaf or args.event_json_file or args.entryid): - print("either --leaf or --event-json-file is required", file=sys.stderr) - sys.exit(1) + ctx.error("either --leaf or --event-json-file is required") + return False leaf = None if args.leaf: - leaf = bytes.fromhex(args.leaf) + try: + leaf = bytes.fromhex(args.leaf) + except ValueError: + ctx.error("failed to parse leaf hash") + return False + elif args.event_json_file: - event = json.loads(open_event_json(args.event_json_file)) + try: + event = json.loads(open_event_json(args.event_json_file)) + except ValueError: + ctx.error("failed to parse event json") + return False leaf = v3leaf_hash(event) - print(leaf.hex()) elif args.entryid: identity = entryid_to_identity(args.entryid) - event = get_event(ctx, identity, True) + try: + event = get_event(ctx, identity, True) + except HTTPError as e: + ctx.error("failed to obtain event: %s", e) + return False leaf = v3leaf_hash(event) - print(leaf.hex()) if leaf is None: - print("failed to obtain leaf hash", file=sys.stderr) - sys.exit(1) + ctx.error("failed to obtain leaf hash") + return False if args.receipt_file: with open(args.receipt_file, "rb") as file: @@ -132,10 +145,13 @@ def main(): verified = verify_transparent_statement(transparent_statement, leaf) if verified: - print("signature verification succeeded") - else: - print("signature verification failed") + print("verification succeeded") + return True + print("verification failed") + return False if __name__ == "__main__": - main() + if not main(): + sys.exit(1) + sys.exit(0) diff --git a/tests/test_register_signed_statement.py b/tests/test_register_signed_statement.py index f6b7471..2723cd3 100644 --- a/tests/test_register_signed_statement.py +++ b/tests/test_register_signed_statement.py @@ -48,7 +48,6 @@ def test_create_and_register_statement(self): # create a signed statement create_hashed_signed_statement( [ - "--use-draft-04-labels", # TEMPORARY: Until backend support catches up "--signing-key-file", "my-signing-key.pem", "--payload-file", diff --git a/tests/test_verify_receipt.py b/tests/test_verify_receipt.py new file mode 100644 index 0000000..1795726 --- /dev/null +++ b/tests/test_verify_receipt.py @@ -0,0 +1,429 @@ +""" +Positive test cases for verify_receipt +""" + +import os +import json +import io +import unittest +import shutil +import tempfile +import unittest +from contextlib import redirect_stdout + +from datatrails_scitt_samples.datatrails.servicecontext import ServiceContext + +from datatrails_scitt_samples.datatrails.eventpreimage import get_event +from datatrails_scitt_samples.datatrails.entryid import entryid_to_identity + + +from datatrails_scitt_samples.scripts.generate_example_key import ( + main as generate_example_key, +) +from datatrails_scitt_samples.scripts.create_hashed_signed_statement import ( + main as create_hashed_signed_statement, +) +from datatrails_scitt_samples.scripts.register_signed_statement import ( + main as register_signed_statement, +) +from datatrails_scitt_samples.scripts.verify_receipt import ( + main as verify_receipt, +) + + +class TestVerifyReciept(unittest.TestCase): + """ + Tests verification of a known receipt. + """ + + def setUp(self): + self.test_dir = tempfile.mkdtemp() + self.parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + self.bad_json_file = os.path.join(self.test_dir, "bad.json") + with open(self.bad_json_file, "w") as file: + file.write("this is not json") + + def tearDown(self): + shutil.rmtree(self.test_dir) + + @unittest.skipUnless( + os.getenv("DATATRAILS_CLIENT_SECRET") != "", + "test requires authentication via env DATATRAILS_xxx", + ) + def test_verify_failed_for_tampered_event(self): + """ + registers a statement then verifies its receipt + """ + # generate an example key + generate_example_key(["--signing-key-file", "my-signing-key.pem"]) + + # create a signed statement + create_hashed_signed_statement( + [ + "--signing-key-file", + "my-signing-key.pem", + "--payload-file", + os.path.join( + self.parent_dir, + "datatrails_scitt_samples", + "artifacts", + "thedroid.json", + ), + "--content-type", + "application/json", + "--subject", + "TestRegisterSignedStatement:test_create_and_register_statement", + "--issuer", + "https://github.com/datatrails/datatrails-scitt-samples", + "--output-file", + f"{self.test_dir}/signed-statement.cbor", + ] + ) + self.assertTrue(os.path.exists(f"{self.test_dir}/signed-statement.cbor")) + + # register the signed statement + output = io.StringIO() + with redirect_stdout(output): + register_signed_statement( + [ + "--signed-statement-file", + f"{self.test_dir}/signed-statement.cbor", + "--output-file", + f"{self.test_dir}/transparent-statement.cbor", + "--output-receipt-file", + f"{self.test_dir}/statement-receipt.cbor", + ] + ) + + result = json.loads(output.getvalue()) + self.assertTrue("leaf" in result) + self.assertTrue("entryid" in result) + self.assertTrue(os.path.exists(f"{self.test_dir}/statement-receipt.cbor")) + self.assertTrue(os.path.exists(f"{self.test_dir}/transparent-statement.cbor")) + + entryid = result["entryid"] + identity = entryid_to_identity(entryid) + ctx = ServiceContext.from_env("tests") + event = get_event(ctx, identity, True) + + event_json_file = os.path.join(self.test_dir, f"{entryid}.json") + with open(event_json_file, "w") as file: + file.write(json.dumps(event)) + + # First verify the event as is + + # Verify the leaf value directly + verified = False + output = io.StringIO() + with redirect_stdout(output): + verified = verify_receipt( + [ + "--transparent-statement-file", + f"{self.test_dir}/transparent-statement.cbor", + "--event-json-file", + event_json_file, + ] + ) + self.assertEqual(output.getvalue().strip(), "verification succeeded") + self.assertTrue(verified) + + event["event_attributes"]["test_verify_failed_for_tampered_event"] = "tampered" + with open(event_json_file, "w") as file: + file.write(json.dumps(event)) + + output = io.StringIO() + with redirect_stdout(output): + verified = verify_receipt( + [ + "--transparent-statement-file", + f"{self.test_dir}/transparent-statement.cbor", + "--event-json-file", + event_json_file, + ] + ) + self.assertEqual(output.getvalue().strip(), "verification failed") + self.assertFalse(verified) + + @unittest.skipUnless( + os.getenv("DATATRAILS_CLIENT_SECRET") != "", + "test requires authentication via env DATATRAILS_xxx", + ) + def test_verify_transparent_statement_by_leaf(self): + """ + registers a statement then verifies its receipt + """ + # generate an example key + generate_example_key(["--signing-key-file", "my-signing-key.pem"]) + + # create a signed statement + create_hashed_signed_statement( + [ + "--signing-key-file", + "my-signing-key.pem", + "--payload-file", + os.path.join( + self.parent_dir, + "datatrails_scitt_samples", + "artifacts", + "thedroid.json", + ), + "--content-type", + "application/json", + "--subject", + "TestRegisterSignedStatement:test_create_and_register_statement", + "--issuer", + "https://github.com/datatrails/datatrails-scitt-samples", + "--output-file", + f"{self.test_dir}/signed-statement.cbor", + ] + ) + self.assertTrue(os.path.exists(f"{self.test_dir}/signed-statement.cbor")) + + # register the signed statement + output = io.StringIO() + with redirect_stdout(output): + register_signed_statement( + [ + "--signed-statement-file", + f"{self.test_dir}/signed-statement.cbor", + "--output-file", + f"{self.test_dir}/transparent-statement.cbor", + "--output-receipt-file", + f"{self.test_dir}/statement-receipt.cbor", + ] + ) + + result = json.loads(output.getvalue()) + self.assertTrue("leaf" in result) + self.assertTrue("entryid" in result) + self.assertTrue(os.path.exists(f"{self.test_dir}/statement-receipt.cbor")) + self.assertTrue(os.path.exists(f"{self.test_dir}/transparent-statement.cbor")) + + # Verify the leaf value directly + verified = False + output = io.StringIO() + with redirect_stdout(output): + verified = verify_receipt( + [ + "--transparent-statement-file", + f"{self.test_dir}/transparent-statement.cbor", + "--leaf", + result["leaf"], + ] + ) + self.assertEqual(output.getvalue().strip(), "verification succeeded") + self.assertTrue(verified) + + @unittest.skipUnless( + os.getenv("DATATRAILS_CLIENT_SECRET") != "", + "test requires authentication via env DATATRAILS_xxx", + ) + def test_verify_transparent_statement_by_entryid(self): + """ + registers a statement then verifies its receipt + """ + # generate an example key + generate_example_key(["--signing-key-file", "my-signing-key.pem"]) + + # create a signed statement + create_hashed_signed_statement( + [ + "--signing-key-file", + "my-signing-key.pem", + "--payload-file", + os.path.join( + self.parent_dir, + "datatrails_scitt_samples", + "artifacts", + "thedroid.json", + ), + "--content-type", + "application/json", + "--subject", + "TestRegisterSignedStatement:test_create_and_register_statement", + "--issuer", + "https://github.com/datatrails/datatrails-scitt-samples", + "--output-file", + f"{self.test_dir}/signed-statement.cbor", + ] + ) + self.assertTrue(os.path.exists(f"{self.test_dir}/signed-statement.cbor")) + + # register the signed statement + output = io.StringIO() + with redirect_stdout(output): + register_signed_statement( + [ + "--signed-statement-file", + f"{self.test_dir}/signed-statement.cbor", + "--output-file", + f"{self.test_dir}/transparent-statement.cbor", + "--output-receipt-file", + f"{self.test_dir}/statement-receipt.cbor", + ] + ) + + result = json.loads(output.getvalue()) + self.assertTrue("leaf" in result) + self.assertTrue("entryid" in result) + self.assertTrue(os.path.exists(f"{self.test_dir}/statement-receipt.cbor")) + self.assertTrue(os.path.exists(f"{self.test_dir}/transparent-statement.cbor")) + + # Verify the leaf value directly + verified = False + output = io.StringIO() + with redirect_stdout(output): + verified = verify_receipt( + [ + "--transparent-statement-file", + f"{self.test_dir}/transparent-statement.cbor", + "--entryid", + result["entryid"], + ] + ) + self.assertEqual(output.getvalue().strip(), "verification succeeded") + self.assertTrue(verified) + + @unittest.skipUnless( + os.getenv("DATATRAILS_CLIENT_SECRET") != "", + "test requires authentication via env DATATRAILS_xxx", + ) + def test_verify_receipt_by_leaf(self): + """ + registers a statement then verifies its receipt + """ + # generate an example key + generate_example_key(["--signing-key-file", "my-signing-key.pem"]) + + # create a signed statement + create_hashed_signed_statement( + [ + "--signing-key-file", + "my-signing-key.pem", + "--payload-file", + os.path.join( + self.parent_dir, + "datatrails_scitt_samples", + "artifacts", + "thedroid.json", + ), + "--content-type", + "application/json", + "--subject", + "TestRegisterSignedStatement:test_create_and_register_statement", + "--issuer", + "https://github.com/datatrails/datatrails-scitt-samples", + "--output-file", + f"{self.test_dir}/signed-statement.cbor", + ] + ) + self.assertTrue(os.path.exists(f"{self.test_dir}/signed-statement.cbor")) + + # register the signed statement + output = io.StringIO() + with redirect_stdout(output): + register_signed_statement( + [ + "--signed-statement-file", + f"{self.test_dir}/signed-statement.cbor", + "--output-file", + f"{self.test_dir}/transparent-statement.cbor", + "--output-receipt-file", + f"{self.test_dir}/statement-receipt.cbor", + ] + ) + + result = json.loads(output.getvalue()) + self.assertTrue("leaf" in result) + self.assertTrue("entryid" in result) + self.assertTrue(os.path.exists(f"{self.test_dir}/statement-receipt.cbor")) + self.assertTrue(os.path.exists(f"{self.test_dir}/transparent-statement.cbor")) + + # Verify the leaf value directly + verified = False + output = io.StringIO() + with redirect_stdout(output): + verified = verify_receipt( + [ + "--receipt-file", + f"{self.test_dir}/statement-receipt.cbor", + "--leaf", + result["leaf"], + ] + ) + self.assertEqual(output.getvalue().strip(), "verification succeeded") + self.assertTrue(verified) + + @unittest.skipUnless( + os.getenv("DATATRAILS_CLIENT_SECRET") != "", + "test requires authentication via env DATATRAILS_xxx", + ) + def test_verify_receipt_by_entryid(self): + """ + registers a statement then verifies its receipt + """ + # generate an example key + generate_example_key(["--signing-key-file", "my-signing-key.pem"]) + + # create a signed statement + create_hashed_signed_statement( + [ + "--signing-key-file", + "my-signing-key.pem", + "--payload-file", + os.path.join( + self.parent_dir, + "datatrails_scitt_samples", + "artifacts", + "thedroid.json", + ), + "--content-type", + "application/json", + "--subject", + "TestRegisterSignedStatement:test_create_and_register_statement", + "--issuer", + "https://github.com/datatrails/datatrails-scitt-samples", + "--output-file", + f"{self.test_dir}/signed-statement.cbor", + ] + ) + self.assertTrue(os.path.exists(f"{self.test_dir}/signed-statement.cbor")) + + # register the signed statement + output = io.StringIO() + with redirect_stdout(output): + register_signed_statement( + [ + "--signed-statement-file", + f"{self.test_dir}/signed-statement.cbor", + "--output-file", + f"{self.test_dir}/transparent-statement.cbor", + "--output-receipt-file", + f"{self.test_dir}/statement-receipt.cbor", + ] + ) + + result = json.loads(output.getvalue()) + self.assertTrue("leaf" in result) + self.assertTrue("entryid" in result) + self.assertTrue(os.path.exists(f"{self.test_dir}/statement-receipt.cbor")) + self.assertTrue(os.path.exists(f"{self.test_dir}/transparent-statement.cbor")) + + # Verify the leaf value directly + verified = False + output = io.StringIO() + with redirect_stdout(output): + verified = verify_receipt( + [ + "--receipt-file", + f"{self.test_dir}/statement-receipt.cbor", + "--entryid", + result["entryid"], + ] + ) + self.assertEqual(output.getvalue().strip(), "verification succeeded") + self.assertTrue(verified) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_verify_receipt_bad_options.py b/tests/test_verify_receipt_bad_options.py new file mode 100644 index 0000000..55effd8 --- /dev/null +++ b/tests/test_verify_receipt_bad_options.py @@ -0,0 +1,87 @@ +""" +Negative test cases for verify_receipt +""" + +import os +import json +import io +import unittest +import shutil +import tempfile +import unittest +from contextlib import redirect_stdout + +from datatrails_scitt_samples.scripts.generate_example_key import ( + main as generate_example_key, +) +from datatrails_scitt_samples.scripts.create_hashed_signed_statement import ( + main as create_hashed_signed_statement, +) +from datatrails_scitt_samples.scripts.register_signed_statement import ( + main as register_signed_statement, +) +from datatrails_scitt_samples.scripts.verify_receipt import ( + main as verify_receipt, +) + + +class TestVerifyReciept(unittest.TestCase): + """ + Tests verification of a known receipt. + """ + + def setUp(self): + self.test_dir = tempfile.mkdtemp() + self.parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + self.bad_json_file = os.path.join(self.test_dir, "bad.json") + with open(self.bad_json_file, "w") as file: + file.write("this is not json") + + def tearDown(self): + shutil.rmtree(self.test_dir) + + def test_verify_receipt_leaf_not_hex(self): + """Cover various bad input cases""" + + # Leaf + verified = verify_receipt( + [ + "--transparent-statement-file", + f"{self.test_dir}/transparent-statement.cbor", + "--leaf", + "this is not hex", + ] + ) + self.assertFalse(verified) + + def test_verify_receipt_event_file_not_json(self): + """Cover various bad input cases""" + + # Leaf + verified = verify_receipt( + [ + "--transparent-statement-file", + f"{self.test_dir}/transparent-statement.cbor", + "--event-json-file", + self.bad_json_file, + ] + ) + self.assertFalse(verified) + + def test_verify_receipt_bad_entryid(self): + """Cover various bad input cases""" + + # Leaf + verified = verify_receipt( + [ + "--transparent-statement-file", + f"{self.test_dir}/transparent-statement.cbor", + "--entryid", + "this is not found", + ] + ) + self.assertFalse(verified) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_verify_receipt_signature.py b/tests/test_verify_receipt_signature.py deleted file mode 100644 index 608899f..0000000 --- a/tests/test_verify_receipt_signature.py +++ /dev/null @@ -1,21 +0,0 @@ -""" -Known Answer Test (KAT) unit tests for verifying a receipt -""" - -import unittest - -# from datatrails_scitt_samples.cose_receipt_verification import verify_receipt_mmriver -# from datatrails_scitt_samples.scripts.fileaccess import read_cbor_file -# from .constants import KNOWN_RECEIPT_FILE - - -class TestVerifyReciept(unittest.TestCase): - """ - Tests verification of a known receipt. - """ - - @unittest.skip("Requires knowing the leaf hash") - def test_verify_kat_receipt(self): - """ - tests we can verify the signature of a known receipt. - """