source: TI12-security/trunk/python/ndg.security.common/ndg/security/common/AttAuthority/__init__.py @ 2931

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

Updated Attribute Authority adding new info to the map configuration:

  • The DN of Attribute Authority cert
  • the cert DN of the Login Service https server
  • the cert DN for the https server making REQUESTS to the Login Service. This enables the latter to validate requests and prevent phishing attacks.

Also added a new WSDL operation getAllHostsInfo. This combines getHostInfo and getTrustedHostInfo simplifying code for the WAYF.

ndg.security.common/ndg/security/common/m2CryptoSSLUtility.py: HostCheck? class can now accept multiple peerCertDNs to validate against.

  • Property svn:keywords set to Id
Line 
1#!/usr/bin/env python
2"""NDG Security Attribute Authority client - client interface classes to the
3Attribute Authority.  These have been separated from their
4original location in the SecurityClient since they have the
5unusual place of being required by both client and server
6NDG security packages.  For the server side they are required
7as the CredWallet invoked by the Session Manager acts as a
8client to Attribute Authorities when negotiating the required
9Attribute Certificate.
10
11Make requests for Attribute Certificates used for authorisation
12
13NERC Data Grid Project
14"""
15__author__ = "P J Kershaw"
16__date__ = "17/11/06"
17__copyright__ = "(C) 2007 STFC & NERC"
18__contact__ = "P.J.Kershaw@rl.ac.uk"
19__license__ = \
20"""This software may be distributed under the terms of the Q Public
21License, version 1.0 or later."""
22__contact__ = "P.J.Kershaw@rl.ac.uk"
23__revision__ = "$Id$"
24
25__all__ = [
26    'AttAuthorityClient',
27    'AttAuthorityClientError',
28    'AttributeRequestDenied',
29    'NoTrustedHosts',]
30
31# Determine https http transport
32import urlparse, httplib
33from ZSI.wstools.Utility import HTTPResponse
34
35from AttAuthority_services import AttAuthorityServiceLocator
36from ndg.security.common.wsSecurity import SignatureHandler
37from ndg.security.common.AttCert import AttCert, AttCertParse
38from ndg.security.common.m2CryptoSSLUtility import HTTPSConnection, \
39    HostCheck
40
41
42#_____________________________________________________________________________
43class AttAuthorityClientError(Exception):
44    """Exception handling for AttributeAuthorityClient class"""
45
46class AttributeRequestDenied(Exception):
47    """Raise when a getAttCert call to the AA is denied"""
48
49class NoTrustedHosts(AttAuthorityClientError):
50    """Raise from getTrustedHosts if there are no trusted hosts defined in
51    the map configuration"""
52
53class NoMatchingRoleInTrustedHosts(AttAuthorityClientError):
54    """Raise from getTrustedHosts if there is no mapping to any of the
55    trusted hosts for the given input role name"""
56
57#_____________________________________________________________________________
58class AttAuthorityClient(object):
59    """Client interface to Attribute Authority web service
60   
61    @type excepMap: dict
62    @cvar excepMap: map exception strings returned from SOAP fault to client
63    Exception class to call"""
64   
65    excepMap = {
66        'AttAuthorityNoTrustedHosts': NoTrustedHosts,
67        'AttAuthorityNoMatchingRoleInTrustedHosts': NoMatchingRoleInTrustedHosts
68        }
69   
70    #_________________________________________________________________________
71    def __init__(self, 
72                 uri=None, 
73                 tracefile=None,
74                 sslCACertList=[],
75                 sslCACertFilePathList=[],
76                 sslPeerCertCN=None, 
77                 setSignatureHandler=True,
78                 **signatureHandlerKw):
79        """
80        @type uri: string
81        @param uri: URI for Attribute Authority WS.  Setting it will also
82        initialise the Service Proxy
83                                         
84        @param tracefile: set to file object such as sys.stderr to give
85        extra WS debug information
86       
87        @type sslCACertList: list
88        @param sslCACertList: This keyword is for use with SSL connections
89        only.  Set a list of one ore more CA certificates.  The peer cert.
90        must verify against at least one of these otherwise the connection
91        is dropped.
92       
93        @type sslCACertFilePathList: list
94        @param sslCACertFilePathList: the same as the above except CA certs
95        can be passed as a list of file paths to read from
96       
97        @type sslPeerCertCN: string
98        @param sslPeerCertCN: set an alternate CommonName to match with peer
99        cert.  This keyword is for use with SSL connections only.
100                     
101        @type setSignatureHandler: bool
102        @param setSignatureHandler: flag to determine whether to apply
103        WS-Security Signature Handler or not
104
105        @type signatureHandlerKw: dict
106        @param signatureHandlerKw: keywords to configure signature handler"""
107
108        self.__srv = None
109        self.__uri = None
110        self._transdict = {}       
111       
112        if uri:
113            self.__setURI(uri)
114
115        if sslPeerCertCN:
116            self.__setSSLPeerCertCN(sslPeerCertCN)
117       
118        if sslCACertList:
119            self.__setSSLCACertList(sslCACertList)
120        elif sslCACertFilePathList:
121            self.__setSSLCACertFilePathList(sslCACertFilePathList)
122       
123        # WS-Security Signature handler - set only if any of the keywords were
124        # set
125        if setSignatureHandler:
126            self.__signatureHandler = SignatureHandler(**signatureHandlerKw)
127        else:
128            self.__signatureHandler = None
129           
130        self.__tracefile = tracefile
131       
132        # Instantiate Attribute Authority WS proxy
133        if self.__uri:
134            self.initService()
135       
136
137    #_________________________________________________________________________
138    def __setURI(self, uri):
139        """Set URI for service
140        @type uri: string
141        @param uri: URI for service to connect to"""
142        if not isinstance(uri, basestring):
143            raise AttAuthorityClientError, \
144                        "Attribute Authority WSDL URI must be a valid string"
145       
146        self.__uri = uri
147        try:
148            scheme = urlparse.urlparse(self.__uri)[0]
149        except TypeError:
150            raise AttributeAuthorityClientError, \
151                "Error parsing transport type from URI"
152               
153        if scheme == "https":
154            self._transport = HTTPSConnection
155        else:
156            self._transport = None
157
158
159    #_________________________________________________________________________
160    def __getURI(self):
161        """Get URI for service
162        @rtype: string
163        @return: uri for service to be invoked"""
164        return self.__uri
165           
166    uri = property(fset=__setURI, fget=__getURI,doc="Attribute Authority URI")
167
168
169    #_________________________________________________________________________
170    def __setSSLPeerCertCN(self, cn):
171        """For use with HTTPS connections only.  Specify the Common
172        Name to match with Common Name of the peer certificate.  This is not
173        needed if the peer cert CN = peer hostname"""
174        if self._transport != HTTPSConnection:
175            return
176       
177        if self._transdict.get('postConnectionCheck'):
178            self._transdict['postConnectionCheck'].peerCertCN = cn
179        else:
180            self._transdict['postConnectionCheck'] = HostCheck(peerCertCN=cn)
181
182    sslPeerCertCN = property(fset=__setSSLPeerCertCN, 
183doc="for https connections, set CN of peer cert if other than peer hostname")
184
185
186    #_________________________________________________________________________
187    def __setSSLCACertList(self, caCertList):
188        """For use with HTTPS connections only.  Specify CA certs to one of
189        which the peer cert must verify its signature against"""
190        if self._transport != HTTPSConnection:
191            return
192       
193        if self._transdict.get('postConnectionCheck'):
194            self._transdict['postConnectionCheck'].caCertList = caCertList
195        else:
196            self._transdict['postConnectionCheck'] = \
197                                            HostCheck(caCertList=caCertList)
198
199    sslCACertList = property(fset=__setSSLCACertList, 
200doc="for https connections, set list of CA certs from which to verify peer cert")
201
202
203    #_________________________________________________________________________
204    def __setSSLCACertFilePathList(self, caCertFilePathList):
205        """For use with HTTPS connections only.  Specify CA certs to one of
206        which the peer cert must verify its signature against"""
207        if self._transport != HTTPSConnection:
208            return
209       
210        if self._transdict.get('postConnectionCheck'):
211            self._transdict['postConnectionCheck'].caCertFilePathList = \
212                                                            caCertFilePathList
213        else:
214            self._transdict['postConnectionCheck'] = \
215                            HostCheck(caCertFilePathList=caCertFilePathList)
216
217    sslCACertFilePathList = property(fset=__setSSLCACertFilePathList, 
218doc="for https connections, set list of CA cert files from which to verify peer cert")
219
220   
221    #_________________________________________________________________________
222    def __setSignatureHandler(self, signatureHandler):
223        """Set SignatureHandler object property method - set to None to for no
224        digital signature and verification"""
225        if signatureHandler is not None and \
226           not isinstance(signatureHandler, signatureHandler):
227            raise AttributeError, \
228    "Signature Handler must be %s type or None for no message security" % \
229        "ndg.security.common.wsSecurity.SignatureHandler"
230                           
231        self.__signatureHandler = signatureHandler
232   
233
234    #_________________________________________________________________________
235    def __getSignatureHandler(self):
236        "Get SignatureHandler object property method"
237        return self.__signatureHandler
238   
239    signatureHandler = property(fget=__getSignatureHandler,
240                                fset=__setSignatureHandler,
241                                doc="SignatureHandler object")
242   
243       
244    #_________________________________________________________________________
245    def initService(self, uri=None):
246        """Set the WS proxy for the Attribute Authority
247       
248        @type uri: string
249        @param uri: URI for service to invoke"""
250       
251        if uri:
252            self.__setURI(uri)
253
254        # WS-Security Signature handler object is passed to binding
255        try:
256            locator = AttAuthorityServiceLocator()
257            self.__srv = locator.getAttAuthority(self.__uri, 
258                                         sig_handler=self.__signatureHandler,
259                                         tracefile=self.__tracefile,
260                                         transport=self._transport,
261                                         transdict=self._transdict)
262        except HTTPResponse, e:
263            raise AttAuthorityClientError, \
264                "Error initialising service for \"%s\": %s %s" % \
265                (self.__uri, e.status, e.reason)
266
267                                   
268    #_________________________________________________________________________
269    def getHostInfo(self):
270        """Get host information for the data provider which the
271        Attribute Authority represents
272       
273        @rtype: dict
274        @return: dictionary of host information derived from the Attribute
275        Authority's map configuration
276        """
277
278        try:
279            # Convert return tuple into list to enable use of pop() later
280            response = list(self.__srv.getHostInfo())
281        except httplib.BadStatusLine, e:
282            raise AttAuthorityClientError, "HTTP bad status line: %s" % e
283
284        except Exception, e:
285            # Try to detect exception type from SOAP fault message
286            errMsg = str(e)
287            for excep in self.excepMap:
288                if excep in errMsg:
289                    raise self.excepMap[excep]
290               
291            # Catch all   
292            raise e
293
294        # Unpack response into dict
295        hostInfoKw = ['hostname',
296                      'aaURI',
297                      'aaDN',
298                      'loginURI',
299                      'loginServerDN',
300                      'loginRequestServerDN']
301        hostInfoKw.reverse()
302        hostInfo = dict([(k, response.pop()) for k in hostInfoKw])
303
304        return hostInfo
305
306                                   
307    #_________________________________________________________________________
308    def getTrustedHostInfo(self, role=None):
309        """Get list of trusted hosts for an Attribute Authority
310       
311        @type role: string
312        @param role: get information for trusted hosts that have a mapping to
313        this role
314       
315        @rtype: dict
316        @return: dictionary of host information indexed by hostname derived
317        from the map configuration"""
318           
319        try:
320            trustedHosts = self.__srv.getTrustedHostInfo(role)
321
322        except httplib.BadStatusLine, e:
323            raise AttAuthorityClientError, "HTTP bad status line: %s" % e
324
325        except Exception, e:
326            # Try to detect exception type from SOAP fault message
327            errMsg = str(e)
328            for excep in self.excepMap:
329                if excep in errMsg:
330                    raise self.excepMap[excep]
331               
332            # Catch all   
333            raise e
334
335        # Convert into dictionary form as used by AttAuthority class
336        trustedHostInfo = {}
337        for host in trustedHosts:
338            hostname = host.get_element_hostname()
339           
340            trustedHostInfo[hostname] = \
341            {
342                'aaURI': host.AaURI,
343                'aaDN': host.AaDN,
344                'loginURI': host.LoginURI,
345                'loginServerDN': host.LoginServerDN,
346                'loginRequestServerDN': host.LoginRequestServerDN,
347                'role': host.RoleList
348            }
349           
350        return trustedHostInfo
351
352                                   
353    #_________________________________________________________________________
354    def getAllHostsInfo(self):
355        """Get list of all hosts for an Attribute Authority i.e. itself and
356        all the hosts it trusts
357       
358        @rtype: dict
359        @return: dictionary of host information indexed by hostname derived
360        from the map configuration"""
361           
362        try:
363            hosts = self.__srv.getAllHostsInfo()
364
365        except httplib.BadStatusLine, e:
366            raise AttAuthorityClientError, "HTTP bad status line: %s" % e
367
368        except Exception, e:
369            # Try to detect exception type from SOAP fault message
370            errMsg = str(e)
371            for excep in self.excepMap:
372                if excep in errMsg:
373                    raise self.excepMap[excep]
374               
375            # Catch all   
376            raise e
377
378        # Convert into dictionary form as used by AttAuthority class
379        allHostInfo = {}
380        for host in hosts:
381            hostname = host.Hostname
382           
383            allHostInfo[hostname] = \
384            {
385                'aaURI': host.AaURI,
386                'aaDN': host.AaDN,
387                'loginURI': host.LoginURI,
388                'loginServerDN': host.LoginServerDN,
389                'loginRequestServerDN': host.LoginRequestServerDN,
390            }
391           
392            # The Attribute Authority called doesn't have a role list for its
393            # entry
394            if hasattr(host, 'RoleList'):
395               allHostInfo[hostname]['role']= host.RoleList
396
397        return allHostInfo   
398
399
400    #_________________________________________________________________________
401    def getAttCert(self, userId=None, userCert=None, userAttCert=None):
402        """Request attribute certificate from NDG Attribute Authority Web
403        Service.
404       
405        @type userId: string
406        @param userId: DN of the X.509 certificate used in SOAP digital
407        signature corresponds to the *holder* of the Attribute Certificate
408        that is issued.  Set this additional field to specify an alternate
409        user ID to associate with the AC.  This is useful in the case where,
410        as in the DEWS project, the holder will be a server cert. rather than
411        a user proxy cert.
412       
413        If this keword is omitted, userId in the AC will default to the same
414        value as the holder DN.
415       
416        @type userCert: string
417        @param userCert: certificate corresponding to proxy private key and
418        proxy cert used to sign the request.  Enables server to establish
419        chain of trust proxy -> user cert -> CA cert.  If a standard
420        private key is used to sign the request, this argument is not
421        needed.
422       
423        @type userAttCert: string / AttCert
424        @param userAttCert: user attribute certificate from which to make a
425        mapped certificate at the target attribute authority.  userAttCert
426        must have been issued from a trusted host to the target.  This is not
427        necessary if the user is registered at the target Attribute Authority.
428       
429        @rtype ndg.security.common.AttCert.AttCert
430        @return attribute certificate for user.  iIf access is refused,
431        AttributeRequestDenied is raised"""
432
433        # Ensure cert is serialized before passing over web service interface
434        if isinstance(userAttCert, AttCert):
435            userAttCert = userAttCert.toString()
436
437        try:
438            sAttCert, msg = self.__srv.getAttCert(userId,userCert,userAttCert) 
439        except httplib.BadStatusLine, e:
440            raise AttAuthorityClientError, \
441                'Calling "%s" HTTP bad status line: %s' % (self.__uri, e)
442
443        except Exception, e:
444            # Try to detect exception type from SOAP fault message
445            errMsg = str(e)
446            for excep in self.excepMap:
447                if excep in errMsg:
448                    raise self.excepMap[excep]
449               
450            # Catch all   
451            raise e
452       
453        if sAttCert:
454            return AttCertParse(sAttCert)
455        else:
456            raise AttributeRequestDenied, msg
457
458                                   
459    #_________________________________________________________________________
460    def getX509Cert(self):
461        """Retrieve the X.509 certificate of the Attribute Authority
462       
463        @rtype: string
464        @return X.509 certificate for Attribute Authority"""
465       
466        try:
467            return self.__srv.getX509Cert()               
468        except httplib.BadStatusLine, e:
469            raise AttAuthorityClientError, "HTTP bad status line: %s" % e
Note: See TracBrowser for help on using the repository browser.