def test_GET_unencrypted_data(self):
     # testing case of an unencrypted object with encrypted metadata from
     # a later POST
     env = {'REQUEST_METHOD': 'GET',
            CRYPTO_KEY_CALLBACK: fetch_crypto_keys}
     req = Request.blank('/v1/a/c/o', environ=env)
     body = 'FAKE APP'
     obj_key = fetch_crypto_keys()['object']
     hdrs = {'Etag': md5hex(body),
             'content-type': 'text/plain',
             'content-length': len(body),
             'x-object-transient-sysmeta-crypto-meta-test':
                 base64.b64encode(encrypt('encrypt me', obj_key, FAKE_IV)) +
                 ';swift_meta=' + get_crypto_meta_header(),
             'x-object-sysmeta-test': 'do not encrypt me'}
     self.app.register('GET', '/v1/a/c/o', HTTPOk, body=body, headers=hdrs)
     resp = req.get_response(self.decrypter)
     self.assertEqual(body, resp.body)
     self.assertEqual('200 OK', resp.status)
     self.assertEqual(md5hex(body), resp.headers['Etag'])
     self.assertEqual('text/plain', resp.headers['Content-Type'])
     # POSTed user meta was encrypted
     self.assertEqual('encrypt me', resp.headers['x-object-meta-test'])
     # PUT sysmeta was not encrypted
     self.assertEqual('do not encrypt me',
                      resp.headers['x-object-sysmeta-test'])
Example #2
0
 def test_GET_unencrypted_data(self):
     # testing case of an unencrypted object with encrypted metadata from
     # a later POST
     env = {'REQUEST_METHOD': 'GET', CRYPTO_KEY_CALLBACK: fetch_crypto_keys}
     req = Request.blank('/v1/a/c/o', environ=env)
     body = 'FAKE APP'
     obj_key = fetch_crypto_keys()['object']
     hdrs = {
         'Etag':
         md5hex(body),
         'content-type':
         'text/plain',
         'content-length':
         len(body),
         'x-object-transient-sysmeta-crypto-meta-test':
         base64.b64encode(encrypt('encrypt me', obj_key, FAKE_IV)) +
         ';swift_meta=' + get_crypto_meta_header(),
         'x-object-sysmeta-test':
         'do not encrypt me'
     }
     self.app.register('GET', '/v1/a/c/o', HTTPOk, body=body, headers=hdrs)
     resp = req.get_response(self.decrypter)
     self.assertEqual(body, resp.body)
     self.assertEqual('200 OK', resp.status)
     self.assertEqual(md5hex(body), resp.headers['Etag'])
     self.assertEqual('text/plain', resp.headers['Content-Type'])
     # POSTed user meta was encrypted
     self.assertEqual('encrypt me', resp.headers['x-object-meta-test'])
     # PUT sysmeta was not encrypted
     self.assertEqual('do not encrypt me',
                      resp.headers['x-object-sysmeta-test'])
Example #3
0
 def _test_override_etag_bad_meta(self, method, bad_crypto_meta):
     env = {
         'REQUEST_METHOD': method,
         CRYPTO_KEY_CALLBACK: fetch_crypto_keys
     }
     req = Request.blank('/v1/a/c/o', environ=env)
     body = 'FAKE APP'
     key = fetch_crypto_keys()['object']
     enc_body = encrypt(body, key, FAKE_IV)
     hdrs = self._make_response_headers(len(body), md5hex(body),
                                        fetch_crypto_keys(), 'not used')
     # simulate missing crypto meta from encrypted override etag
     hdrs['X-Object-Sysmeta-Container-Update-Override-Etag'] = \
         encrypt_and_append_meta(
             md5hex(body), key, crypto_meta=bad_crypto_meta)
     self.app.register(method,
                       '/v1/a/c/o',
                       HTTPOk,
                       body=enc_body,
                       headers=hdrs)
     resp = req.get_response(self.decrypter)
     self.assertEqual('500 Internal Error', resp.status)
     self.assertIn(
         'Error decrypting header '
         'X-Object-Sysmeta-Container-Update-Override-Etag',
         self.decrypter.logger.get_lines_for_level('error')[0])
     return resp
Example #4
0
 def test_GET_decryption_override(self):
     # This covers the case of an old un-encrypted object
     env = {
         'REQUEST_METHOD': 'GET',
         CRYPTO_KEY_CALLBACK: fetch_crypto_keys,
         'swift.crypto.override': True
     }
     req = Request.blank('/v1/a/c/o', environ=env)
     body = 'FAKE APP'
     hdrs = {
         'Etag': md5hex(body),
         'content-type': 'text/plain',
         'content-length': len(body),
         'x-object-meta-test': 'do not encrypt me',
         'x-object-sysmeta-test': 'do not encrypt me'
     }
     self.app.register('GET', '/v1/a/c/o', HTTPOk, body=body, headers=hdrs)
     resp = req.get_response(self.decrypter)
     self.assertEqual(body, resp.body)
     self.assertEqual('200 OK', resp.status)
     self.assertEqual(md5hex(body), resp.headers['Etag'])
     self.assertEqual('text/plain', resp.headers['Content-Type'])
     self.assertEqual('do not encrypt me',
                      resp.headers['x-object-meta-test'])
     self.assertEqual('do not encrypt me',
                      resp.headers['x-object-sysmeta-test'])
    def test_headers_case(self):
        body = 'fAkE ApP'
        req = Request.blank('/v1/a/c/o', body='FaKe')
        req.environ[CRYPTO_KEY_CALLBACK] = fetch_crypto_keys
        plaintext_etag = md5hex(body)
        body_key = os.urandom(32)
        enc_body = encrypt(body, body_key, FAKE_IV)
        hdrs = self._make_response_headers(
            len(enc_body), plaintext_etag, fetch_crypto_keys(), body_key)

        hdrs.update({
            'x-Object-mEta-ignoRes-caSe': 'thIs pArt WilL bE cOol',
        })
        self.app.register(
            'GET', '/v1/a/c/o', HTTPOk, body=enc_body, headers=hdrs)

        status, headers, app_iter = req.call_application(self.decrypter)
        self.assertEqual(status, '200 OK')
        expected = {
            'Etag': '7f7837924188f7b511a9e3881a9f77a8',
            'X-Object-Sysmeta-Container-Update-Override-Etag':
            'encrypt me, too',
            'X-Object-Meta-Test': 'encrypt me',
            'Content-Length': '8',
            'X-Object-Meta-Ignores-Case': 'thIs pArt WilL bE cOol',
            'X-Object-Sysmeta-Test': 'do not encrypt me',
            'Content-Type': 'text/plain',
        }
        self.assertEqual(dict(headers), expected)
        self.assertEqual('fAkE ApP', ''.join(app_iter))
Example #6
0
 def test_GET_multiseg_with_range(self):
     env = {'REQUEST_METHOD': 'GET', CRYPTO_KEY_CALLBACK: fetch_crypto_keys}
     req = Request.blank('/v1/a/c/o', environ=env)
     req.headers['Content-Range'] = 'bytes 3-10/17'
     chunks = ['0123', '45678', '9abcdef']
     body = ''.join(chunks)
     plaintext_etag = md5hex(body)
     body_key = os.urandom(32)
     ctxt = Crypto().create_encryption_ctxt(body_key, FAKE_IV)
     enc_body = [encrypt(chunk, ctxt=ctxt) for chunk in chunks]
     enc_body = [enc_body[0][3:], enc_body[1], enc_body[2][:2]]
     hdrs = self._make_response_headers(sum(map(len,
                                                enc_body)), plaintext_etag,
                                        fetch_crypto_keys(), body_key)
     hdrs['content-range'] = req.headers['Content-Range']
     self.app.register('GET',
                       '/v1/a/c/o',
                       HTTPOk,
                       body=enc_body,
                       headers=hdrs)
     resp = req.get_response(self.decrypter)
     self.assertEqual('3456789a', resp.body)
     self.assertEqual('200 OK', resp.status)
     self.assertEqual(plaintext_etag, resp.headers['Etag'])
     self.assertEqual('text/plain', resp.headers['Content-Type'])
Example #7
0
    def test_headers_case(self):
        body = 'fAkE ApP'
        req = Request.blank('/v1/a/c/o', body='FaKe')
        req.environ[CRYPTO_KEY_CALLBACK] = fetch_crypto_keys
        plaintext_etag = md5hex(body)
        body_key = os.urandom(32)
        enc_body = encrypt(body, body_key, FAKE_IV)
        hdrs = self._make_response_headers(len(enc_body), plaintext_etag,
                                           fetch_crypto_keys(), body_key)

        hdrs.update({
            'x-Object-mEta-ignoRes-caSe': 'thIs pArt WilL bE cOol',
        })
        self.app.register('GET',
                          '/v1/a/c/o',
                          HTTPOk,
                          body=enc_body,
                          headers=hdrs)

        status, headers, app_iter = req.call_application(self.decrypter)
        self.assertEqual(status, '200 OK')
        expected = {
            'Etag': '7f7837924188f7b511a9e3881a9f77a8',
            'X-Object-Sysmeta-Container-Update-Override-Etag':
            'encrypt me, too',
            'X-Object-Meta-Test': 'encrypt me',
            'Content-Length': '8',
            'X-Object-Meta-Ignores-Case': 'thIs pArt WilL bE cOol',
            'X-Object-Sysmeta-Test': 'do not encrypt me',
            'Content-Type': 'text/plain',
        }
        self.assertEqual(dict(headers), expected)
        self.assertEqual('fAkE ApP', ''.join(app_iter))
Example #8
0
    def test_PUT_with_bad_etag_in_other_footers(self):
        # verify that etag supplied in footers from other middleware overrides
        # header etag when validating inbound plaintext etags
        plaintext = 'FAKE APP'
        plaintext_etag = md5hex(plaintext)
        other_footers = {
            'Etag': 'bad etag',
            'X-Object-Sysmeta-Other': 'other sysmeta',
            'X-Object-Sysmeta-Container-Update-Override-Etag': 'other override'
        }

        env = {
            'REQUEST_METHOD':
            'PUT',
            CRYPTO_KEY_CALLBACK:
            fetch_crypto_keys,
            'swift.callback.update_footers':
            lambda footers: footers.update(other_footers)
        }
        hdrs = {
            'content-type': 'text/plain',
            'content-length': str(len(plaintext)),
            'Etag': plaintext_etag
        }
        req = Request.blank('/v1/a/c/o',
                            environ=env,
                            body=plaintext,
                            headers=hdrs)
        self.app.register('PUT', '/v1/a/c/o', HTTPCreated, {})
        resp = req.get_response(self.encrypter)
        self.assertEqual('422 Unprocessable Entity', resp.status)
        self.assertNotIn('Etag', resp.headers)
