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

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/python/ndg.security.common/ndg/security/common/m2CryptoSSLUtility.py@4336
Revision 4336, 8.8 KB checked in by pjkersha, 12 years ago (diff)

Fixes for BADC Data Browser integration tests:

  • ndg.security.common.X509.X500DN: fix for DNs where field content contains commas - X509_Name.as_text is not safe for this use X509_Name.str
  • ndg.security.common.m2CryptoSSLUtility: use ndg.security.common.X509.X500DN for accurate comparison of DNs in HostCheck? class peer cert checking.
  • 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__ = "P.J.Kershaw@rl.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       
76    def __call__(self, peerCert, host=None):
77        """Carry out checks on server ID
78        @param peerCert: MyProxy server host certificate as M2Crypto.X509.X509
79        instance
80        @param host: name of host to check
81        """
82        if peerCert is None:
83            raise SSL.Checker.NoCertificate('SSL Peer did not return '
84                                            'certificate')
85
86        peerCertDN = '/'+peerCert.get_subject().as_text().replace(', ', '/')
87        try:
88            SSL.Checker.Checker.__call__(self, peerCert, host=self.peerCertCN)
89           
90        except SSL.Checker.WrongHost, e:
91            # Try match against peerCertDN set   
92            if peerCertDN != self.peerCertDN:
93                raise e
94
95        # At least one match should be found in the list - first convert to
96        # NDG X500DN type to allow per field matching for DN comparison
97        peerCertX500DN = X500DN(dn=peerCertDN)
98       
99        if self.acceptedDNs:
100           matchFound = False
101           for dn in self.acceptedDNs:
102               x500dn = X500DN(dn=dn)
103               if x500dn == peerCertX500DN:
104                   matchFound = True
105                   break
106               
107           if not matchFound:
108               raise InvalidCertDN('Peer cert DN "%s" doesn\'t match '
109                                   'verification list' % peerCertDN)
110
111        if len(self.__caCertStack) > 0:
112            try:
113                self.__caCertStack.verifyCertChain(\
114                           x509Cert2Verify=X509Cert(m2CryptoX509=peerCert))
115            except Exception, e:
116                raise InvalidCertSignature("Peer certificate verification "
117                                           "against CA cert failed: %s" % e)
118             
119        # They match - drop the exception and return all OK instead         
120        return True
121   
122   
123    def __setCACertList(self, caCertList):
124        """Set list of CA certs - peer cert must validate against at least one
125        of these"""
126        self.__caCertStack = X509Stack()
127        for caCert in caCertList:
128            self.__caCertStack.push(caCert)
129
130    caCertList = property(fset=__setCACertList,
131              doc="list of CA certs - peer cert must validate against one")
132
133
134    #_________________________________________________________________________
135    def __setCACertsFromFileList(self, caCertFilePathList):
136        '''Read CA certificates from file and add them to the X.509
137        stack
138       
139        @type caCertFilePathList: list or tuple
140        @param caCertFilePathList: list of file paths for CA certificates to
141        be used to verify certificate used to sign message'''
142       
143        if not isinstance(caCertFilePathList, list) and \
144           not isinstance(caCertFilePathList, tuple):
145            raise AttributeError(
146                        'Expecting a list or tuple for "caCertFilePathList"')
147
148        self.__caCertStack = X509Stack()
149
150        for caCertFilePath in caCertFilePathList:
151            self.__caCertStack.push(X509.load_cert(caCertFilePath))
152       
153    caCertFilePathList = property(fset=__setCACertsFromFileList,
154    doc="list of CA cert file paths - peer cert must validate against one")
155
156
157class HTTPSConnection(_HTTPSConnection):
158    """Modified version of M2Crypto equivalent to enable custom checks with
159    the peer and timeout settings
160   
161    @type defReadTimeout: M2Crypto.SSL.timeout
162    @cvar defReadTimeout: default timeout for read operations
163    @type defWriteTimeout: M2Crypto.SSL.timeout
164    @cvar defWriteTimeout: default timeout for write operations"""   
165    defReadTimeout = SSL.timeout(sec=20.)
166    defWriteTimeout = SSL.timeout(sec=20.)
167   
168    def __init__(self, *args, **kw):
169        '''Overload to enable setting of post connection check
170        callback to SSL.Connection
171       
172        type *args: tuple
173        param *args: args which apply to M2Crypto.httpslib.HTTPSConnection
174        type **kw: dict
175        param **kw: additional keywords
176        @type postConnectionCheck: SSL.Checker.Checker derivative
177        @keyword postConnectionCheck: set class for checking peer
178        @type readTimeout: M2Crypto.SSL.timeout
179        @keyword readTimeout: readTimeout - set timeout for read
180        @type writeTimeout: M2Crypto.SSL.timeout
181        @keyword writeTimeout: similar to read timeout'''
182       
183        self._postConnectionCheck = kw.pop('postConnectionCheck',
184                                           SSL.Checker.Checker)
185       
186        if 'readTimeout' in kw:
187            if not isinstance(kw['readTimeout'], SSL.timeout):
188                raise AttributeError("readTimeout must be of type " + \
189                                     "M2Crypto.SSL.timeout")
190            self.readTimeout = kw.pop('readTimeout')
191        else:
192            self.readTimeout = HTTPSConnection.defReadTimeout
193             
194        if 'writeTimeout' in kw:
195            if not isinstance(kw['writeTimeout'], SSL.timeout):
196                raise AttributeError("writeTimeout must be of type " + \
197                                     "M2Crypto.SSL.timeout") 
198            self.writeTimeout = kw.pop('writeTimeout')
199        else:
200            self.writeTimeout = HTTPSConnection.defWriteTimeout
201   
202        self._clntCertFilePath = kw.pop('clntCertFilePath', None)
203        self._clntPriKeyFilePath = kw.pop('clntPriKeyFilePath', None)
204       
205        _HTTPSConnection.__init__(self, *args, **kw)
206       
207        # load up certificate stuff
208        if self._clntCertFilePath is not None and \
209           self._clntPriKeyFilePath is not None:
210            self.ssl_ctx.load_cert(self._clntCertFilePath, 
211                                   self._clntPriKeyFilePath)
212       
213       
214    def connect(self):
215        '''Overload M2Crypto.httpslib.HTTPSConnection to enable
216        custom post connection check of peer certificate and socket timeout'''
217
218        self.sock = SSL.Connection(self.ssl_ctx)
219        self.sock.set_post_connection_check_callback(self._postConnectionCheck)
220
221        self.sock.set_socket_read_timeout(self.readTimeout)
222        self.sock.set_socket_write_timeout(self.writeTimeout)
223
224        self.sock.connect((self.host, self.port))
225
226    def putrequest(self, method, url, **kw):
227        '''Overload to work around bug with unicode type URL'''
228        url = str(url)
229        _HTTPSConnection.putrequest(self, method, url, **kw)
Note: See TracBrowser for help on using the repository browser.