Source code for M2Crypto.AuthCookie

"""Secure Authenticator Cookies

Copyright (c) 1999-2002 Ng Pheng Siong. All rights reserved."""

import logging
import re
import time

from http.cookies import CookieError, SimpleCookie

from M2Crypto import Rand, m2, util

from typing import Pattern, Optional, Tuple, Union  # noqa

_MIX_FORMAT = "exp=%f&data=%s&digest="
_MIX_RE = re.compile(r"exp=(\d+\.\d+)&data=(.+)&digest=(\S*)")

log = logging.getLogger(__name__)


[docs] def mix(expiry: float, data: str, format: str = _MIX_FORMAT) -> str: return format % (expiry, data)
[docs] def unmix( dough: Union[str, bytes], regex: Pattern = _MIX_RE ) -> Optional[Tuple[float, str]]: mo = regex.match(dough) if mo: return float(mo.group(1)), mo.group(2) else: return None
[docs] def unmix3( dough: Union[str, bytes], regex: Pattern = _MIX_RE ) -> Optional[Tuple[float, str, str]]: mo = regex.match(dough) if mo: return float(mo.group(1)), mo.group(2), mo.group(3) else: return None
_TOKEN: str = "_M2AUTH_"
[docs] class AuthCookie: def __init__(self, expiry: float, data: str, dough: str, mac: str) -> None: """ Create new authentication cookie :param expiry: expiration time (in seconds) :param data: cookie payload (as a string) :param dough: expiry & data concatenated to URL compliant string :param mac: SHA1-based HMAC of dough and random key """ self._expiry = expiry self._data = data self._mac = mac self._cookie = SimpleCookie() self._cookie[_TOKEN] = "%s%s" % (dough, mac) self._name = "%s%s" % (dough, mac) # WebKit only.
[docs] def expiry(self) -> float: """Return the cookie's expiry time.""" return self._expiry
[docs] def data(self) -> str: """Return the data portion of the cookie.""" return self._data
[docs] def mac(self) -> str: """Return the cookie's MAC.""" return self._mac
[docs] def output(self, header: str = "Set-Cookie:") -> str: """Return the cookie's output in "Set-Cookie" format.""" return self._cookie.output(header=header)
[docs] def value(self) -> str: """Return the cookie's output minus the "Set-Cookie: " portion.""" return self._cookie[_TOKEN].value
[docs] def isExpired(self) -> bool: """Return 1 if the cookie has expired, 0 otherwise.""" return isinstance(self._expiry, (float, int)) and (time.time() > self._expiry)
# Following two methods are for WebKit only. # I may wish to push them to WKAuthCookie, but they are part # of the API now. Oh well.
[docs] def name(self) -> str: return self._name
[docs] def headerValue(self) -> str: return self.value()
[docs] class AuthCookieJar: _keylen: int = 20 def __init__(self) -> None: self._key = Rand.rand_bytes(self._keylen) def _hmac(self, key: bytes, data: str) -> str: return util.bin_to_hex(m2.hmac(key, data.encode(), m2.sha256()))
[docs] def makeCookie(self, expiry: float, data: str) -> AuthCookie: """ Make a cookie :param expiry: expiration time (float in seconds) :param data: cookie content :return: AuthCookie object """ if not isinstance(expiry, (int, float)): raise ValueError('Expiration time must be number, not "%s' % expiry) dough = mix(expiry, data) return AuthCookie(expiry, data, dough, self._hmac(self._key, dough))
[docs] def isGoodCookie(self, cookie: AuthCookie) -> bool: if cookie.isExpired(): return False c = self.makeCookie(cookie._expiry, cookie._data) return ( (c._expiry == cookie._expiry) and (c._data == cookie._data) and (c._mac == cookie._mac) and (c.output() == cookie.output()) )
[docs] def isGoodCookieString( self, cookie_str: Union[dict, str], _debug: bool = False ) -> bool: c = SimpleCookie() try: c.load(cookie_str) except CookieError: log.warning("Illegal characters in cookie string (CVE-2026-0672)") return False if _TOKEN not in c: log.debug("_TOKEN not in c (keys = %s)", dir(c)) return False undough = unmix3(c[_TOKEN].value) if undough is None: log.debug("undough is None") return False exp, data, mac = undough c2 = self.makeCookie(exp, data) if _debug and (c2._mac == mac): log.error("cookie_str = %s", cookie_str) log.error("c2.isExpired = %s", c2.isExpired()) log.error("mac = %s", mac) log.error("c2._mac = %s", c2._mac) log.error("c2._mac == mac: %s", str(c2._mac == mac)) return (not c2.isExpired()) and (c2._mac == mac)