Example #9
0
 def _test_req_metadata_not_encrypted(self, method):
     # check that metadata is not decrypted if it does not have crypto meta;
     # testing for case of an unencrypted POST to an object.
     env = {
         'REQUEST_METHOD': method,
         CRYPTO_KEY_CALLBACK: fetch_crypto_keys
     }
     req = Request.blank('/v1/a/c/o', environ=env)
     body = 'FAKE APP'
     plaintext_etag = md5hex(body)
     body_key = os.urandom(32)
     enc_body = encrypt(body, body_key, FAKE_IV)
     hdrs = self._make_response_headers(len(body), plaintext_etag,
                                        fetch_crypto_keys(), body_key)
     hdrs.pop('x-object-transient-sysmeta-crypto-meta-test')
     hdrs['x-object-meta-test'] = 'plaintext not encrypted'
     self.app.register(method,
                       '/v1/a/c/o',
                       HTTPOk,
                       body=enc_body,
                       headers=hdrs)
     resp = req.get_response(self.decrypter)
     self.assertEqual('200 OK', resp.status)
     self.assertEqual(plaintext_etag, resp.headers['Etag'])
     self.assertEqual('text/plain', resp.headers['Content-Type'])
     self.assertEqual('plaintext not encrypted',
                      resp.headers['x-object-meta-test'])
    def test_GET_multipart_content_type(self):
        # *just* having multipart content type shouldn't trigger the mime doc
        # code path
        body_key = os.urandom(32)
        plaintext = 'Cwm fjord veg balks nth pyx quiz'
        plaintext_etag = md5hex(plaintext)
        ciphertext = encrypt(plaintext, body_key, FAKE_IV)

        # register request with fake swift
        hdrs = self._make_response_headers(
            len(ciphertext), plaintext_etag, fetch_crypto_keys(), body_key)
        hdrs['content-type'] = \
            'multipart/byteranges;boundary=multipartboundary'
        self.app.register('GET', '/v1/a/c/o', HTTPOk, body=ciphertext,
                          headers=hdrs)

        # issue request
        env = {'REQUEST_METHOD': 'GET',
               CRYPTO_KEY_CALLBACK: fetch_crypto_keys}
        req = Request.blank('/v1/a/c/o', environ=env)
        resp = req.get_response(self.decrypter)

        self.assertEqual('200 OK', resp.status)
        self.assertEqual(plaintext_etag, resp.headers['Etag'])
        self.assertEqual(len(plaintext), int(resp.headers['Content-Length']))
        self.assertEqual('multipart/byteranges;boundary=multipartboundary',
                         resp.headers['Content-Type'])
        self.assertEqual(plaintext, resp.body)
Example #11
0
    def test_GET_multipart_content_type(self):
        # *just* having multipart content type shouldn't trigger the mime doc
        # code path
        body_key = os.urandom(32)
        plaintext = 'Cwm fjord veg balks nth pyx quiz'
        plaintext_etag = md5hex(plaintext)
        ciphertext = encrypt(plaintext, body_key, FAKE_IV)

        # register request with fake swift
        hdrs = self._make_response_headers(len(ciphertext), plaintext_etag,
                                           fetch_crypto_keys(), body_key)
        hdrs['content-type'] = \
            'multipart/byteranges;boundary=multipartboundary'
        self.app.register('GET',
                          '/v1/a/c/o',
                          HTTPOk,
                          body=ciphertext,
                          headers=hdrs)

        # issue request
        env = {'REQUEST_METHOD': 'GET', CRYPTO_KEY_CALLBACK: fetch_crypto_keys}
        req = Request.blank('/v1/a/c/o', environ=env)
        resp = req.get_response(self.decrypter)

        self.assertEqual('200 OK', resp.status)
        self.assertEqual(plaintext_etag, resp.headers['Etag'])
        self.assertEqual(len(plaintext), int(resp.headers['Content-Length']))
        self.assertEqual('multipart/byteranges;boundary=multipartboundary',
                         resp.headers['Content-Type'])
        self.assertEqual(plaintext, resp.body)
    def _test_request_success(self, method, body):
        env = {'REQUEST_METHOD': method,
               CRYPTO_KEY_CALLBACK: fetch_crypto_keys}
        req = Request.blank('/v1/a/c/o', environ=env)
        plaintext_etag = md5hex(body)
        body_key = os.urandom(32)
        enc_body = encrypt(body, body_key, FAKE_IV)
        hdrs = self._make_response_headers(
            len(enc_body), plaintext_etag, fetch_crypto_keys(), body_key)

        # there shouldn't be any x-object-meta- headers, but if there are
        # then the decrypted header will win where there is a name clash...
        hdrs.update({
            'x-object-meta-test': 'unexpected, overwritten by decrypted value',
            'x-object-meta-distinct': 'unexpected but distinct from encrypted'
        })
        self.app.register(
            method, '/v1/a/c/o', HTTPOk, body=enc_body, headers=hdrs)
        resp = req.get_response(self.decrypter)
        self.assertEqual('200 OK', resp.status)
        self.assertEqual(plaintext_etag, resp.headers['Etag'])
        self.assertEqual('text/plain', resp.headers['Content-Type'])
        self.assertEqual('encrypt me', resp.headers['x-object-meta-test'])
        self.assertEqual('unexpected but distinct from encrypted',
                         resp.headers['x-object-meta-distinct'])
        self.assertEqual('do not encrypt me',
                         resp.headers['x-object-sysmeta-test'])
        self.assertEqual(
            'encrypt me, too',
            resp.headers['X-Object-Sysmeta-Container-Update-Override-Etag'])
        self.assertNotIn('X-Object-Sysmeta-Crypto-Body-Meta', resp.headers)
        self.assertNotIn('X-Object-Sysmeta-Crypto-Etag', resp.headers)
        return resp
Example #13
0
 def _test_bad_crypto_meta_for_user_metadata(self, method, bad_crypto_meta):
     # use bad iv for metadata headers
     env = {
         'REQUEST_METHOD': method,
         CRYPTO_KEY_CALLBACK: fetch_crypto_keys
     }
     req = Request.blank('/v1/a/c/o', environ=env)
     body = 'FAKE APP'
     key = fetch_crypto_keys()['object']
     enc_body = encrypt(body, key, FAKE_IV)
     hdrs = self._make_response_headers(len(body), md5hex(body),
                                        fetch_crypto_keys(), 'not used')
     enc_val = base64.b64encode(encrypt('encrypt me', key, FAKE_IV))
     if bad_crypto_meta:
         enc_val += ';swift_meta=' + get_crypto_meta_header(
             crypto_meta=bad_crypto_meta)
     hdrs['x-object-transient-sysmeta-crypto-meta-test'] = enc_val
     self.app.register(method,
                       '/v1/a/c/o',
                       HTTPOk,
                       body=enc_body,
                       headers=hdrs)
     resp = req.get_response(self.decrypter)
     self.assertEqual('500 Internal Error', resp.status)
     self.assertIn(
         'Error decrypting header X-Object-Transient-Sysmeta-Crypto-Meta-'
         'Test',
         self.decrypter.logger.get_lines_for_level('error')[0])
     return resp
Example #14
0
    def _test_PUT_with_empty_etag_override_in_headers(self, plaintext):
        # verify that an override etag value of '' from other middleware is
        # passed through unencrypted
        plaintext_etag = md5hex(plaintext)
        override_etag = ''
        env = {'REQUEST_METHOD': 'PUT', CRYPTO_KEY_CALLBACK: fetch_crypto_keys}
        hdrs = {
            'content-type': 'text/plain',
            'content-length': str(len(plaintext)),
            'Etag': plaintext_etag,
            'X-Object-Sysmeta-Container-Update-Override-Etag': override_etag
        }
        req = Request.blank('/v1/a/c/o',
                            environ=env,
                            body=plaintext,
                            headers=hdrs)
        self.app.register('PUT', '/v1/a/c/o', HTTPCreated, {})
        resp = req.get_response(self.encrypter)

        self.assertEqual('201 Created', resp.status)
        self.assertEqual(plaintext_etag, resp.headers['Etag'])
        self.assertEqual(1, len(self.app.calls), self.app.calls)
        self.assertEqual(('PUT', '/v1/a/c/o'), self.app.calls[0])
        req_hdrs = self.app.headers[0]
        self.assertIn('X-Object-Sysmeta-Container-Update-Override-Etag',
                      req_hdrs)
        self.assertEqual(
            override_etag,
            req_hdrs['X-Object-Sysmeta-Container-Update-Override-Etag'])
Example #15
0
 def test_GET_cipher_mismatch_for_metadata(self):
     # Cipher does not match
     env = {'REQUEST_METHOD': 'GET', CRYPTO_KEY_CALLBACK: fetch_crypto_keys}
     req = Request.blank('/v1/a/c/o', environ=env)
     body = 'FAKE APP'
     key = fetch_crypto_keys()['object']
     enc_body = encrypt(body, key, FAKE_IV)
     bad_crypto_meta = fake_get_crypto_meta()
     bad_crypto_meta['cipher'] = 'unknown_cipher'
     hdrs = self._make_response_headers(len(enc_body), md5hex(body),
                                        fetch_crypto_keys(), 'not used')
     hdrs.update({
         'x-object-transient-sysmeta-crypto-meta-test':
         base64.b64encode(encrypt('encrypt me', key, FAKE_IV)) +
         ';swift_meta=' +
         get_crypto_meta_header(crypto_meta=bad_crypto_meta)
     })
     self.app.register('GET',
                       '/v1/a/c/o',
                       HTTPOk,
                       body=enc_body,
                       headers=hdrs)
     resp = req.get_response(self.decrypter)
     self.assertEqual('500 Internal Error', resp.status)
     self.assertEqual('Error decrypting header', resp.body)
     self.assertIn(
         'Error decrypting header X-Object-Transient-Sysmeta-Crypto-Meta-'
         'Test',
         self.decrypter.logger.get_lines_for_level('error')[0])
Example #16
0
    def test_PUT_multiseg_good_client_etag(self):
        body_key = os.urandom(32)
        chunks = ['some', 'chunks', 'of data']
        body = ''.join(chunks)
        env = {
            'REQUEST_METHOD': 'PUT',
            CRYPTO_KEY_CALLBACK: fetch_crypto_keys,
            'wsgi.input': FileLikeIter(chunks)
        }
        hdrs = {
            'content-type': 'text/plain',
            'content-length': str(len(body)),
            'Etag': md5hex(body)
        }
        req = Request.blank('/v1/a/c/o', environ=env, headers=hdrs)
        self.app.register('PUT', '/v1/a/c/o', HTTPCreated, {})

        with mock.patch(
                'swift.common.middleware.crypto.crypto_utils.'
                'Crypto.create_random_key', lambda *args: body_key):
            resp = req.get_response(self.encrypter)

        self.assertEqual('201 Created', resp.status)
        # verify object is encrypted by getting direct from the app
        get_req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'GET'})
        self.assertEqual(encrypt(body, body_key, FAKE_IV),
                         get_req.get_response(self.app).body)
Example #17
0
    def _test_PUT_with_empty_etag_override_in_footers(self, plaintext):
        # verify that an override etag value of '' from other middleware is
        # passed through unencrypted
        plaintext_etag = md5hex(plaintext)
        override_etag = ''
        other_footers = {
            'X-Object-Sysmeta-Container-Update-Override-Etag': override_etag}
        env = {'REQUEST_METHOD': 'PUT',
               CRYPTO_KEY_CALLBACK: fetch_crypto_keys,
               'swift.callback.update_footers':
                   lambda footers: footers.update(other_footers)}

        hdrs = {'content-type': 'text/plain',
                'content-length': str(len(plaintext)),
                'Etag': plaintext_etag}
        req = Request.blank(
            '/v1/a/c/o', environ=env, body=plaintext, headers=hdrs)
        self.app.register('PUT', '/v1/a/c/o', HTTPCreated, {})
        resp = req.get_response(self.encrypter)

        self.assertEqual('201 Created', resp.status)
        self.assertEqual(plaintext_etag, resp.headers['Etag'])
        self.assertEqual(1, len(self.app.calls), self.app.calls)
        self.assertEqual(('PUT', '/v1/a/c/o'), self.app.calls[0])
        req_hdrs = self.app.headers[0]
        self.assertIn(
            'X-Object-Sysmeta-Container-Update-Override-Etag', req_hdrs)
        self.assertEqual(
            override_etag,
            req_hdrs['X-Object-Sysmeta-Container-Update-Override-Etag'])
