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

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

Tests/SecurityClientTest?.py: mods for tests on gabriel

NDG/Session.py: SessionMgrCredRepos?.auditCredentials can now accept a 'dn' keyword so that only the records
corresponding to the given Distinguished Name are audited.

NDG/CredWallet.py: init now contains a call to audit the Credential Repository via
SessionMgrCredRepos?.auditCredentials(). reqAuthorisation() now correctly checks for existing ACs set in the
wallet.

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