Source code for M2Crypto.AuthCookie

from __future__ import absolute_import

"""Secure Authenticator Cookies

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

import logging
import re
import time

from http.cookies import SimpleCookie

from M2Crypto import Rand, m2, util

from typing import re as type_re, AnyStr, Optional, 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, data, format=_MIX_FORMAT): # type: (float, AnyStr, str) -> AnyStr return format % (expiry, data)
[docs] def unmix(dough, regex=_MIX_RE): # type: (AnyStr, type_re) -> object mo = regex.match(dough) if mo: return float(mo.group(1)), mo.group(2) else: return None
[docs] def unmix3(dough, regex=_MIX_RE): # type: (AnyStr, type_re) -> Optional[tuple[float, AnyStr, AnyStr]] mo = regex.match(dough) if mo: return float(mo.group(1)), mo.group(2), mo.group(3) else: return None
_TOKEN = '_M2AUTH_' # type: str
[docs] class AuthCookieJar(object): _keylen = 20 # type: int def __init__(self): # type: () -> None self._key = Rand.rand_bytes(self._keylen) def _hmac(self, key, data): # type: (bytes, str) -> str return util.bin_to_hex(m2.hmac(key, data.encode(), m2.sha1()))
[docs] def makeCookie(self, expiry, data): # type: (float, 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): # type: (AuthCookie) -> Union[bool, int] assert isinstance(cookie, AuthCookie) if cookie.isExpired(): return 0 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, _debug=False): # type: (Union[dict, bytes], bool) -> Union[bool, int] c = SimpleCookie() c.load(cookie_str) if _TOKEN not in c: log.debug('_TOKEN not in c (keys = %s)', dir(c)) return 0 undough = unmix3(c[_TOKEN].value) if undough is None: log.debug('undough is None') return 0 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)
[docs] class AuthCookie(object): def __init__(self, expiry, data, dough, mac): # type: (float, str, str, 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): # type: () -> float """Return the cookie's expiry time.""" return self._expiry
[docs] def data(self): # type: () -> str """Return the data portion of the cookie.""" return self._data
[docs] def mac(self): # type: () -> str """Return the cookie's MAC.""" return self._mac
[docs] def output(self, header="Set-Cookie:"): # type: (Optional[str]) -> str """Return the cookie's output in "Set-Cookie" format.""" return self._cookie.output(header=header)
[docs] def value(self): # type: () -> str """Return the cookie's output minus the "Set-Cookie: " portion. """ return self._cookie[_TOKEN].value
[docs] def isExpired(self): # type: () -> 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): # type: () -> str return self._name
[docs] def headerValue(self): # type: () -> str return self.value()