def _validate_hash(self, keystore: KeyStore): """ Validates the given hash from a `keystore` corresponds to the trust data's calculated hash. Raises a `ValidationError` should the hashes not match. """ data = {"signed": self.signed, "signatures": self.signatures} data_dump = bytearray(json.dumps(data, separators=(",", ":")), "utf-8") hash_b64, len_ = keystore.get_hash(self.kind) hash_ = base64.b64decode(hash_b64).hex() data_hash = hashlib.sha256(data_dump).hexdigest() data_len = len(data_dump) if hash_ != data_hash or len_ != data_len: raise ValidationError( "failed validating trust data hash.", { "given_hash": hash_, "calculated_hash": data_hash, "given_len": len_, "calculated_len": data_len, }, )
def test_validate_hash_error(td, mock_schema_path, mock_keystore): data = trust_data("tests/data/sample_root.json") data["signatures"][0]["sig"] = "Q" + data["signatures"][0]["sig"][1:] trust_data_ = td.TrustData(data, "root") ks = KeyStore() with pytest.raises(ValidationError) as err: trust_data_._validate_hash(ks) assert "failed validating trust data hash." in str(err.value)
def _validate_signature(self, keystore: KeyStore): """ Validates the signature of the trust data, using keys from a `keystore`. Raises a `ValidationError` should the the signature be faulty. """ msg = json.dumps(self.signed, separators=(",", ":")) for signature in self.signatures: key_id = "root" if self.kind == "root" else signature["keyid"] pub_key = keystore.get_key(key_id) sig = signature["sig"] try: verify_signature(pub_key, sig, msg) except Exception: raise ValidationError( "failed to verify signature of trust data.", { "key_id": key_id, "trust_data_type": self.signed.get("_type") }, )
def test_validate(td, mock_schema_path, mock_keystore, data: dict, role: str): ks = KeyStore() _trust_data = td.TrustData(data, role) _trust_data.validate(ks)
def test_validate_hash(td, mock_schema_path, mock_keystore, data: dict, role: str): ks = KeyStore() trust_data_ = td.TrustData(data, role) assert trust_data_._validate_hash(ks) is None
def process_chain_of_trust( host: str, image: Image, req_delegations: list ): # pylint: disable=too-many-branches """ Processes the whole chain of trust, provided by the notary server (`host`) for any given `image`. The 'root', 'snapshot', 'timestamp', 'targets' and potentially 'targets/releases' are requested in this order and afterwards validated, also according to the `policy_rule`. Returns the signed image targets, which contain the digests. Raises `NotFoundExceptions` should no required delegetions be present in the trust data, or no image targets be found. """ tuf_roles = ["root", "snapshot", "timestamp", "targets"] trust_data = {} key_store = KeyStore() # get all trust data and collect keys (from root and targets), as well as # hashes (from snapshot and timestamp) for role in tuf_roles: trust_data[role] = get_trust_data(host, image, TUFRole(role)) key_store.update(trust_data[role]) # if the 'targets.json' has delegation roles defined, get their trust data # as well if trust_data["targets"].has_delegations(): for delegation in trust_data["targets"].get_delegations(): trust_data[delegation] = get_delegation_trust_data( host, image, TUFRole(delegation) ) # validate all trust data's signatures, expiry dates and hashes. # when delegations are added to the repository, but weren't yet used for signing, the # delegation files don't exist yet and are `None`. in this case they can't be # validated and must be skipped for role in trust_data: if trust_data[role] is not None: trust_data[role].validate(key_store) # validate needed delegations if req_delegations: if trust_data["targets"].has_delegations(): delegations = trust_data["targets"].get_delegations() req_delegations_set = set(req_delegations) delegations_set = set(delegations) delegations_set.discard("targets/releases") # make an intersection between required delegations and actually # present ones if not req_delegations_set.issubset(delegations_set): missing = list(req_delegations_set - delegations_set) raise NotFoundException( "could not find delegation roles {} in trust data.".format( str(missing) ) ) else: raise NotFoundException("could not find any delegations in trust data.") # if certain delegations are required, then only take the targets fields of the # required delegation JSON's. otherwise take the targets field of the targets JSON, as # long as no delegations are defined in the targets JSON. should there be delegations # defined in the targets JSON the targets field of the releases JSON will be used. # unfortunately there is a case, where delegations could have been added to a # repository, but no signatures were created using the delegations. in this special # case, the releases JSON doesn't exist yet and the targets JSON must be used instead if req_delegations: if not all(trust_data[target_role] for target_role in req_delegations): tuf_roles = [ target_role for target_role in req_delegations if not trust_data[target_role] ] msg = f"no trust data for delegation roles {tuf_roles} for image {image}" raise NotFoundException(msg, {"tuf_roles": tuf_roles}) image_targets = [ trust_data[target_role].signed.get("targets", {}) for target_role in req_delegations ] else: targets_key = ( "targets/releases" if trust_data["targets"].has_delegations() and trust_data["targets/releases"] else "targets" ) image_targets = [trust_data[targets_key].signed.get("targets", {})] if not any(image_targets): raise NotFoundException("could not find any image digests in trust data.") return image_targets