def test_invalid_entry(self): chain_id = secrets.token_hex(32) did = "{}:{}".format(DID_METHOD_NAME, chain_id) # Entry with invalid property names with pytest.raises(ValidationError): self.VALIDATOR.validate({ "added": { "managementKey": [ ManagementKey( alias="my-man-key-1", controller=did, key_type=KeyType.EdDSA, priority=0, ).to_entry_dict(did) ] } }) # Entry with invalid property names with pytest.raises(ValidationError): self.VALIDATOR.validate({ "add": { "managementKey": [ ManagementKey( alias="my-man-key-1", controller=did, key_type=KeyType.EdDSA, priority=0, ).to_entry_dict(did) ] }, "remove": { "managementKey": [{ "id": "management-1" }] }, }) # Entry with additional properties with pytest.raises(ValidationError): self.VALIDATOR.validate({ "add": { "managementKey": [ ManagementKey( alias="my-man-key-1", controller=did, key_type=KeyType.EdDSA, priority=0, ).to_entry_dict(did) ] }, "revoke": { "managementKey": [{ "id": "management-1" }] }, "additional": {}, })
def test_equality(self, controller): private_key = secrets.token_bytes(32) key1 = AbstractDIDKey( alias="test-key", key_type=KeyType.EdDSA, controller=controller, priority_requirement=1, private_key=private_key, ) key2 = AbstractDIDKey( alias="test-key", key_type=KeyType.EdDSA, controller=controller, priority_requirement=1, private_key=private_key, ) key3 = ManagementKey( alias="test-key", key_type=KeyType.EdDSA, controller=controller, priority_requirement=1, priority=0, private_key=private_key, ) key4 = AbstractDIDKey( alias="test-key-2", key_type=KeyType.EdDSA, controller=controller, priority_requirement=1, private_key=private_key, ) assert key1 == key2 assert key1.__eq__(key3) == NotImplemented assert key1 != key4
def _process_management_key_additions( entry_content, signing_key_required_priority, new_keys, active_keys, all_keys, chain_id, network, ): for key_data in entry_content["add"].get("managementKey", []): alias = _get_alias(key_data["id"]) if ( not validate_management_key_id_against_chain_id(key_data["id"], chain_id) or not validate_id_against_network(key_data["id"], network) or alias in new_keys or alias in active_keys ): return True, signing_key_required_priority new_management_key = ManagementKey.from_entry_dict(key_data) if new_management_key in all_keys: return True, signing_key_required_priority new_keys[alias] = new_management_key signing_key_required_priority = min( signing_key_required_priority, key_data["priority"] ) return False, signing_key_required_priority
def test_valid_entry(self): did = "{}:{}".format(DID_METHOD_NAME, secrets.token_hex(32)) validate_did_management_ext_ids_v100([b"DIDManagement", b"1.0.0"]) validate_did_management_ext_ids_v100( [b"DIDManagement", b"1.0.0", b"asdfasdfasdf"]) valid_entry = { "didMethodVersion": "0.2.0", "managementKey": [ ManagementKey( alias="my-man-key-1", controller=did, key_type=KeyType.EdDSA, priority=0, ).to_entry_dict(did), ManagementKey( alias="my-man-key-2", controller=did, key_type=KeyType.RSA, priority=1, ).to_entry_dict(did), ], "didKey": [ DIDKey( alias="my-did-key", controller=did, key_type=KeyType.RSA.ECDSA, purpose=DIDKeyPurpose.PublicKey, ).to_entry_dict(did) ], "service": [ Service( alias="gmail-service", service_type="email-service", endpoint="https://gmail.com", priority_requirement=1, custom_fields={ "description": "My email service" }, ).to_entry_dict(did) ], } self.VALIDATOR.validate(valid_entry)
def test_entry_with_invalid_did_method_version(self): did = "{}:{}".format(DID_METHOD_NAME, secrets.token_hex(32)) entry = { "didMethodVersion": "0.3.0", "managementKey": [ ManagementKey( alias="my-man-key-1", controller=did, key_type=KeyType.EdDSA, priority=0, ).to_entry_dict(did), ManagementKey( alias="my-man-key-2", controller=did, key_type=KeyType.RSA, priority=1, ).to_entry_dict(did), ], } with pytest.raises(ValidationError): self.VALIDATOR.validate(entry)
def test_entry_with_missing_required_fields(self): with pytest.raises(ValidationError): self.VALIDATOR.validate({}) missing_management_keys = json.dumps({"didMethodVersion": "0.2.0"}) with pytest.raises(ValidationError): self.VALIDATOR.validate(missing_management_keys) did = "{}:{}".format(DID_METHOD_NAME, secrets.token_hex(32)) missing_did_method_version = { "managementKey": [ ManagementKey( alias="my-man-key", controller=did, key_type=KeyType.EdDSA, priority=0, ).to_entry_dict(did) ] } with pytest.raises(ValidationError): self.VALIDATOR.validate(missing_did_method_version)
def management_key( self, alias, priority, key_type=KeyType.EdDSA, controller=None, priority_requirement=None, ): """ Creates a new management key for the DID. Parameters ---------- alias: str A human-readable nickname for the key. It should be unique across the keys defined in the DID document. priority: int A non-negative integer showing the hierarchical level of the key. Keys with lower priority override keys with higher priority. key_type: KeyType, optional Identifies the type of signature that the key pair can be used to generate and verify. controller: str, optional An entity that controls the key. It must be a valid DID. If the argument is not passed in, the default value is used which is the current DID itself. priority_requirement: int, optional A non-negative integer showing the minimum hierarchical level a key must have in order to remove this key. """ if not controller: controller = self.id key = ManagementKey(alias, priority, key_type, controller, priority_requirement) self._check_alias_is_unique_and_add_to_used(self.used_key_aliases, alias) self.management_keys.append(key) return self
def test_valid_entry(self): chain_id = secrets.token_hex(32) did = "{}:{}".format(DID_METHOD_NAME, chain_id) key_id = "{}#{}".format(did, "my-man-key-1") validate_did_update_ext_ids_v100( [b"DIDUpdate", b"1.0.0", key_id.encode("utf-8"), b"affe"], chain_id) # Entry with only additions should be valid self.VALIDATOR.validate({ "add": { "managementKey": [ ManagementKey( alias="my-man-key-1", controller=did, key_type=KeyType.EdDSA, priority=0, ).to_entry_dict(did), ManagementKey( alias="my-man-key-2", controller=did, key_type=KeyType.RSA, priority=1, ).to_entry_dict(did), ], "didKey": [ DIDKey( alias="my-did-key", controller=did, key_type=KeyType.RSA.ECDSA, purpose=DIDKeyPurpose.PublicKey, ).to_entry_dict(did) ], } }) # Entry with only revocations should be valid self.VALIDATOR.validate({ "revoke": { "managementKey": [{ "id": "management-key-1" }], "didKey": [ { "id": "did-key-1" }, { "id": "did-key-2", "purpose": ["authentication"] }, ], "service": [{ "id": "service-1" }], } }) # Entry with both additions and revocations should be valid self.VALIDATOR.validate({ "add": { "managementKey": [ ManagementKey( alias="my-man-key-1", controller=did, key_type=KeyType.EdDSA, priority=0, ).to_entry_dict(did), ManagementKey( alias="my-man-key-2", controller=did, key_type=KeyType.RSA, priority=1, ).to_entry_dict(did), ], "didKey": [ DIDKey( alias="my-did-key", controller=did, key_type=KeyType.RSA.ECDSA, purpose=DIDKeyPurpose.PublicKey, ).to_entry_dict(did) ], "service": [ Service( alias="gmail-service", service_type="email-service", endpoint="https://gmail.com", priority_requirement=1, custom_fields={ "description": "My email service" }, ).to_entry_dict(did) ], }, "revoke": { "managementKey": [{ "id": "management-key-1" }], "didKey": [ { "id": "did-key-1" }, { "id": "did-key-2", "purpose": ["publicKey"] }, ], "service": [{ "id": "service-1" }], }, })
def process_did_management_entry_v100( chain_id, parsed_content, management_keys, did_keys, services, skipped_entries, network, ): """ Extracts the management keys, DID keys and services from a DIDManagement entry. This method only does validation of the logic rules for a DIDManagement entry (e.g. that at least one management key with priority 0 is present). Thus, it must be called only with a parsed entry, which has already undergone validation checks for proper formatting of its ExtIDs and content. Parameters ---------- chain_id: str The DIDManagement chain ID. parsed_content: dict The parsed DIDManagement entry. management_keys: dict Will be updated to contain the management keys found in the entry. did_keys: dict Will be updated to contain the DID keys found in the entry. services: dict Will be updated to contain the services found in the entry. skipped_entries: int Will be incremented by one in case the DIDManagement entry is not valid. network: Network The Factom network on which the DID is recorded Returns ------- tuple 3-tuple (bool, str, int). The first element signifies if the caller should continue parsing the chain; the second element contains the current DID method specification version; the third element contains the number of skipped entries in the DIDManagement chain. Raises ------ MalformedDIDManagementEntry If the DIDManagement entry does not conform to the DID specification """ # Store the new management_keys, did_keys and services in separate objects, instead of # modifying the original ones directly. This ensures that if an exception occurs during # the processing of the entry, the original values will not be modified. new_management_keys = {} new_did_keys = {} new_services = {} method_version = parsed_content["didMethodVersion"] found_key_with_priority_zero = False for key_data in parsed_content["managementKey"]: if not validate_management_key_id_against_chain_id(key_data["id"], chain_id): raise MalformedDIDManagementEntry( "Invalid key identifier '{}' for chain ID '{}'".format( key_data["id"], chain_id ) ) if not validate_id_against_network(key_data["id"], network): raise MalformedDIDManagementEntry( "Invalid key identifier '{}' for network ID '{}'".format( key_data["id"], network.value ) ) alias = _get_alias(key_data["id"]) if alias in new_management_keys: raise MalformedDIDManagementEntry("Duplicate management key found") new_management_keys[alias] = ManagementKey.from_entry_dict(key_data) if key_data["priority"] == 0: found_key_with_priority_zero = True if not found_key_with_priority_zero: raise MalformedDIDManagementEntry( "Entry must contain at least one management key with priority 0" ) for key_data in parsed_content.get("didKey", []): if not validate_id_against_network(key_data["id"], network): raise MalformedDIDManagementEntry( "Invalid key identifier '{}' for network ID '{}'".format( key_data["id"], network.value ) ) alias = _get_alias(key_data["id"]) if alias in new_did_keys: raise MalformedDIDManagementEntry("Duplicate DID key found") new_did_keys[alias] = DIDKey.from_entry_dict(key_data) for service_data in parsed_content.get("service", []): if not validate_id_against_network(service_data["id"], network): raise MalformedDIDManagementEntry( "Invalid service identifier '{}' for network ID '{}'".format( service_data["id"], network.value ) ) alias = _get_alias(service_data["id"]) if alias in new_services: raise MalformedDIDManagementEntry("Duplicate service found") new_services[alias] = Service.from_entry_dict(service_data) # Only change the original keys & services if the processing of the whole entry has been successful management_keys.update(new_management_keys) did_keys.update(new_did_keys) services.update(new_services) return True, method_version, skipped_entries