Example #18
0
    def test_GET_multipart_ciphertext(self):
        # build fake multipart response body
        body_key = os.urandom(32)
        plaintext = 'Cwm fjord veg balks nth pyx quiz'
        plaintext_etag = md5hex(plaintext)
        ciphertext = encrypt(plaintext, body_key, FAKE_IV)
        parts = ((0, 3, 'text/plain'), (4, 9, 'text/plain; charset=us-ascii'),
                 (24, 32, 'text/plain'))
        length = len(ciphertext)
        body = ''
        for start, end, ctype in parts:
            body += '--multipartboundary\r\n'
            body += 'Content-Type: %s\r\n' % ctype
            body += 'Content-Range: bytes %s-%s/%s' % (start, end - 1, length)
            body += '\r\n\r\n' + ciphertext[start:end] + '\r\n'
        body += '--multipartboundary--'

        # register request with fake swift
        hdrs = self._make_response_headers(len(body), plaintext_etag,
                                           fetch_crypto_keys(), body_key)
        hdrs['content-type'] = \
            'multipart/byteranges;boundary=multipartboundary'
        self.app.register('GET',
                          '/v1/a/c/o',
                          HTTPPartialContent,
                          body=body,
                          headers=hdrs)

        # issue request
        env = {'REQUEST_METHOD': 'GET', CRYPTO_KEY_CALLBACK: fetch_crypto_keys}
        req = Request.blank('/v1/a/c/o', environ=env)
        resp = req.get_response(self.decrypter)

        self.assertEqual('206 Partial Content', resp.status)
        self.assertEqual(plaintext_etag, resp.headers['Etag'])
        self.assertEqual(len(body), int(resp.headers['Content-Length']))
        self.assertEqual('multipart/byteranges;boundary=multipartboundary',
                         resp.headers['Content-Type'])

        # the multipart headers could be re-ordered, so parse response body to
        # verify expected content
        resp_lines = resp.body.split('\r\n')
        resp_lines.reverse()
        for start, end, ctype in parts:
            self.assertEqual('--multipartboundary', resp_lines.pop())
            expected_header_lines = {
                'Content-Type: %s' % ctype,
                'Content-Range: bytes %s-%s/%s' % (start, end - 1, length)
            }
            resp_header_lines = {resp_lines.pop(), resp_lines.pop()}
            self.assertEqual(expected_header_lines, resp_header_lines)
            self.assertEqual('', resp_lines.pop())
            self.assertEqual(plaintext[start:end], resp_lines.pop())
        self.assertEqual('--multipartboundary--', resp_lines.pop())

        # we should have consumed the whole response body
        self.assertFalse(resp_lines)
 def test_GET_missing_etag_crypto_meta(self):
     env = {'REQUEST_METHOD': 'GET',
            CRYPTO_KEY_CALLBACK: fetch_crypto_keys}
     req = Request.blank('/v1/a/c/o', environ=env)
     body = 'FAKE APP'
     key = fetch_crypto_keys()['object']
     enc_body = encrypt(body, key, FAKE_IV)
     hdrs = self._make_response_headers(
         len(body), md5hex(body), fetch_crypto_keys(), 'not used')
     # simulate missing crypto meta from encrypted etag
     hdrs['X-Object-Sysmeta-Crypto-Etag'] = \
         base64.b64encode(encrypt(md5hex(body), key, FAKE_IV))
     self.app.register('GET', '/v1/a/c/o', HTTPOk, body=enc_body,
                       headers=hdrs)
     resp = req.get_response(self.decrypter)
     self.assertEqual('500 Internal Error', resp.status)
     self.assertIn('Error decrypting header', resp.body)
     self.assertIn('Error decrypting header X-Object-Sysmeta-Crypto-Etag',
                   self.decrypter.logger.get_lines_for_level('error')[0])
    def test_GET_multipart_ciphertext(self):
        # build fake multipart response body
        body_key = os.urandom(32)
        plaintext = 'Cwm fjord veg balks nth pyx quiz'
        plaintext_etag = md5hex(plaintext)
        ciphertext = encrypt(plaintext, body_key, FAKE_IV)
        parts = ((0, 3, 'text/plain'),
                 (4, 9, 'text/plain; charset=us-ascii'),
                 (24, 32, 'text/plain'))
        length = len(ciphertext)
        body = ''
        for start, end, ctype in parts:
            body += '--multipartboundary\r\n'
            body += 'Content-Type: %s\r\n' % ctype
            body += 'Content-Range: bytes %s-%s/%s' % (start, end - 1, length)
            body += '\r\n\r\n' + ciphertext[start:end] + '\r\n'
        body += '--multipartboundary--'

        # register request with fake swift
        hdrs = self._make_response_headers(
            len(body), plaintext_etag, fetch_crypto_keys(), body_key)
        hdrs['content-type'] = \
            'multipart/byteranges;boundary=multipartboundary'
        self.app.register('GET', '/v1/a/c/o', HTTPPartialContent, body=body,
                          headers=hdrs)

        # issue request
        env = {'REQUEST_METHOD': 'GET',
               CRYPTO_KEY_CALLBACK: fetch_crypto_keys}
        req = Request.blank('/v1/a/c/o', environ=env)
        resp = req.get_response(self.decrypter)

        self.assertEqual('206 Partial Content', resp.status)
        self.assertEqual(plaintext_etag, resp.headers['Etag'])
        self.assertEqual(len(body), int(resp.headers['Content-Length']))
        self.assertEqual('multipart/byteranges;boundary=multipartboundary',
                         resp.headers['Content-Type'])

        # the multipart headers could be re-ordered, so parse response body to
        # verify expected content
        resp_lines = resp.body.split('\r\n')
        resp_lines.reverse()
        for start, end, ctype in parts:
            self.assertEqual('--multipartboundary', resp_lines.pop())
            expected_header_lines = {
                'Content-Type: %s' % ctype,
                'Content-Range: bytes %s-%s/%s' % (start, end - 1, length)}
            resp_header_lines = {resp_lines.pop(), resp_lines.pop()}
            self.assertEqual(expected_header_lines, resp_header_lines)
            self.assertEqual('', resp_lines.pop())
            self.assertEqual(plaintext[start:end], resp_lines.pop())
        self.assertEqual('--multipartboundary--', resp_lines.pop())

        # we should have consumed the whole response body
        self.assertFalse(resp_lines)
    def _test_412_response(self, method):
        # simulate a 412 response to a conditional GET which has an Etag header
        data = 'the object content'
        env = {CRYPTO_KEY_CALLBACK: fetch_crypto_keys}
        req = Request.blank('/v1/a/c/o', environ=env, method=method)
        resp_body = 'I am sorry, you have failed to meet a precondition'
        hdrs = self._make_response_headers(
            len(resp_body), md5hex(data), fetch_crypto_keys(), 'not used')
        self.app.register(method, '/v1/a/c/o', HTTPPreconditionFailed,
                          body=resp_body, headers=hdrs)
        resp = req.get_response(self.decrypter)

        self.assertEqual('412 Precondition Failed', resp.status)
        # the response body should not be decrypted, it is already plaintext
        self.assertEqual(resp_body if method == 'GET' else '', resp.body)
        # whereas the Etag and other headers should be decrypted
        self.assertEqual(md5hex(data), resp.headers['Etag'])
        self.assertEqual('text/plain', resp.headers['Content-Type'])
        self.assertEqual('encrypt me', resp.headers['x-object-meta-test'])
        self.assertEqual('do not encrypt me',
                         resp.headers['x-object-sysmeta-test'])
 def _test_override_etag_bad_meta(self, method, bad_crypto_meta):
     env = {'REQUEST_METHOD': method,
            CRYPTO_KEY_CALLBACK: fetch_crypto_keys}
     req = Request.blank('/v1/a/c/o', environ=env)
     body = 'FAKE APP'
     key = fetch_crypto_keys()['object']
     enc_body = encrypt(body, key, FAKE_IV)
     hdrs = self._make_response_headers(
         len(body), md5hex(body), fetch_crypto_keys(), 'not used')
     # simulate missing crypto meta from encrypted override etag
     hdrs['X-Object-Sysmeta-Container-Update-Override-Etag'] = \
         encrypt_and_append_meta(
             md5hex(body), key, crypto_meta=bad_crypto_meta)
     self.app.register(method, '/v1/a/c/o', HTTPOk, body=enc_body,
                       headers=hdrs)
     resp = req.get_response(self.decrypter)
     self.assertEqual('500 Internal Error', resp.status)
     self.assertIn('Error decrypting header '
                   'X-Object-Sysmeta-Container-Update-Override-Etag',
                   self.decrypter.logger.get_lines_for_level('error')[0])
     return resp
Example #23
0
 def test_GET_missing_etag_crypto_meta(self):
     env = {'REQUEST_METHOD': 'GET', CRYPTO_KEY_CALLBACK: fetch_crypto_keys}
     req = Request.blank('/v1/a/c/o', environ=env)
     body = 'FAKE APP'
     key = fetch_crypto_keys()['object']
     enc_body = encrypt(body, key, FAKE_IV)
     hdrs = self._make_response_headers(len(body), md5hex(body),
                                        fetch_crypto_keys(), 'not used')
     # simulate missing crypto meta from encrypted etag
     hdrs['X-Object-Sysmeta-Crypto-Etag'] = \
         base64.b64encode(encrypt(md5hex(body), key, FAKE_IV))
     self.app.register('GET',
                       '/v1/a/c/o',
                       HTTPOk,
                       body=enc_body,
                       headers=hdrs)
     resp = req.get_response(self.decrypter)
     self.assertEqual('500 Internal Error', resp.status)
     self.assertIn('Error decrypting header', resp.body)
     self.assertIn('Error decrypting header X-Object-Sysmeta-Crypto-Etag',
                   self.decrypter.logger.get_lines_for_level('error')[0])
 def test_GET_decryption_override(self):
     # This covers the case of an old un-encrypted object
     env = {'REQUEST_METHOD': 'GET',
            CRYPTO_KEY_CALLBACK: fetch_crypto_keys,
            'swift.crypto.override': True}
     req = Request.blank('/v1/a/c/o', environ=env)
     body = 'FAKE APP'
     hdrs = {'Etag': md5hex(body),
             'content-type': 'text/plain',
             'content-length': len(body),
             'x-object-meta-test': 'do not encrypt me',
             'x-object-sysmeta-test': 'do not encrypt me'}
     self.app.register('GET', '/v1/a/c/o', HTTPOk, body=body, headers=hdrs)
     resp = req.get_response(self.decrypter)
     self.assertEqual(body, resp.body)
     self.assertEqual('200 OK', resp.status)
     self.assertEqual(md5hex(body), resp.headers['Etag'])
     self.assertEqual('text/plain', resp.headers['Content-Type'])
     self.assertEqual('do not encrypt me',
                      resp.headers['x-object-meta-test'])
     self.assertEqual('do not encrypt me',
                      resp.headers['x-object-sysmeta-test'])
