prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN 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.
  
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `src/cryptography/fernet.py`
  Content:
  ```
  1 # This file is dual licensed under the terms of the Apache License, Version
  2 # 2.0, and the BSD License. See the LICENSE file in the root of this repository
  3 # for complete details.
  4 
  5 from __future__ import absolute_import, division, print_function
  6 
  7 import base64
  8 import binascii
  9 import os
  10 import struct
  11 import time
  12 
  13 import six
  14 
  15 from cryptography.exceptions import InvalidSignature
  16 from cryptography.hazmat.backends import default_backend
  17 from cryptography.hazmat.primitives import hashes, padding
  18 from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
  19 from cryptography.hazmat.primitives.hmac import HMAC
  20 
  21 
  22 class InvalidToken(Exception):
  23     pass
  24 
  25 
  26 _MAX_CLOCK_SKEW = 60
  27 
  28 
  29 class Fernet(object):
  30     def __init__(self, key, backend=None):
  31         if backend is None:
  32             backend = default_backend()
  33 
  34         key = base64.urlsafe_b64decode(key)
  35         if len(key) != 32:
  36             raise ValueError(
  37                 "Fernet key must be 32 url-safe base64-encoded bytes."
  38             )
  39 
  40         self._signing_key = key[:16]
  41         self._encryption_key = key[16:]
  42         self._backend = backend
  43 
  44     @classmethod
  45     def generate_key(cls):
  46         return base64.urlsafe_b64encode(os.urandom(32))
  47 
  48     def encrypt(self, data):
  49         current_time = int(time.time())
  50         iv = os.urandom(16)
  51         return self._encrypt_from_parts(data, current_time, iv)
  52 
  53     def _encrypt_from_parts(self, data, current_time, iv):
  54         if not isinstance(data, bytes):
  55             raise TypeError("data must be bytes.")
  56 
  57         padder = padding.PKCS7(algorithms.AES.block_size).padder()
  58         padded_data = padder.update(data) + padder.finalize()
  59         encryptor = Cipher(
  60             algorithms.AES(self._encryption_key), modes.CBC(iv), self._backend
  61         ).encryptor()
  62         ciphertext = encryptor.update(padded_data) + encryptor.finalize()
  63 
  64         basic_parts = (
  65             b"\x80" + struct.pack(">Q", current_time) + iv + ciphertext
  66         )
  67 
  68         h = HMAC(self._signing_key, hashes.SHA256(), backend=self._backend)
  69         h.update(basic_parts)
  70         hmac = h.finalize()
  71         return base64.urlsafe_b64encode(basic_parts + hmac)
  72 
  73     def decrypt(self, token, ttl=None):
  74         if not isinstance(token, bytes):
  75             raise TypeError("token must be bytes.")
  76 
  77         current_time = int(time.time())
  78 
  79         try:
  80             data = base64.urlsafe_b64decode(token)
  81         except (TypeError, binascii.Error):
  82             raise InvalidToken
  83 
  84         if not data or six.indexbytes(data, 0) != 0x80:
  85             raise InvalidToken
  86 
  87         try:
  88             timestamp, = struct.unpack(">Q", data[1:9])
  89         except struct.error:
  90             raise InvalidToken
  91         if ttl is not None:
  92             if timestamp + ttl < current_time:
  93                 raise InvalidToken
  94         if current_time + _MAX_CLOCK_SKEW < timestamp:
  95             raise InvalidToken
  96         h = HMAC(self._signing_key, hashes.SHA256(), backend=self._backend)
  97         h.update(data[:-32])
  98         try:
  99             h.verify(data[-32:])
  100         except InvalidSignature:
  101             raise InvalidToken
  102 
  103         iv = data[9:25]
  104         ciphertext = data[25:-32]
  105         decryptor = Cipher(
  106             algorithms.AES(self._encryption_key), modes.CBC(iv), self._backend
  107         ).decryptor()
  108         plaintext_padded = decryptor.update(ciphertext)
  109         try:
  110             plaintext_padded += decryptor.finalize()
  111         except ValueError:
  112             raise InvalidToken
  113         unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
  114 
  115         unpadded = unpadder.update(plaintext_padded)
  116         try:
  117             unpadded += unpadder.finalize()
  118         except ValueError:
  119             raise InvalidToken
  120         return unpadded
  121 
  122 
  123 class MultiFernet(object):
  124     def __init__(self, fernets):
  125         fernets = list(fernets)
  126         if not fernets:
  127             raise ValueError(
  128                 "MultiFernet requires at least one Fernet instance"
  129             )
  130         self._fernets = fernets
  131 
  132     def encrypt(self, msg):
  133         return self._fernets[0].encrypt(msg)
  134 
  135     def decrypt(self, msg, ttl=None):
  136         for f in self._fernets:
  137             try:
  138                 return f.decrypt(msg, ttl)
  139             except InvalidToken:
  140                 pass
  141         raise InvalidToken
  142
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: 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 @@
           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:
  
verification_info: {"golden_diff": "diff --git a/src/cryptography/fernet.py b/src/cryptography/fernet.py\n--- a/src/cryptography/fernet.py\n+++ b/src/cryptography/fernet.py\n@@ -91,8 +91,10 @@\n         if ttl is not None:\n             if timestamp + ttl < current_time:\n                 raise InvalidToken\n-        if current_time + _MAX_CLOCK_SKEW < timestamp:\n-            raise InvalidToken\n+\n+            if current_time + _MAX_CLOCK_SKEW < timestamp:\n+                raise InvalidToken\n+\n         h = HMAC(self._signing_key, hashes.SHA256(), backend=self._backend)\n         h.update(data[:-32])\n         try:\n", "issue": "Any interest for an override parameter for _MAX_CLOCK_SKEW?\nIt 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.\n\nOur 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.\n\nI can think of several options to improve the situation here, but most of them involve changes to this library's code.\n\n1) Add an optional parameter to Fernet's `__init__()` function or `decrypt()` function to disable the clock skew check altogether.\n2) 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.\n3) 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.\n4) 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)\n\nWould 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.\n\n", "before_files": [{"content": "# This file is dual licensed under the terms of the Apache License, Version\n# 2.0, and the BSD License. See the LICENSE file in the root of this repository\n# for complete details.\n\nfrom __future__ import absolute_import, division, print_function\n\nimport base64\nimport binascii\nimport os\nimport struct\nimport time\n\nimport six\n\nfrom cryptography.exceptions import InvalidSignature\nfrom cryptography.hazmat.backends import default_backend\nfrom cryptography.hazmat.primitives import hashes, padding\nfrom cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes\nfrom cryptography.hazmat.primitives.hmac import HMAC\n\n\nclass InvalidToken(Exception):\n    pass\n\n\n_MAX_CLOCK_SKEW = 60\n\n\nclass Fernet(object):\n    def __init__(self, key, backend=None):\n        if backend is None:\n            backend = default_backend()\n\n        key = base64.urlsafe_b64decode(key)\n        if len(key) != 32:\n            raise ValueError(\n                \"Fernet key must be 32 url-safe base64-encoded bytes.\"\n            )\n\n        self._signing_key = key[:16]\n        self._encryption_key = key[16:]\n        self._backend = backend\n\n    @classmethod\n    def generate_key(cls):\n        return base64.urlsafe_b64encode(os.urandom(32))\n\n    def encrypt(self, data):\n        current_time = int(time.time())\n        iv = os.urandom(16)\n        return self._encrypt_from_parts(data, current_time, iv)\n\n    def _encrypt_from_parts(self, data, current_time, iv):\n        if not isinstance(data, bytes):\n            raise TypeError(\"data must be bytes.\")\n\n        padder = padding.PKCS7(algorithms.AES.block_size).padder()\n        padded_data = padder.update(data) + padder.finalize()\n        encryptor = Cipher(\n            algorithms.AES(self._encryption_key), modes.CBC(iv), self._backend\n        ).encryptor()\n        ciphertext = encryptor.update(padded_data) + encryptor.finalize()\n\n        basic_parts = (\n            b\"\\x80\" + struct.pack(\">Q\", current_time) + iv + ciphertext\n        )\n\n        h = HMAC(self._signing_key, hashes.SHA256(), backend=self._backend)\n        h.update(basic_parts)\n        hmac = h.finalize()\n        return base64.urlsafe_b64encode(basic_parts + hmac)\n\n    def decrypt(self, token, ttl=None):\n        if not isinstance(token, bytes):\n            raise TypeError(\"token must be bytes.\")\n\n        current_time = int(time.time())\n\n        try:\n            data = base64.urlsafe_b64decode(token)\n        except (TypeError, binascii.Error):\n            raise InvalidToken\n\n        if not data or six.indexbytes(data, 0) != 0x80:\n            raise InvalidToken\n\n        try:\n            timestamp, = struct.unpack(\">Q\", data[1:9])\n        except struct.error:\n            raise InvalidToken\n        if ttl is not None:\n            if timestamp + ttl < current_time:\n                raise InvalidToken\n        if current_time + _MAX_CLOCK_SKEW < timestamp:\n            raise InvalidToken\n        h = HMAC(self._signing_key, hashes.SHA256(), backend=self._backend)\n        h.update(data[:-32])\n        try:\n            h.verify(data[-32:])\n        except InvalidSignature:\n            raise InvalidToken\n\n        iv = data[9:25]\n        ciphertext = data[25:-32]\n        decryptor = Cipher(\n            algorithms.AES(self._encryption_key), modes.CBC(iv), self._backend\n        ).decryptor()\n        plaintext_padded = decryptor.update(ciphertext)\n        try:\n            plaintext_padded += decryptor.finalize()\n        except ValueError:\n            raise InvalidToken\n        unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()\n\n        unpadded = unpadder.update(plaintext_padded)\n        try:\n            unpadded += unpadder.finalize()\n        except ValueError:\n            raise InvalidToken\n        return unpadded\n\n\nclass MultiFernet(object):\n    def __init__(self, fernets):\n        fernets = list(fernets)\n        if not fernets:\n            raise ValueError(\n                \"MultiFernet requires at least one Fernet instance\"\n            )\n        self._fernets = fernets\n\n    def encrypt(self, msg):\n        return self._fernets[0].encrypt(msg)\n\n    def decrypt(self, msg, ttl=None):\n        for f in self._fernets:\n            try:\n                return f.decrypt(msg, ttl)\n            except InvalidToken:\n                pass\n        raise InvalidToken\n", "path": "src/cryptography/fernet.py"}], "after_files": [{"content": "# This file is dual licensed under the terms of the Apache License, Version\n# 2.0, and the BSD License. See the LICENSE file in the root of this repository\n# for complete details.\n\nfrom __future__ import absolute_import, division, print_function\n\nimport base64\nimport binascii\nimport os\nimport struct\nimport time\n\nimport six\n\nfrom cryptography.exceptions import InvalidSignature\nfrom cryptography.hazmat.backends import default_backend\nfrom cryptography.hazmat.primitives import hashes, padding\nfrom cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes\nfrom cryptography.hazmat.primitives.hmac import HMAC\n\n\nclass InvalidToken(Exception):\n    pass\n\n\n_MAX_CLOCK_SKEW = 60\n\n\nclass Fernet(object):\n    def __init__(self, key, backend=None):\n        if backend is None:\n            backend = default_backend()\n\n        key = base64.urlsafe_b64decode(key)\n        if len(key) != 32:\n            raise ValueError(\n                \"Fernet key must be 32 url-safe base64-encoded bytes.\"\n            )\n\n        self._signing_key = key[:16]\n        self._encryption_key = key[16:]\n        self._backend = backend\n\n    @classmethod\n    def generate_key(cls):\n        return base64.urlsafe_b64encode(os.urandom(32))\n\n    def encrypt(self, data):\n        current_time = int(time.time())\n        iv = os.urandom(16)\n        return self._encrypt_from_parts(data, current_time, iv)\n\n    def _encrypt_from_parts(self, data, current_time, iv):\n        if not isinstance(data, bytes):\n            raise TypeError(\"data must be bytes.\")\n\n        padder = padding.PKCS7(algorithms.AES.block_size).padder()\n        padded_data = padder.update(data) + padder.finalize()\n        encryptor = Cipher(\n            algorithms.AES(self._encryption_key), modes.CBC(iv), self._backend\n        ).encryptor()\n        ciphertext = encryptor.update(padded_data) + encryptor.finalize()\n\n        basic_parts = (\n            b\"\\x80\" + struct.pack(\">Q\", current_time) + iv + ciphertext\n        )\n\n        h = HMAC(self._signing_key, hashes.SHA256(), backend=self._backend)\n        h.update(basic_parts)\n        hmac = h.finalize()\n        return base64.urlsafe_b64encode(basic_parts + hmac)\n\n    def decrypt(self, token, ttl=None):\n        if not isinstance(token, bytes):\n            raise TypeError(\"token must be bytes.\")\n\n        current_time = int(time.time())\n\n        try:\n            data = base64.urlsafe_b64decode(token)\n        except (TypeError, binascii.Error):\n            raise InvalidToken\n\n        if not data or six.indexbytes(data, 0) != 0x80:\n            raise InvalidToken\n\n        try:\n            timestamp, = struct.unpack(\">Q\", data[1:9])\n        except struct.error:\n            raise InvalidToken\n        if ttl is not None:\n            if timestamp + ttl < current_time:\n                raise InvalidToken\n\n            if current_time + _MAX_CLOCK_SKEW < timestamp:\n                raise InvalidToken\n\n        h = HMAC(self._signing_key, hashes.SHA256(), backend=self._backend)\n        h.update(data[:-32])\n        try:\n            h.verify(data[-32:])\n        except InvalidSignature:\n            raise InvalidToken\n\n        iv = data[9:25]\n        ciphertext = data[25:-32]\n        decryptor = Cipher(\n            algorithms.AES(self._encryption_key), modes.CBC(iv), self._backend\n        ).decryptor()\n        plaintext_padded = decryptor.update(ciphertext)\n        try:\n            plaintext_padded += decryptor.finalize()\n        except ValueError:\n            raise InvalidToken\n        unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()\n\n        unpadded = unpadder.update(plaintext_padded)\n        try:\n            unpadded += unpadder.finalize()\n        except ValueError:\n            raise InvalidToken\n        return unpadded\n\n\nclass MultiFernet(object):\n    def __init__(self, fernets):\n        fernets = list(fernets)\n        if not fernets:\n            raise ValueError(\n                \"MultiFernet requires at least one Fernet instance\"\n            )\n        self._fernets = fernets\n\n    def encrypt(self, msg):\n        return self._fernets[0].encrypt(msg)\n\n    def decrypt(self, msg, ttl=None):\n        for f in self._fernets:\n            try:\n                return f.decrypt(msg, ttl)\n            except InvalidToken:\n                pass\n        raise InvalidToken\n", "path": "src/cryptography/fernet.py"}]}