def test_dump_then_load_crypto_meta(self): actual = crypto_utils.load_crypto_meta( crypto_utils.dump_crypto_meta(self.meta)) self.assertEqual(self.meta, actual) actual = crypto_utils.load_crypto_meta( crypto_utils.dump_crypto_meta(self.meta_with_key)) self.assertEqual(self.meta_with_key, actual)
def encrypt_user_metadata(self, req, keys): """ Encrypt user-metadata header values. Replace each x-object-meta-<key> user metadata header with a corresponding x-object-transient-sysmeta-crypto-meta-<key> header which has the crypto metadata required to decrypt appended to the encrypted value. :param req: a swob Request :param keys: a dict of encryption keys """ prefix = get_object_transient_sysmeta('crypto-meta-') user_meta_headers = [h for h in req.headers.items() if is_user_meta(self.server_type, h[0]) and h[1]] crypto_meta = None for name, val in user_meta_headers: short_name = strip_user_meta_prefix(self.server_type, name) new_name = prefix + short_name enc_val, crypto_meta = encrypt_header_val( self.crypto, val, keys[self.server_type]) req.headers[new_name] = append_crypto_meta(enc_val, crypto_meta) req.headers.pop(name) # store a single copy of the crypto meta items that are common to all # encrypted user metadata independently of any such meta that is stored # with the object body because it might change on a POST. This is done # for future-proofing - the meta stored here is not currently used # during decryption. if crypto_meta: meta = dump_crypto_meta({'cipher': crypto_meta['cipher'], 'key_id': keys['id']}) req.headers[get_object_transient_sysmeta('crypto-meta')] = meta
def encrypt_user_metadata(self, req, keys): """ Encrypt user-metadata header values. Replace each x-object-meta-<key> user metadata header with a corresponding x-object-transient-sysmeta-crypto-meta-<key> header which has the crypto metadata required to decrypt appended to the encrypted value. :param req: a swob Request :param keys: a dict of encryption keys """ prefix = get_object_transient_sysmeta('crypto-meta-') user_meta_headers = [ h for h in req.headers.items() if is_user_meta(self.server_type, h[0]) and h[1] ] crypto_meta = None for name, val in user_meta_headers: short_name = strip_user_meta_prefix(self.server_type, name) new_name = prefix + short_name enc_val, crypto_meta = encrypt_header_val(self.crypto, val, keys[self.server_type]) req.headers[new_name] = append_crypto_meta(enc_val, crypto_meta) req.headers.pop(name) # store a single copy of the crypto meta items that are common to all # encrypted user metadata independently of any such meta that is stored # with the object body because it might change on a POST. This is done # for future-proofing - the meta stored here is not currently used # during decryption. if crypto_meta: meta = dump_crypto_meta({ 'cipher': crypto_meta['cipher'], 'key_id': keys['id'] }) req.headers[get_object_transient_sysmeta('crypto-meta')] = meta
def footers_callback(footers): if inner_callback: # pass on footers dict to any other callback that was # registered before this one. It may override any footers that # were set. inner_callback(footers) plaintext_etag = None if self.body_crypto_ctxt: plaintext_etag = self.plaintext_md5.hexdigest() # If client (or other middleware) supplied etag, then validate # against plaintext etag etag_to_check = footers.get('Etag') or client_etag if (etag_to_check is not None and plaintext_etag != etag_to_check): raise HTTPUnprocessableEntity(request=Request(self.env)) # override any previous notion of etag with the ciphertext etag footers['Etag'] = self.ciphertext_md5.hexdigest() # Encrypt the plaintext etag using the object key and persist # as sysmeta along with the crypto parameters that were used. encrypted_etag, etag_crypto_meta = encrypt_header_val( self.crypto, plaintext_etag, self.keys['object']) footers['X-Object-Sysmeta-Crypto-Etag'] = \ append_crypto_meta(encrypted_etag, etag_crypto_meta) footers['X-Object-Sysmeta-Crypto-Body-Meta'] = \ dump_crypto_meta(self.body_crypto_meta) # Also add an HMAC of the etag for use when evaluating # conditional requests footers['X-Object-Sysmeta-Crypto-Etag-Mac'] = _hmac_etag( self.keys['object'], plaintext_etag) else: # No data was read from body, nothing was encrypted, so don't # set any crypto sysmeta for the body, but do re-instate any # etag provided in inbound request if other middleware has not # already set a value. if client_etag is not None: footers.setdefault('Etag', client_etag) # When deciding on the etag that should appear in container # listings, look for: # * override in the footer, otherwise # * override in the header, and finally # * MD5 of the plaintext received # This may be None if no override was set and no data was read. An # override value of '' will be passed on. container_listing_etag = footers.get( 'X-Object-Sysmeta-Container-Update-Override-Etag', container_listing_etag_header) if container_listing_etag is None: container_listing_etag = plaintext_etag if (container_listing_etag and (container_listing_etag != MD5_OF_EMPTY_STRING or plaintext_etag)): # Encrypt the container-listing etag using the container key # and a random IV, and use it to override the container update # value, with the crypto parameters appended. We use the # container key here so that only that key is required to # decrypt all etag values in a container listing when handling # a container GET request. Don't encrypt an EMPTY_ETAG # unless there actually was some body content, in which case # the container-listing etag is possibly conveying some # non-obvious information. val, crypto_meta = encrypt_header_val( self.crypto, container_listing_etag, self.keys['container']) crypto_meta['key_id'] = self.keys['id'] footers['X-Object-Sysmeta-Container-Update-Override-Etag'] = \ append_crypto_meta(val, crypto_meta)
def get_crypto_meta_header(crypto_meta=None): if crypto_meta is None: crypto_meta = fake_get_crypto_meta() return dump_crypto_meta(crypto_meta)
def footers_callback(footers): if inner_callback: # pass on footers dict to any other callback that was # registered before this one. It may override any footers that # were set. inner_callback(footers) plaintext_etag = None if self.body_crypto_ctxt: plaintext_etag = self.plaintext_md5.hexdigest() # If client (or other middleware) supplied etag, then validate # against plaintext etag etag_to_check = footers.get('Etag') or client_etag if (etag_to_check is not None and plaintext_etag != etag_to_check): raise HTTPUnprocessableEntity(request=Request(self.env)) # override any previous notion of etag with the ciphertext etag footers['Etag'] = self.ciphertext_md5.hexdigest() # Encrypt the plaintext etag using the object key and persist # as sysmeta along with the crypto parameters that were used. encrypted_etag, etag_crypto_meta = encrypt_header_val( self.crypto, plaintext_etag, self.keys['object']) footers['X-Object-Sysmeta-Crypto-Etag'] = \ append_crypto_meta(encrypted_etag, etag_crypto_meta) footers['X-Object-Sysmeta-Crypto-Body-Meta'] = \ dump_crypto_meta(self.body_crypto_meta) # Also add an HMAC of the etag for use when evaluating # conditional requests footers['X-Object-Sysmeta-Crypto-Etag-Mac'] = _hmac_etag( self.keys['object'], plaintext_etag) else: # No data was read from body, nothing was encrypted, so don't # set any crypto sysmeta for the body, but do re-instate any # etag provided in inbound request if other middleware has not # already set a value. if client_etag is not None: footers.setdefault('Etag', client_etag) # When deciding on the etag that should appear in container # listings, look for: # * override in the footer, otherwise # * override in the header, and finally # * MD5 of the plaintext received # This may be None if no override was set and no data was read container_listing_etag = footers.get( 'X-Object-Sysmeta-Container-Update-Override-Etag', container_listing_etag_header) or plaintext_etag if (container_listing_etag is not None and (container_listing_etag != MD5_OF_EMPTY_STRING or plaintext_etag)): # Encrypt the container-listing etag using the container key # and a random IV, and use it to override the container update # value, with the crypto parameters appended. We use the # container key here so that only that key is required to # decrypt all etag values in a container listing when handling # a container GET request. Don't encrypt an EMPTY_ETAG # unless there actually was some body content, in which case # the container-listing etag is possibly conveying some # non-obvious information. val, crypto_meta = encrypt_header_val(self.crypto, container_listing_etag, self.keys['container']) crypto_meta['key_id'] = self.keys['id'] footers['X-Object-Sysmeta-Container-Update-Override-Etag'] = \ append_crypto_meta(val, crypto_meta)
def test_dump_crypto_meta(self): actual = crypto_utils.dump_crypto_meta(self.meta) self.assertEqual(self.serialized_meta, actual) actual = crypto_utils.dump_crypto_meta(self.meta_with_key) self.assertEqual(self.serialized_meta_with_key, actual)