source: TI12-security/trunk/python/ndg.security.common/ndg/security/common/sessionCookie.py @ 3153

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/python/ndg.security.common/ndg/security/common/sessionCookie.py@3153
Revision 3153, 11.5 KB checked in by pjkersha, 12 years ago (diff)

NDG Security release updated to 0.8.7. Changes in this version enable full support for multiple CAs and use of MyProxy? SimpleCA dynamically created cert.s. These differ from previous use of MyProxy? in that they are issued directly from a CA instead of a user cert held in the repository.

setup.py,
ndg.security.server/setup.py,
ndg.security.server/ndg/security/server/MyProxy.py,
ndg.security.client/setup.py,
ndg.security.test/setup.py,
ndg.security.common/setup.py: updated NDG Security release num

ndg.security.server/ndg/security/server/SessionMgr/init.py,
ndg.security.test/ndg/security/test/sessionMgrClient/SessionMgrClientTest.py: SessionCookie? class not used. Pylons framework handles this.

ndg.security.test/ndg/security/test/sessionCookie: renamed package from SessionCookie?

  • Property svn:keywords set to Id
Line 
1"""NDG Session Cookie used by Session Manager UserSession and Login
2Service CGI code.
3
4NERC Data Grid Project
5"""
6__author__ = "P J Kershaw"
7__date__ = "27/10/06"
8__copyright__ = "(C) 2007 STFC & NERC"
9__license__ = \
10"""This software may be distributed under the terms of the Q Public
11License, version 1.0 or later."""
12__contact__ = "P.J.Kershaw@rl.ac.uk"
13__revision__ = '$Id$'
14
15import base64
16from datetime import datetime, timedelta
17from Cookie import SimpleCookie
18from M2Crypto import X509, BIO, RSA
19
20from ndg.security.common.X509 import X509Cert, X509CertRead
21
22#_____________________________________________________________________________
23class SessionCookieError(Exception):
24    "Handle exception from SessionCookie class"
25   
26       
27#_____________________________________________________________________________
28class _MetaSessionCookie(type):
29    """Enable SessionCookie to have read only class variables e.g.
30   
31    print sessionCookie.cookieTags is allowed but,
32   
33    sessionCookie.cookieTags = None
34   
35    ... raises - AttributeError: can't set attribute"""
36    def __getTag(cls):
37        '''tag refs the morsel containing the encrypted user DN, session ID
38        and the encrypted session manager address.'''
39        return "ndgSID1"
40
41    tag = property(fget=__getTag)
42
43    def __getSessIDlen(cls):
44        '''This sets the session ID length (!)'''
45        return 32
46   
47    sessIDlen = property(fget=__getSessIDlen)
48   
49   
50#_____________________________________________________________________________
51class SessionCookie(object):
52    """Handler for creation and parsing of NDG Security Session Cookies"""
53   
54    __metaclass__ = _MetaSessionCookie
55
56    # Follow standard format for cookie path and expiry attributes
57    __cookiePathTag = "path"
58    __cookiePath = "/"
59    __cookieDomainTag = 'domain'
60    __cookieExpiryTag = "expires"
61 
62    morselArgSep = ','
63    nMorselArgs = 3
64   
65    # Quotes are vital (and part of the official cookei format) - otherwise it
66    # will not be parsed correctly
67    __sessCookieExpiryFmt = "\"%a, %d-%b-%Y %H:%M:%S GMT\""
68
69       
70    #_________________________________________________________________________   
71    def __init__(self):
72        """NDG Security Session Cookie"""
73
74        self.__x509Cert = None
75        self.__priKey = None
76       
77        self.__simpleCookie = None
78        self.__userDN = None
79        self.__sessID = None
80        self.__sessMgrURI = None
81
82   
83    #_________________________________________________________________________
84    def __setCert(self, cert):
85        """filter and convert input cert for encryption of cookie morsel
86       
87        @type: ndg.security.common.X509.X509Cert / M2Crypto.X509.X509 /
88        string
89        @param cert: X.509 certificate. 
90       
91        @rtype ndg.security.common.X509.X509Cert
92        @return X.509 certificate object"""
93       
94        if isinstance(cert, X509Cert):
95            # ndg.security.common.X509.X509Cert type / None
96            return cert
97           
98        elif isinstance(cert, X509.X509):
99            # M2Crypto.X509.X509 type
100            return X509Cert(m2CryptoX509=cert)
101           
102        elif isinstance(cert, basestring):
103            return X509CertParse(cert)
104       
105        else:
106            raise AttributeError, "X.509 Cert. must be type: " + \
107                "ndg.security.common.X509.X509Cert, M2Crypto.X509.X509 or " +\
108                "a base64 encoded string"
109
110
111    #_________________________________________________________________________
112    def __setX509Cert(self, x509Cert):
113        "Set property method for X.509 cert. to encrypt cookie morsel"
114        self.__x509Cert = self.__setCert(x509Cert)
115       
116    x509Cert = property(fset=__setX509Cert,
117                        doc="Set X.509 Cert. to encrypt cookie morsel")
118
119 
120    #_________________________________________________________________________
121    def __setX509CertFromFile(self, x509CertFilePath):
122        "Set X.509 cert property method"
123       
124        if isinstance(x509CertFilePath, basestring):
125            self.__x509Cert = X509CertRead(x509CertFilePath)
126           
127        elif x509CertFilePath is not None:
128            raise AttributeError, \
129                "Signature X.509 cert. file path must be a valid string"
130       
131       
132    x509CertFilePath = property(fset=__setX509CertFromFile,
133                   doc="File path X.509 cert. for encryption of morsel")
134
135 
136    #_________________________________________________________________________
137    def __setPriKeyPwd(self, priKeyPwd):
138        """Set method for private key file password used to decrypt cookie
139        morsel"""
140        if priKeyPwd is not None and not isinstance(priKeyPwd, basestring):
141            raise AttributeError, \
142                "Signing private key password must be None or a valid string"
143       
144        self.__priKeyPwd = priKeyPwd
145       
146    priKeyPwd = property(fset=__setPriKeyPwd,
147             doc="Password protecting private key file used to sign message")
148
149 
150    #_________________________________________________________________________
151    def __setSigningPriKey(self, signingPriKey):
152        """Set method for client private key
153       
154        Nb. if input is a string, priKeyPwd will need to be set if
155        the key is password protected.
156       
157        @type signingPriKey: M2Crypto.RSA.RSA / string
158        @param signingPriKey: private key used to sign message"""
159       
160        if isinstance(signingPriKey, basestring):
161            pwdCallback = lambda *ar, **kw: self.__priKeyPwd
162            self.__priKey = RSA.load_key_string(signingPriKey,
163                                                       callback=pwdCallback)
164
165        elif isinstance(signingPriKey, RSA.RSA):
166            self.__priKey = signingPriKey
167                   
168        else:
169            raise AttributeError, "Signing private key must be a valid " + \
170                                  "M2Crypto.RSA.RSA type or a string"
171               
172    signingPriKey = property(fset=__setSigningPriKey,
173                             doc="Private key used to sign outbound message")
174
175 
176    #_________________________________________________________________________
177    def __setPriKeyFromFile(self, priKeyFilePath):
178        """Set method for client private key file path
179       
180        priKeyPwd MUST be set prior to a call to this method"""
181        if isinstance(priKeyFilePath, basestring):                           
182            try:
183                # Read Private key to sign with   
184                priKeyFile = BIO.File(open(priKeyFilePath)) 
185                pwdCallback = lambda *ar, **kw: self.__priKeyPwd                                           
186                self.__priKey = RSA.load_key_bio(priKeyFile, 
187                                                        callback=pwdCallback)           
188            except Exception, e:
189                raise AttributeError, \
190                                "Setting private key for signature: %s" % e
191       
192        else:
193            raise AttributeError, \
194                        "Private key file path must be a valid string"
195       
196    priKeyFilePath = property(fset=__setPriKeyFromFile,
197                      doc="File path for private key used to sign message")
198
199
200    def __str__(self):
201        return str(self.__simpleCookie)
202   
203   
204    def __getUserDN(self):
205        return self.__userDN
206       
207    userDN = property(fget=__getUserDN, doc="user Distinguished Name")
208   
209   
210    def __getSessID(self):
211        return self.__sessID
212       
213    sessID = property(fget=__getSessID, doc="user Session ID")
214   
215   
216    def __getSessMgrURI(self):
217        return self.__sessMgrURI
218       
219    sessMgrURI = property(fget=__getSessMgrURI, doc="Session Manager URI")
220
221
222    def parse(self, cookieStr):
223        '''Parse from string text
224       
225        @rtype tuple
226        @return (userDN, sessID, sessMgrURI)'''
227       
228        # Nb. SimpleCookie doesn't like unicode
229        self.__simpleCookie = SimpleCookie(str(cookieStr))
230           
231        try:
232            # Check for expected cookie morsel
233            b64EncMorsel = self.__simpleCookie[self.__class__.tag].value
234            encrMorsel = base64.urlsafe_b64decode(b64EncMorsel)
235            morsel = self.__priKey.private_decrypt(encrMorsel, 
236                                                       RSA.pkcs1_padding)
237            morselArgs = morsel.split(self.morselArgSep)
238           
239        except KeyError:
240            raise SessionCookieError, 'Missing cookie morsel "%s"' % \
241                                      SessionCookie.tag
242
243        if len(morselArgs) != self.__class__.nMorselArgs:
244            raise SessionCookieError, \
245                        "Expecting three input parameters for cookie morsel"
246
247        self.__userDN, self.__sessID, self.__sessMgrURI = morselArgs
248
249        if len(self.__sessID) < SessionCookie.sessIDlen:
250            raise SessionCookieError, "Session ID has an invalid length"
251     
252     
253    def create(self,
254               userDN,
255               sessID,
256               sessMgrURI,
257               dtExpiry=None,
258               strExpiry=None,
259               lifetime=28800):
260        '''Create an NDG Session cookie.  Requires x509Cert to be set'''
261        morselArgs = userDN, sessID, sessMgrURI
262        self.__userDN, self.__sessID, self.__sessMgrURI = morselArgs
263                       
264        morsel = self.__class__.morselArgSep.join(morselArgs)
265 
266        rsaPubKey = self.__x509Cert.pubKey.get_rsa()
267        encrMorsel = rsaPubKey.public_encrypt(morsel, RSA.pkcs1_padding)
268        b64EncMorsel = base64.urlsafe_b64encode(encrMorsel)
269
270
271        # Set the cookie expiry
272        if dtExpiry is not None:
273            if not isinstance(dtExpiry, datetime):
274                SessionCookieError, \
275                    "Expecting valid datetime object with dtExpiry keyword"
276               
277            strExpiry=dtExpiry.strftime(self.__class__.__sessCookieExpiryFmt)
278           
279        elif strExpiry is not None:
280            if not isinstance(strExpiry, basestring):
281                raise SessionCookieError, "strExpiry is not a valid string"
282           
283            # SimpleCookie doesn't like unicode
284            strExpiry = str(strExpiry)
285           
286        elif lifetime is not None:
287            dtExpiry = datetime.utcnow() + timedelta(seconds=lifetime)
288            strExpiry=dtExpiry.strftime(self.__class__.__sessCookieExpiryFmt)
289         
290        self.__simpleCookie = SimpleCookie()             
291        tag = self.__class__.tag
292       
293        self.__simpleCookie[tag] = b64EncMorsel
294       
295        # Use standard format for cookie path and expiry
296        self.__simpleCookie[tag][self.__cookiePathTag] = self.__cookiePath
297        self.__simpleCookie[tag][self.__cookieExpiryTag] = strExpiry
298
299        return self.__simpleCookie
300   
301   
302    #_________________________________________________________________________
303    @classmethod   
304    def isValid(cls, cookie, raiseExcep=False):
305        """Check cookie has the expected session keys.  Cookie may be a
306        string or SimpleCookie type"""
307       
308        if isinstance(cookie, basestring):
309            cookie = SimpleCookie(cookie)
310           
311        elif not isinstance(cookie, SimpleCookie):
312            if raiseExcep:
313                raise SessionCookieError,"Input cookie must be a string or "+\
314                                        "SimpleCookie type"
315            else:
316                return False
317       
318        if self.tag not in cookie:
319            if raiseExcep:
320                raise SessionCookieError, \
321                    'Input cookie missing security tag: "%s"' % self.tag
322            else:
323                return False
324       
325        return True
Note: See TracBrowser for help on using the repository browser.