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

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

Changes to incoporate new getHostInfo Attribute Authority WS method.

Tests/AttAuthorityIOtest.py: new unit test test method

Tests/SecurityClientTest?.py: minor changes to test settings

dist/NDG-Security-0.68.tar.gz: new distribution

www/html/attAuthority.wsdl: updated WSDL contains getHostInfo method.

conf/mapConfig.xml: contains new tags for information about the service provider of the AA e.g. loginURI,
service provider name. This is used by the new getHostInfo WS method.

conf/attAuthorityProperties.xml: remove old commented out tags.

NDG/AttAuthorityIO.py: added HostInfo?* classes for handling getHostInfo WS method I/O.

NDG/attAuthority_services_server.py and NDG/attAuthority_services.py: updated inline with WSDL changes.

NDG/AttAuthority.py:

  • readMapConfig updated to include new 'thisHost' tags.
  • self.mapConfig dictionary re-ordered to include top level keys 'thisHost' and 'trustedHosts'
  • New hostInfo property

NDG/AttCert.py: trivial fixes to commenting

NDG/XMLMsg.py: simplify error message for "Invalid keywords set for update..." error

NDG/CredWallet.py:

  • Client public key is now read in at the point where the corresponding pub key file path is set - i.e. in

setClntPubKeyFilePath method. This means the equivalent code in reqAuthorisation is not needed.

  • reqAuthorisation method has a new flag refreshAttCert. If set, the wallet is checked first for an existing

AC issued by the target AA. If found this is returned, and the call to the AA is skipped.

NDG/SecurityClient.py: added AttAuthorityClient?.getHostInfo WS wrapper method.

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