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

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

ndgSetup.sh: fixed slight typo.

mapConfig.xml: added pubKey tag to allow client to Attribute Authority to use it to encrypt
outbound messages to it.

ndgSessionClient.py:

  • include code to set public key of Attribute Authority so that Session Manager can encrypt

messages to it.

  • -r/--req-autho option now requires the AA WSDL URI. -a is now used to set the AA pub key
  • see previous point.

AttAuthorityIO.py:

  • Changed tag 'clntCert' to 'encrCert' so as to be consistent with SessionMgrIO.py code.

attAuthority_services_server.py:

  • Moved encrypt/decrypt code here from AttAuthority? class to be consistent with

sessionMgr_services_server.py.

AttAuthority?.py:

  • Now inherits from dict to allow convenient access to properties file parameters as dictionary

items.

  • Added code to include pubKey tag from mapConfig file in trustedHostInfo returned from

getTrustedHostInfo.

SessionMgrIO.py:

output XML.

  • Shifted test code into separate file in Tests/

SessionClient?.py:

  • Added aaPubKey to reqAuthorisation method - see above re. passing AA public key for

encryption of messages.

sessionMgr_services_server.py:

  • Changes to comments.

Session.py:

private key info of client to allow encrypt of responses from other WSs that SessionMgr? calls.
These are actually passed into CredWallet? instance of UserSession?.

  • AA Public key is passed into reqAuthorisation. This is written to a temp file for use by

XMLSec encryption code.

CredWallet?.py:

  • CredWalletAuthorisationDenied? - make sure extAttCertList gets set to []
  • Added pub/private functionality for encryption of messages to and from Attribute Authorities.
  • reqAuthorisation and getAATrustedHostInfo methods - read in client public key using

straight open/read: using X509Cert.asString() misses out the actual MIME encoded cert text(!)

  • Changed reqAuthorisation() - a required role is now optional with mapFromTrustedHosts flag set.

It does help though with finding a suitable AttCert? for mapping.

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