source: TI12-security/trunk/python/ndg/security/common/CredWallet.py @ 1642

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

Created common package for modules common to client and server packages. Moved into a separate package
as egg setuptools requires security package to be clear of modules.

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