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

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

Fixes following update to NOCS deployment.

  • m2CryptoSSLUtility.HTTPSConnection now overrides putrequest in order to ensure that the URL path is string type. unicode type gives an error
  • added a unit test for BrowsePDP - gatekeeper for MOLES/CSML access control.
  • 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
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 where the expected DN is
49        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(\
84                                        'SSL Peer did not return 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
96        if self.acceptedDNs and \
97           not len([dn for dn in self.acceptedDNs if peerCertDN==dn]):
98            raise InvalidCertDN, \
99            'Peer cert DN "%s" doesn\'t match verification list' % peerCertDN
100
101        if len(self.__caCertStack) > 0:
102            try:
103                self.__caCertStack.verifyCertChain(\
104                           x509Cert2Verify=X509Cert(m2CryptoX509=peerCert))
105            except Exception, e:
106                raise InvalidCertSignature(
107                "Peer certificate verification against CA cert failed: %s" % e)
108             
109        # They match - drop the exception and return all OK instead         
110        return True
111   
112   
113    def __setCACertList(self, caCertList):
114        """Set list of CA certs - peer cert must validate against at least one
115        of these"""
116        self.__caCertStack = X509Stack()
117        for caCert in caCertList:
118            self.__caCertStack.push(caCert)
119
120    caCertList = property(fset=__setCACertList,
121              doc="list of CA certs - peer cert must validate against one")
122
123
124    #_________________________________________________________________________
125    def __setCACertsFromFileList(self, caCertFilePathList):
126        '''Read CA certificates from file and add them to the X.509
127        stack
128       
129        @type caCertFilePathList: list or tuple
130        @param caCertFilePathList: list of file paths for CA certificates to
131        be used to verify certificate used to sign message'''
132       
133        if not isinstance(caCertFilePathList, list) and \
134           not isinstance(caCertFilePathList, tuple):
135            raise AttributeError(
136                        'Expecting a list or tuple for "caCertFilePathList"')
137
138        self.__caCertStack = X509Stack()
139
140        for caCertFilePath in caCertFilePathList:
141            self.__caCertStack.push(X509.load_cert(caCertFilePath))
142       
143    caCertFilePathList = property(fset=__setCACertsFromFileList,
144    doc="list of CA cert file paths - peer cert must validate against one")
145
146
147class HTTPSConnection(_HTTPSConnection):
148    """Modified version of M2Crypto equivalent to enable custom checks with
149    the peer and timeout settings
150   
151    @type defReadTimeout: M2Crypto.SSL.timeout
152    @cvar defReadTimeout: default timeout for read operations
153    @type defWriteTimeout: M2Crypto.SSL.timeout
154    @cvar defWriteTimeout: default timeout for write operations"""   
155    defReadTimeout = SSL.timeout(sec=20.)
156    defWriteTimeout = SSL.timeout(sec=20.)
157   
158    def __init__(self, *args, **kw):
159        '''Overload to enable setting of post connection check
160        callback to SSL.Connection
161       
162        type *args: tuple
163        param *args: args which apply to M2Crypto.httpslib.HTTPSConnection
164        type **kw: dict
165        param **kw: additional keywords
166        @type postConnectionCheck: SSL.Checker.Checker derivative
167        @keyword postConnectionCheck: set class for checking peer
168        @type readTimeout: M2Crypto.SSL.timeout
169        @keyword readTimeout: readTimeout - set timeout for read
170        @type writeTimeout: M2Crypto.SSL.timeout
171        @keyword writeTimeout: similar to read timeout'''
172       
173        if 'postConnectionCheck' in kw:
174            self._postConnectionCheck = kw.pop('postConnectionCheck')
175        else:
176            self._postConnectionCheck = SSL.Checker.Checker
177       
178        if 'readTimeout' in kw:
179            if not isinstance(kw['readTimeout'], SSL.timeout):
180                raise AttributeError("readTimeout must be of type " + \
181                                     "M2Crypto.SSL.timeout")
182            self.readTimeout = kw.pop('readTimeout')
183        else:
184            self.readTimeout = HTTPSConnection.defReadTimeout
185             
186        if 'writeTimeout' in kw:
187            if not isinstance(kw['writeTimeout'], SSL.timeout):
188                raise AttributeError("writeTimeout must be of type " + \
189                                     "M2Crypto.SSL.timeout") 
190            self.writeTimeout = kw.pop('writeTimeout')
191        else:
192            self.writeTimeout = HTTPSConnection.defWriteTimeout
193           
194        _HTTPSConnection.__init__(self, *args, **kw)
195       
196       
197    def connect(self):
198        '''Overload M2Crypto.httpslib.HTTPSConnection to enable
199        custom post connection check of peer certificate and socket timeout'''
200        self.sock = SSL.Connection(self.ssl_ctx)
201        self.sock.set_post_connection_check_callback(
202                                                 self._postConnectionCheck)
203
204        self.sock.set_socket_read_timeout(self.readTimeout)
205        self.sock.set_socket_write_timeout(self.writeTimeout)
206
207        self.sock.connect((self.host, self.port))
208
209    def putrequest(self, method, url, **kw):
210        '''Overload to work around bug with unicode type URL'''
211        url = str(url)
212        _HTTPSConnection.putrequest(self, method, url, **kw)
Note: See TracBrowser for help on using the repository browser.