source: TI12-security/trunk/python/ndg.security.common/ndg/security/common/m2CryptoSSLUtility.py @ 4587

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/python/ndg.security.common/ndg/security/common/m2CryptoSSLUtility.py@4587
Revision 4587, 8.7 KB checked in by pjkersha, 12 years ago (diff)
  • Completed integration work for common WSGI/SOAP client based interfaces (ndg.security.server.wsgi.utils.sessionmanagerclient and ndg.security.server.wsgi.utils.attributeauthorityclient) with Pylons Single Sign On package (ndg.security.server.sso)
  • Integrated Single Sign On service into Combined Services Paste service as a Pylons app. This also includes Session Manager, Attribute Authority, OpenID. SSO Service will eventually be removed replaced with OpenID based SSO.
  • Property svn:keywords set to Id
Line 
1"""Extend M2Crypto SSL functionality for cert verification and custom
2timeout settings.
3
4NERC Data Grid Project"""
5__author__ = "P J Kershaw"
6__date__ = "02/07/07"
7__copyright__ = "(C) 2007 STFC & NERC"
8__license__ = \
9"""This software may be distributed under the terms of the Q Public
10License, version 1.0 or later."""
11__contact__ = "Philip.Kershaw@stfc.ac.uk"
12__revision__ = '$Id$'
13
14import httplib
15import socket
16
17from M2Crypto import SSL, X509
18from M2Crypto.httpslib import HTTPSConnection as _HTTPSConnection
19
20from ndg.security.common.X509 import X509Cert, X509Stack, X500DN
21
22class InvalidCertSignature(SSL.Checker.SSLVerificationError):
23    """Raise if verification against CA cert public key fails"""
24
25class InvalidCertDN(SSL.Checker.SSLVerificationError):
26    """Raise if verification against a list acceptable DNs fails"""
27   
28
29class HostCheck(SSL.Checker.Checker, object):
30    """Override SSL.Checker.Checker to enable alternate Common Name
31    setting match for peer cert"""
32
33    def __init__(self, 
34                 peerCertDN=None, 
35                 peerCertCN=None,
36                 acceptedDNs=[], 
37                 caCertList=[],
38                 caCertFilePathList=[], 
39                 **kw):
40        """Override parent class __init__ to enable setting of myProxyServerDN
41        setting
42       
43        @type peerCertDN: string/list
44        @param peerCertDN: Set the expected Distinguished Name of the
45        server to avoid errors matching hostnames.  This is useful
46        where the hostname is not fully qualified. 
47
48        *param acceptedDNs: a list of acceptable DNs.  This enables validation
49        where the expected DN is where against a limited list of certs.
50       
51        @type peerCertCN: string
52        @param peerCertCN: enable alternate Common Name to peer
53        hostname
54       
55        @type caCertList: list type of M2Crypto.X509.X509 types
56        @param caCertList: CA X.509 certificates - if set the peer cert's
57        CA signature is verified against one of these.  At least one must
58        verify
59       
60        @type caCertFilePathList: list string types
61        @param caCertFilePathList: same as caCertList except input as list
62        of CA cert file paths"""
63       
64        SSL.Checker.Checker.__init__(self, **kw)
65       
66        self.peerCertDN = peerCertDN
67        self.peerCertCN = peerCertCN
68        self.acceptedDNs = acceptedDNs
69       
70        if caCertList:
71            self.caCertList = caCertList
72        elif caCertFilePathList:
73            self.caCertFilePathList = caCertFilePathList
74           
75    def __call__(self, peerCert, host=None):
76        """Carry out checks on server ID
77        @param peerCert: MyProxy server host certificate as M2Crypto.X509.X509
78        instance
79        @param host: name of host to check
80        """
81        if peerCert is None:
82            raise SSL.Checker.NoCertificate('SSL Peer did not return '
83                                            'certificate')
84
85        peerCertDN = '/'+peerCert.get_subject().as_text().replace(', ', '/')
86        try:
87            SSL.Checker.Checker.__call__(self, peerCert, host=self.peerCertCN)
88           
89        except SSL.Checker.WrongHost, e:
90            # Try match against peerCertDN set   
91            if peerCertDN != self.peerCertDN:
92                raise e
93
94        # At least one match should be found in the list - first convert to
95        # NDG X500DN type to allow per field matching for DN comparison
96        peerCertX500DN = X500DN(dn=peerCertDN)
97       
98        if self.acceptedDNs:
99           matchFound = False
100           for dn in self.acceptedDNs:
101               x500dn = X500DN(dn=dn)
102               if x500dn == peerCertX500DN:
103                   matchFound = True
104                   break
105               
106           if not matchFound:
107               raise InvalidCertDN('Peer cert DN "%s" doesn\'t match '
108                                   'verification list' % peerCertDN)
109
110        if len(self.__caCertStack) > 0:
111            try:
112                self.__caCertStack.verifyCertChain(\
113                           x509Cert2Verify=X509Cert(m2CryptoX509=peerCert))
114            except Exception, e:
115                raise InvalidCertSignature("Peer certificate verification "
116                                           "against CA cert failed: %s" % e)
117             
118        # They match - drop the exception and return all OK instead         
119        return True
120     
121    def __setCACertList(self, caCertList):
122        """Set list of CA certs - peer cert must validate against at least one
123        of these"""
124        self.__caCertStack = X509Stack()
125        for caCert in caCertList:
126            self.__caCertStack.push(caCert)
127
128    caCertList = property(fset=__setCACertList,
129              doc="list of CA certs - peer cert must validate against one")
130
131    def __setCACertsFromFileList(self, caCertFilePathList):
132        '''Read CA certificates from file and add them to the X.509
133        stack
134       
135        @type caCertFilePathList: list or tuple
136        @param caCertFilePathList: list of file paths for CA certificates to
137        be used to verify certificate used to sign message'''
138       
139        if not isinstance(caCertFilePathList, list) and \
140           not isinstance(caCertFilePathList, tuple):
141            raise AttributeError(
142                        'Expecting a list or tuple for "caCertFilePathList"')
143
144        self.__caCertStack = X509Stack()
145
146        for caCertFilePath in caCertFilePathList:
147            self.__caCertStack.push(X509.load_cert(caCertFilePath))
148       
149    caCertFilePathList = property(fset=__setCACertsFromFileList,
150    doc="list of CA cert file paths - peer cert must validate against one")
151
152
153class HTTPSConnection(_HTTPSConnection):
154    """Modified version of M2Crypto equivalent to enable custom checks with
155    the peer and timeout settings
156   
157    @type defReadTimeout: M2Crypto.SSL.timeout
158    @cvar defReadTimeout: default timeout for read operations
159    @type defWriteTimeout: M2Crypto.SSL.timeout
160    @cvar defWriteTimeout: default timeout for write operations"""   
161    defReadTimeout = SSL.timeout(sec=20.)
162    defWriteTimeout = SSL.timeout(sec=20.)
163   
164    def __init__(self, *args, **kw):
165        '''Overload to enable setting of post connection check
166        callback to SSL.Connection
167       
168        type *args: tuple
169        param *args: args which apply to M2Crypto.httpslib.HTTPSConnection
170        type **kw: dict
171        param **kw: additional keywords
172        @type postConnectionCheck: SSL.Checker.Checker derivative
173        @keyword postConnectionCheck: set class for checking peer
174        @type readTimeout: M2Crypto.SSL.timeout
175        @keyword readTimeout: readTimeout - set timeout for read
176        @type writeTimeout: M2Crypto.SSL.timeout
177        @keyword writeTimeout: similar to read timeout'''
178       
179        self._postConnectionCheck = kw.pop('postConnectionCheck',
180                                           SSL.Checker.Checker)
181       
182        if 'readTimeout' in kw:
183            if not isinstance(kw['readTimeout'], SSL.timeout):
184                raise AttributeError("readTimeout must be of type " + \
185                                     "M2Crypto.SSL.timeout")
186            self.readTimeout = kw.pop('readTimeout')
187        else:
188            self.readTimeout = HTTPSConnection.defReadTimeout
189             
190        if 'writeTimeout' in kw:
191            if not isinstance(kw['writeTimeout'], SSL.timeout):
192                raise AttributeError("writeTimeout must be of type " + \
193                                     "M2Crypto.SSL.timeout") 
194            self.writeTimeout = kw.pop('writeTimeout')
195        else:
196            self.writeTimeout = HTTPSConnection.defWriteTimeout
197   
198        self._clntCertFilePath = kw.pop('clntCertFilePath', None)
199        self._clntPriKeyFilePath = kw.pop('clntPriKeyFilePath', None)
200       
201        _HTTPSConnection.__init__(self, *args, **kw)
202       
203        # load up certificate stuff
204        if self._clntCertFilePath is not None and \
205           self._clntPriKeyFilePath is not None:
206            self.ssl_ctx.load_cert(self._clntCertFilePath, 
207                                   self._clntPriKeyFilePath)
208       
209       
210    def connect(self):
211        '''Overload M2Crypto.httpslib.HTTPSConnection to enable
212        custom post connection check of peer certificate and socket timeout'''
213
214        self.sock = SSL.Connection(self.ssl_ctx)
215        self.sock.set_post_connection_check_callback(self._postConnectionCheck)
216
217        self.sock.set_socket_read_timeout(self.readTimeout)
218        self.sock.set_socket_write_timeout(self.writeTimeout)
219
220        self.sock.connect((self.host, self.port))
221
222    def putrequest(self, method, url, **kw):
223        '''Overload to work around bug with unicode type URL'''
224        url = str(url)
225        _HTTPSConnection.putrequest(self, method, url, **kw)
Note: See TracBrowser for help on using the repository browser.