Changeset 2556


Ignore:
Timestamp:
07/06/07 16:31:29 (12 years ago)
Author:
pjkersha
Message:

ndg.security.test/ndg/security/test/SessionCookie/init.py: created
separate SessionCookie? unit test package.

ndg.security.test/ndg/security/test/SessionCookie/sessionCookieTest.cfg:
configures PKI for encryption of cookie info

ndg.security.common/ndg/security/common/SessionCookie.py: major refactoring -

  • use a single morsel rather two. This contains: userDN, sessID, and

sessMgrURI

  • morsel is encrypted using PKI
Location:
TI12-security/trunk/python
Files:
4 added
3 edited

Legend:

Unmodified
Added
Removed
  • TI12-security/trunk/python/ndg.security.common/ndg/security/common/SessionCookie.py

    r2270 r2556  
    1414__revision__ = '$Id$' 
    1515 
     16import base64 
    1617from datetime import datetime 
    1718from Cookie import SimpleCookie 
     19from M2Crypto import X509, BIO, RSA 
     20 
     21from ndg.security.common.X509 import X509Cert, X509CertRead 
    1822 
    1923#_____________________________________________________________________________ 
     
    3135     
    3236    ... raises - AttributeError: can't set attribute""" 
    33     def __getTags(cls): 
    34         '''ndgID1 is the session ID and ndgID2 is the encrypted session  
    35         manager WSDL address.''' 
    36         return ("ndgID1", "ndgID2") 
    37  
    38     tags = property(fget=__getTags) 
     37    def __getTag(cls): 
     38        '''tag refs the morsel containing the encrypted user DN, session ID  
     39        and the encrypted session manager address.''' 
     40        return "ndgSID1" 
     41 
     42    tag = property(fget=__getTag) 
    3943 
    4044    def __getSessIDlen(cls): 
     
    4751#_____________________________________________________________________________ 
    4852class SessionCookie(object): 
    49     """Class encapsulates Session Cookie for handling by UserSession and 
    50     LoginService""" 
     53    """Handler for creation and parsing of NDG Security Session Cookies""" 
    5154     
    5255    __metaclass__ = _MetaSessionCookie 
     
    5861    __cookieExpiryTag = "expires" 
    5962  
     63    morselArgSep = ',' 
     64    nMorselArgs = 3 
     65     
    6066    # Quotes are vital (and part of the official cookei format) - otherwise it 
    6167    # will not be parsed correctly 
     
    6470         
    6571    #_________________________________________________________________________     
    66     def __init__(self, 
    67                  cookieStr=None,  
    68                  expiryStr=None, 
    69                  dtExpiry=None, 
    70                  cookiePath=None, 
    71                  cookieDomain=None, 
    72                  **cookieTagKw): 
    73         """Cookie creation              
    74              
    75         Caller should set the cookie e.g. in a CGI script 
    76         print "Content-type: text/html" 
    77         print str(cookie) + os.linesep 
    78  
    79         cookieStr:                   Optionally, create new object by parsing a 
    80                                cookie string 
    81                                Use UserSession.encrypt() to encrypt the 
    82                                SessionManager WSDL address.  The site  
    83                                SessionManager holds the encryption key. 
    84         expiryStr|dtExpiry:    expiry date time for the cookie.  Input as a  
    85                                string formatted in the standard way for  
    86                                cookies or input a datetime type for  
    87                                conversion. 
    88         cookiePath:            cookie path - defaults to '/' 
    89         cookieDomain:          The domain for the cookie.  Default is the 
    90                                web server address.  Override to set to a  
    91                                site wide cookie e.g. .rl.ac.uk.  Nb. that this 
    92                                has security implications 
    93          
    94         **cookieTagKw:         session ID for cookie (ndgID1) and 
    95                                encrypted Session Manager WSDL address (ndgID2) 
    96                                ** style arg means they can be passed as a 
    97                                dictionary without the caller needing to  
    98                                know the tag names. 
    99         """ 
    100  
    101         # Domain for cookie used by createCookie method - if not set, it will 
    102         # default to web server domain name 
    103         self.__cookieDomain = cookieDomain 
    104  
     72    def __init__(self): 
     73        """NDG Security Session Cookie""" 
     74 
     75        self.__x509Cert = None 
     76        self.__priKey = None 
     77         
     78        self.__simpleCookie = None 
     79        self.__userDN = None 
     80        self.__sessID = None 
     81        self.__sessMgrURI = None 
     82 
     83     
     84    #_________________________________________________________________________ 
     85    def __setCert(self, cert): 
     86        """filter and convert input cert for encryption of cookie morsel 
     87         
     88        @type: ndg.security.common.X509.X509Cert / M2Crypto.X509.X509 / 
     89        string  
     90        @param cert: X.509 certificate.   
     91         
     92        @rtype ndg.security.common.X509.X509Cert 
     93        @return X.509 certificate object""" 
     94         
     95        if isinstance(cert, X509Cert): 
     96            # ndg.security.common.X509.X509Cert type / None 
     97            return cert 
     98             
     99        elif isinstance(cert, X509.X509): 
     100            # M2Crypto.X509.X509 type 
     101            return X509Cert(m2CryptoX509=cert) 
     102             
     103        elif isinstance(cert, basestring): 
     104            return X509CertParse(cert) 
     105         
     106        else: 
     107            raise AttributeError, "X.509 Cert. must be type: " + \ 
     108                "ndg.security.common.X509.X509Cert, M2Crypto.X509.X509 or " +\ 
     109                "a base64 encoded string" 
     110 
     111 
     112    #_________________________________________________________________________ 
     113    def __setX509Cert(self, x509Cert): 
     114        "Set property method for X.509 cert. to encrypt cookie morsel" 
     115        self.__x509Cert = self.__setCert(x509Cert) 
     116         
     117    x509Cert = property(fset=__setX509Cert, 
     118                        doc="Set X.509 Cert. to encrypt cookie morsel") 
     119 
     120  
     121    #_________________________________________________________________________ 
     122    def __setX509CertFromFile(self, x509CertFilePath): 
     123        "Set X.509 cert property method" 
     124         
     125        if isinstance(x509CertFilePath, basestring): 
     126            self.__x509Cert = X509CertRead(x509CertFilePath) 
     127             
     128        elif x509CertFilePath is not None: 
     129            raise AttributeError, \ 
     130                "Signature X.509 cert. file path must be a valid string" 
     131         
     132         
     133    x509CertFilePath = property(fset=__setX509CertFromFile, 
     134                   doc="File path X.509 cert. for encryption of morsel") 
     135 
     136  
     137    #_________________________________________________________________________ 
     138    def __setPriKeyPwd(self, priKeyPwd): 
     139        """Set method for private key file password used to decrypt cookie 
     140        morsel""" 
     141        if priKeyPwd is not None and not isinstance(priKeyPwd, basestring): 
     142            raise AttributeError, \ 
     143                "Signing private key password must be None or a valid string" 
     144         
     145        self.__priKeyPwd = priKeyPwd 
     146         
     147    priKeyPwd = property(fset=__setPriKeyPwd, 
     148             doc="Password protecting private key file used to sign message") 
     149 
     150  
     151    #_________________________________________________________________________ 
     152    def __setSigningPriKey(self, signingPriKey): 
     153        """Set method for client private key 
     154         
     155        Nb. if input is a string, priKeyPwd will need to be set if 
     156        the key is password protected. 
     157         
     158        @type signingPriKey: M2Crypto.RSA.RSA / string 
     159        @param signingPriKey: private key used to sign message""" 
     160         
     161        if isinstance(signingPriKey, basestring): 
     162            pwdCallback = lambda *ar, **kw: self.__priKeyPwd 
     163            self.__signingPriKey = RSA.load_key_string(signingPriKey, 
     164                                                       callback=pwdCallback) 
     165 
     166        elif isinstance(signingPriKey, RSA.RSA): 
     167            self.__signingPriKey = signingPriKey  
     168                    
     169        else: 
     170            raise AttributeError, "Signing private key must be a valid " + \ 
     171                                  "M2Crypto.RSA.RSA type or a string" 
     172                 
     173    signingPriKey = property(fset=__setSigningPriKey, 
     174                             doc="Private key used to sign outbound message") 
     175 
     176  
     177    #_________________________________________________________________________ 
     178    def __setPriKeyFromFile(self, priKeyFilePath): 
     179        """Set method for client private key file path 
     180         
     181        priKeyPwd MUST be set prior to a call to this method""" 
     182        if isinstance(priKeyFilePath, basestring):                            
     183            try: 
     184                # Read Private key to sign with     
     185                priKeyFile = BIO.File(open(priKeyFilePath))  
     186                pwdCallback = lambda *ar, **kw: self.__priKeyPwd                                            
     187                self.__signingPriKey = RSA.load_key_bio(priKeyFile,  
     188                                                        callback=pwdCallback)            
     189            except Exception, e: 
     190                raise AttributeError, \ 
     191                                "Setting private key for signature: %s" % e 
     192         
     193        else: 
     194            raise AttributeError, \ 
     195                        "Private key file path must be a valid string" 
     196         
     197    priKeyFilePath = property(fset=__setPriKeyFromFile, 
     198                      doc="File path for private key used to sign message") 
     199 
     200 
     201    def __str__(self): 
     202        return str(self.__simpleCookie) 
     203     
     204     
     205    def __getUserDN(self): 
     206        return self.__userDN 
     207        
     208    userDN = property(fget=__getUserDN, doc="user Distinguished Name") 
     209     
     210     
     211    def __getSessID(self): 
     212        return self.__sessID 
     213        
     214    sessID = property(fget=__getSessID, doc="user Session ID") 
     215     
     216     
     217    def __getSessMgrURI(self): 
     218        return self.__sessMgrURI 
     219        
     220    sessMgrURI = property(fget=__getSessMgrURI, doc="Session Manager URI") 
     221 
     222 
     223    def parse(self, cookieStr): 
     224        '''Parse from string text 
     225         
     226        @rtype tuple 
     227        @return (userDN, sessID, sessMgrURI)''' 
     228         
    105229        # Nb. SimpleCookie doesn't like unicode 
    106         self.__cookie = SimpleCookie(str(cookieStr)) 
     230        self.__simpleCookie = SimpleCookie(str(cookieStr)) 
    107231             
    108232        try: 
    109             # Check for expected cookie morsels 
    110             sessID = self.__cookie[SessionCookie.tags[0]].value 
    111             encrSessionMgrURI = self.__cookie[SessionCookie.tags[1]].value 
     233            # Check for expected cookie morsel 
     234            morsel = simpleCookie[self.__class__.tag].value 
     235            decrMorsel = self.__priKey.decrypt(morsel) 
     236            morselArgs = self.morselArgSep.split(decrMorsel) 
    112237             
    113238        except KeyError: 
    114             try: 
    115                 # if not, check for setting from keywords 
    116                 sessID = cookieTagKw[SessionCookie.tags[0]] 
    117                 encrSessionMgrURI = cookieTagKw[SessionCookie.tags[1]] 
    118                  
    119             except KeyError:             
    120                 tags = SessionCookie.tags 
    121                 msg = len(tags) > 1 and \ 
    122                     's %s and %s' % (', '.join(tags[:-1]), tags[-1]) \ 
    123                     or ' ' + tags[-1] 
    124                 raise SessionCookieError, "Missing Cookie morsel%s" % msg 
    125  
    126              
    127         if len(sessID) < SessionCookie.sessIDlen: 
    128             SessionCookieError, "Session ID has an invalid length" 
    129              
    130         if encrSessionMgrURI[:7] == 'http://' or \ 
    131            encrSessionMgrURI[:8] == 'https://': 
    132             SessionCookieError, "Input Session Manager URI does not " + \ 
    133                                 "appear to have been encrypted" 
    134                                
    135         if dtExpiry: 
     239            raise SessionCookieError, 'Missing cookie morsel "%s"' % \ 
     240                                      SessionCookie.tag 
     241         
     242        if len(morselArgs) != self.__class__.nMorselArgs: 
     243            raise SessionCookieError, \ 
     244                        "Expecting three input parameters for cookie morsel" 
     245 
     246        self.__userDN, self.__sessID, self.__sessMgrURI = morselArgs 
     247 
     248        if len(self.__sessID) < SessionCookie.sessIDlen: 
     249            raise SessionCookieError, "Session ID has an invalid length" 
     250       
     251       
     252    def create(self,dtExpiry=None,strExpiry=None,lifetime=28800,*morselArgs): 
     253        '''Create an NDG Session cookie''' 
     254         
     255        if len(morselArgs) != self.__class__.nMorselArgs: 
     256            raise SessionCookieError, \ 
     257                        "Expecting three input parameters for cookie morsel" 
     258         
     259        self.__userDN, self.__sessID, self.__sessMgrURI = morselArgs 
     260                         
     261        morsel = morselArgs.join(self.__class__.morselArgSep) 
     262        encrMorsel = self.__priKey.encrypt(morsel) 
     263        b64EncMorsel = base64.encodestring(encrMorsel) 
     264 
     265 
     266        # Set the cookie expiry 
     267        if dtExpiry is not None: 
    136268            if not isinstance(dtExpiry, datetime): 
    137269                SessionCookieError, \ 
    138270                    "Expecting valid datetime object with dtExpiry keyword" 
    139271                 
    140             expiryStr = dtExpiry.strftime(self.__sessCookieExpiryFmt) 
    141              
    142         elif not expiryStr or not isinstance(expiryStr, basestring): 
    143             try: 
    144                 # Check for expiry already set in SimpleCookie type - this 
    145                 # would be true if str keyword was set 
    146                 exp = \ 
    147                 self.__cookie[SessionCookie.tags[0]][self.__cookieExpiryTag] 
    148             except KeyError: 
    149                 raise SessionCookieError, "No cookie expiry was set" 
    150              
    151              
    152         try:              
    153             tagValues = (sessID, encrSessionMgrURI) 
    154             i=0 
    155             for tag in SessionCookie.tags: 
    156                  
    157                 if tag not in self.__cookie: 
    158                     self.__cookie[tag] = tagValues[i] 
    159                  
    160                 # Use standard format for cookie path and expiry 
    161                 self.__cookie[tag][self.__cookiePathTag] = \ 
    162                                             cookiePath or self.__cookiePath 
    163                                                              
    164                 if expiryStr: 
    165                     self.__cookie[tag][self.__cookieExpiryTag] = expiryStr 
    166                                              
    167                 # Default domain is the host.  This is the safest option from 
    168                 # a security perspective 
    169                 if cookieDomain: 
    170                     self.__cookie[tag][self.__cookieDomainTag] = cookieDomain 
    171                      
    172                 i += 1 
    173              
    174         except Exception, e: 
    175             raise SessionCookieError, "Creating Session Cookie: %s" % e 
    176          
    177      
    178     def asSimpleCookie(self): 
    179         return self.__cookie 
    180      
    181     def asString(self): 
    182         return self.__cookie.output() 
    183      
    184     def __call__(self): 
    185         return self.asSimpleCookie() 
    186      
    187     def __str__(self): 
    188         return self.asString() 
    189      
    190     def __repr__(self): 
    191         return self.asString() 
    192  
    193  
    194     #_________________________________________________________________________ 
    195     def __setCookieDomain(self, cookieDomain): 
    196         """Set domain for cookie - set to None to assume domain of web server 
    197         """ 
    198  
    199         if not isinstance(cookieDomain, basestring) and \ 
    200            cookieDomain is not None: 
    201             raise SessionCookieError, \ 
    202                 "Expecting string or None type for \"cookieDomain\"" 
    203                          
    204         self.__cookieDomain = cookieDomain 
    205  
    206     cookieDomain = property(fset=__setCookieDomain, doc="Set cookie domain") 
    207         
    208  
    209     #_________________________________________________________________________ 
    210     def __getSessionID(self): 
    211         """Return the session ID from the cookie""" 
    212         return self.__cookie[SessionCookie.tags[0]].value  
    213      
    214     sessionID = property(fget=__getSessionID, doc="Get session ID") 
    215  
    216     #_________________________________________________________________________ 
    217     def __getEncrSessionMgrURI(self): 
    218         """Return the Encrypted Session manager URI from the cookie""" 
    219         return self.__cookie[SessionCookie.tags[1]].value 
    220  
    221     encrSessionMgrURI = property(fget=__getEncrSessionMgrURI,  
    222                                  doc="Get encrypted Session Manager URI") 
    223  
     272            strExpiry=dtExpiry.strftime(self.__class__.__sessCookieExpiryFmt) 
     273             
     274        elif strExpiry is not None: 
     275            if not isinstance(strExpiry, basestring): 
     276                raise SessionCookieError, "strExpiry is not a valid string" 
     277             
     278            # SimpleCookie doesn't like unicode 
     279            strExpiry = str(strExpiry) 
     280             
     281        elif lifetime is not None: 
     282            dtExpiry = datetime.utcnow() + timedelta(seconds=lifetime) 
     283            strExpiry=dtExpiry.strftime(self.__class__.__sessCookieExpiryFmt) 
     284          
     285        self.__simpleCookie = SimpleCookie()               
     286        tag = self.__class__.tag 
     287         
     288        self.__simpleCookie[tag] = b64EncMorsel 
     289         
     290        # Use standard format for cookie path and expiry 
     291        self.__simpleCookie[tag][self.__cookiePathTag] = self.__cookiePath 
     292        self.__simpleCookie[tag][self.__cookieExpiryTag] = strExpiry 
     293 
     294        return self.__simpleCookie 
     295     
    224296     
    225297    #_________________________________________________________________________ 
     
    239311                return False 
    240312         
    241         missingTags = [tag for tag in self.tags if tag not in cookie] 
    242         if missingTags: 
     313        if self.tag not in cookie: 
    243314            if raiseExcep: 
    244315                raise SessionCookieError, \ 
    245             "Input cookie missing security tag(s): " + ", ".join(missingTags) 
     316                    'Input cookie missing security tag: "%s"' % self.tag 
    246317            else: 
    247318                return False 
    248  
    249         if len(cookie[self.tags[0]].value) < cls.sessIDlen: 
    250             if raiseExcep: 
    251                 raise SessionCookieError, "Session ID has an invalid length" 
    252             else: 
    253                 return False 
    254319         
    255320        return True 
  • TI12-security/trunk/python/ndg.security.server/ndg/security/server/AttAuthority

    • Property svn:ignore set to
      *.pid
  • TI12-security/trunk/python/ndg.security.test/ndg/security/test/AttAuthority

    • Property svn:ignore set to
      *.pem
Note: See TracChangeset for help on using the changeset viewer.