pvl/login/pubtkt.py
author Tero Marttila <terom@paivola.fi>
Mon, 13 Jan 2014 02:28:19 +0200
changeset 349 3c20473d0bdc
parent 348 089ec3eddc92
child 351 147f5e86b139
permissions -rw-r--r--
pvl.login: pimp out form with domain, and iconized panel for ticket
import base64
import calendar
import datetime
import ipaddr
import hashlib
import M2Crypto

import logging; log = logging.getLogger('pvl.login.pubtkt')

def datetime2unix (dt) :
    """
        datetime.datetime -> float
    """

    return calendar.timegm(dt.utctimetuple())

class Error (Exception) :
    pass

class ParseError (Error) :
    pass

class VerifyError (Error) :
    def __init__ (self, pubtkt, error) :
        self.pubtkt = pubtkt
        self.error = error

class ExpiredError (VerifyError) :
    def __init__ (self, pubtkt, now) :
        self.pubtkt = pubtkt
        self.now = now

class ServerKeys (object) :
    @classmethod
    def config (cls, public_key, private_key) :
        return cls(
                public  = M2Crypto.RSA.load_pub_key(public_key),
                private = M2Crypto.RSA.load_key(private_key),
        )

    def __init__ (self, public, private) :
        self.public = public
        self.private = private

class PubTkt (object) :
    @classmethod
    def load (cls, cookie, public_key) :
        """
            Load and verify a pubtkt from a cookie.

            Raise ParseError, VerifyError.
        """
        
        pubtkt, hash, sig = cls.parse(cookie)

        log.debug("parsed %s hash=%s sig=%s", pubtkt, hash.encode('hex'), sig.encode('hex'))
        
        try :
            if not public_key.verify(hash, sig, 'sha1') :
                raise VerifyError(pubtkt, "Unable to verify signature")
        except M2Crypto.RSA.RSAError as ex :
            raise VerifyError(pubtkt, str(ex))
        
        now = datetime.datetime.now()

        log.debug("validating %s < %s", pubtkt.validuntil, now)

        if pubtkt.validuntil < now :
            raise ExpiredError(pubtkt, now)

        return pubtkt

    @classmethod
    def parse (cls, cookie) :
        """
            Load a pubtkt from a cookie

            Raises ParseError.
        """
        
        if ';sig=' in cookie :
            data, sig = cookie.rsplit(';sig=', 1)
        else :
            raise ParseError("Missing signature")
        
        sig = base64.b64decode(sig)
        hash = hashlib.sha1(data).digest()

        try :
            attrs = dict(field.split('=', 1) for field in data.split(';'))
        except ValueError as ex :
            raise ParseError(str(ex))
        
        try :
            return cls.build(**attrs), hash, sig
        except (TypeError, ValueError) as ex :
            raise ParseError(str(ex))
    
    @classmethod
    def build (cls, uid, validuntil, cip=None, tokens=None, udata=None, graceperiod=None, bauth=None) :
        """
            Build a pubtkt from items.

            Raises TypeError or ValueError..
        """
        
        return cls(uid,
                validuntil  = datetime.datetime.fromtimestamp(int(validuntil)),
                cip         = ipaddr.IPAddress(cip) if cip else None,
                tokens      = tokens.split(',') if tokens else (),
                udata       = udata,
                graceperiod = datetime.datetime.fromtimestamp(int(graceperiod)) if graceperiod else None,
                bauth       = bauth,
        )

    @classmethod
    def new (cls, uid, expiry, **opts) :
        now = datetime.datetime.now()

        return cls(uid, now + expiry, **opts)

    def __init__ (self, uid, validuntil, cip=None, tokens=(), udata=None, graceperiod=None, bauth=None) :
        self.uid = uid
        self.validuntil = validuntil
        self.cip = cip
        self.tokens = tokens
        self.udata = udata
        self.graceperiod = graceperiod
        self.bauth = bauth

    def iteritems (self) :
        yield 'uid', self.uid
        yield 'validuntil', int(datetime2unix(self.validuntil))

        if self.cip :
            yield 'cip', self.cip
        
        if self.tokens :
            yield 'tokens', ','.join(self.tokens)
        
        if self.udata :
            yield 'udata', self.udata
        
        if self.graceperiod :
            yield 'graceperiod', int(datetime2unix(self.graceperiod))
        
        if self.bauth :
            yield 'bauth', self.bauth

    def __str__ (self) :
        """
            The (unsigned) pubtkt
        """

        return ';'.join('%s=%s' % (key, value) for key, value in self.iteritems())

    def sign (self, private_key) :
        data = str(self)
        hash = hashlib.sha1(data).digest()
        sign = private_key.sign(hash, 'sha1')

        return '%s;sig=%s' % (self, base64.b64encode(sign))

    def valid (self) :
        """
            Return remaining ticket validity.
        """

        return self.validuntil - datetime.datetime.now()

    def grace (self) :
        """
            Return remaining ticket grace.
        """

        return self.graceperiod - datetime.datetime.now()