Example #25
0
    def _test_PUT_with_etag_override_in_headers(self, override_etag):
        # verify handling of another middleware's
        # container-update-override-etag in headers
        plaintext = b'FAKE APP'
        plaintext_etag = md5hex(plaintext)

        env = {'REQUEST_METHOD': 'PUT', CRYPTO_KEY_CALLBACK: fetch_crypto_keys}
        hdrs = {
            'content-type': 'text/plain',
            'content-length': str(len(plaintext)),
            'Etag': plaintext_etag,
            'X-Object-Sysmeta-Container-Update-Override-Etag': override_etag
        }
        req = Request.blank('/v1/a/c/o',
                            environ=env,
                            body=plaintext,
                            headers=hdrs)
        self.app.register('PUT', '/v1/a/c/o', HTTPCreated, {})
        resp = req.get_response(self.encrypter)

        self.assertEqual('201 Created', resp.status)
        self.assertEqual(plaintext_etag, resp.headers['Etag'])

        # verify metadata items
        self.assertEqual(1, len(self.app.calls), self.app.calls)
        self.assertEqual(('PUT', '/v1/a/c/o'), self.app.calls[0])
        req_hdrs = self.app.headers[0]

        # verify encrypted etag for container update
        self.assertIn('X-Object-Sysmeta-Container-Update-Override-Etag',
                      req_hdrs)
        parts = req_hdrs[
            'X-Object-Sysmeta-Container-Update-Override-Etag'].rsplit(';', 1)
        self.assertEqual(2, len(parts))
        cont_key = fetch_crypto_keys()['container']

        # extract crypto_meta from end of etag for container update
        param = parts[1].strip()
        crypto_meta_tag = 'swift_meta='
        self.assertTrue(param.startswith(crypto_meta_tag), param)
        actual_meta = json.loads(
            urlparse.unquote_plus(param[len(crypto_meta_tag):]))
        self.assertEqual(Crypto().cipher, actual_meta['cipher'])
        self.assertEqual(fetch_crypto_keys()['id'], actual_meta['key_id'])

        cont_etag_iv = base64.b64decode(actual_meta['iv'])
        self.assertEqual(FAKE_IV, cont_etag_iv)
        exp_etag = encrypt(override_etag.encode('ascii'), cont_key,
                           cont_etag_iv)
        self.assertEqual(exp_etag, base64.b64decode(parts[0]))
Example #26
0
    def _test_412_response(self, method):
        # simulate a 412 response to a conditional GET which has an Etag header
        data = 'the object content'
        env = {CRYPTO_KEY_CALLBACK: fetch_crypto_keys}
        req = Request.blank('/v1/a/c/o', environ=env, method=method)
        resp_body = 'I am sorry, you have failed to meet a precondition'
        hdrs = self._make_response_headers(len(resp_body), md5hex(data),
                                           fetch_crypto_keys(), 'not used')
        self.app.register(method,
                          '/v1/a/c/o',
                          HTTPPreconditionFailed,
                          body=resp_body,
                          headers=hdrs)
        resp = req.get_response(self.decrypter)

        self.assertEqual('412 Precondition Failed', resp.status)
        # the response body should not be decrypted, it is already plaintext
        self.assertEqual(resp_body if method == 'GET' else '', resp.body)
        # whereas the Etag and other headers should be decrypted
        self.assertEqual(md5hex(data), resp.headers['Etag'])
        self.assertEqual('text/plain', resp.headers['Content-Type'])
        self.assertEqual('encrypt me', resp.headers['x-object-meta-test'])
        self.assertEqual('do not encrypt me',
                         resp.headers['x-object-sysmeta-test'])
Example #27
0
    def _test_PUT_with_etag_override_in_headers(self, override_etag):
        # verify handling of another middleware's
        # container-update-override-etag in headers
        plaintext = b'FAKE APP'
        plaintext_etag = md5hex(plaintext)

        env = {'REQUEST_METHOD': 'PUT',
               CRYPTO_KEY_CALLBACK: fetch_crypto_keys}
        hdrs = {'content-type': 'text/plain',
                'content-length': str(len(plaintext)),
                'Etag': plaintext_etag,
                'X-Object-Sysmeta-Container-Update-Override-Etag':
                    override_etag}
        req = Request.blank(
            '/v1/a/c/o', environ=env, body=plaintext, headers=hdrs)
        self.app.register('PUT', '/v1/a/c/o', HTTPCreated, {})
        resp = req.get_response(self.encrypter)

        self.assertEqual('201 Created', resp.status)
        self.assertEqual(plaintext_etag, resp.headers['Etag'])

        # verify metadata items
        self.assertEqual(1, len(self.app.calls), self.app.calls)
        self.assertEqual(('PUT', '/v1/a/c/o'), self.app.calls[0])
        req_hdrs = self.app.headers[0]

        # verify encrypted etag for container update
        self.assertIn(
            'X-Object-Sysmeta-Container-Update-Override-Etag', req_hdrs)
        parts = req_hdrs[
            'X-Object-Sysmeta-Container-Update-Override-Etag'].rsplit(';', 1)
        self.assertEqual(2, len(parts))
        cont_key = fetch_crypto_keys()['container']

        # extract crypto_meta from end of etag for container update
        param = parts[1].strip()
        crypto_meta_tag = 'swift_meta='
        self.assertTrue(param.startswith(crypto_meta_tag), param)
        actual_meta = json.loads(
            urlparse.unquote_plus(param[len(crypto_meta_tag):]))
        self.assertEqual(Crypto().cipher, actual_meta['cipher'])
        self.assertEqual(fetch_crypto_keys()['id'], actual_meta['key_id'])

        cont_etag_iv = base64.b64decode(actual_meta['iv'])
        self.assertEqual(FAKE_IV, cont_etag_iv)
        exp_etag = encrypt(override_etag.encode('ascii'),
                           cont_key, cont_etag_iv)
        self.assertEqual(exp_etag, base64.b64decode(parts[0]))
 def test_GET_missing_key_callback(self):
     # Do not provide keys, and do not set override flag
     env = {'REQUEST_METHOD': 'GET'}
     req = Request.blank('/v1/a/c/o', environ=env)
     body = 'FAKE APP'
     enc_body = encrypt(body, fetch_crypto_keys()['object'], FAKE_IV)
     hdrs = self._make_response_headers(
         len(body), md5hex('not the body'), fetch_crypto_keys(), 'not used')
     self.app.register(
         'GET', '/v1/a/c/o', HTTPOk, body=enc_body, headers=hdrs)
     resp = req.get_response(self.decrypter)
     self.assertEqual('500 Internal Error', resp.status)
     self.assertEqual('Unable to retrieve encryption keys.',
                      resp.body)
     self.assertIn('missing callback',
                   self.decrypter.logger.get_lines_for_level('error')[0])
    def _test_bad_key(self, method):
        # use bad key
        def bad_fetch_crypto_keys():
            keys = fetch_crypto_keys()
            keys['object'] = 'bad key'
            return keys

        env = {'REQUEST_METHOD': method,
               CRYPTO_KEY_CALLBACK: bad_fetch_crypto_keys}
        req = Request.blank('/v1/a/c/o', environ=env)
        body = 'FAKE APP'
        key = fetch_crypto_keys()['object']
        enc_body = encrypt(body, key, FAKE_IV)
        hdrs = self._make_response_headers(
            len(body), md5hex(body), fetch_crypto_keys(), 'not used')
        self.app.register(method, '/v1/a/c/o', HTTPOk, body=enc_body,
                          headers=hdrs)
        return req.get_response(self.decrypter)
Example #30
0
 def test_GET_missing_key_callback(self):
     # Do not provide keys, and do not set override flag
     env = {'REQUEST_METHOD': 'GET'}
     req = Request.blank('/v1/a/c/o', environ=env)
     body = 'FAKE APP'
     enc_body = encrypt(body, fetch_crypto_keys()['object'], FAKE_IV)
     hdrs = self._make_response_headers(len(body), md5hex('not the body'),
                                        fetch_crypto_keys(), 'not used')
     self.app.register('GET',
                       '/v1/a/c/o',
                       HTTPOk,
                       body=enc_body,
                       headers=hdrs)
     resp = req.get_response(self.decrypter)
     self.assertEqual('500 Internal Error', resp.status)
     self.assertEqual('Unable to retrieve encryption keys.', resp.body)
     self.assertIn('missing callback',
                   self.decrypter.logger.get_lines_for_level('error')[0])
    def test_GET_error_in_key_callback(self):
        def raise_exc():
            raise Exception('Testing')

        env = {'REQUEST_METHOD': 'GET',
               CRYPTO_KEY_CALLBACK: raise_exc}
        req = Request.blank('/v1/a/c/o', environ=env)
        body = 'FAKE APP'
        enc_body = encrypt(body, fetch_crypto_keys()['object'], FAKE_IV)
        hdrs = self._make_response_headers(
            len(body), md5hex(body), fetch_crypto_keys(), 'not used')
        self.app.register(
            'GET', '/v1/a/c/o', HTTPOk, body=enc_body, headers=hdrs)
        resp = req.get_response(self.decrypter)
        self.assertEqual('500 Internal Error', resp.status)
        self.assertEqual('Unable to retrieve encryption keys.',
                         resp.body)
        self.assertIn('from callback: Testing',
                      self.decrypter.logger.get_lines_for_level('error')[0])
 def _test_GET_with_bad_crypto_meta_for_object_body(self, bad_crypto_meta):
     # use bad iv for object body
     env = {'REQUEST_METHOD': 'GET',
            CRYPTO_KEY_CALLBACK: fetch_crypto_keys}
     req = Request.blank('/v1/a/c/o', environ=env)
     body = 'FAKE APP'
     key = fetch_crypto_keys()['object']
     enc_body = encrypt(body, key, FAKE_IV)
     hdrs = self._make_response_headers(
         len(body), md5hex(body), fetch_crypto_keys(), 'not used')
     hdrs['X-Object-Sysmeta-Crypto-Body-Meta'] = \
         get_crypto_meta_header(crypto_meta=bad_crypto_meta)
     self.app.register('GET', '/v1/a/c/o', HTTPOk, body=enc_body,
                       headers=hdrs)
     resp = req.get_response(self.decrypter)
     self.assertEqual('500 Internal Error', resp.status)
     self.assertEqual('Error decrypting object', resp.body)
     self.assertIn('Error decrypting object',
                   self.decrypter.logger.get_lines_for_level('error')[0])
