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 validate_expiry(self): """ Validate the expiry date of the trust data. Raise a `ValidationError` should the date be expired. """ expire = parser.parse(self.signed.get("expires")) now = datetime.now(pytz.utc) if expire < now: msg = "Trust data {trust_data_kind} has expired." raise ValidationError(message=msg, trust_data_kind=self.kind)
def _validate_schema(self, data: dict): """ Validates the schema of the given trust `data`. Raises a `ValidationError` should the schema not conform. """ with open(self.schema_path, "r") as schema_file: schema = json.load(schema_file) try: json_validate(instance=data, schema=schema, format_checker=JFormatChecker()) except JValidationError: raise ValidationError("trust data has invalid format.", {"trust_data_type": self.kind})
def _validate_expiry(self): """ Validates the expiry date of the trust data. Raises a `ValidationError` should the date be expired. """ expire = parser.parse(self.signed.get("expires")) now = datetime.now(pytz.utc) if expire < now: raise ValidationError( "trust data expired.", { "expire": str(expire), "trust_data_type": self.signed.get("_type") }, )
def validate_signature(self, keystore: KeyStore): """ Validate the signature of the trust data, using keys from a `keystore`. Raise 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 as err: msg = "Failed to verify signature of trust data {trust_data_kind}." raise ValidationError(message=msg, trust_data_kind=self.kind) from err
def validate_hash(self, keystore: KeyStore): """ Validate the given hash from a `keystore` corresponds to the trust data's calculated hash. Raise 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: msg = "Failed to validate hash of trust data {trust_data_kind}." raise ValidationError(message=msg, trust_data_kind=self.kind)
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") }, )
async def validate(self, image: Image, **kwargs): if not self.approve: msg = "Static deny." raise ValidationError(message=msg)
def __get_cosign_validated_digests(self, image: str, key: str): """ Get and process Cosign validation output for a given `image` and `key` and either return a list of valid digests or raise a suitable exception in case no valid signature is found or Cosign fails. """ returncode, stdout, stderr = self.__invoke_cosign(image, key) logging.info( "COSIGN output for image: %s; RETURNCODE: %s; STDOUT: %s; STDERR: %s", image, returncode, stdout, stderr, ) digests = [] if returncode == 0: for sig in stdout.splitlines(): try: sig_data = json.loads(sig) try: digest = sig_data["critical"]["image"].get( "docker-manifest-digest", "") if re.match(r"sha256:[0-9A-Fa-f]{64}", digest) is None: msg = "Digest '{digest}' does not match expected digest pattern." raise InvalidFormatException(message=msg, digest=digest) except Exception as err: msg = ( "Could not retrieve valid and unambiguous digest from data " "received by Cosign: {err_type}: {err}") raise UnexpectedCosignData(message=msg, err_type=type(err).__name__, err=str(err)) from err # remove prefix 'sha256' digests.append(digest.removeprefix("sha256:")) except json.JSONDecodeError: logging.info("non-json signature data from Cosign: %s", sig) pass elif "Error: no matching signatures:\nfailed to verify signature\n" in stderr: msg = "Failed to verify signature of trust data." raise ValidationError( message=msg, trust_data_type="dev.cosignproject.cosign/signature", stderr=stderr, ) elif "Error: no matching signatures:\n\nmain.go:" in stderr: msg = 'No trust data for image "{image}".' raise NotFoundException( message=msg, trust_data_type="dev.cosignproject.cosign/signature", stderr=stderr, image=str(image), ) else: msg = 'Unexpected Cosign exception for image "{image}": {stderr}.' raise CosignError( message=msg, trust_data_type="dev.cosignproject.cosign/signature", stderr=stderr, image=str(image), ) if not digests: msg = ("Could not extract any digest from data received by Cosign " "despite successful image verification.") raise UnexpectedCosignData(message=msg) return digests