jwcrypto icon indicating copy to clipboard operation
jwcrypto copied to clipboard

use UTC instead of computer timezone to check exp, nbf and other time-related claims

Open daweedm opened this issue 3 years ago • 2 comments
trafficstars

In jwt.py, among others checks which focus on time-related claims, jwcrypto uses a "timezoned" time (time.time()) in order to process the checks :

        if self._check_claims is None:
            if 'exp' in claims:
                self._check_exp(claims['exp'], time.time(), self._leeway)
            if 'nbf' in claims:
                self._check_nbf(claims['nbf'], time.time(), self._leeway)

this approach causes issues when using jwcrypto with a server having a timezone that is different of a client.

I think we should use datetime.utcnow() by default instead, what do you think about it ?

I can make a PR if needed.

daweedm avatar Sep 05 '22 14:09 daweedm

Yeah, we should definitely always use UTC. A PR is definitely welcome, the question is how to upgrade w/o breaking currently working servers as a side effect ...

simo5 avatar Sep 05 '22 15:09 simo5

Actually, time.time() is UTC. The confusion most likely stems from comparing timestamps from datetime.datetime.utcnow() vs datetime.datetime.now()

I'm currently in EDT, so UTC-04.

Running the code below:

import datetime
import time


print("time.time():                ", time.time())
print("datetime.datetime.utcnow(): ", datetime.datetime.utcnow().timestamp())
print("datetime.datetime.now():    ", datetime.datetime.now().timestamp())
print("datetime.datetime.utcnow(): ", datetime.datetime.utcnow())
print("datetime.datetime.now():    ", datetime.datetime.now())

Gives us:

time.time():                 1687834846.4905422
datetime.datetime.utcnow():  1687849246.490579
datetime.datetime.now():     1687834846.490584
datetime.datetime.utcnow():  2023-06-27 03:00:46.490598
datetime.datetime.now():     2023-06-26 23:00:46.490612

The time.time() and datetime.datetime.now() are the same which makes it seem as though time.time() is giving us local time.

Running the below code instead:

import datetime
import time


print("time.time():                ", time.time())
print("datetime.datetime.utcnow(): ", datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc).timestamp())
print("datetime.datetime.now():    ", datetime.datetime.now().replace(tzinfo=datetime.timezone.utc).timestamp())
print("datetime.datetime.utcnow(): ", datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc))
print("datetime.datetime.now():    ", datetime.datetime.now().replace(tzinfo=datetime.timezone.utc))

Gives us:

time.time():                 1687832852.3228416
datetime.datetime.utcnow():  1687832852.322877
datetime.datetime.now():     1687818452.322895
datetime.datetime.utcnow():  2023-06-27 02:27:32.322908+00:00
datetime.datetime.now():     2023-06-26 22:27:32.322923+00:00

Here we can see that time.time() and datetime.datetime.utcnow() are the same which is what we desire.

This happens because the datetime.timestamp() function, as according to the Python docs, assumes any timezone naive datetime objects are in local time and converts them to UTC, and both datetime.datetime.now() and datetime.datetime.utcnow() are timezone naive objects. Hence why we made them timezone aware in the second example.

It's worth noting that in my example, I incorrectly labeled my current local time from datetime.datetime.now() as UTC to demonstrate the point better, but obviously, if we had the correct timezone on it, then it would give the same timestamp as time.time()

See:

import datetime
import time

from pytz import timezone


print("time.time():                ", time.time())
print("datetime.datetime.utcnow(): ", datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc).timestamp())
print("datetime.datetime.now():    ", datetime.datetime.now(timezone('US/Eastern')).timestamp())
print("datetime.datetime.utcnow(): ", datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc))
print("datetime.datetime.now():    ", datetime.datetime.now(timezone('US/Eastern')))

Gives us:

time.time():                 1687836103.5745661
datetime.datetime.utcnow():  1687836103.574606
datetime.datetime.now():     1687836103.578064
datetime.datetime.utcnow():  2023-06-27 03:21:43.578093+00:00
datetime.datetime.now():     2023-06-26 23:21:43.578112-04:00

Flat-Irons avatar Jun 27 '23 03:06 Flat-Irons