Example #33
0
    def test_PUT_with_etag_override_in_headers(self):
        # verify handling of another middleware's
        # container-update-override-etag in headers
        plaintext = "FAKE APP"
        plaintext_etag = md5hex(plaintext)

        env = {"REQUEST_METHOD": "PUT", CRYPTO_KEY_CALLBACK: fetch_crypto_keys}
        hdrs = {
            "content-type": "text/plain",
            "content-length": str(len(plaintext)),
            "Etag": plaintext_etag,
            "X-Object-Sysmeta-Container-Update-Override-Etag": "final etag",
        }
        req = Request.blank("/v1/a/c/o", environ=env, body=plaintext, headers=hdrs)
        self.app.register("PUT", "/v1/a/c/o", HTTPCreated, {})
        resp = req.get_response(self.encrypter)

        self.assertEqual("201 Created", resp.status)
        self.assertEqual(plaintext_etag, resp.headers["Etag"])

        # verify metadata items
        self.assertEqual(1, len(self.app.calls), self.app.calls)
        self.assertEqual(("PUT", "/v1/a/c/o"), self.app.calls[0])
        req_hdrs = self.app.headers[0]

        # verify encrypted etag for container update
        self.assertIn("X-Object-Sysmeta-Container-Update-Override-Etag", req_hdrs)
        parts = req_hdrs["X-Object-Sysmeta-Container-Update-Override-Etag"].rsplit(";", 1)
        self.assertEqual(2, len(parts))
        cont_key = fetch_crypto_keys()["container"]

        # extract crypto_meta from end of etag for container update
        param = parts[1].strip()
        crypto_meta_tag = "swift_meta="
        self.assertTrue(param.startswith(crypto_meta_tag), param)
        actual_meta = json.loads(urllib.unquote_plus(param[len(crypto_meta_tag) :]))
        self.assertEqual(Crypto().cipher, actual_meta["cipher"])
        self.assertEqual(fetch_crypto_keys()["id"], actual_meta["key_id"])

        cont_etag_iv = base64.b64decode(actual_meta["iv"])
        self.assertEqual(FAKE_IV, cont_etag_iv)
        self.assertEqual(encrypt("final etag", cont_key, cont_etag_iv), base64.b64decode(parts[0]))
 def test_GET_multiseg(self):
     env = {'REQUEST_METHOD': 'GET',
            CRYPTO_KEY_CALLBACK: fetch_crypto_keys}
     req = Request.blank('/v1/a/c/o', environ=env)
     chunks = ['some', 'chunks', 'of data']
     body = ''.join(chunks)
     plaintext_etag = md5hex(body)
     body_key = os.urandom(32)
     ctxt = Crypto().create_encryption_ctxt(body_key, FAKE_IV)
     enc_body = [encrypt(chunk, ctxt=ctxt) for chunk in chunks]
     hdrs = self._make_response_headers(
         sum(map(len, enc_body)), plaintext_etag, fetch_crypto_keys(),
         body_key)
     self.app.register(
         'GET', '/v1/a/c/o', HTTPOk, body=enc_body, headers=hdrs)
     resp = req.get_response(self.decrypter)
     self.assertEqual(body, resp.body)
     self.assertEqual('200 OK', resp.status)
     self.assertEqual(plaintext_etag, resp.headers['Etag'])
     self.assertEqual('text/plain', resp.headers['Content-Type'])
Example #35
0
    def test_GET_error_in_key_callback(self):
        def raise_exc():
            raise Exception('Testing')

        env = {'REQUEST_METHOD': 'GET', CRYPTO_KEY_CALLBACK: raise_exc}
        req = Request.blank('/v1/a/c/o', environ=env)
        body = 'FAKE APP'
        enc_body = encrypt(body, fetch_crypto_keys()['object'], FAKE_IV)
        hdrs = self._make_response_headers(len(body), md5hex(body),
                                           fetch_crypto_keys(), 'not used')
        self.app.register('GET',
                          '/v1/a/c/o',
                          HTTPOk,
                          body=enc_body,
                          headers=hdrs)
        resp = req.get_response(self.decrypter)
        self.assertEqual('500 Internal Error', resp.status)
        self.assertEqual('Unable to retrieve encryption keys.', resp.body)
        self.assertIn('from callback: Testing',
                      self.decrypter.logger.get_lines_for_level('error')[0])
Example #36
0
 def _test_GET_with_bad_crypto_meta_for_object_body(self, bad_crypto_meta):
     # use bad iv for object body
     env = {'REQUEST_METHOD': 'GET', CRYPTO_KEY_CALLBACK: fetch_crypto_keys}
     req = Request.blank('/v1/a/c/o', environ=env)
     body = 'FAKE APP'
     key = fetch_crypto_keys()['object']
     enc_body = encrypt(body, key, FAKE_IV)
     hdrs = self._make_response_headers(len(body), md5hex(body),
                                        fetch_crypto_keys(), 'not used')
     hdrs['X-Object-Sysmeta-Crypto-Body-Meta'] = \
         get_crypto_meta_header(crypto_meta=bad_crypto_meta)
     self.app.register('GET',
                       '/v1/a/c/o',
                       HTTPOk,
                       body=enc_body,
                       headers=hdrs)
     resp = req.get_response(self.decrypter)
     self.assertEqual('500 Internal Error', resp.status)
     self.assertEqual('Error decrypting object', resp.body)
     self.assertIn('Error decrypting object',
                   self.decrypter.logger.get_lines_for_level('error')[0])
Example #37
0
    def test_GET_multipart_no_body_crypto_meta(self):
        # build fake multipart response body
        plaintext = 'Cwm fjord veg balks nth pyx quiz'
        plaintext_etag = md5hex(plaintext)
        parts = ((0, 3, 'text/plain'), (4, 9, 'text/plain; charset=us-ascii'),
                 (24, 32, 'text/plain'))
        length = len(plaintext)
        body = ''
        for start, end, ctype in parts:
            body += '--multipartboundary\r\n'
            body += 'Content-Type: %s\r\n' % ctype
            body += 'Content-Range: bytes %s-%s/%s' % (start, end - 1, length)
            body += '\r\n\r\n' + plaintext[start:end] + '\r\n'
        body += '--multipartboundary--'

        # register request with fake swift
        hdrs = {
            'Etag': plaintext_etag,
            'content-type': 'multipart/byteranges;boundary=multipartboundary',
            'content-length': len(body)
        }
        self.app.register('GET',
                          '/v1/a/c/o',
                          HTTPPartialContent,
                          body=body,
                          headers=hdrs)

        # issue request
        env = {'REQUEST_METHOD': 'GET', CRYPTO_KEY_CALLBACK: fetch_crypto_keys}
        req = Request.blank('/v1/a/c/o', environ=env)
        resp = req.get_response(self.decrypter)

        self.assertEqual('206 Partial Content', resp.status)
        self.assertEqual(plaintext_etag, resp.headers['Etag'])
        self.assertEqual(len(body), int(resp.headers['Content-Length']))
        self.assertEqual('multipart/byteranges;boundary=multipartboundary',
                         resp.headers['Content-Type'])

        # the multipart response body should be unchanged
        self.assertEqual(body, resp.body)
Example #38
0
    def _test_request_success(self, method, body):
        env = {
            'REQUEST_METHOD': method,
            CRYPTO_KEY_CALLBACK: fetch_crypto_keys
        }
        req = Request.blank('/v1/a/c/o', environ=env)
        plaintext_etag = md5hex(body)
        body_key = os.urandom(32)
        enc_body = encrypt(body, body_key, FAKE_IV)
        hdrs = self._make_response_headers(len(enc_body), plaintext_etag,
                                           fetch_crypto_keys(), body_key)

        # there shouldn't be any x-object-meta- headers, but if there are
        # then the decrypted header will win where there is a name clash...
        hdrs.update({
            'x-object-meta-test':
            'unexpected, overwritten by decrypted value',
            'x-object-meta-distinct':
            'unexpected but distinct from encrypted'
        })
        self.app.register(method,
                          '/v1/a/c/o',
                          HTTPOk,
                          body=enc_body,
                          headers=hdrs)
        resp = req.get_response(self.decrypter)
        self.assertEqual('200 OK', resp.status)
        self.assertEqual(plaintext_etag, resp.headers['Etag'])
        self.assertEqual('text/plain', resp.headers['Content-Type'])
        self.assertEqual('encrypt me', resp.headers['x-object-meta-test'])
        self.assertEqual('unexpected but distinct from encrypted',
                         resp.headers['x-object-meta-distinct'])
        self.assertEqual('do not encrypt me',
                         resp.headers['x-object-sysmeta-test'])
        self.assertEqual(
            'encrypt me, too',
            resp.headers['X-Object-Sysmeta-Container-Update-Override-Etag'])
        self.assertNotIn('X-Object-Sysmeta-Crypto-Body-Meta', resp.headers)
        self.assertNotIn('X-Object-Sysmeta-Crypto-Etag', resp.headers)
        return resp
Example #39
0
    def test_PUT_with_bad_etag_in_other_footers(self):
        # verify that etag supplied in footers from other middleware overrides
        # header etag when validating inbound plaintext etags
        plaintext = "FAKE APP"
        plaintext_etag = md5hex(plaintext)
        other_footers = {
            "Etag": "bad etag",
            "X-Object-Sysmeta-Other": "other sysmeta",
            "X-Object-Sysmeta-Container-Update-Override-Etag": "other override",
        }

        env = {
            "REQUEST_METHOD": "PUT",
            CRYPTO_KEY_CALLBACK: fetch_crypto_keys,
            "swift.callback.update_footers": lambda footers: footers.update(other_footers),
        }
        hdrs = {"content-type": "text/plain", "content-length": str(len(plaintext)), "Etag": plaintext_etag}
        req = Request.blank("/v1/a/c/o", environ=env, body=plaintext, headers=hdrs)
        self.app.register("PUT", "/v1/a/c/o", HTTPCreated, {})
        resp = req.get_response(self.encrypter)
        self.assertEqual("422 Unprocessable Entity", resp.status)
        self.assertNotIn("Etag", resp.headers)
 def test_GET_cipher_mismatch_for_body(self):
     # Cipher does not match
     env = {'REQUEST_METHOD': 'GET',
            CRYPTO_KEY_CALLBACK: fetch_crypto_keys}
     req = Request.blank('/v1/a/c/o', environ=env)
     body = 'FAKE APP'
     enc_body = encrypt(body, fetch_crypto_keys()['object'], FAKE_IV)
     bad_crypto_meta = fake_get_crypto_meta()
     bad_crypto_meta['cipher'] = 'unknown_cipher'
     hdrs = self._make_response_headers(
         len(enc_body), md5hex(body), fetch_crypto_keys(), 'not used')
     hdrs['X-Object-Sysmeta-Crypto-Body-Meta'] = \
         get_crypto_meta_header(crypto_meta=bad_crypto_meta)
     self.app.register(
         'GET', '/v1/a/c/o', HTTPOk, body=enc_body, headers=hdrs)
     resp = req.get_response(self.decrypter)
     self.assertEqual('500 Internal Error', resp.status)
     self.assertEqual('Error decrypting object', resp.body)
     self.assertIn('Error decrypting object',
                   self.decrypter.logger.get_lines_for_level('error')[0])
     self.assertIn('Bad crypto meta: Cipher',
                   self.decrypter.logger.get_lines_for_level('error')[0])
 def _test_req_metadata_not_encrypted(self, method):
     # check that metadata is not decrypted if it does not have crypto meta;
     # testing for case of an unencrypted POST to an object.
     env = {'REQUEST_METHOD': method,
            CRYPTO_KEY_CALLBACK: fetch_crypto_keys}
     req = Request.blank('/v1/a/c/o', environ=env)
     body = 'FAKE APP'
     plaintext_etag = md5hex(body)
     body_key = os.urandom(32)
     enc_body = encrypt(body, body_key, FAKE_IV)
     hdrs = self._make_response_headers(
         len(body), plaintext_etag, fetch_crypto_keys(), body_key)
     hdrs.pop('x-object-transient-sysmeta-crypto-meta-test')
     hdrs['x-object-meta-test'] = 'plaintext not encrypted'
     self.app.register(
         method, '/v1/a/c/o', HTTPOk, body=enc_body, headers=hdrs)
     resp = req.get_response(self.decrypter)
     self.assertEqual('200 OK', resp.status)
     self.assertEqual(plaintext_etag, resp.headers['Etag'])
     self.assertEqual('text/plain', resp.headers['Content-Type'])
     self.assertEqual('plaintext not encrypted',
                      resp.headers['x-object-meta-test'])
