def get_verifier(context, img_signature_certificate_uuid, img_signature_hash_method, img_signature, img_signature_key_type): """Instantiate signature properties and use them to create a verifier. :param context: the user context for authentication :param img_signature_certificate_uuid: uuid of signing certificate stored in key manager :param img_signature_hash_method: string denoting hash method used to compute signature :param img_signature: string of base64 encoding of signature :param img_signature_key_type: string denoting type of keypair used to compute signature :returns: instance of cryptography.hazmat.primitives.asymmetric.AsymmetricVerificationContext :raises: SignatureVerificationError if we fail to build the verifier """ image_meta_props = {'img_signature_uuid': img_signature_certificate_uuid, 'img_signature_hash_method': img_signature_hash_method, 'img_signature': img_signature, 'img_signature_key_type': img_signature_key_type} for key in image_meta_props.keys(): if image_meta_props[key] is None: raise exception.SignatureVerificationError( reason=_('Required image properties for signature verification' ' do not exist. Cannot verify signature. Missing' ' property: %s') % key) signature = get_signature(img_signature) hash_method = get_hash_method(img_signature_hash_method) signature_key_type = SignatureKeyType.lookup(img_signature_key_type) public_key = get_public_key(context, img_signature_certificate_uuid, signature_key_type) # create the verifier based on the signature key type verifier = signature_key_type.create_verifier(signature, hash_method, public_key) if verifier: return verifier else: # Error creating the verifier raise exception.SignatureVerificationError( reason=_('Error occurred while creating the verifier'))
def get_certificate(context, signature_certificate_uuid): """Create the certificate object from the retrieved certificate data. :param context: the user context for authentication :param signature_certificate_uuid: the uuid to use to retrieve the certificate :returns: the certificate cryptography object :raises: SignatureVerificationError if the retrieval fails or the format is invalid """ keymgr_api = key_manager.API() try: # The certificate retrieved here is a castellan certificate object cert = keymgr_api.get(context, signature_certificate_uuid) except ManagedObjectNotFoundError as e: raise exception.SignatureVerificationError( reason=_('Certificate not found with ID: %s') % signature_certificate_uuid) except KeyManagerError as e: # The problem encountered may be backend-specific, since castellan # can use different backends. Rather than importing all possible # backends here, the generic "Exception" is used. msg = (_LE("Unable to retrieve certificate with ID %(id)s: %(e)s") % {'id': signature_certificate_uuid, 'e': encodeutils.exception_to_unicode(e)}) LOG.error(msg) raise exception.SignatureVerificationError( reason=_('Unable to retrieve certificate with ID: %s') % signature_certificate_uuid) if cert.format not in CERTIFICATE_FORMATS: raise exception.SignatureVerificationError( reason=_('Invalid certificate format: %s') % cert.format) if cert.format == X_509: # castellan always encodes certificates in DER format cert_data = cert.get_encoded() certificate = x509.load_der_x509_certificate(cert_data, default_backend()) return certificate
def update(self, certificate): """Process the certificate to be verified. Raises an exception if the certificate is invalid. Stores it otherwise. :param certificate: the cryptography certificate to be verified :raises: SignatureVerificationError if the certificate is not of the right type or if it is outside its valid date range. """ if not isinstance(certificate, x509.Certificate): raise exception.SignatureVerificationError( "The certificate must be an x509.Certificate object.") if self._enforce_valid_dates: if not is_within_valid_dates(certificate): raise exception.SignatureVerificationError( "The certificate is outside its valid date range.") self._signed_certificate = certificate
def lookup(cls, name): """Look up the signature key type. :param name: the name of the signature key type :returns: the SignatureKeyType object :raises: SignatureVerificationError if signature key type is invalid """ if name not in cls.REGISTERED_TYPES: raise exception.SignatureVerificationError( reason=_('Invalid signature key type: %s') % name) return cls.REGISTERED_TYPES[name]
def get_hash_method(hash_method_name): """Verify the hash method name and create the hash method. :param hash_method_name: the name of the hash method to retrieve :returns: the hash method, a cryptography object :raises: SignatureVerificationError if the hash method name is invalid """ if hash_method_name not in HASH_METHODS: raise exception.SignatureVerificationError( reason=_('Invalid signature hash method: %s') % hash_method_name) return HASH_METHODS[hash_method_name]
def set_data(self, data, size=None): if size is None: size = 0 # NOTE(markwash): zero -> unknown size # Create the verifier for signature verification (if correct properties # are present) extra_props = self.image.extra_properties if (signature_utils.should_create_verifier(extra_props)): # NOTE(bpoulos): if creating verifier fails, exception will be # raised img_signature = extra_props[signature_utils.SIGNATURE] hash_method = extra_props[signature_utils.HASH_METHOD] key_type = extra_props[signature_utils.KEY_TYPE] cert_uuid = extra_props[signature_utils.CERT_UUID] verifier = signature_utils.get_verifier( context=self.context, img_signature_certificate_uuid=cert_uuid, img_signature_hash_method=hash_method, img_signature=img_signature, img_signature_key_type=key_type) else: verifier = None location, size, checksum, loc_meta = self.store_api.add_to_backend( CONF, self.image.image_id, utils.LimitingReader(utils.CooperativeReader(data), CONF.image_size_cap), size, context=self.context, verifier=verifier) # NOTE(bpoulos): if verification fails, exception will be raised if verifier: try: verifier.verify() LOG.info(_LI("Successfully verified signature for image %s"), self.image.image_id) except crypto_exception.InvalidSignature: self.store_api.delete_from_backend(location, context=self.context) raise cursive_exception.SignatureVerificationError( _('Signature verification failed')) self.image.locations = [{ 'url': location, 'metadata': loc_meta, 'status': 'active' }] self.image.size = size self.image.checksum = checksum self.image.status = 'active'
def test_raises_when_all_stores_must_succeed(self, mock_import): img_repo = mock.MagicMock() image_import = import_flow._ImportToStore(TASK_ID1, TASK_TYPE, img_repo, "http://url", IMAGE_ID1, "store1", True, True) image = self.img_factory.new_image(image_id=UUID1) img_repo.get.return_value = image mock_import.set_image_data.side_effect = \ cursive_exception.SignatureVerificationError( "Signature verification failed") self.assertRaises(cursive_exception.SignatureVerificationError, image_import.execute)
def get_signature(signature_data): """Decode the signature data and returns the signature. :param signature_data: the base64-encoded signature data :returns: the decoded signature :raises: SignatureVerificationError if the signature data is malformatted """ try: signature = base64.decode_as_bytes(signature_data) except (TypeError, binascii.Error): raise exception.SignatureVerificationError( reason=_('The signature data was not properly ' 'encoded using base64')) return signature
def test_doesnt_raise_when_not_all_stores_must_succeed(self, mock_import): img_repo = mock.MagicMock() image_import = import_flow._ImportToStore(TASK_ID1, TASK_TYPE, img_repo, "http://url", IMAGE_ID1, "store1", False, True) image = self.img_factory.new_image(image_id=UUID1) img_repo.get.return_value = image mock_import.set_image_data.side_effect = \ cursive_exception.SignatureVerificationError( "Signature verification failed") try: image_import.execute() self.assertEqual(image.extra_properties['os_glance_failed_import'], "store1") except cursive_exception.SignatureVerificationError: self.fail("Exception shouldn't be raised")
def test_raises_when_all_stores_must_succeed(self, mock_import): img_repo = mock.MagicMock() task_repo = mock.MagicMock() wrapper = import_flow.ImportActionWrapper(img_repo, IMAGE_ID1, TASK_ID1) image_import = import_flow._ImportToStore(TASK_ID1, TASK_TYPE, task_repo, wrapper, "http://url", "store1", True, True) extra_properties = {'os_glance_import_task': TASK_ID1} image = self.img_factory.new_image(image_id=UUID1, extra_properties=extra_properties) img_repo.get.return_value = image mock_import.set_image_data.side_effect = \ cursive_exception.SignatureVerificationError( "Signature verification failed") self.assertRaises(cursive_exception.SignatureVerificationError, image_import.execute)
def load_certificate(self, cert_name): # Load the raw certificate file data. path = os.path.join(self.cert_path, cert_name) with open(path, 'rb') as cert_file: data = cert_file.read() # Convert the raw certificate data into a certificate object, first # as a PEM-encoded certificate and, if that fails, then as a # DER-encoded certificate. If both fail, the certificate cannot be # loaded. try: return x509.load_pem_x509_certificate(data, default_backend()) except Exception: try: return x509.load_der_x509_certificate(data, default_backend()) except Exception: raise exception.SignatureVerificationError( "Failed to load certificate: %s" % path )
def _verify_signature(self, verifier, location, loc_meta): """ Verify signature of uploaded data. :param verifier: for signature verification """ # NOTE(bpoulos): if verification fails, exception will be raised if verifier is not None: try: verifier.verify() msg = _LI("Successfully verified signature for image %s") LOG.info(msg, self.image.image_id) except crypto_exception.InvalidSignature: if CONF.enabled_backends: self.store_api.delete(location, loc_meta.get('store'), context=self.context) else: self.store_api.delete_from_backend(location, context=self.context) raise cursive_exception.SignatureVerificationError( _('Signature verification failed'))
def get_public_key(context, signature_certificate_uuid, signature_key_type): """Create the public key object from a retrieved certificate. :param context: the user context for authentication :param signature_certificate_uuid: the uuid to use to retrieve the certificate :param signature_key_type: a SignatureKeyType object :returns: the public key cryptography object :raises: SignatureVerificationError if public key format is invalid """ certificate = get_certificate(context, signature_certificate_uuid) # Note that this public key could either be # RSAPublicKey, DSAPublicKey, or EllipticCurvePublicKey public_key = certificate.public_key() # Confirm the type is of the type expected based on the signature key type if not isinstance(public_key, signature_key_type.public_key_type): raise exception.SignatureVerificationError( reason=_('Invalid public key type for signature key type: %s') % signature_key_type.name) return public_key
def verify(self): """Locate the certificate's signing certificate and verify it. Locate the certificate's signing certificate in the context certificate cache, using both subject/issuer name matching and signature verification. If the certificate is self-signed, verify that it is also located in the context's certificate cache. Construct the certificate chain from certificates in the context certificate cache. Verify that the signing certificate can have a sufficient number of child certificates to support the chain. :raises: SignatureVerificationError if certificate validation fails for any reason, including mismatched signatures or a failure to find the required signing certificate. """ signed_certificate = self._signed_certificate certificate_chain = [('base', signed_certificate)] # Build the certificate chain. while True: signing_certificate_tuple = None # Search for the signing certificate for certificate_tuple in self._signing_certificates: _, candidate = certificate_tuple if is_issuer(candidate, signed_certificate): signing_certificate_tuple = certificate_tuple break # If a valid signing certificate is found, prepare to find the # next link in the certificate chain. Otherwise, raise an error. if signing_certificate_tuple: # If the certificate is self-signed, the root of the # certificate chain has been found. Otherwise, repeat the # verification process using the newly found signing # certificate. if signed_certificate == signing_certificate_tuple[1]: break else: certificate_chain.insert(0, signing_certificate_tuple) signed_certificate = signing_certificate_tuple[1] else: uuid = certificate_chain[0][0] raise exception.SignatureVerificationError( "Certificate chain building failed. Could not locate the " "signing certificate for %s in the set of trusted " "certificates." % "the base certificate" if uuid == 'base' else "certificate '%s'" % uuid) if self._enforce_path_length: # Verify that each certificate's path length constraint allows # for it to support the rest of the certificate chain. for i in range(len(certificate_chain)): certificate = certificate_chain[i][1] # No need to check the last certificate in the chain. if certificate == certificate_chain[-1][1]: break try: constraints = certificate.extensions.get_extension_for_oid( x509.oid.ExtensionOID.BASIC_CONSTRAINTS).value except x509.extensions.ExtensionNotFound: raise exception.SignatureVerificationError( "Certificate validation failed. The signing " "certificate '%s' does not have a basic constraints " "extension." % certificate_chain[i][0]) # Path length only applies to non-self-issued intermediate # certificates. Do not include the current or end certificates # when computing path length. chain_length = len(certificate_chain[i:]) chain_length = (chain_length - 2) if chain_length > 2 else 0 if constraints.path_length < chain_length: raise exception.SignatureVerificationError( "Certificate validation failed. The signing " "certificate '%s' is not configured to support " "certificate chains of sufficient " "length." % certificate_chain[i][0])