diff --git a/cert_issuer/blockchain_handlers/bitcoin/__init__.py b/cert_issuer/blockchain_handlers/bitcoin/__init__.py index 5548c6a2..9a12f364 100644 --- a/cert_issuer/blockchain_handlers/bitcoin/__init__.py +++ b/cert_issuer/blockchain_handlers/bitcoin/__init__.py @@ -49,12 +49,12 @@ def instantiate_blockchain_handlers(app_config, file_mode=True): if file_mode: certificate_batch_handler = CertificateBatchHandler(secret_manager=secret_manager, - certificate_handler=CertificateV3Handler(), + certificate_handler=CertificateV3Handler(app_config), merkle_tree=MerkleTreeGenerator(), config=app_config) else: certificate_batch_handler = CertificateBatchWebHandler(secret_manager=secret_manager, - certificate_handler=CertificateWebV3Handler(), + certificate_handler=CertificateWebV3Handler(app_config), merkle_tree=MerkleTreeGenerator(), config=app_config) if chain.is_mock_type(): diff --git a/cert_issuer/certificate_handlers.py b/cert_issuer/certificate_handlers.py index 857da667..86edaa83 100644 --- a/cert_issuer/certificate_handlers.py +++ b/cert_issuer/certificate_handlers.py @@ -11,6 +11,9 @@ class CertificateV3Handler(CertificateHandler): + def __init__(self, app_config): + self.app_config = app_config + def get_byte_array_to_issue(self, certificate_metadata): certificate_json = self._get_certificate_to_issue(certificate_metadata) return JSONLDHandler.normalize_to_utf8(certificate_json) @@ -22,7 +25,7 @@ def add_proof(self, certificate_metadata, merkle_proof): :return: """ certificate_json = self._get_certificate_to_issue(certificate_metadata) - certificate_json = ProofHandler().add_proof(certificate_json, merkle_proof) + certificate_json = ProofHandler().add_proof(certificate_json, merkle_proof, self.app_config) with open(certificate_metadata.blockchain_cert_file_name, 'w') as out_file: out_file.write(json.dumps(certificate_json)) @@ -33,11 +36,14 @@ def _get_certificate_to_issue(self, certificate_metadata): return certificate_json class CertificateWebV3Handler(CertificateHandler): + def __init__(self, app_config): + self.app_config = app_config + def get_byte_array_to_issue(self, certificate_json): return JSONLDHandler.normalize_to_utf8(certificate_json) def add_proof(self, certificate_json, merkle_proof): - certificate_json = ProofHandler().add_proof(certificate_json, merkle_proof) + certificate_json = ProofHandler().add_proof(certificate_json, merkle_proof, self.app_config) return certificate_json class CertificateBatchWebHandler(BatchHandler): diff --git a/cert_issuer/config.py b/cert_issuer/config.py index 16dbb644..c7dad9d8 100644 --- a/cert_issuer/config.py +++ b/cert_issuer/config.py @@ -114,6 +114,18 @@ def add_arguments(p): env_var='CONTEXT_FILE_PATHS', nargs='+' ) + p.add_argument('--multiple_proofs', + default='chained', + type=str, + choices=['chained', 'concurrent'], + help='How to handle a document that was previously signed by another party. \n' + + 'If the document has not been signed yet, a single proof will be added. \n' + + '"chained": Chained proof also sign the previous proof(s) of the document, making them ' + + 'untemperable with in the final document, ie: a notary signs over the signatures of the buyer ' + + 'and the seller in a real estate transaction. \n' + + '"concurrent": Concurrent proofs mean the parties independently sign the document without ' + + 'the other parties\' signatures. Defaults to chained proofs.' + ) def get_config(): diff --git a/cert_issuer/proof_handler.py b/cert_issuer/proof_handler.py index 3f10c799..cd477a83 100644 --- a/cert_issuer/proof_handler.py +++ b/cert_issuer/proof_handler.py @@ -6,20 +6,31 @@ class ProofHandler: def __init__(self): self.contextUrls = ContextUrls() - def add_proof(self, certificate_json, merkle_proof): + def add_proof(self, certificate_json, merkle_proof, app_config=None): if 'proof' in certificate_json: if not isinstance(certificate_json['proof'], list): # convert proof to list initial_proof = certificate_json['proof'] certificate_json['proof'] = [initial_proof] - previous_proof = certificate_json['proof'][-1] - certificate_json['proof'].append(ChainedProof2021(previous_proof, merkle_proof).to_json_object()) - self.update_context_for_chained_proof(certificate_json) + if self.is_multiple_proof_config_chained(app_config): + self.add_chained_proof(certificate_json, merkle_proof) + else: + certificate_json['proof'].append(merkle_proof) else: certificate_json['proof'] = merkle_proof self.update_context_for_single_proof(certificate_json) return certificate_json + def is_multiple_proof_config_chained(self, app_config): + if app_config is None: + return True + return app_config.multiple_proofs == 'chained' + + def add_chained_proof(self, certificate_json, merkle_proof): + previous_proof = certificate_json['proof'][-1] + certificate_json['proof'].append(ChainedProof2021(previous_proof, merkle_proof).to_json_object()) + self.update_context_for_chained_proof(certificate_json) + def update_context_for_chained_proof(self, certificate_json): context = certificate_json['@context'] if self.contextUrls.merkle_proof_2019() not in context: diff --git a/tests/test_certificate_handler.py b/tests/test_certificate_handler.py index f9bbe1b8..7e730bf3 100644 --- a/tests/test_certificate_handler.py +++ b/tests/test_certificate_handler.py @@ -189,7 +189,7 @@ def xtest_add_proof(self, mock_open): assert file_call in call_strings def test_web_add_proof(self): - handler = CertificateWebV3Handler() + handler = CertificateWebV3Handler(None) proof = {'a': 'merkel'} chain = mock.Mock() certificate_json = { diff --git a/tests/test_proof_handler.py b/tests/test_proof_handler.py index b23ef4ca..19263d95 100644 --- a/tests/test_proof_handler.py +++ b/tests/test_proof_handler.py @@ -43,6 +43,38 @@ def test_single_signature_3_1_update_context_merkle_proof_2019(self): output = self.handler.add_proof(fixture_certificate_json, fixture_proof) self.assertIn(self.contextUrls.merkle_proof_2019(), output['@context']) + def test_multiple_non_chained_signature(self): + fixture_initial_proof = { + 'type': 'Ed25519Signature2020', + 'created': '2022-05-02T16:36:22.933Z', + 'verificationMethod': 'did:key:z6MkjHnntGvtLjwfAMHWTAXXGJHhVL3DPtaT9BHmyTjWpjqs#z6MkjHnntGvtLjwfAMHWTAXXGJHhVL3DPtaT9BHmyTjWpjqs', + 'proofPurpose': 'assertionMethod', + 'proofValue': 'zAvFt59599JweBZ4zPP6Ge8LhKgECtBvDRmjG5VQbgEkPCiyMcM9QAPanJgSCs6RRGcKu96qNpfmpe9eTygpFZP6' + } + fixture_certificate_json = { + '@context': [ + 'https://www.w3.org/2018/credentials/v1', + 'https://w3id.org/blockcerts/v3' + ], + 'kek': 'kek', + 'proof': fixture_initial_proof + } + fixture_proof = { + 'type': 'MerkleProof2019', + 'created': '2022-05-05T08:05:14.912828', + 'proofValue': 'zMcm4LfQFUZkWZyLJp1bqtXF8vkZZwp79x7Nvt5BmN2XV4usLLtDoeqiq3et923mcWfXde4a3m4f57yUZcATCbBXV1byb5AXbV8EzT6E8B9JKf3scvxxZCBVePtV4SrhYysAiLNJ9N2R8LgnpJ47wnQHkaTB1AMxrcLEHUTxm4zJTtQqf9orDLf3L4VoLzmST7ZzsDjuX9cw2hZ3Aazhhjy7swG44xfF1PC73SyCv77pDnJ6BSHm3azmbVG6BXv1EPtwF4J1YRqwojBEWk9nDgduACR7b9qNhQ46ND4B5vL8p3LkqTh', + 'proofPurpose': 'assertionMethod', + 'verificationMethod': 'did:ion:EiA_Z6LQILbB2zj_eVrqfQ2xDm4HNqeJUw5Kj2Z7bFOOeQ#key-1' + } + class MockConfig: + multiple_proofs = 'concurrent' + + output = self.handler.add_proof(fixture_certificate_json, fixture_proof, MockConfig()) + self.assertEqual(output['proof'], [ + fixture_initial_proof, + fixture_proof + ]) + def test_multiple_two_chained_signature(self): fixture_initial_proof = { 'type': 'Ed25519Signature2020',