Example #42
0
 def test_GET_multiseg(self):
     env = {'REQUEST_METHOD': 'GET', CRYPTO_KEY_CALLBACK: fetch_crypto_keys}
     req = Request.blank('/v1/a/c/o', environ=env)
     chunks = ['some', 'chunks', 'of data']
     body = ''.join(chunks)
     plaintext_etag = md5hex(body)
     body_key = os.urandom(32)
     ctxt = Crypto().create_encryption_ctxt(body_key, FAKE_IV)
     enc_body = [encrypt(chunk, ctxt=ctxt) for chunk in chunks]
     hdrs = self._make_response_headers(sum(map(len,
                                                enc_body)), plaintext_etag,
                                        fetch_crypto_keys(), body_key)
     self.app.register('GET',
                       '/v1/a/c/o',
                       HTTPOk,
                       body=enc_body,
                       headers=hdrs)
     resp = req.get_response(self.decrypter)
     self.assertEqual(body, resp.body)
     self.assertEqual('200 OK', resp.status)
     self.assertEqual(plaintext_etag, resp.headers['Etag'])
     self.assertEqual('text/plain', resp.headers['Content-Type'])
    def test_GET_multipart_no_body_crypto_meta(self):
        # build fake multipart response body
        plaintext = 'Cwm fjord veg balks nth pyx quiz'
        plaintext_etag = md5hex(plaintext)
        parts = ((0, 3, 'text/plain'),
                 (4, 9, 'text/plain; charset=us-ascii'),
                 (24, 32, 'text/plain'))
        length = len(plaintext)
        body = ''
        for start, end, ctype in parts:
            body += '--multipartboundary\r\n'
            body += 'Content-Type: %s\r\n' % ctype
            body += 'Content-Range: bytes %s-%s/%s' % (start, end - 1, length)
            body += '\r\n\r\n' + plaintext[start:end] + '\r\n'
        body += '--multipartboundary--'

        # register request with fake swift
        hdrs = {
            'Etag': plaintext_etag,
            'content-type': 'multipart/byteranges;boundary=multipartboundary',
            'content-length': len(body)}
        self.app.register('GET', '/v1/a/c/o', HTTPPartialContent, body=body,
                          headers=hdrs)

        # issue request
        env = {'REQUEST_METHOD': 'GET',
               CRYPTO_KEY_CALLBACK: fetch_crypto_keys}
        req = Request.blank('/v1/a/c/o', environ=env)
        resp = req.get_response(self.decrypter)

        self.assertEqual('206 Partial Content', resp.status)
        self.assertEqual(plaintext_etag, resp.headers['Etag'])
        self.assertEqual(len(body), int(resp.headers['Content-Length']))
        self.assertEqual('multipart/byteranges;boundary=multipartboundary',
                         resp.headers['Content-Type'])

        # the multipart response body should be unchanged
        self.assertEqual(body, resp.body)
    def _test_GET_multipart_bad_body_crypto_meta(self, bad_crypto_meta):
        # build fake multipart response body
        key = fetch_crypto_keys()['object']
        ctxt = Crypto().create_encryption_ctxt(key, FAKE_IV)
        plaintext = 'Cwm fjord veg balks nth pyx quiz'
        plaintext_etag = md5hex(plaintext)
        ciphertext = encrypt(plaintext, ctxt=ctxt)
        parts = ((0, 3, 'text/plain'),
                 (4, 9, 'text/plain; charset=us-ascii'),
                 (24, 32, 'text/plain'))
        length = len(ciphertext)
        body = ''
        for start, end, ctype in parts:
            body += '--multipartboundary\r\n'
            body += 'Content-Type: %s\r\n' % ctype
            body += 'Content-Range: bytes %s-%s/%s' % (start, end - 1, length)
            body += '\r\n\r\n' + ciphertext[start:end] + '\r\n'
        body += '--multipartboundary--'

        # register request with fake swift
        hdrs = self._make_response_headers(
            len(body), plaintext_etag, fetch_crypto_keys(), 'not used')
        hdrs['content-type'] = \
            'multipart/byteranges;boundary=multipartboundary'
        hdrs['X-Object-Sysmeta-Crypto-Body-Meta'] = \
            get_crypto_meta_header(bad_crypto_meta)
        self.app.register('GET', '/v1/a/c/o', HTTPOk, body=body, headers=hdrs)

        # issue request
        env = {'REQUEST_METHOD': 'GET',
               CRYPTO_KEY_CALLBACK: fetch_crypto_keys}
        req = Request.blank('/v1/a/c/o', environ=env)
        resp = req.get_response(self.decrypter)

        self.assertEqual('500 Internal Error', resp.status)
        self.assertEqual('Error decrypting object', resp.body)
        self.assertIn('Error decrypting object',
                      self.decrypter.logger.get_lines_for_level('error')[0])
 def test_GET_multiseg_with_range(self):
     env = {'REQUEST_METHOD': 'GET',
            CRYPTO_KEY_CALLBACK: fetch_crypto_keys}
     req = Request.blank('/v1/a/c/o', environ=env)
     req.headers['Content-Range'] = 'bytes 3-10/17'
     chunks = ['0123', '45678', '9abcdef']
     body = ''.join(chunks)
     plaintext_etag = md5hex(body)
     body_key = os.urandom(32)
     ctxt = Crypto().create_encryption_ctxt(body_key, FAKE_IV)
     enc_body = [encrypt(chunk, ctxt=ctxt) for chunk in chunks]
     enc_body = [enc_body[0][3:], enc_body[1], enc_body[2][:2]]
     hdrs = self._make_response_headers(
         sum(map(len, enc_body)), plaintext_etag, fetch_crypto_keys(),
         body_key)
     hdrs['content-range'] = req.headers['Content-Range']
     self.app.register(
         'GET', '/v1/a/c/o', HTTPOk, body=enc_body, headers=hdrs)
     resp = req.get_response(self.decrypter)
     self.assertEqual('3456789a', resp.body)
     self.assertEqual('200 OK', resp.status)
     self.assertEqual(plaintext_etag, resp.headers['Etag'])
     self.assertEqual('text/plain', resp.headers['Content-Type'])
Example #46
0
    def _test_bad_key(self, method):
        # use bad key
        def bad_fetch_crypto_keys():
            keys = fetch_crypto_keys()
            keys['object'] = 'bad key'
            return keys

        env = {
            'REQUEST_METHOD': method,
            CRYPTO_KEY_CALLBACK: bad_fetch_crypto_keys
        }
        req = Request.blank('/v1/a/c/o', environ=env)
        body = 'FAKE APP'
        key = fetch_crypto_keys()['object']
        enc_body = encrypt(body, key, FAKE_IV)
        hdrs = self._make_response_headers(len(body), md5hex(body),
                                           fetch_crypto_keys(), 'not used')
        self.app.register(method,
                          '/v1/a/c/o',
                          HTTPOk,
                          body=enc_body,
                          headers=hdrs)
        return req.get_response(self.decrypter)
 def _test_bad_crypto_meta_for_user_metadata(self, method, bad_crypto_meta):
     # use bad iv for metadata headers
     env = {'REQUEST_METHOD': method,
            CRYPTO_KEY_CALLBACK: fetch_crypto_keys}
     req = Request.blank('/v1/a/c/o', environ=env)
     body = 'FAKE APP'
     key = fetch_crypto_keys()['object']
     enc_body = encrypt(body, key, FAKE_IV)
     hdrs = self._make_response_headers(
         len(body), md5hex(body), fetch_crypto_keys(), 'not used')
     enc_val = base64.b64encode(encrypt('encrypt me', key, FAKE_IV))
     if bad_crypto_meta:
         enc_val += ';swift_meta=' + get_crypto_meta_header(
             crypto_meta=bad_crypto_meta)
     hdrs['x-object-transient-sysmeta-crypto-meta-test'] = enc_val
     self.app.register(method, '/v1/a/c/o', HTTPOk, body=enc_body,
                       headers=hdrs)
     resp = req.get_response(self.decrypter)
     self.assertEqual('500 Internal Error', resp.status)
     self.assertIn(
         'Error decrypting header X-Object-Transient-Sysmeta-Crypto-Meta-'
         'Test', self.decrypter.logger.get_lines_for_level('error')[0])
     return resp
Example #48
0
 def test_GET_cipher_mismatch_for_body(self):
     # Cipher does not match
     env = {'REQUEST_METHOD': 'GET', CRYPTO_KEY_CALLBACK: fetch_crypto_keys}
     req = Request.blank('/v1/a/c/o', environ=env)
     body = 'FAKE APP'
     enc_body = encrypt(body, fetch_crypto_keys()['object'], FAKE_IV)
     bad_crypto_meta = fake_get_crypto_meta()
     bad_crypto_meta['cipher'] = 'unknown_cipher'
     hdrs = self._make_response_headers(len(enc_body), md5hex(body),
                                        fetch_crypto_keys(), 'not used')
     hdrs['X-Object-Sysmeta-Crypto-Body-Meta'] = \
         get_crypto_meta_header(crypto_meta=bad_crypto_meta)
     self.app.register('GET',
                       '/v1/a/c/o',
                       HTTPOk,
                       body=enc_body,
                       headers=hdrs)
     resp = req.get_response(self.decrypter)
     self.assertEqual('500 Internal Error', resp.status)
     self.assertEqual('Error decrypting object', resp.body)
     self.assertIn('Error decrypting object',
                   self.decrypter.logger.get_lines_for_level('error')[0])
     self.assertIn('Bad crypto meta: Cipher',
                   self.decrypter.logger.get_lines_for_level('error')[0])
Example #49
0
    def test_PUT_multiseg_good_client_etag(self):
        body_key = os.urandom(32)
        chunks = ['some', 'chunks', 'of data']
        body = ''.join(chunks)
        env = {'REQUEST_METHOD': 'PUT',
               CRYPTO_KEY_CALLBACK: fetch_crypto_keys,
               'wsgi.input': FileLikeIter(chunks)}
        hdrs = {'content-type': 'text/plain',
                'content-length': str(len(body)),
                'Etag': md5hex(body)}
        req = Request.blank('/v1/a/c/o', environ=env, headers=hdrs)
        self.app.register('PUT', '/v1/a/c/o', HTTPCreated, {})

        with mock.patch(
            'swift.common.middleware.crypto.crypto_utils.'
            'Crypto.create_random_key',
                lambda *args: body_key):
            resp = req.get_response(self.encrypter)

        self.assertEqual('201 Created', resp.status)
        # verify object is encrypted by getting direct from the app
        get_req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'GET'})
        self.assertEqual(encrypt(body, body_key, FAKE_IV),
                         get_req.get_response(self.app).body)
Example #50
0
    def _test_GET_multipart_bad_body_crypto_meta(self, bad_crypto_meta):
        # build fake multipart response body
        key = fetch_crypto_keys()['object']
        ctxt = Crypto().create_encryption_ctxt(key, FAKE_IV)
        plaintext = 'Cwm fjord veg balks nth pyx quiz'
        plaintext_etag = md5hex(plaintext)
        ciphertext = encrypt(plaintext, ctxt=ctxt)
        parts = ((0, 3, 'text/plain'), (4, 9, 'text/plain; charset=us-ascii'),
                 (24, 32, 'text/plain'))
        length = len(ciphertext)
        body = ''
        for start, end, ctype in parts:
            body += '--multipartboundary\r\n'
            body += 'Content-Type: %s\r\n' % ctype
            body += 'Content-Range: bytes %s-%s/%s' % (start, end - 1, length)
            body += '\r\n\r\n' + ciphertext[start:end] + '\r\n'
        body += '--multipartboundary--'

        # register request with fake swift
        hdrs = self._make_response_headers(len(body), plaintext_etag,
                                           fetch_crypto_keys(), 'not used')
        hdrs['content-type'] = \
            'multipart/byteranges;boundary=multipartboundary'
        hdrs['X-Object-Sysmeta-Crypto-Body-Meta'] = \
            get_crypto_meta_header(bad_crypto_meta)
        self.app.register('GET', '/v1/a/c/o', HTTPOk, body=body, headers=hdrs)

        # issue request
        env = {'REQUEST_METHOD': 'GET', CRYPTO_KEY_CALLBACK: fetch_crypto_keys}
        req = Request.blank('/v1/a/c/o', environ=env)
        resp = req.get_response(self.decrypter)

        self.assertEqual('500 Internal Error', resp.status)
        self.assertEqual('Error decrypting object', resp.body)
        self.assertIn('Error decrypting object',
                      self.decrypter.logger.get_lines_for_level('error')[0])
