diff options
-rwxr-xr-x | .travis/install.sh | 18 | ||||
-rw-r--r-- | cryptography/fernet.py | 131 | ||||
-rw-r--r-- | cryptography/hazmat/bindings/openssl/binding.py | 8 | ||||
-rw-r--r-- | cryptography/hazmat/bindings/utils.py | 8 | ||||
-rw-r--r-- | dev-requirements.txt | 7 | ||||
-rw-r--r-- | docs/conf.py | 11 | ||||
-rw-r--r-- | docs/cryptography-docs.py | 16 | ||||
-rw-r--r-- | docs/doing-a-release.rst | 36 | ||||
-rw-r--r-- | docs/fernet.rst | 76 | ||||
-rw-r--r-- | docs/hazmat/primitives/symmetric-encryption.rst | 2 | ||||
-rw-r--r-- | docs/index.rst | 2 | ||||
-rw-r--r-- | tasks.py | 27 | ||||
-rw-r--r-- | tests/hazmat/bindings/test_bindings.py | 32 | ||||
-rw-r--r-- | tests/hazmat/bindings/test_openssl.py | 3 | ||||
-rw-r--r-- | tests/test_fernet.py | 100 | ||||
-rw-r--r-- | tests/vectors/fernet/generate.json | 9 | ||||
-rw-r--r-- | tests/vectors/fernet/invalid.json | 58 | ||||
-rw-r--r-- | tests/vectors/fernet/verify.json | 9 | ||||
-rw-r--r-- | tox.ini | 3 |
19 files changed, 528 insertions, 28 deletions
diff --git a/.travis/install.sh b/.travis/install.sh index fdd71907..4aa39799 100755 --- a/.travis/install.sh +++ b/.travis/install.sh @@ -5,24 +5,8 @@ set -x if [[ "${OPENSSL}" == "0.9.8" ]]; then sudo add-apt-repository "deb http://archive.ubuntu.com/ubuntu/ lucid main" -fi - -if [[ "${TOX_ENV}" == "pypy" ]]; then - sudo add-apt-repository -y ppa:pypy/ppa -fi - -sudo apt-get -y update - -if [[ "${OPENSSL}" == "0.9.8" ]]; then + sudo apt-get -y update sudo apt-get install -y --force-yes libssl-dev/lucid fi -if [[ "${TOX_ENV}" == "pypy" ]]; then - sudo apt-get install -y pypy - - # This is required because we need to get rid of the Travis installed PyPy - # or it'll take precedence over the PPA installed one. - sudo rm -rf /usr/local/pypy/bin -fi - pip install tox coveralls diff --git a/cryptography/fernet.py b/cryptography/fernet.py new file mode 100644 index 00000000..c19309d5 --- /dev/null +++ b/cryptography/fernet.py @@ -0,0 +1,131 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import base64 +import binascii +import os +import struct +import time + +import six + +from cryptography.exceptions import InvalidSignature +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import padding, hashes +from cryptography.hazmat.primitives.hmac import HMAC +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes + + +class InvalidToken(Exception): + pass + + +_MAX_CLOCK_SKEW = 60 + + +class Fernet(object): + def __init__(self, key, backend=None): + if backend is None: + backend = default_backend() + + key = base64.urlsafe_b64decode(key) + if len(key) != 32: + raise ValueError( + "Fernet key must be 32 url-safe base64-encoded bytes" + ) + + self._signing_key = key[:16] + self._encryption_key = key[16:] + self._backend = backend + + @classmethod + def generate_key(cls): + return base64.urlsafe_b64encode(os.urandom(32)) + + def encrypt(self, data): + current_time = int(time.time()) + iv = os.urandom(16) + return self._encrypt_from_parts(data, current_time, iv) + + def _encrypt_from_parts(self, data, current_time, iv): + if isinstance(data, six.text_type): + raise TypeError( + "Unicode-objects must be encoded before encryption" + ) + + padder = padding.PKCS7(algorithms.AES.block_size).padder() + padded_data = padder.update(data) + padder.finalize() + encryptor = Cipher( + algorithms.AES(self._encryption_key), modes.CBC(iv), self._backend + ).encryptor() + ciphertext = encryptor.update(padded_data) + encryptor.finalize() + + basic_parts = ( + b"\x80" + struct.pack(">Q", current_time) + iv + ciphertext + ) + + h = HMAC(self._signing_key, hashes.SHA256(), backend=self._backend) + h.update(basic_parts) + hmac = h.finalize() + return base64.urlsafe_b64encode(basic_parts + hmac) + + def decrypt(self, token, ttl=None): + if isinstance(token, six.text_type): + raise TypeError( + "Unicode-objects must be encoded before decryption" + ) + + current_time = int(time.time()) + + try: + data = base64.urlsafe_b64decode(token) + except (TypeError, binascii.Error): + raise InvalidToken + + if six.indexbytes(data, 0) != 0x80: + raise InvalidToken + + try: + timestamp, = struct.unpack(">Q", data[1:9]) + except struct.error: + raise InvalidToken + if ttl is not None: + if timestamp + ttl < current_time: + raise InvalidToken + if current_time + _MAX_CLOCK_SKEW < timestamp: + raise InvalidToken + h = HMAC(self._signing_key, hashes.SHA256(), backend=self._backend) + h.update(data[:-32]) + try: + h.verify(data[-32:]) + except InvalidSignature: + raise InvalidToken + + iv = data[9:25] + ciphertext = data[25:-32] + decryptor = Cipher( + algorithms.AES(self._encryption_key), modes.CBC(iv), self._backend + ).decryptor() + plaintext_padded = decryptor.update(ciphertext) + try: + plaintext_padded += decryptor.finalize() + except ValueError: + raise InvalidToken + unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder() + + unpadded = unpadder.update(plaintext_padded) + try: + unpadded += unpadder.finalize() + except ValueError: + raise InvalidToken + return unpadded diff --git a/cryptography/hazmat/bindings/openssl/binding.py b/cryptography/hazmat/bindings/openssl/binding.py index 4f6b99ae..2a1e1184 100644 --- a/cryptography/hazmat/bindings/openssl/binding.py +++ b/cryptography/hazmat/bindings/openssl/binding.py @@ -13,7 +13,9 @@ from __future__ import absolute_import, division, print_function -from cryptography.hazmat.bindings.utils import build_ffi +from cryptography.hazmat.bindings.utils import ( + build_ffi, binding_available +) _OSX_PRE_INCLUDE = """ #ifdef __APPLE__ @@ -79,3 +81,7 @@ class Binding(object): cls.ffi, cls.lib = build_ffi(cls._module_prefix, cls._modules, _OSX_PRE_INCLUDE, _OSX_POST_INCLUDE, ["crypto", "ssl"]) + + @classmethod + def is_available(cls): + return binding_available(cls._ensure_ffi_initialized) diff --git a/cryptography/hazmat/bindings/utils.py b/cryptography/hazmat/bindings/utils.py index 69290eb3..40fd07f8 100644 --- a/cryptography/hazmat/bindings/utils.py +++ b/cryptography/hazmat/bindings/utils.py @@ -87,3 +87,11 @@ def build_ffi(module_prefix, modules, pre_include, post_include, libraries): delattr(lib, name) return ffi, lib + + +def binding_available(initializer): + try: + initializer() + return True + except cffi.VerificationError: + return False diff --git a/dev-requirements.txt b/dev-requirements.txt index cd975d5c..b2a6c79c 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,8 +1,11 @@ +coverage flake8 +invoke +iso8601 pretend pytest -coverage sphinx -tox sphinx_rtd_theme +tox +twine -e . diff --git a/docs/conf.py b/docs/conf.py index 5dbcdab8..00660314 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -60,10 +60,13 @@ copyright = '2013-2014, Individual Contributors' # |version| and |release|, also used in various other places throughout the # built documents. # -# The short X.Y version. -version = '0.1dev' -# The full version, including alpha/beta/rc tags. -release = '0.1dev' + +base_dir = os.path.join(os.path.dirname(__file__), os.pardir) +about = {} +with open(os.path.join(base_dir, "cryptography", "__about__.py")) as f: + exec(f.read(), about) + +version = release = about["__version__"] # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/cryptography-docs.py b/docs/cryptography-docs.py index ea7e8eef..0252d693 100644 --- a/docs/cryptography-docs.py +++ b/docs/cryptography-docs.py @@ -6,17 +6,29 @@ from sphinx.util.compat import Directive, make_admonition DANGER_MESSAGE = """ This is a "Hazardous Materials" module. You should **ONLY** use it if you're 100% absolutely sure that you know what you're doing because this module is -full of land mines, dragons, and dinosaurs with laser guns. """ +full of land mines, dragons, and dinosaurs with laser guns. +""" + +DANGER_ALTERNATE = """ + +You may instead be interested in :doc:`{alternate}`. +""" class HazmatDirective(Directive): + has_content = True + def run(self): + message = DANGER_MESSAGE + if self.content: + message += DANGER_ALTERNATE.format(alternate=self.content[0]) + ad = make_admonition( Hazmat, self.name, [], self.options, - nodes.paragraph("", DANGER_MESSAGE), + nodes.paragraph("", message), self.lineno, self.content_offset, self.block_text, diff --git a/docs/doing-a-release.rst b/docs/doing-a-release.rst new file mode 100644 index 00000000..0f382064 --- /dev/null +++ b/docs/doing-a-release.rst @@ -0,0 +1,36 @@ +Doing a Release +=============== + +Doing a release of ``cryptography`` is a two part process. + +Bumping the version number +-------------------------- + +The first step in doing a release is bumping the version number in the +software. + +* Update the version number in ``cryptography/__about__.py``. +* Do a commit indicating this. +* Send a pull request with this. +* Wait for it to be merged. + +Performing the release +---------------------- + +The commit which merged the version number bump is now the official release +commit for this release. You will need to have ``gpg`` installed and a ``gpg`` +key in order to do a release. Once this has happened: + +* Run ``invoke release {version}``. + +The release should now be available on PyPI and a tag should be available in +the repository. You should verify that ``pip install cryptography`` works +correctly: + +.. code-block:: pycon + + >>> import cryptography + >>> cryptography.__version__ + '...' + +Verify that this is the version you just released. diff --git a/docs/fernet.rst b/docs/fernet.rst new file mode 100644 index 00000000..13295c0c --- /dev/null +++ b/docs/fernet.rst @@ -0,0 +1,76 @@ +Fernet (Symmetric encryption) +============================= + +.. currentmodule:: cryptography.fernet + +Fernet provides guarantees that a message encrypted using it cannot be +manipulated or read without the key. `Fernet`_ is an implementation of +symmetric (also known as "secret key") authenticated cryptography. + +.. class:: Fernet(key) + + This class provides both encryption and decryption facilities. + + .. doctest:: + + >>> from cryptography.fernet import Fernet + >>> key = Fernet.generate_key() + >>> f = Fernet(key) + >>> token = f.encrypt(b"my deep dark secret") + >>> token + '...' + >>> f.decrypt(token) + 'my deep dark secret' + + :param bytes key: A URL-safe base64-encoded 32-byte key. This **must** be + kept secret. Anyone with this key is able to create and + read messages. + + .. classmethod:: generate_key() + + Generates a fresh fernet key. Keep this some place safe! If you lose it + you'll no longer be able to decrypt messages; if anyone else gains + access to it, they'll be able to decrypt all of your messages, and + they'll also be able forge arbitrary messages which will be + authenticated and decrypted. + + .. method:: encrypt(plaintext) + + :param bytes plaintext: The message you would like to encrypt. + :returns bytes: A secure message which cannot be read or altered + without the key. It is URL-safe base64-encoded. This is + referred to as a "Fernet token". + + .. note:: + + The encrypted message contains the current time when it was + generated in *plaintext*, the time a message was created will + therefore be visible to a possible attacker. + + .. method:: decrypt(token, ttl=None) + + :param bytes token: The Fernet token. This is the result of calling + :meth:`encrypt`. + :param int ttl: Optionally, the number of seconds old a message may be + for it to be valid. If the message is older than + ``ttl`` seconds (from the time it was originally + created) an exception will be raised. If ``ttl`` is not + provided (or is ``None``), the age of the message is + not considered. + :returns bytes: The original plaintext. + :raises cryptography.fernet.InvalidToken: If the ``token`` is in any + way invalid, this exception + is raised. A token may be + invalid for a number of + reasons: it is older than the + ``ttl``, it is malformed, or + it does not have a valid + signature. + + +.. class:: InvalidToken + + See :meth:`Fernet.decrypt` for more information. + + +.. _`Fernet`: https://github.com/fernet/spec/ diff --git a/docs/hazmat/primitives/symmetric-encryption.rst b/docs/hazmat/primitives/symmetric-encryption.rst index 30896a05..e05248ff 100644 --- a/docs/hazmat/primitives/symmetric-encryption.rst +++ b/docs/hazmat/primitives/symmetric-encryption.rst @@ -1,4 +1,4 @@ -.. hazmat:: +.. hazmat:: /fernet Symmetric Encryption diff --git a/docs/index.rst b/docs/index.rst index 5eb3de7d..4bbfe7fd 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -56,6 +56,7 @@ The recipes layer .. toctree:: :maxdepth: 2 + fernet exceptions glossary @@ -78,4 +79,5 @@ The ``cryptography`` open source project contributing security api-stability + doing-a-release community diff --git a/tasks.py b/tasks.py new file mode 100644 index 00000000..f72f43ba --- /dev/null +++ b/tasks.py @@ -0,0 +1,27 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from __future__ import absolute_import, division, print_function + +import invoke + + +@invoke.task +def release(version): + """ + ``version`` should be a string like '0.4' or '1.0'. + """ + invoke.run("git tag -s {0}".format(version)) + invoke.run("git push --tags") + + invoke.run("python setup.py sdist") + invoke.run("twine upload -s dist/cryptography-{0}*".format(version)) diff --git a/tests/hazmat/bindings/test_bindings.py b/tests/hazmat/bindings/test_bindings.py new file mode 100644 index 00000000..7af1d581 --- /dev/null +++ b/tests/hazmat/bindings/test_bindings.py @@ -0,0 +1,32 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import, division, print_function + +import cffi + +from cryptography.hazmat.bindings.utils import binding_available +from cryptography.hazmat.bindings.openssl.binding import Binding + + +def dummy_initializer(): + ffi = cffi.FFI() + ffi.verify(source="#include <fake_header.h>") + + +def test_binding_available(): + assert binding_available(Binding._ensure_ffi_initialized) is True + + +def test_binding_unavailable(): + assert binding_available(dummy_initializer) is False diff --git a/tests/hazmat/bindings/test_openssl.py b/tests/hazmat/bindings/test_openssl.py index 31f736ab..d1e85058 100644 --- a/tests/hazmat/bindings/test_openssl.py +++ b/tests/hazmat/bindings/test_openssl.py @@ -20,3 +20,6 @@ class TestOpenSSL(object): assert binding assert binding.lib assert binding.ffi + + def test_is_available(self): + assert Binding.is_available() is True diff --git a/tests/test_fernet.py b/tests/test_fernet.py new file mode 100644 index 00000000..45188c47 --- /dev/null +++ b/tests/test_fernet.py @@ -0,0 +1,100 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import base64 +import calendar +import json +import os +import time + +import iso8601 + +import pytest + +import six + +from cryptography.fernet import Fernet, InvalidToken +from cryptography.hazmat.backends import default_backend + + +def json_parametrize(keys, fname): + path = os.path.join(os.path.dirname(__file__), "vectors", "fernet", fname) + with open(path) as f: + data = json.load(f) + return pytest.mark.parametrize(keys, [ + tuple([entry[k] for k in keys]) + for entry in data + ]) + + +class TestFernet(object): + @json_parametrize( + ("secret", "now", "iv", "src", "token"), "generate.json", + ) + def test_generate(self, secret, now, iv, src, token, backend): + f = Fernet(secret.encode("ascii"), backend=backend) + actual_token = f._encrypt_from_parts( + src.encode("ascii"), + calendar.timegm(iso8601.parse_date(now).utctimetuple()), + b"".join(map(six.int2byte, iv)) + ) + assert actual_token == token.encode("ascii") + + @json_parametrize( + ("secret", "now", "src", "ttl_sec", "token"), "verify.json", + ) + def test_verify(self, secret, now, src, ttl_sec, token, backend, + monkeypatch): + f = Fernet(secret.encode("ascii"), backend=backend) + current_time = calendar.timegm(iso8601.parse_date(now).utctimetuple()) + monkeypatch.setattr(time, "time", lambda: current_time) + payload = f.decrypt(token.encode("ascii"), ttl=ttl_sec) + assert payload == src.encode("ascii") + + @json_parametrize(("secret", "token", "now", "ttl_sec"), "invalid.json") + def test_invalid(self, secret, token, now, ttl_sec, backend, monkeypatch): + f = Fernet(secret.encode("ascii"), backend=backend) + current_time = calendar.timegm(iso8601.parse_date(now).utctimetuple()) + monkeypatch.setattr(time, "time", lambda: current_time) + with pytest.raises(InvalidToken): + f.decrypt(token.encode("ascii"), ttl=ttl_sec) + + def test_invalid_start_byte(self, backend): + f = Fernet(Fernet.generate_key(), backend=backend) + with pytest.raises(InvalidToken): + f.decrypt(base64.urlsafe_b64encode(b"\x81")) + + def test_timestamp_too_short(self, backend): + f = Fernet(Fernet.generate_key(), backend=backend) + with pytest.raises(InvalidToken): + f.decrypt(base64.urlsafe_b64encode(b"\x80abc")) + + def test_unicode(self, backend): + f = Fernet(base64.urlsafe_b64encode(b"\x00" * 32), backend=backend) + with pytest.raises(TypeError): + f.encrypt(six.u("")) + with pytest.raises(TypeError): + f.decrypt(six.u("")) + + @pytest.mark.parametrize("message", [b"", b"Abc!", b"\x00\xFF\x00\x80"]) + def test_roundtrips(self, message, backend): + f = Fernet(Fernet.generate_key(), backend=backend) + assert f.decrypt(f.encrypt(message)) == message + + def test_default_backend(self): + f = Fernet(Fernet.generate_key()) + assert f._backend is default_backend() + + def test_bad_key(self, backend): + with pytest.raises(ValueError): + Fernet(base64.urlsafe_b64encode(b"abc"), backend=backend) diff --git a/tests/vectors/fernet/generate.json b/tests/vectors/fernet/generate.json new file mode 100644 index 00000000..d1f3e083 --- /dev/null +++ b/tests/vectors/fernet/generate.json @@ -0,0 +1,9 @@ +[ + { + "token": "gAAAAAAdwJ6wAAECAwQFBgcICQoLDA0ODy021cpGVWKZ_eEwCGM4BLLF_5CV9dOPmrhuVUPgJobwOz7JcbmrR64jVmpU4IwqDA==", + "now": "1985-10-26T01:20:00-07:00", + "iv": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], + "src": "hello", + "secret": "cw_0x689RpI-jtRR7oE8h_eQsKImvJapLeSbXpwF4e4=" + } +] diff --git a/tests/vectors/fernet/invalid.json b/tests/vectors/fernet/invalid.json new file mode 100644 index 00000000..d80e7b4a --- /dev/null +++ b/tests/vectors/fernet/invalid.json @@ -0,0 +1,58 @@ +[ + { + "desc": "incorrect mac", + "token": "gAAAAAAdwJ6xAAECAwQFBgcICQoLDA0OD3HkMATM5lFqGaerZ-fWPAl1-szkFVzXTuGb4hR8AKtwcaX1YdykQUFBQUFBQUFBQQ==", + "now": "1985-10-26T01:20:01-07:00", + "ttl_sec": 60, + "secret": "cw_0x689RpI-jtRR7oE8h_eQsKImvJapLeSbXpwF4e4=" + }, + { + "desc": "too short", + "token": "gAAAAAAdwJ6xAAECAwQFBgcICQoLDA0OD3HkMATM5lFqGaerZ-fWPA==", + "now": "1985-10-26T01:20:01-07:00", + "ttl_sec": 60, + "secret": "cw_0x689RpI-jtRR7oE8h_eQsKImvJapLeSbXpwF4e4=" + }, + { + "desc": "invalid base64", + "token": "%%%%%%%%%%%%%AECAwQFBgcICQoLDA0OD3HkMATM5lFqGaerZ-fWPAl1-szkFVzXTuGb4hR8AKtwcaX1YdykRtfsH-p1YsUD2Q==", + "now": "1985-10-26T01:20:01-07:00", + "ttl_sec": 60, + "secret": "cw_0x689RpI-jtRR7oE8h_eQsKImvJapLeSbXpwF4e4=" + }, + { + "desc": "payload size not multiple of block size", + "token": "gAAAAAAdwJ6xAAECAwQFBgcICQoLDA0OD3HkMATM5lFqGaerZ-fWPOm73QeoCk9uGib28Xe5vz6oxq5nmxbx_v7mrfyudzUm", + "now": "1985-10-26T01:20:01-07:00", + "ttl_sec": 60, + "secret": "cw_0x689RpI-jtRR7oE8h_eQsKImvJapLeSbXpwF4e4=" + }, + { + "desc": "payload padding error", + "token": "gAAAAAAdwJ6xAAECAwQFBgcICQoLDA0ODz4LEpdELGQAad7aNEHbf-JkLPIpuiYRLQ3RtXatOYREu2FWke6CnJNYIbkuKNqOhw==", + "now": "1985-10-26T01:20:01-07:00", + "ttl_sec": 60, + "secret": "cw_0x689RpI-jtRR7oE8h_eQsKImvJapLeSbXpwF4e4=" + }, + { + "desc": "far-future TS (unacceptable clock skew)", + "token": "gAAAAAAdwStRAAECAwQFBgcICQoLDA0OD3HkMATM5lFqGaerZ-fWPAnja1xKYyhd-Y6mSkTOyTGJmw2Xc2a6kBd-iX9b_qXQcw==", + "now": "1985-10-26T01:20:01-07:00", + "ttl_sec": 60, + "secret": "cw_0x689RpI-jtRR7oE8h_eQsKImvJapLeSbXpwF4e4=" + }, + { + "desc": "expired TTL", + "token": "gAAAAAAdwJ6xAAECAwQFBgcICQoLDA0OD3HkMATM5lFqGaerZ-fWPAl1-szkFVzXTuGb4hR8AKtwcaX1YdykRtfsH-p1YsUD2Q==", + "now": "1985-10-26T01:21:31-07:00", + "ttl_sec": 60, + "secret": "cw_0x689RpI-jtRR7oE8h_eQsKImvJapLeSbXpwF4e4=" + }, + { + "desc": "incorrect IV (causes padding error)", + "token": "gAAAAAAdwJ6xBQECAwQFBgcICQoLDA0OD3HkMATM5lFqGaerZ-fWPAkLhFLHpGtDBRLRTZeUfWgHSv49TF2AUEZ1TIvcZjK1zQ==", + "now": "1985-10-26T01:20:01-07:00", + "ttl_sec": 60, + "secret": "cw_0x689RpI-jtRR7oE8h_eQsKImvJapLeSbXpwF4e4=" + } +] diff --git a/tests/vectors/fernet/verify.json b/tests/vectors/fernet/verify.json new file mode 100644 index 00000000..08c480f5 --- /dev/null +++ b/tests/vectors/fernet/verify.json @@ -0,0 +1,9 @@ +[ + { + "token": "gAAAAAAdwJ6wAAECAwQFBgcICQoLDA0ODy021cpGVWKZ_eEwCGM4BLLF_5CV9dOPmrhuVUPgJobwOz7JcbmrR64jVmpU4IwqDA==", + "now": "1985-10-26T01:20:01-07:00", + "ttl_sec": 60, + "src": "hello", + "secret": "cw_0x689RpI-jtRR7oE8h_eQsKImvJapLeSbXpwF4e4=" + } +] @@ -3,9 +3,10 @@ envlist = py26,py27,pypy,py32,py33,docs,pep8,py3pep8 [testenv] deps = - pytest coverage + iso8601 pretend + pytest commands = coverage run --source=cryptography/,tests/ -m pytest --capture=no --strict coverage report -m |