def parse_cookie(name, seed, kaka, enc_key=None): """ Parse and verify a cookie value. Parses a cookie created by `make_cookie` and verifies it has not been tampered with. You need to provide the same `seed` and `enc_key` used when creating the cookie, otherwise the verification fails. See `make_cookie` for details about the verification. :param seed: A seed key used for the HMAC signature :type seed: bytes :param kaka: The cookie :param enc_key: The encryption key used. :type enc_key: bytes or None :raises InvalidCookieSign: When verification fails. :return: A tuple consisting of (payload, timestamp) or None if parsing fails """ if not kaka: return None if isinstance(seed, str): seed = seed.encode("utf-8") parts = cookie_parts(name, kaka) if parts is None: return None elif len(parts) == 3: # verify the cookie signature cleartext, timestamp, sig = parts if not verify_cookie_signature(sig, seed, cleartext, timestamp): raise InvalidCookieSign() return cleartext, timestamp elif len(parts) == 4: # encrypted and signed timestamp = parts[0] iv = base64.b64decode(parts[1]) ciphertext = base64.b64decode(parts[2]) tag = base64.b64decode(parts[3]) # Make sure the key is 32-Bytes long key = _make_hashed_key((enc_key, seed)) crypt = AEAD(key, iv) # timestamp does not need to be encrypted, just MAC'ed, # so we add it to 'Associated Data' only. crypt.add_associated_data(timestamp.encode("utf-8")) try: cleartext = crypt.decrypt_and_verify(ciphertext, tag) except AESError: raise InvalidCookieSign() return cleartext.decode("utf-8"), timestamp return None
def parse_cookie(name, seed, kaka, enc_key=None): """Parses and verifies a cookie value Parses a cookie created by `make_cookie` and verifies it has not been tampered with. You need to provide the same `seed` and `enc_key` used when creating the cookie, otherwise the verification fails. See `make_cookie` for details about the verification. :param seed: A seed key used for the HMAC signature :type seed: bytes :param kaka: The cookie :param enc_key: The encryption key used. :type enc_key: bytes or None :raises InvalidCookieSign: When verification fails. :return: A tuple consisting of (payload, timestamp) or None if parsing fails """ if not kaka: return None if isinstance(seed, text_type): seed = seed.encode('utf-8') parts = cookie_parts(name, kaka) if parts is None: return None elif len(parts) == 3: # verify the cookie signature cleartext, timestamp, sig = parts if not verify_cookie_signature(sig, seed, cleartext, timestamp): raise InvalidCookieSign() return cleartext, timestamp elif len(parts) == 4: # encrypted and signed timestamp = parts[0] iv = base64.b64decode(parts[1]) ciphertext = base64.b64decode(parts[2]) tag = base64.b64decode(parts[3]) # Make sure the key is 32-Bytes long key = _make_hashed_key((enc_key, seed)) crypt = AEAD(key, iv) # timestamp does not need to be encrypted, just MAC'ed, # so we add it to 'Associated Data' only. crypt.add_associated_data(timestamp.encode('utf-8')) try: cleartext = crypt.decrypt_and_verify(ciphertext, tag) except AESError: raise InvalidCookieSign() return cleartext.decode('utf-8'), timestamp return None
def test_AEAD_good(aead_key, aead_iv, cleartext): extra = ["some", "extra", "data"] k = AEAD(aead_key, aead_iv) for d in extra: k.add_associated_data(d) ciphertext, tag = k.encrypt_and_tag(cleartext) # get a fresh AEAD object c = AEAD(aead_key, aead_iv) for d in extra: c.add_associated_data(d) cleartext2 = c.decrypt_and_verify(ciphertext, tag) assert cleartext2 == cleartext
def make_cookie( name, load, seed, expire=0, domain="", path="", timestamp="", enc_key=None, secure=True, httponly=True, ): """ Create and return a cookie. The cookie is secured against tampering. If you only provide a `seed`, a HMAC gets added to the cookies value and this is checked, when the cookie is parsed again. If you provide both `seed` and `enc_key`, the cookie gets protected by using AEAD encryption. This provides both a MAC over the whole cookie and encrypts the `load` in a single step. The `seed` and `enc_key` parameters should be byte strings of at least 16 bytes length each. Those are used as cryptographic keys. :param name: Cookie name :type name: text :param load: Cookie load :type load: text :param seed: A seed key for the HMAC function :type seed: byte string :param expire: Number of minutes before this cookie goes stale :type expire: int :param domain: The domain of the cookie :param path: The path specification for the cookie :param timestamp: A time stamp :type timestamp: text :param enc_key: The key to use for cookie encryption. :type enc_key: byte string :return: A tuple to be added to headers """ cookie = SimpleCookie() if not timestamp: timestamp = str(int(time.time())) bytes_load = load.encode("utf-8") bytes_timestamp = timestamp.encode("utf-8") if enc_key: # Make sure the key is 256-bit long, for AES-128-SIV # # This should go away once we push the keysize requirements up # to the top level APIs. key = _make_hashed_key((enc_key, seed)) # Random 128-Bit IV iv = os.urandom(16) crypt = AEAD(key, iv) # timestamp does not need to be encrypted, just MAC'ed, # so we add it to 'Associated Data' only. crypt.add_associated_data(bytes_timestamp) ciphertext, tag = crypt.encrypt_and_tag(bytes_load) cookie_payload = [ bytes_timestamp, base64.b64encode(iv), base64.b64encode(ciphertext), base64.b64encode(tag), ] else: cookie_payload = [ bytes_load, bytes_timestamp, cookie_signature(seed, load, timestamp).encode("utf-8"), ] cookie[name] = (b"|".join(cookie_payload)).decode("utf-8") if path: cookie[name]["path"] = path if domain: cookie[name]["domain"] = domain if expire: cookie[name]["expires"] = _expiration(expire, "%a, %d-%b-%Y %H:%M:%S GMT") if secure: cookie[name]["secure"] = secure if httponly: cookie[name]["httponly"] = httponly return tuple(cookie.output().split(": ", 1))
def make_cookie(name, load, seed, expire=0, domain="", path="", timestamp="", enc_key=None): """ Create and return a cookie The cookie is secured against tampering. If you only provide a `seed`, a HMAC gets added to the cookies value and this is checked, when the cookie is parsed again. If you provide both `seed` and `enc_key`, the cookie gets protected by using AEAD encryption. This provides both a MAC over the whole cookie and encrypts the `load` in a single step. The `seed` and `enc_key` parameters should be byte strings of at least 16 bytes length each. Those are used as cryptographic keys. :param name: Cookie name :type name: text :param load: Cookie load :type load: text :param seed: A seed key for the HMAC function :type seed: byte string :param expire: Number of minutes before this cookie goes stale :type expire: int :param domain: The domain of the cookie :param path: The path specification for the cookie :param timestamp: A time stamp :type timestamp: text :param enc_key: The key to use for cookie encryption. :type enc_key: byte string :return: A tuple to be added to headers """ cookie = SimpleCookie() if not timestamp: timestamp = str(int(time.time())) bytes_load = load.encode("utf-8") bytes_timestamp = timestamp.encode("utf-8") if enc_key: # Make sure the key is 256-bit long, for AES-128-SIV # # This should go away once we push the keysize requirements up # to the top level APIs. key = _make_hashed_key((enc_key, seed)) # Random 128-Bit IV iv = os.urandom(16) crypt = AEAD(key, iv) # timestamp does not need to be encrypted, just MAC'ed, # so we add it to 'Associated Data' only. crypt.add_associated_data(bytes_timestamp) ciphertext, tag = crypt.encrypt_and_tag(bytes_load) cookie_payload = [bytes_timestamp, base64.b64encode(iv), base64.b64encode(ciphertext), base64.b64encode(tag)] else: cookie_payload = [ bytes_load, bytes_timestamp, cookie_signature(seed, load, timestamp).encode('utf-8')] cookie[name] = (b"|".join(cookie_payload)).decode('utf-8') if path: cookie[name]["path"] = path if domain: cookie[name]["domain"] = domain if expire: cookie[name]["expires"] = _expiration(expire, "%a, %d-%b-%Y %H:%M:%S GMT") return tuple(cookie.output().split(": ", 1))
def test_AEAD_bad_aad(aead_key, aead_iv, cleartext): extra = ["some", "extra", "data"] k = AEAD(aead_key, aead_iv) for d in extra: k.add_associated_data(d) ciphertext, tag = k.encrypt_and_tag(cleartext) # get a fresh AEAD object c = AEAD(aead_key, aead_iv) # skip one aad item, MAC is wrong now for d in extra[:1]: c.add_associated_data(d) with pytest.raises(AESError): c.decrypt_and_verify(ciphertext, tag)