Example #51
0
    def test_PUT_with_bad_etag_in_other_footers(self):
        # verify that etag supplied in footers from other middleware overrides
        # header etag when validating inbound plaintext etags
        plaintext = 'FAKE APP'
        plaintext_etag = md5hex(plaintext)
        other_footers = {
            'Etag': 'bad etag',
            'X-Object-Sysmeta-Other': 'other sysmeta',
            'X-Object-Sysmeta-Container-Update-Override-Etag':
                'other override'}

        env = {'REQUEST_METHOD': 'PUT',
               CRYPTO_KEY_CALLBACK: fetch_crypto_keys,
               'swift.callback.update_footers':
                   lambda footers: footers.update(other_footers)}
        hdrs = {'content-type': 'text/plain',
                'content-length': str(len(plaintext)),
                'Etag': plaintext_etag}
        req = Request.blank(
            '/v1/a/c/o', environ=env, body=plaintext, headers=hdrs)
        self.app.register('PUT', '/v1/a/c/o', HTTPCreated, {})
        resp = req.get_response(self.encrypter)
        self.assertEqual('422 Unprocessable Entity', resp.status)
        self.assertNotIn('Etag', resp.headers)
 def test_GET_cipher_mismatch_for_metadata(self):
     # Cipher does not match
     env = {'REQUEST_METHOD': 'GET',
            CRYPTO_KEY_CALLBACK: fetch_crypto_keys}
     req = Request.blank('/v1/a/c/o', environ=env)
     body = 'FAKE APP'
     key = fetch_crypto_keys()['object']
     enc_body = encrypt(body, key, FAKE_IV)
     bad_crypto_meta = fake_get_crypto_meta()
     bad_crypto_meta['cipher'] = 'unknown_cipher'
     hdrs = self._make_response_headers(
         len(enc_body), md5hex(body), fetch_crypto_keys(), 'not used')
     hdrs.update({'x-object-transient-sysmeta-crypto-meta-test':
                  base64.b64encode(encrypt('encrypt me', key, FAKE_IV)) +
                  ';swift_meta=' +
                  get_crypto_meta_header(crypto_meta=bad_crypto_meta)})
     self.app.register(
         'GET', '/v1/a/c/o', HTTPOk, body=enc_body, headers=hdrs)
     resp = req.get_response(self.decrypter)
     self.assertEqual('500 Internal Error', resp.status)
     self.assertEqual('Error decrypting header', resp.body)
     self.assertIn(
         'Error decrypting header X-Object-Transient-Sysmeta-Crypto-Meta-'
         'Test', self.decrypter.logger.get_lines_for_level('error')[0])
Example #53
0
    def _test_PUT_with_other_footers(self, override_etag):
        # verify handling of another middleware's footer callback
        cont_key = fetch_crypto_keys()['container']
        body_key = os.urandom(32)
        object_key = fetch_crypto_keys()['object']
        plaintext = 'FAKE APP'
        plaintext_etag = md5hex(plaintext)
        ciphertext = encrypt(plaintext, body_key, FAKE_IV)
        ciphertext_etag = md5hex(ciphertext)
        other_footers = {
            'Etag': plaintext_etag,
            'X-Object-Sysmeta-Other': 'other sysmeta',
            'X-Object-Sysmeta-Container-Update-Override-Size':
            'other override',
            'X-Object-Sysmeta-Container-Update-Override-Etag': override_etag
        }

        env = {
            'REQUEST_METHOD':
            'PUT',
            CRYPTO_KEY_CALLBACK:
            fetch_crypto_keys,
            'swift.callback.update_footers':
            lambda footers: footers.update(other_footers)
        }
        hdrs = {
            'content-type': 'text/plain',
            'content-length': str(len(plaintext)),
            'Etag': 'correct etag is in footers'
        }
        req = Request.blank('/v1/a/c/o',
                            environ=env,
                            body=plaintext,
                            headers=hdrs)
        self.app.register('PUT', '/v1/a/c/o', HTTPCreated, {})

        with mock.patch(
                'swift.common.middleware.crypto.crypto_utils.'
                'Crypto.create_random_key', lambda *args: body_key):
            resp = req.get_response(self.encrypter)

        self.assertEqual('201 Created', resp.status)
        self.assertEqual(plaintext_etag, resp.headers['Etag'])

        # verify metadata items
        self.assertEqual(1, len(self.app.calls), self.app.calls)
        self.assertEqual('PUT', self.app.calls[0][0])
        req_hdrs = self.app.headers[0]

        # verify that other middleware's footers made it to app, including any
        # container update overrides but nothing Etag-related
        other_footers.pop('Etag')
        other_footers.pop('X-Object-Sysmeta-Container-Update-Override-Etag')
        for k, v in other_footers.items():
            self.assertEqual(v, req_hdrs[k])

        # verify encryption footers are ok
        encrypted_etag, _junk, etag_meta = \
            req_hdrs['X-Object-Sysmeta-Crypto-Etag'].partition('; swift_meta=')
        self.assertTrue(etag_meta)
        actual_meta = json.loads(urllib.unquote_plus(etag_meta))
        self.assertEqual(Crypto().cipher, actual_meta['cipher'])

        self.assertEqual(ciphertext_etag, req_hdrs['Etag'])
        actual = base64.b64decode(encrypted_etag)
        etag_iv = base64.b64decode(actual_meta['iv'])
        self.assertEqual(encrypt(plaintext_etag, object_key, etag_iv), actual)

        # verify encrypted etag for container update
        self.assertIn('X-Object-Sysmeta-Container-Update-Override-Etag',
                      req_hdrs)
        parts = req_hdrs[
            'X-Object-Sysmeta-Container-Update-Override-Etag'].rsplit(';', 1)
        self.assertEqual(2, len(parts))

        # extract crypto_meta from end of etag for container update
        param = parts[1].strip()
        crypto_meta_tag = 'swift_meta='
        self.assertTrue(param.startswith(crypto_meta_tag), param)
        actual_meta = json.loads(
            urllib.unquote_plus(param[len(crypto_meta_tag):]))
        self.assertEqual(Crypto().cipher, actual_meta['cipher'])

        cont_key = fetch_crypto_keys()['container']
        cont_etag_iv = base64.b64decode(actual_meta['iv'])
        self.assertEqual(FAKE_IV, cont_etag_iv)
        self.assertEqual(encrypt(override_etag, cont_key, cont_etag_iv),
                         base64.b64decode(parts[0]))

        # verify body crypto meta
        actual = req_hdrs['X-Object-Sysmeta-Crypto-Body-Meta']
        actual = json.loads(urllib.unquote_plus(actual))
        self.assertEqual(Crypto().cipher, actual['cipher'])
        self.assertEqual(FAKE_IV, base64.b64decode(actual['iv']))

        # verify wrapped body key
        expected_wrapped_key = encrypt(body_key, object_key, FAKE_IV)
        self.assertEqual(expected_wrapped_key,
                         base64.b64decode(actual['body_key']['key']))
        self.assertEqual(FAKE_IV, base64.b64decode(actual['body_key']['iv']))
        self.assertEqual(fetch_crypto_keys()['id'], actual['key_id'])
Example #54
0
 def setUp(self):
     self.plaintext = 'unencrypted body content'
     self.plaintext_etag = md5hex(self.plaintext)
     self._setup_crypto_app()
Example #55
0
    def test_PUT_req(self):
        body_key = os.urandom(32)
        object_key = fetch_crypto_keys()['object']
        plaintext = 'FAKE APP'
        plaintext_etag = md5hex(plaintext)
        ciphertext = encrypt(plaintext, body_key, FAKE_IV)
        ciphertext_etag = md5hex(ciphertext)

        env = {'REQUEST_METHOD': 'PUT',
               CRYPTO_KEY_CALLBACK: fetch_crypto_keys}
        hdrs = {'etag': plaintext_etag,
                'content-type': 'text/plain',
                'content-length': str(len(plaintext)),
                'x-object-meta-etag': 'not to be confused with the Etag!',
                'x-object-meta-test': 'encrypt me',
                'x-object-sysmeta-test': 'do not encrypt me'}
        req = Request.blank(
            '/v1/a/c/o', environ=env, body=plaintext, headers=hdrs)
        self.app.register('PUT', '/v1/a/c/o', HTTPCreated, {})
        with mock.patch(
            'swift.common.middleware.crypto.crypto_utils.'
            'Crypto.create_random_key',
                return_value=body_key):
            resp = req.get_response(self.encrypter)
        self.assertEqual('201 Created', resp.status)
        self.assertEqual(plaintext_etag, resp.headers['Etag'])

        # verify metadata items
        self.assertEqual(1, len(self.app.calls), self.app.calls)
        self.assertEqual('PUT', self.app.calls[0][0])
        req_hdrs = self.app.headers[0]

        # verify body crypto meta
        actual = req_hdrs['X-Object-Sysmeta-Crypto-Body-Meta']
        actual = json.loads(urlparse.unquote_plus(actual))
        self.assertEqual(Crypto().cipher, actual['cipher'])
        self.assertEqual(FAKE_IV, base64.b64decode(actual['iv']))

        # verify wrapped body key
        expected_wrapped_key = encrypt(body_key, object_key, FAKE_IV)
        self.assertEqual(expected_wrapped_key,
                         base64.b64decode(actual['body_key']['key']))
        self.assertEqual(FAKE_IV,
                         base64.b64decode(actual['body_key']['iv']))
        self.assertEqual(fetch_crypto_keys()['id'], actual['key_id'])

        # verify etag
        self.assertEqual(ciphertext_etag, req_hdrs['Etag'])

        encrypted_etag, _junk, etag_meta = \
            req_hdrs['X-Object-Sysmeta-Crypto-Etag'].partition('; swift_meta=')
        # verify crypto_meta was appended to this etag
        self.assertTrue(etag_meta)
        actual_meta = json.loads(urlparse.unquote_plus(etag_meta))
        self.assertEqual(Crypto().cipher, actual_meta['cipher'])

        # verify encrypted version of plaintext etag
        actual = base64.b64decode(encrypted_etag)
        etag_iv = base64.b64decode(actual_meta['iv'])
        enc_etag = encrypt(plaintext_etag, object_key, etag_iv)
        self.assertEqual(enc_etag, actual)

        # verify etag MAC for conditional requests
        actual_hmac = base64.b64decode(
            req_hdrs['X-Object-Sysmeta-Crypto-Etag-Mac'])
        self.assertEqual(actual_hmac, hmac.new(
            object_key, plaintext_etag, hashlib.sha256).digest())

        # verify encrypted etag for container update
        self.assertIn(
            'X-Object-Sysmeta-Container-Update-Override-Etag', req_hdrs)
        parts = req_hdrs[
            'X-Object-Sysmeta-Container-Update-Override-Etag'].rsplit(';', 1)
        self.assertEqual(2, len(parts))

        # extract crypto_meta from end of etag for container update
        param = parts[1].strip()
        crypto_meta_tag = 'swift_meta='
        self.assertTrue(param.startswith(crypto_meta_tag), param)
        actual_meta = json.loads(
            urlparse.unquote_plus(param[len(crypto_meta_tag):]))
        self.assertEqual(Crypto().cipher, actual_meta['cipher'])
        self.assertEqual(fetch_crypto_keys()['id'], actual_meta['key_id'])

        cont_key = fetch_crypto_keys()['container']
        cont_etag_iv = base64.b64decode(actual_meta['iv'])
        self.assertEqual(FAKE_IV, cont_etag_iv)
        self.assertEqual(encrypt(plaintext_etag, cont_key, cont_etag_iv),
                         base64.b64decode(parts[0]))

        # content-type is not encrypted
        self.assertEqual('text/plain', req_hdrs['Content-Type'])

        # user meta is encrypted
        self._verify_user_metadata(req_hdrs, 'Test', 'encrypt me', object_key)
        self._verify_user_metadata(
            req_hdrs, 'Etag', 'not to be confused with the Etag!', object_key)

        # sysmeta is not encrypted
        self.assertEqual('do not encrypt me',
                         req_hdrs['X-Object-Sysmeta-Test'])

        # verify object is encrypted by getting direct from the app
        get_req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'GET'})
        resp = get_req.get_response(self.app)
        self.assertEqual(ciphertext, resp.body)
        self.assertEqual(ciphertext_etag, resp.headers['Etag'])
