source: TI12-security/trunk/python/NDG/CredWallet.py @ 1301

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/python/NDG/CredWallet.py@1301
Revision 1301, 45.5 KB checked in by pjkersha, 13 years ago (diff)

setup.py, README: altered version info to post Alpha development
NDG/LogClient.py, SimpleCAClient.py and GatekeeperClient?.py: catch HTTPResponse error from ZSI ServiceProxy?. This
allows more error info for file not found type problems.

NDG/Session.py, NDG/CredWallet.py: use AttAuthorityClient? wrapper rather than direct calls to ZSI.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1"""NDG Credentials Wallet
2
3NERC Data Grid Project
4
5P J Kershaw 30/11/05
6
7Copyright (C) 2005 CCLRC & NERC
8
9This software may be distributed under the terms of the Q Public License,
10version 1.0 or later.
11"""
12
13reposID = '$Id$'
14
15
16# Temporary store of certificates for use with CredWallet reqAuthorisation()
17import tempfile
18
19# Check Attribute Certificate validity times
20from datetime import datetime
21from datetime import timedelta
22
23
24# Access Attribute Authority's web service using ZSI - allow pass if not
25# loaded since it's possible to make AttAuthority instance locally without
26# using the WS
27aaImportError = True
28try:
29    from SecurityClient import AttAuthorityClient, AttAuthorityClientError
30    aaImportError = False
31   
32except ImportError:
33    pass
34
35# Likewise - may want to use WS and not use AttAuthority locally in which case
36# no need to import it
37try:
38    from AttAuthority import *
39    aaImportError = False
40except:
41    pass
42
43if aaImportError:
44    raise ImportError("Either AttAuthority or ZSI modules must be " + \
45                      "present to allow interoperation with Attribute " +\
46                      "Authorities")
47
48# Authentication X.509 Certificate
49from X509 import *
50
51# Authorisation - attribute certificate
52from AttCert import *
53
54
55#_____________________________________________________________________________
56class CredWalletError(Exception):   
57    """Exception handling for NDG CredentialWallet class."""
58   
59    def __init__(self, msg):
60        self.__msg = msg
61         
62    def __str__(self):
63        return self.__msg
64
65
66
67
68#_____________________________________________________________________________
69class CredWalletAuthorisationDenied(Exception):   
70    """Handling exception where CredWallet is denied authorisation by an
71    Attribute Authority."""
72   
73    def __init__(self, msg=None, extAttCertList=[], trustedHostInfo={}):
74        """Raise exception for authorisation denied with option to give
75        caller hint to certificates that could used to try to obtain a
76        mapped certificate
77       
78        msg:                error message
79        extAttCertList:     list of candidate Attribute Certificates that
80                            could be used to try to get a mapped certificate
81                            from the target Attribute Authority
82        trustedHostInfo:    dictionary indexed by host name giving details
83                            of WSDL URI and roles for trusted hosts"""
84
85        self.__msg = msg
86        self.__trustedHostInfo = trustedHostInfo
87       
88        # Prevent None type setting
89        if extAttCertList is None:
90            extAttCertList = []
91           
92        self.__extAttCertList = extAttCertList
93
94       
95    def __str__(self):
96        return self.__msg
97
98
99    def __getMsg(self):
100        """Get message text"""
101        return self.__msg
102
103    msg = property(fget=__getMsg, doc="Error message text")
104
105
106    def __getTrustedHostInfo(self):
107        """Get message text"""
108        return self.__msg
109
110    trustedHostInfo = property(fget=__getTrustedHostInfo, 
111                               doc="WSDL and roles details for trusted hosts")
112   
113   
114    def __getExtAttCertList(self):
115        """Return list of candidate Attribute Certificates that could be used
116        to try to get a mapped certificate from the target Attribute Authority
117        """
118        return self.__extAttCertList
119
120
121    extAttCertList = property(fget=__getExtAttCertList,
122                              doc="list of candidate Attribute " + \
123                              "Certificates that could be used " + \
124                              "to try to get a mapped certificate " + \
125                              "from the target Attribute Authority")
126
127
128
129#_____________________________________________________________________________       
130# CredWallet is a 'new-style' class inheriting from "object" and making use
131# of new Get/Set methods for hiding of attributes
132class CredWallet(object):
133    """Volatile store of user credentials associated with a user session"""
134
135    def __init__(self,
136                 proxyCertTxt,
137                 caPubKeyFilePath=None,
138                 clntPubKeyFilePath=None,
139                 clntPriKeyFilePath=None,
140                 clntPriKeyPwd=None,
141                 credRepos=None,
142                 mapFromTrustedHosts=False,
143                 rtnExtAttCertList=True):
144        """Create store of user credentials for their current session
145
146        proxy certificate:      users proxy certificate as string text
147        caPubKeyFilePath:       Certificate Authority's certificate - used in
148                                validation of signed Attribute Certificates.
149                                If not set here, it must be input in call
150                                to reqAuthorisation.
151        clntPubKeyFilePath:     Public key certificate for this client.
152                                Setting this enables return message from AA
153                                WSDL to be encrypted by the AA.
154        clntPriKeyFilePath:     Client's Private key used to decrypt response
155                                from AA.
156        clntPriKeyPwd:          Password protecting the client private key.
157        credRepos:              Credential Repository instance
158        mapFromTrustedHosts:    sets behaviour for reqAuthorisation().  If
159                                set True and authorisation fails with the
160                                given Attribute Authority, attempt to get
161                                authorisation using Attribute Certificates
162                                issued by other trusted AAs
163        rtnExtAttCertList:     behaviour for reqAuthorisation().  If True,
164                                and authorisation fails with the given
165                                Attribute Authority, return a list of
166                                Attribute Certificates from other trusted AAs
167                                which could be used to obtain a mapped
168                                Attribute Certificate on a subsequent
169                                authorisation attempt"""
170
171
172        # Makes implicit call to __setProxyCert - Checks the proxy certificate
173        # and make an NDG.X509Cert instance
174        self.proxyCertTxt = proxyCertTxt
175       
176        self.__setCApubKeyFilePath(caPubKeyFilePath)
177        self.__setClntPubKeyFilePath(clntPubKeyFilePath)
178        self.__setClntPriKeyFilePath(clntPriKeyFilePath)
179        self.__setClntPriKeyPwd(clntPriKeyPwd)
180               
181        self.__credRepos = credRepos
182       
183        # Set behaviour for authorisation requests
184        self.__mapFromTrustedHosts = mapFromTrustedHosts
185        self.__rtnExtAttCertList = rtnExtAttCertList
186       
187       
188        # Get the distinguished name from the proxy certificate
189        self.__dn = self.__proxyCert.dn.serialise()
190       
191       
192        # Credentials are stored as a dictionary one element per attribute
193        # certicate held and indexed by certificate issuer name
194        self.__credentials = {}
195
196
197        # Make a connection to the Credentials Repository
198        if self.__credRepos:
199            if not isinstance(self.__credRepos, CredRepos):
200                raise CredWalletError, \
201                    "Input Credentials Repository instance must be of a " + \
202                    "class derived from \"CredRepos\""
203   
204       
205            # Check for valid attribute certificates for the user
206            try:
207                self.__credRepos.auditCredentials(dn=self.__dn)
208                userCred = self.__credRepos.getCredentials(self.__dn)
209   
210            except Exception, e:
211                raise CredWalletError, \
212                "Error updating wallet with credentials from repository: " + \
213                    str(e)
214   
215   
216            # Update wallet with attribute certificates stored in the
217            # repository.  Store ID and certificate instantiated as an AttCert
218            # type
219            try:
220                for cred in userCred:
221                   
222                    attCert = AttCertParse(cred.attCert)
223                    issuerName = attCert['issuerName']
224                   
225                    self.__credentials[issuerName] = \
226                                             {'id':cred.id, 'attCert':attCert}   
227            except Exception, e:
228                try:
229                    raise CredWalletError, \
230                            "Error parsing Attribute Certificate ID '" + \
231                                    cred.id + "' retrieved from the " + \
232                                    "Credentials Repository: %s" % str(e)               
233                except:
234                    raise CredWalletError, "Error parsing Attribute " + \
235                                          "Certificate retrieved from " + \
236                                          "the Credentials Repository: %s:" \
237                                          % str(e)
238           
239           
240            # Filter out expired or otherwise invalid certificates
241            self.audit()
242       
243
244
245
246    def __str__(self):
247        return "<Credential Wallet instance>"
248
249
250    #_________________________________________________________________________   
251    def __setProxyCert(self, proxyCertTxt):
252        """Set a new proxy certificate for the wallet
253
254        proxyCertTxt: input certificate as a string"""
255       
256        try:
257            if not isinstance(proxyCertTxt, basestring):
258                raise CredWalletError(\
259                                "Proxy Certificate must be input as a string")
260        except Exception, e:
261            raise CredWalletError("Input proxy certificate: %s" % e)
262
263        self.__proxyCertTxt = proxyCertTxt
264        self.__proxyCert = X509Cert()
265        self.__proxyCert.parse(proxyCertTxt)
266   
267
268    #_________________________________________________________________________
269    # Set Proxy Certificate text also updates the proxyCert X509Cert
270    # instance
271    def __setProxyCertTxt(self, value):
272        """Set proxy cert string and from it update equivalent X509Cert
273        instance"""
274        self.__setProxyCert(value)
275
276           
277    def __getProxyCertTxt(self):
278        """Get proxy cert as a string"""
279        return self.__proxyCertTxt
280 
281       
282    def __delProxyCertTxt(self):
283        """Prevent deletion of proxy cert string"""
284        raise AttributeError("\"proxyCertTxt\" cannot be deleted")
285
286 
287    # Publish attribute as read/write
288    proxyCertTxt = property(fget=__getProxyCertTxt,
289                            fset=__setProxyCertTxt,
290                            fdel=__delProxyCertTxt,
291                            doc="String text of proxy certificate")
292
293    def __getProxyCert(self):
294        """Get proxy cert X509Cert instance"""
295        return self.__proxyCert
296
297
298    # Proxy Cert instance is read-only - to set it, set proxyCertTxt
299    proxyCert = property(fget=__getProxyCert,
300                         doc="X.509 proxy certificate instance")
301   
302   
303    #_________________________________________________________________________
304    # Credentials are read-only
305    def __getCredentials(self):
306        return self.__credentials
307
308    # Publish attribute
309    credentials = property(fget=__getCredentials,
310                           doc="List of Attribute Certificates")   
311
312
313    #_________________________________________________________________________
314    def __setCApubKeyFilePath(self, caPubKeyFilePath):
315       
316        if not isinstance(caPubKeyFilePath, basestring) and \
317           caPubKeyFilePath is not None:
318            raise CredWalletError(\
319                "Input CA Certificate file path is not a valid string")
320               
321        self.__caPubKeyFilePath = caPubKeyFilePath
322       
323       
324    caPubKeyFilePath = property(fset=__setCApubKeyFilePath,
325                              doc="CA Certificate  - use to check AC XML Sig")
326
327
328    #_________________________________________________________________________
329    def __setClntPubKeyFilePath(self, clntPubKeyFilePath):
330       
331        if not isinstance(clntPubKeyFilePath, basestring) and \
332           clntPubKeyFilePath is not None:
333            raise CredWalletError(\
334                "Input Client Certificate file path is not a valid string")
335               
336        self.__clntPubKeyFilePath = clntPubKeyFilePath
337       
338        # Read the file into string ready to be passed over WS interface as
339        # required
340        if self.__clntPubKeyFilePath:
341            try:
342                self.__clntPubKey = open(self.__clntPubKeyFilePath).read()
343               
344            except IOError, (errNo, errMsg):
345                raise CredWalletError(\
346                            "Reading client public key file \"%s\": %s" %\
347                            (self.__clntPubKeyFilePath, errMsg))
348                                   
349            except Exception, e:
350                raise CredWalletError(\
351                            "Reading client public key file \"%s\": %s" %\
352                            (self.__clntPubKeyFilePath, str(e)))               
353       
354       
355    clntPubKeyFilePath = property(fset=__setClntPubKeyFilePath,
356                        doc="Client Public Key - use to encrypt resp from AA")
357
358
359    #_________________________________________________________________________
360    def __setClntPriKeyFilePath(self, clntPriKeyFilePath):
361       
362        if not isinstance(clntPriKeyFilePath, basestring) and \
363           clntPriKeyFilePath is not None:
364            raise CredWalletError(\
365                "Input Client Private Key file path is not a valid string")
366               
367        self.__clntPriKeyFilePath = clntPriKeyFilePath
368       
369       
370    clntPriKeyFilePath = property(fset=__setClntPriKeyFilePath,
371                    doc="Client Private Key - use to decrypt resp from AA")
372
373
374    #_________________________________________________________________________
375    def __setClntPriKeyPwd(self, clntPriKeyPwd):
376       
377        if not isinstance(clntPriKeyPwd, basestring) and \
378           clntPriKeyPwd is not None:
379            raise CredWalletError(\
380                "Input Client Private Key password is not a valid string")
381               
382        self.__clntPriKeyPwd = clntPriKeyPwd
383       
384       
385    clntPriKeyPwd = property(fset=__setClntPriKeyPwd,
386                             doc="Password for Client Private Key")
387
388
389    #_________________________________________________________________________
390    def isValid(self, **x509CertKeys):
391        """Check wallet's proxy cert.  If expired return False"""
392        try:
393            return self.__proxyCert.isValidTime(**x509CertKeys)
394
395        except Exception, e:
396            raise CredWalletError("Credential Wallet: %s" % e)
397
398   
399    #_________________________________________________________________________
400    def addCredential(self, attCert, bUpdateCredRepos=True):
401        """Add a new attribute certificate to the list of credentials held.
402        Return True if certificate was added otherwise False.  - If an
403        existing certificate from the same issuer has a later expiry it will
404        take precence and the new input certificate is ignored.
405
406        attCert:            new attribute Certificate to be added
407        bUpdateCredRepos:   if set to True, and a repository exisits it will
408                            be updated with the new credentials also"""
409
410        # Check input
411        try:
412            if not isinstance(attCert, AttCert):
413                raise CredWalletError(\
414                    "Attribute Certificate must be an AttCert type object")
415                   
416        except Exception, e:
417            raise CredWalletError("Attribute Certificate input: %s" % e)
418
419
420        # Check certificate validity
421        try:
422            attCert.isValid(raiseExcep=True)
423           
424        except AttCertError, e:
425            raise CredWalletError("Adding Credential: %s" % e)
426       
427
428        # Check to see if there is an existing Attribute Certificate held
429        # that was issued by the same host.  If so, compare the expiry time.
430        # The one with the latest expiry will be retained and the other
431        # ingored
432        bUpdateCred = True
433        issuerName = attCert['issuerName']
434       
435        if issuerName in self.__credentials:
436            # There is an existing certificate held with the same issuing
437            # host name as the new certificate
438            attCertOld = self.__credentials[issuerName]['attCert']
439
440            # Get expiry times in datetime format to allow comparison
441            dtAttCertOldNotAfter = attCertOld.getValidityNotAfter(\
442                                                            asDatetime=True)
443            dtAttCertNotAfter = attCert.getValidityNotAfter(asDatetime=True)
444
445            # If the new certificate has an earlier expiry time then ignore it
446            bUpdateCred = dtAttCertNotAfter > dtAttCertOldNotAfter
447
448               
449        if bUpdateCred:
450            # Update: Nb. -1 ID value flags item as new.  Items read in
451            # from the CredentialRepository during creation of the wallet will
452            # have +ve IDs previously allocated by the database
453            self.__credentials[issuerName] = {'id': -1, 'attCert': attCert}
454
455            # Update the Credentials Repository - the permanent store of user
456            # authorisation credentials.  This allows credentials for previous
457            # sessions to be re-instated
458            if self.__credRepos and bUpdateCredRepos:
459                self.updateCredRepos()
460
461        # Flag to caller to indicate whether the input certificate was added
462        # to the credentials or an exsiting certificate from the same issuer
463        # took precedence
464        return bUpdateCred
465           
466
467
468    def audit(self):
469        """Check the credentials held in the wallet removing any that have
470        expired or are otherwise invalid."""
471
472        # Nb. No signature check is carried out.  To do a check, access is
473        # needed to the cert of the CA that issued the Attribute Authority's
474        # cert
475        #
476        # P J Kershaw 12/09/05
477        for key, val in self.__credentials.items():
478            if not val['attCert'].isValid(chkSig=False):
479                del self.__credentials[key]
480
481
482
483               
484    def updateCredRepos(self, auditCred=True):
485        """Copy over non-persistent credentials held by wallet into the
486        perminent repository."""
487
488        if not self.__credRepos:
489            raise CredWalletError(
490                  "No Credential Repository has been created for this wallet")
491                           
492        # Filter out invalid certs unless auditCred flag is explicitly set to
493        # false
494        if auditCred: self.audit()
495
496        # Update the database - only add new entries i.e. with an ID of -1
497        attCertList = [i['attCert'] for i in self.__credentials.values() \
498                        if i['id'] == -1]
499
500        self.__credRepos.addCredentials(self.__dn, attCertList)
501
502       
503    def __reqAuthorisation(self,
504                           aaPropFilePath=None,
505                           aaWSDL=None,
506                           aaPubKeyFilePath=None,
507                           extAttCert=None,
508                           bDebug=False):
509       
510        """Wrapper to Attribute Authority authorisation request.  See
511        reqAuthorisation for the classes' public interface.
512
513        To call the Attribute Authority as a Web Service, specify a WSDL
514        otherwise set the properties file path.
515       
516        If successful, a new attribute certificate is issued to the user
517        and added into the wallet
518
519        aaWSDL|aaPropFilePath:  to call as a web service, specify the file
520                                path or URI for the Attribute Authority's
521                                WSDL.  Otherwise, to run on the local machine,
522                                specify a local Attribute Authority
523                                configuration file.
524        extAttCert:             an existing Attribute Certificate which can be
525                                used to making a mapping should the user not
526                                be registered with the Attribute Authority"""
527           
528        if aaWSDL is not None:
529            try:
530                aaClnt = AttAuthorityClient(aaWSDL=aaWSDL,
531                                            aaPubKeyFilePath=aaPubKeyFilePath)
532                                   
533                authzResp = aaClnt.reqAuthorisation(self.__proxyCertTxt, 
534                                        userAttCert=extAttCert, 
535                                        clntPriKeyPwd=self.__clntPriKeyPwd)               
536            except Exception, e:
537                raise CredWalletError, "Requesting authorisation: %s" % str(e)
538
539
540            if authzResp['statCode'] == authorisationResp.accessDenied:
541                raise CredWalletAuthorisationDenied,\
542                            "Authorisation denied: %s" % authzResp['errMsg']
543
544            elif authzResp['statCode'] == authorisationResp.accessGranted:
545                attCert = authzResp['credential']
546               
547            else:
548                raise CredWalletError, "Attribute Authority authorisation " +\
549                                       "status code not recognised"
550           
551        elif aaPropFilePath is not None:
552
553            # Call local based Attribute Authority with settings from the
554            # configuration file aaPropFilePath
555
556            if not isinstance(aaPropFilePath, basestring):
557                raise CredWalletError("Attribute Authority Configuration " + \
558                                      "file path must be a valid string")
559                                   
560            try:
561                # Make a new attribute authority instance
562                aa = AttAuthority(aaPropFilePath)
563
564                # Request a new attribute certificate from the Attribute
565                # Authority
566                attCert = aa.authorise(proxyCert=self.__proxyCertTxt,
567                                       userAttCert=extAttCert)
568               
569            except AttAuthorityAccessDenied, e:
570                raise CredWalletAuthorisationDenied(\
571                                    "Authorisation denied: %s" % e)
572           
573            except Exception, e:
574                raise CredWalletError("Requesting authorisation: %s" % e)
575
576        else:
577            raise CredWalletError("Error requesting authorisation: " + \
578                                  "a WSDL file or Attribute Authority " + \
579                                  "configuration file must be specified")
580       
581
582        # Update attribute Certificate instance with CA's certificate ready
583        # for signature check in addCredential()
584        if self.__caPubKeyFilePath is None:
585            raise CredWalletError("No CA certificate has been set")
586       
587        attCert.certFilePathList = self.__caPubKeyFilePath
588
589       
590        # Add credential into wallet
591        #
592        # Nb. if the certificates signature is invalid, it will be rejected
593        self.addCredential(attCert)
594
595
596        return attCert
597
598
599
600
601    def getAATrustedHostInfo(self,
602                             userRole=None,
603                             aaWSDL=None,
604                             aaPubKeyFilePath=None,
605                             aaPropFilePath=None,
606                             bDebug=False):
607        """Wrapper to Attribute Authority getTrustedHostInfo
608       
609        userRole:               get hosts which have a mapping to this role
610        aaWSDL|aaPropFilePath:  to call as a web service, specify the file
611                                path or URI for the Attribute Authority's
612                                WSDL.  Otherwise, to run on the local machine,
613                                specify a local Attribute Authority
614                                configuration file."""
615       
616        if aaWSDL is not None:
617            # Call Attribute Authority WS
618            try:
619                aaClnt = AttAuthorityClient(aaWSDL=aaWSDL,
620                                aaPubKeyFilePath=aaPubKeyFilePath,
621                                clntPubKeyFilePath=self.__clntPubKeyFilePath)
622                                   
623                trustedHostInfo = aaClnt.getTrustedHostInfo(role=userRole,
624                                        clntPriKeyPwd=self.__clntPriKeyPwd)               
625                return trustedHostInfo
626                           
627            except Exception, e:
628                raise CredWalletError, \
629                            "Requesting trusted host information: %s" % str(e)               
630
631        elif aaPropFilePath is not None:
632
633            # Call local based Attribute Authority with settings from the
634            # configuration file aaPropFilePath
635            if not instance(aaWSDL, basestring):
636                raise CredWalletError, "Attribute Authority Configuration " +\
637                                      "file path must be a valid string"
638                                   
639            try:
640                # Make a new attribute authority instance
641                aa = AttAuthority(aaPropFilePath)
642
643                # Request a new attribute certificate from the Attribute
644                # Authority
645                return aa.getTrustedHostInfo(role=userRole)
646               
647            except Exception, e:
648                raise CredWalletError, "Requesting trusted host info: %s" % e
649
650        else:
651            raise CredWalletError, "Error requesting trusted hosts info: " + \
652                                   "a WSDL file or Attribute Authority " + \
653                                   "configuration file must be specified"
654
655
656    #_________________________________________________________________________
657    def reqAuthorisation(self,
658                         reqRole=None,
659                         aaPropFilePath=None,
660                         aaWSDL=None,
661                         aaPubKeyFilePath=None,
662                         mapFromTrustedHosts=None,
663                         rtnExtAttCertList=None,
664                         extAttCertList=None,
665                         extTrustedHostList=None,
666                         refreshAttCert=False):
667       
668        """For a given role, get authorisation from an Attribute Authority
669        using a user's proxy certificate.  If this fails try to make a mapped
670        Attribute Certificate by using a certificate from another host which
671        has a trust relationship to the Attribute Authority in question.
672
673        reqRole:                the required role to get access for
674        aaWSDL|aaPropFilePath:  to call as a web service, specify the file
675                                path or URI for the Attribute Authority's
676                                WSDL.  Otherwise, to run on the local machine,
677                                specify a local Attribute Authority
678                                configuration file.
679                               
680        aaPubKeyFilePath:       Public key of AA used to encrypt client
681                                requests to the AA.
682
683        mapFromTrustedHosts:    if authorisation fails via the user's proxy
684                                certificate, then it is possible to get a
685                                mapped certificate by using certificates from
686                                other AA's.  Set this flag to True, to allow
687                                this second stage of generating a mapped
688                                certificate from the certificate stored in the
689                                wallet credentials.
690
691                                If set to False, it is possible to return the
692                                list of certificates available for mapping and
693                                then choose which one or ones to use for
694                                mapping by re-calling reqAuthorisation with
695                                extAttCertList set to these certificates
696
697                                The list is returned via
698                                CredWalletAuthorisationDenied exception
699
700                                If no value is set, the default value held
701                                in self.__mapFromTrustedHosts is used
702
703        rtnExtAttCertList:      If authorisation fails, make a list of
704                                candidate certificates from other Attribute
705                                Authorities which the user could use to retry
706                                and get a mapped certificate.
707                               
708                                If mapFromTrustedHosts is set True this flags
709                                value is overriden and effectively set to
710                                True.
711
712                                If no value is set, the default value held
713                                in self.__rtnExtAttCertList is used
714                               
715                                The list is returned via a
716                                CredWalletAuthorisationDenied exception object
717                               
718        extAttCertList:         Attribute Certificate or list of certificates
719                                from other Attribute Authorities.  These can
720                                be used to get a mapped certificate if access
721                                fails based on the user's proxy certificate
722                                credentials.  They are tried out in turn until
723                                access is granted so the order of the list
724                                decides the order in which they will be tried
725
726        extTrustedHostList:     same as extAttCertList keyword, but instead
727                                providing Attribute Certificates, give a list
728                                of Attribute Authority hosts.  These will be
729                                matched up to Attribute Certificates held in
730                                the wallet.  Matching certificates will then
731                                be used to try to get mapped authorisation.
732       
733        refreshAttCert:         if set to True, the authorisation request will
734                                will go ahead even if the wallet already
735                                contains an Attribute Certificate from
736                                the target Attribute Authority.  The existing
737                                AC in the wallet will be replaced by the new
738                                one obtained from this call.
739                               
740                                If set to False, this method will check to see
741                                if an AC issued by the target AA already
742                                exists in the wallet.  If so, it will return
743                                this AC to the caller without proceeding to
744                                make a call to the AA.
745                               
746        The procedure is:
747
748        1) Try authorisation using proxy certificate
749        2) If the Attribute Authority (AA) doesn't recognise the certificate,
750        find out any other hosts which have a trust relationship to the AA.
751        3) Look for Attribute Certificates held in the wallet corresponding
752        to these hosts.
753        4) If no Attribute Certificates are available, call the relevant
754        hosts' AAs to get certificates
755        5) Finally, use these new certificates to try to obtain a mapped
756        certificate from the original AA
757        6) If this fails access is denied"""
758                             
759                             
760        if aaPubKeyFilePath is None:
761            # Try retrieving public key from the web service and making a
762            # temporary file to hold it
763            try:
764                pubKeyReq = PubKeyReq()
765                resp = aaSrv.getPubKey(pubKeyReq=pubKeyReq())
766                pubKeyResp = PubKeyResp(xmlTxt=resp['pubKeyResp'])
767       
768                if 'errMsg' in pubKeyResp and pubKeyResp['errMsg']:
769                    raise Exception(pubKeyResp['errMsg'])
770               
771                aaPubKeyTmpFile = tempfile.NamedTemporaryFile()
772                open(aaPubKeyTmpFile.name,"w").write(pubKeyResp['pubKey'])
773   
774                aaPubKeyFilePath = aaPubKeyTmpFile.name
775               
776            except IOError, (errNo, errMsg):
777                raise CredWalletError(\
778                    "Writing public key to temporary file: %s" % errMsg)
779                                                     
780            except Exception, e:
781                raise CredWalletError(\
782                    "Retrieving Attribute Authority public key: "+ str(e))
783
784
785        if not refreshAttCert and self.__credentials:
786            # Refresh flag is not set so it's OK to check for any existing
787            # Attribute Certificate in the wallet whose issuerName match the
788            # target AA's name
789           
790            # Find out the site ID for the target AA by calling AA's host
791            # info WS method
792            aaClnt = AttAuthorityClient(aaWSDL=aaWSDL, 
793                                 aaPubKeyFilePath=aaPubKeyFilePath,
794                                 clntPubKeyFilePath=self.__clntPubKeyFilePath,
795                                 clntPriKeyFilePath=self.__clntPriKeyFilePath)
796           
797            hostInfo = aaClnt.getHostInfo(clntPriKeyPwd=self.__clntPriKeyPwd)
798            aaName = hostInfo.keys()[0]
799           
800            # Look in the wallet for an AC with the same issuer name
801            if aaName in self.__credentials:
802                # Existing Attribute Certificate found in wallet - Check that
803                # it will be valid for at least the next 2 hours
804                #
805                # TODO: Make this 2 hour offset a configurable parameter
806                #
807                # P J Kershaw 14/06/06
808                dtNow = datetime.utcnow() + timedelta(seconds=7200)
809               
810                attCert = self.__credentials[aaName]['attCert']
811                if attCert.isValidTime(dtNow=dtNow):                                   
812                    return attCert
813           
814           
815        # Check for settings from input, if not set use previous settings
816        # made
817        if mapFromTrustedHosts is not None:
818            self.__mapFromTrustedHosts = mapFromTrustedHosts
819
820        if rtnExtAttCertList is not None:
821            self.__rtnExtAttCertList = rtnExtAttCertList
822
823
824        # Check for list of external trusted hosts (other trusted NDG data
825        # centres)
826        if extTrustedHostList:
827            if not self.__mapFromTrustedHosts:
828                raise CredWalletError("A list of trusted hosts has been " + \
829                                      "input but mapping from trusted " + \
830                                      "hosts is set to disallowed")
831           
832            if isinstance(extTrustedHostList, basestring):
833                extTrustedHostList = [extTrustedHostList]
834
835            # Nb. Any extAttCertList is overriden by extTrustedHostList being
836            # set
837            extAttCertList = []
838            for hostName in extTrustedHostList:
839
840                if hostName in self.__credentials:
841                    extAttCertList.append(\
842                                    self.__credentials[hostName]['attCert'])
843
844
845        # Repeat authorisation attempts until succeed or means are exhausted
846        while True:
847           
848            # Check for candidate certificates for mapping
849            try:
850                # If list is set get the next cert
851                extAttCert = extAttCertList.pop()
852
853            except AttributeError:
854               
855                # No List set - attempt authorisation without
856                # using mapping from trusted hosts
857                extAttCert = None
858                               
859            except IndexError:
860               
861                # List has been emptied without authorisation succeeding -
862                # give up
863                errMsg = "Attempting to obtained a mapped certificate: " + \
864                    "no external attribute certificates are available"
865                   
866                # Add the exception form the last call to the Attribute
867                # Authority if an error exists
868                try:
869                    errMsg += ": %s" % authorisationDenied
870                except NameError:
871                    pass
872
873                raise CredWalletAuthorisationDenied, errMsg
874                                                   
875               
876            # Request Authorisation from Attribute Authority
877            try:
878                attCert = self.__reqAuthorisation(aaWSDL=aaWSDL,
879                                            aaPubKeyFilePath=aaPubKeyFilePath,
880                                            aaPropFilePath=aaPropFilePath,
881                                            extAttCert=extAttCert)               
882                # Access granted
883                return attCert
884           
885            except CredWalletAuthorisationDenied, authorisationDenied:
886
887                # If a required role was set then it's possible to go
888                # to get certificates with mapped roles from trusted hosts
889                # Shouldn't need to set a role - setting a role makes it more
890                # efficient but it's not essential
891                #
892                # P J Kershaw 29/03/06
893#                if not reqRole:
894#                    raise CredWalletAuthorisationDenied(\
895#                        "No user role was input in order to map to " + \
896#                        "a role in a trusted host")
897
898
899                if not mapFromTrustedHosts and not rtnExtAttCertList:
900                    # Creating a mapped certificate is not allowed - raise
901                    # authorisation denied exception saved from earlier
902                    raise authorisationDenied
903
904
905                if isinstance(extAttCertList, list):
906                    # An list of attribute certificates from trusted hosts
907                    # is present continue cycling through this until one of
908                    # them is accepted and a mapped certificate can be derived
909                    continue
910               
911               
912                #  Use the input required role and the AA's trusted host list
913                # to identify attribute certificates from other hosts which
914                # could be used to make a mapped certificate
915                try:
916                    trustedHostInfo = self.getAATrustedHostInfo(reqRole,
917                                            aaWSDL=aaWSDL,
918                                            aaPubKeyFilePath=aaPubKeyFilePath,
919                                            aaPropFilePath=aaPropFilePath)
920                except Exception, e:
921                    raise CredWalletError("Getting trusted hosts: %s" % e)
922
923                if not trustedHostInfo:
924                    raise CredWalletAuthorisationDenied(\
925                        "Attribute Authority has no trusted hosts with " + \
926                        "which to make a mapping")
927
928               
929                # Initialise external certificate list here - if none are
930                # found IndexError will be raised on the next iteration and
931                # an access denied error will be raised
932                extAttCertList = []
933
934                # Look for Attribute Certificates with matching issuer host
935                # names
936                for hostName in self.__credentials:
937
938                    # Nb. Candidate certificates for mappings must have
939                    # original provenance and contain at least one of the
940                    # required roles
941                    attCert = self.__credentials[hostName]['attCert']
942                   
943                    if hostName in trustedHostInfo and attCert.isOriginal():                       
944                        for role in attCert.getRoles():
945                            if role in trustedHostInfo[hostName]['role']:                               
946                                extAttCertList.append(attCert)
947
948
949                if not extAttCertList:
950                    # No certificates in the wallet matched the trusted host
951                    # and required roles
952                    #
953                    # Try each host in turn in order to get a certificate with
954                    # the required credentials in order to do a mapping
955                    for key, val in trustedHostInfo.items():
956
957                        try:
958                            extAttCert = self.__reqAuthorisation(\
959                                                           aaWSDL=val['wsdl'])
960
961                            # Check the certificate contains at least one of
962                            # the required roles
963                            roles = extAttCert.getRoles()
964                            if [True for r in roles if r in val['role']]:
965                               extAttCertList.append(extAttCert)
966
967                               # For efficiency, stop once obtained a valid
968                               # cert - but may want complete list for user to
969                               # choose from
970                               #break
971                               
972                        except Exception, e:
973                            pass    # ignore any errors and continue
974                   
975                if not extAttCertList:                       
976                    raise CredWalletAuthorisationDenied(\
977                        "No certificates are available with which to " + \
978                        "make a mapping to the Attribute Authority")
979
980
981                if not mapFromTrustedHosts:
982                   
983                    # Exit here returning the list of candidate certificates
984                    # that could be used to make a mapped certificate
985                    msg = "User is not registered with Attribute " + \
986                          "Authority - retry using one of the returned " + \
987                          "Attribute Certificates obtained from other " + \
988                          "trusted hosts"
989                         
990                    raise CredWalletAuthorisationDenied(msg=msg,
991                                            extAttCertList=extAttCertList,
992                                            trustedHostInfo=trustedHostInfo)
993               
994            except Exception, authorisationError:
995                # Authorisation request raised an error other than access
996                # denied
997                raise authorisationError
998           
999
1000    #_________________________________________________________________________
1001    def __retrieveURI(self, uri):
1002        """Retrieve content from a URI - use to get public key from a
1003        remote Attribute Authority
1004       
1005        Nb. If tempFile goes out of scope the temporary file containing the
1006        URI content will be deleted also"""
1007       
1008        try:
1009            tempFile = tempfile.NamedTemporaryFile()
1010            (fileName, httpResp) = urllib.urlretrieve(uri,
1011                                                      tempFile.name)
1012        except Exception, e:
1013            raise CredWalletError("Error retrieving from URI " + \
1014                                  "\"%s\": %s" % (uri, str(e)))
1015   
1016        # Expecting plain text format for returned public key file
1017        # 404 error would come back as 'text/html'
1018        if 'text/plain' not in httpResp['Content-type']:
1019            raise CredWalletError("Error retrieving from URI " + \
1020                                  "\"%s\": expecting \"plain/text\"" % uri)
1021           
1022        return tempFile
1023 
1024       
1025#_____________________________________________________________________________
1026class CredReposError(Exception):   
1027    """Exception handling for NDG Credential Repository class."""
1028   
1029    def __init__(self, msg):
1030        self.__msg = msg
1031         
1032    def __str__(self):
1033        return self.__msg
1034 
1035
1036
1037
1038#_____________________________________________________________________________
1039class CredRepos:
1040    """CredWallet's interface class to a Credential Repository"""
1041   
1042
1043    def __init__(self, propFilePath=None, dbPPhrase=None, **prop):
1044        """Initialise Credential Repository abstract base class derive from
1045        this class to define Credentail Repository interface Credential
1046        Wallet
1047
1048        If the connection string or properties file is set a connection
1049        will be made
1050
1051        dbPPhrase:     pass-phrase to database if applicable
1052        propFilePath:  file path to a properties file.  This could contain
1053                       configuration parameters for the repository e.g.
1054                       database connection parameters
1055        **prop:        any other keywords required
1056        """
1057        raise NotImplementedError(\
1058            self.__init__.__doc__.replace('\n       ',''))
1059
1060
1061    def addUser(self, userName, dn):
1062        """A new user to Credentials Repository"""
1063        raise NotImplementedError(
1064            self.addUser.__doc__.replace('\n       ',''))
1065
1066                           
1067    def auditCredentials(self, **attCertValidKeys):
1068        """Check the attribute certificates held in the repository and delete
1069        any that have expired
1070
1071        attCertValidKeys:  keywords which set how to check the Attribute
1072                           Certificate e.g. check validity time, XML
1073                           signature, version etc.  Default is check
1074                           validity time only"""
1075        raise NotImplementedError(
1076            self.auditCredentials.__doc__.replace('\n       ',''))
1077
1078
1079    def getCredentials(self, dn):
1080        """Get the list of credentials for a given user's DN"""
1081        raise NotImplementedError(
1082            self.getCredentials.__doc__.replace('\n       ',''))
1083
1084       
1085    def addCredentials(self, dn, attCertList):
1086        """Add new attribute certificates for a user.  The user must have
1087        been previously registered in the repository
1088
1089        dn:            users Distinguished name
1090        attCertList:   list of attribute certificates"""
1091        raise NotImplementedError(
1092            self.addCredentials.__doc__.replace('\n       ',''))
1093
1094
1095
1096           
1097if __name__ == "__main__":
1098    proxyCertTxt = open('../x509up_u25157').read()
1099    credWallet = CredWallet(proxyCertTxt)
Note: See TracBrowser for help on using the repository browser.