aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPaul Kehrer <paul.l.kehrer@gmail.com>2018-05-12 11:57:32 -0400
committerAlex Gaynor <alex.gaynor@gmail.com>2018-05-12 11:57:32 -0400
commit36ad98fd5e4b7358dc2aa903b6d51569bf19c5f8 (patch)
treeb176b10478a5cfe302ca3ed7193fda5964c16d8b
parent33ae3cea990b307eafaa5f52232eba8315fd05fe (diff)
downloadcryptography-36ad98fd5e4b7358dc2aa903b6d51569bf19c5f8.tar.gz
cryptography-36ad98fd5e4b7358dc2aa903b6d51569bf19c5f8.tar.bz2
cryptography-36ad98fd5e4b7358dc2aa903b6d51569bf19c5f8.zip
Add support for extracting timestamp from a Fernet token (#4229)
* Add API for retrieving the seconds-to-expiry for the token, given a TTL. * Process PR feedback: * Do compute the TTL, but just the age of the token. The caller can decided what to do next. * Factored out the HMAC signature verification to a separate function. * Fixed a copy&paste mistake in the test cases * Tests cleanup. * `struct` no longer needed * Document `def age()` * typo in `age()` documentation * token, not data * remove test for TTL expiry that is already covered by the parameterized `test_invalid()`. * let's call this extract_timestamp and just return timestamp * review comments * it's UNIX I know this
-rw-r--r--CHANGELOG.rst3
-rw-r--r--docs/fernet.rst16
-rw-r--r--src/cryptography/fernet.py21
-rw-r--r--tests/test_fernet.py9
4 files changed, 43 insertions, 6 deletions
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 6ab6b044..4cabaf7f 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -8,6 +8,9 @@ Changelog
.. note:: This version is not yet released and is under active development.
+* Added :meth:`~cryptography.fernet.Fernet.extract_timestamp` to get the
+ authenticated timestamp of a :doc:`Fernet </fernet>` token.
+
.. _v2-2-2:
2.2.2 - 2018-03-27
diff --git a/docs/fernet.rst b/docs/fernet.rst
index a0ffe64f..2d7d2281 100644
--- a/docs/fernet.rst
+++ b/docs/fernet.rst
@@ -80,6 +80,22 @@ has support for implementing key rotation via :class:`MultiFernet`.
:raises TypeError: This exception is raised if ``token`` is not
``bytes``.
+ .. method:: extract_timestamp(token)
+
+ .. versionadded:: 2.3
+
+ Returns the timestamp for the token. The caller can then decide if
+ the token is about to expire and, for example, issue a new token.
+
+ :param bytes token: The Fernet token. This is the result of calling
+ :meth:`encrypt`.
+ :returns int: The UNIX timestamp of the token.
+ :raises cryptography.fernet.InvalidToken: If the ``token``'s signature
+ is invalid this exception
+ is raised.
+ :raises TypeError: This exception is raised if ``token`` is not
+ ``bytes``.
+
.. class:: MultiFernet(fernets)
diff --git a/src/cryptography/fernet.py b/src/cryptography/fernet.py
index 1f33a12d..ac2dd0b6 100644
--- a/src/cryptography/fernet.py
+++ b/src/cryptography/fernet.py
@@ -74,6 +74,12 @@ class Fernet(object):
timestamp, data = Fernet._get_unverified_token_data(token)
return self._decrypt_data(data, timestamp, ttl)
+ def extract_timestamp(self, token):
+ timestamp, data = Fernet._get_unverified_token_data(token)
+ # Verify the token was not tampered with.
+ self._verify_signature(data)
+ return timestamp
+
@staticmethod
def _get_unverified_token_data(token):
if not isinstance(token, bytes):
@@ -93,6 +99,14 @@ class Fernet(object):
raise InvalidToken
return timestamp, data
+ def _verify_signature(self, data):
+ h = HMAC(self._signing_key, hashes.SHA256(), backend=self._backend)
+ h.update(data[:-32])
+ try:
+ h.verify(data[-32:])
+ except InvalidSignature:
+ raise InvalidToken
+
def _decrypt_data(self, data, timestamp, ttl):
current_time = int(time.time())
if ttl is not None:
@@ -102,12 +116,7 @@ class Fernet(object):
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
+ self._verify_signature(data)
iv = data[9:25]
ciphertext = data[25:-32]
diff --git a/tests/test_fernet.py b/tests/test_fernet.py
index 6558d11b..75ecc356 100644
--- a/tests/test_fernet.py
+++ b/tests/test_fernet.py
@@ -122,6 +122,15 @@ class TestFernet(object):
with pytest.raises(ValueError):
Fernet(base64.urlsafe_b64encode(b"abc"), backend=backend)
+ def test_extract_timestamp(self, monkeypatch, backend):
+ f = Fernet(base64.urlsafe_b64encode(b"\x00" * 32), backend=backend)
+ current_time = 1526138327
+ monkeypatch.setattr(time, "time", lambda: current_time)
+ token = f.encrypt(b'encrypt me')
+ assert f.extract_timestamp(token) == current_time
+ with pytest.raises(InvalidToken):
+ f.extract_timestamp(b"nonsensetoken")
+
@pytest.mark.requires_backend_interface(interface=CipherBackend)
@pytest.mark.requires_backend_interface(interface=HMACBackend)