Example #56
0
 def setUp(self):
     skip_if_no_xattrs()
     self.plaintext = 'unencrypted body content'
     self.plaintext_etag = md5hex(self.plaintext)
     self._setup_crypto_app()
Example #57
0
    def test_PUT_req(self):
        body_key = os.urandom(32)
        object_key = fetch_crypto_keys()['object']
        plaintext = 'FAKE APP'
        plaintext_etag = md5hex(plaintext)
        ciphertext = encrypt(plaintext, body_key, FAKE_IV)
        ciphertext_etag = md5hex(ciphertext)

        env = {'REQUEST_METHOD': 'PUT', CRYPTO_KEY_CALLBACK: fetch_crypto_keys}
        hdrs = {
            'etag': plaintext_etag,
            'content-type': 'text/plain',
            'content-length': str(len(plaintext)),
            'x-object-meta-etag': 'not to be confused with the Etag!',
            'x-object-meta-test': 'encrypt me',
            'x-object-sysmeta-test': 'do not encrypt me'
        }
        req = Request.blank('/v1/a/c/o',
                            environ=env,
                            body=plaintext,
                            headers=hdrs)
        self.app.register('PUT', '/v1/a/c/o', HTTPCreated, {})
        with mock.patch(
                'swift.common.middleware.crypto.crypto_utils.'
                'Crypto.create_random_key',
                return_value=body_key):
            resp = req.get_response(self.encrypter)
        self.assertEqual('201 Created', resp.status)
        self.assertEqual(plaintext_etag, resp.headers['Etag'])

        # verify metadata items
        self.assertEqual(1, len(self.app.calls), self.app.calls)
        self.assertEqual('PUT', self.app.calls[0][0])
        req_hdrs = self.app.headers[0]

        # verify body crypto meta
        actual = req_hdrs['X-Object-Sysmeta-Crypto-Body-Meta']
        actual = json.loads(urllib.unquote_plus(actual))
        self.assertEqual(Crypto().cipher, actual['cipher'])
        self.assertEqual(FAKE_IV, base64.b64decode(actual['iv']))

        # verify wrapped body key
        expected_wrapped_key = encrypt(body_key, object_key, FAKE_IV)
        self.assertEqual(expected_wrapped_key,
                         base64.b64decode(actual['body_key']['key']))
        self.assertEqual(FAKE_IV, base64.b64decode(actual['body_key']['iv']))
        self.assertEqual(fetch_crypto_keys()['id'], actual['key_id'])

        # verify etag
        self.assertEqual(ciphertext_etag, req_hdrs['Etag'])

        encrypted_etag, _junk, etag_meta = \
            req_hdrs['X-Object-Sysmeta-Crypto-Etag'].partition('; swift_meta=')
        # verify crypto_meta was appended to this etag
        self.assertTrue(etag_meta)
        actual_meta = json.loads(urllib.unquote_plus(etag_meta))
        self.assertEqual(Crypto().cipher, actual_meta['cipher'])

        # verify encrypted version of plaintext etag
        actual = base64.b64decode(encrypted_etag)
        etag_iv = base64.b64decode(actual_meta['iv'])
        enc_etag = encrypt(plaintext_etag, object_key, etag_iv)
        self.assertEqual(enc_etag, actual)

        # verify etag MAC for conditional requests
        actual_hmac = base64.b64decode(
            req_hdrs['X-Object-Sysmeta-Crypto-Etag-Mac'])
        self.assertEqual(
            actual_hmac,
            hmac.new(object_key, plaintext_etag, hashlib.sha256).digest())

        # verify encrypted etag for container update
        self.assertIn('X-Object-Sysmeta-Container-Update-Override-Etag',
                      req_hdrs)
        parts = req_hdrs[
            'X-Object-Sysmeta-Container-Update-Override-Etag'].rsplit(';', 1)
        self.assertEqual(2, len(parts))

        # extract crypto_meta from end of etag for container update
        param = parts[1].strip()
        crypto_meta_tag = 'swift_meta='
        self.assertTrue(param.startswith(crypto_meta_tag), param)
        actual_meta = json.loads(
            urllib.unquote_plus(param[len(crypto_meta_tag):]))
        self.assertEqual(Crypto().cipher, actual_meta['cipher'])
        self.assertEqual(fetch_crypto_keys()['id'], actual_meta['key_id'])

        cont_key = fetch_crypto_keys()['container']
        cont_etag_iv = base64.b64decode(actual_meta['iv'])
        self.assertEqual(FAKE_IV, cont_etag_iv)
        self.assertEqual(encrypt(plaintext_etag, cont_key, cont_etag_iv),
                         base64.b64decode(parts[0]))

        # content-type is not encrypted
        self.assertEqual('text/plain', req_hdrs['Content-Type'])

        # user meta is encrypted
        self._verify_user_metadata(req_hdrs, 'Test', 'encrypt me', object_key)
        self._verify_user_metadata(req_hdrs, 'Etag',
                                   'not to be confused with the Etag!',
                                   object_key)

        # sysmeta is not encrypted
        self.assertEqual('do not encrypt me',
                         req_hdrs['X-Object-Sysmeta-Test'])

        # verify object is encrypted by getting direct from the app
        get_req = Request.blank('/v1/a/c/o', environ={'REQUEST_METHOD': 'GET'})
        resp = get_req.get_response(self.app)
        self.assertEqual(ciphertext, resp.body)
        self.assertEqual(ciphertext_etag, resp.headers['Etag'])
Example #58
0
 def setUp(self):
     skip_if_no_xattrs()
     self.plaintext = b'unencrypted body content'
     self.plaintext_etag = md5hex(self.plaintext)
     self._setup_crypto_app()
Example #59
0
    def _test_PUT_with_other_footers(self, override_etag):
        # verify handling of another middleware's footer callback
        body_key = os.urandom(32)
        object_key = fetch_crypto_keys()['object']
        plaintext = 'FAKE APP'
        plaintext_etag = md5hex(plaintext)
        ciphertext = encrypt(plaintext, body_key, FAKE_IV)
        ciphertext_etag = md5hex(ciphertext)
        other_footers = {
            'Etag': plaintext_etag,
            'X-Object-Sysmeta-Other': 'other sysmeta',
            'X-Object-Sysmeta-Container-Update-Override-Size':
                'other override',
            'X-Object-Sysmeta-Container-Update-Override-Etag':
                override_etag}

        env = {'REQUEST_METHOD': 'PUT',
               CRYPTO_KEY_CALLBACK: fetch_crypto_keys,
               'swift.callback.update_footers':
                   lambda footers: footers.update(other_footers)}
        hdrs = {'content-type': 'text/plain',
                'content-length': str(len(plaintext)),
                'Etag': 'correct etag is in footers'}
        req = Request.blank(
            '/v1/a/c/o', environ=env, body=plaintext, headers=hdrs)
        self.app.register('PUT', '/v1/a/c/o', HTTPCreated, {})

        with mock.patch(
            'swift.common.middleware.crypto.crypto_utils.'
            'Crypto.create_random_key',
                lambda *args: body_key):
            resp = req.get_response(self.encrypter)

        self.assertEqual('201 Created', resp.status)
        self.assertEqual(plaintext_etag, resp.headers['Etag'])

        # verify metadata items
        self.assertEqual(1, len(self.app.calls), self.app.calls)
        self.assertEqual('PUT', self.app.calls[0][0])
        req_hdrs = self.app.headers[0]

        # verify that other middleware's footers made it to app, including any
        # container update overrides but nothing Etag-related
        other_footers.pop('Etag')
        other_footers.pop('X-Object-Sysmeta-Container-Update-Override-Etag')
        for k, v in other_footers.items():
            self.assertEqual(v, req_hdrs[k])

        # verify encryption footers are ok
        encrypted_etag, _junk, etag_meta = \
            req_hdrs['X-Object-Sysmeta-Crypto-Etag'].partition('; swift_meta=')
        self.assertTrue(etag_meta)
        actual_meta = json.loads(urlparse.unquote_plus(etag_meta))
        self.assertEqual(Crypto().cipher, actual_meta['cipher'])

        self.assertEqual(ciphertext_etag, req_hdrs['Etag'])
        actual = base64.b64decode(encrypted_etag)
        etag_iv = base64.b64decode(actual_meta['iv'])
        self.assertEqual(encrypt(plaintext_etag, object_key, etag_iv), actual)

        # verify encrypted etag for container update
        self.assertIn(
            'X-Object-Sysmeta-Container-Update-Override-Etag', req_hdrs)
        parts = req_hdrs[
            'X-Object-Sysmeta-Container-Update-Override-Etag'].rsplit(';', 1)
        self.assertEqual(2, len(parts))

        # extract crypto_meta from end of etag for container update
        param = parts[1].strip()
        crypto_meta_tag = 'swift_meta='
        self.assertTrue(param.startswith(crypto_meta_tag), param)
        actual_meta = json.loads(
            urlparse.unquote_plus(param[len(crypto_meta_tag):]))
        self.assertEqual(Crypto().cipher, actual_meta['cipher'])

        cont_key = fetch_crypto_keys()['container']
        cont_etag_iv = base64.b64decode(actual_meta['iv'])
        self.assertEqual(FAKE_IV, cont_etag_iv)
        self.assertEqual(encrypt(override_etag, cont_key, cont_etag_iv),
                         base64.b64decode(parts[0]))

        # verify body crypto meta
        actual = req_hdrs['X-Object-Sysmeta-Crypto-Body-Meta']
        actual = json.loads(urlparse.unquote_plus(actual))
        self.assertEqual(Crypto().cipher, actual['cipher'])
        self.assertEqual(FAKE_IV, base64.b64decode(actual['iv']))

        # verify wrapped body key
        expected_wrapped_key = encrypt(body_key, object_key, FAKE_IV)
        self.assertEqual(expected_wrapped_key,
                         base64.b64decode(actual['body_key']['key']))
        self.assertEqual(FAKE_IV,
                         base64.b64decode(actual['body_key']['iv']))
        self.assertEqual(fetch_crypto_keys()['id'], actual['key_id'])