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

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