Repository: pyca/cryptography

Issue:
Any interest for an override parameter for _MAX_CLOCK_SKEW?
It seems like a narrow assumption to assume that the actors communicating via Fernet-encrypted messages will always be able to have somewhat synchronized clocks. In our case, we have no way to amend the clocks of the systems upon which our software runs and they often differ by at least minutes, if not hours or days.

Our current solution is to just stomp on `cryptography.fernet._MAX_CLOCK_SKEW` and set it to some `LARGE_VALUE`. Not ideal, but it gets the job done for now.

I can think of several options to improve the situation here, but most of them involve changes to this library's code.

1) Add an optional parameter to Fernet's `__init__()` function or `decrypt()` function to disable the clock skew check altogether.
2) Add an optional parameter to Fernet's `__init__()` function or `decrypt()` that defaults to `_MAX_CLOCK_SKEW` and use that value for the clock skew check.
3) Add ability to set an offset on the Fernet object (or pass it in to `decrypt()`) that would be applied to the extracted timestamp prior to being checked against `_MAX_CLOCK_SKEW`. It would be the library user's responsibility for determining the appropriate offset value for each external actor.
4) I could subclass Fernet and implement some appropriate behavior in my new class (would essentially involve copying the whole `decrypt()` function into my codebase just to tweak this specific portion's behavior)

Would any of options 1-3 be worth pursuing to the point of a pull-request? I can probably make a pretty good effort on each of them, but I don't want to spend the time if the maintainers don't think any are worth attempting.



Discussion:
If you're seeing this level of skew is there any particular reason you're using the ttl functionality at all? Setting it to `None` makes it ignore age entirely, which sounds like what you really want here.

That's what I was hoping the behavior would be, as I currently don't pass in any value for `ttl`, but unfortunately that doesn't seem to be the case. The check involving `_MAX_CLOCK_SKEW` occurs independently of the `ttl` value:

```
# line 94
 if current_time + _MAX_CLOCK_SKEW < timestamp:
     raise InvalidToken
```

Perhaps there's some missing indentation here?

Ah, I see. I'm not entirely sure why that check exists at all. The fernet spec only has this to say about the timestamp: `If the user has specified a maximum age (or "time-to-live") for the token, ensure the recorded timestamp is not too far in the past.`

@alex do you remember the rationale for the clock skew check?

Uhhh, there's a test for it in the spec IIRC, and the exact value was taken
from one of the other fernet clients.

On Thu, Jan 21, 2016 at 9:39 AM, Paul Kehrer notifications@github.com
wrote:

> Ah, I see. I'm not entirely sure why that check exists at all. The fernet
> spec only has this to say about the timestamp: If the user has specified
> a maximum age (or "time-to-live") for the token, ensure the recorded
> timestamp is not too far in the past.
> 
> @alex https://github.com/alex do you remember the rationale for the
> clock skew check?
> 
> —
> Reply to this email directly or view it on GitHub
> https://github.com/pyca/cryptography/issues/2680#issuecomment-173589024.

## 

"I disapprove of what you say, but I will defend to the death your right to
say it." -- Evelyn Beatrice Hall (summarizing Voltaire)
"The people's good is the highest law." -- Cicero
GPG Key fingerprint: 125F 5C67 DFE9 4084

Ah, here it is:

```
  {
    "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="
  },
```

But that still defines a ttl, so it seems like we should only care when a ttl is set.

Indeed, sounds like a reasonable bug.

On Thu, Jan 21, 2016 at 9:42 AM, Paul Kehrer notifications@github.com
wrote:

> Ah, here it is:
> 
>   {
>     "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="
>   },
> 
> But that still defines a ttl, so it seems like we should only care when a
> ttl is set.
> 
> —
> Reply to this email directly or view it on GitHub
> https://github.com/pyca/cryptography/issues/2680#issuecomment-173590655.

## 

"I disapprove of what you say, but I will defend to the death your right to
say it." -- Evelyn Beatrice Hall (summarizing Voltaire)
"The people's good is the highest law." -- Cicero
GPG Key fingerprint: 125F 5C67 DFE9 4084


Resolving patch:
diff --git a/src/cryptography/fernet.py b/src/cryptography/fernet.py
--- a/src/cryptography/fernet.py
+++ b/src/cryptography/fernet.py
@@ -91,8 +91,10 @@ def decrypt(self, token, ttl=None):
         if ttl is not None:
             if timestamp + ttl < current_time:
                 raise InvalidToken
-        if current_time + _MAX_CLOCK_SKEW < timestamp:
-            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:


Test patch:
diff --git a/tests/test_fernet.py b/tests/test_fernet.py
--- a/tests/test_fernet.py
+++ b/tests/test_fernet.py
@@ -103,6 +103,15 @@ def test_unicode(self, backend):
         with pytest.raises(TypeError):
             f.decrypt(u"")
 
+    def test_timestamp_ignored_no_ttl(self, monkeypatch, backend):
+        f = Fernet(base64.urlsafe_b64encode(b"\x00" * 32), backend=backend)
+        pt = b"encrypt me"
+        token = f.encrypt(pt)
+        ts = "1985-10-26T01:20:01-07:00"
+        current_time = calendar.timegm(iso8601.parse_date(ts).utctimetuple())
+        monkeypatch.setattr(time, "time", lambda: current_time)
+        assert f.decrypt(token, ttl=None) == pt
+
     @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)