Exemple #1
0
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
Exemple #2
0
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
Exemple #3
0
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
Exemple #4
0
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))
Exemple #5
0
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))
Exemple #6
0
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)