components/python/pywbem/patches/01-CVE-2013-6418-CVE-2013-6444.patch
changeset 5293 bb35a9811599
equal deleted inserted replaced
5292:629042c81883 5293:bb35a9811599
       
     1 http://sourceforge.net/p/pywbem/code/622/
       
     2 http://sourceforge.net/p/pywbem/code/627/
       
     3 
       
     4 fixed TOCTOU error when validating peer's certificate
       
     5 
       
     6 By TOCTOU it's meant time-of-check-time-of-use. Up to now, pywbem made
       
     7 two connections for one request (applies just to ssl). The first one
       
     8 made the verification (without the hostname check) and the second one
       
     9 was used for request. No verification was done for the latter, which
       
    10 could be abused.
       
    11 
       
    12 Peer's certificate is now validated when connecting over ssl. To
       
    13 prevent man-in-the-middle attack, verification of hostname is also
       
    14 added. Peer's hostname must match the commonName of its certificate.
       
    15 Or it must be contained in subjectAltName (list of aliases). M2Crypto
       
    16 package is used for that purpose.  Thanks to it both security
       
    17 enhancements could be implemented quiete easily.  Downside is a new
       
    18 dependency added to pywbem. Verification can be skipped if
       
    19 no_verification is set to False.
       
    20 
       
    21 Certificate trust store can now be specified by user. Some default
       
    22 paths, valid for several distributions, were added.
       
    23 
       
    24 Authored by:  miminar 2014-01-17
       
    25 
       
    26 NOTE:  The code and patches are littered with whitespace issues.
       
    27 Generation of patches needs to be done carefully.
       
    28 
       
    29 --- pywbem-0.7.0/cim_http.py.orig	2008-11-05 17:01:51.000000000 -0800
       
    30 +++ pywbem-0.7.0/cim_http.py	2014-01-17 06:11:05.000000000 -0800
       
    31 @@ -4,8 +4,7 @@
       
    32  #
       
    33  # This program is free software; you can redistribute it and/or modify
       
    34  # it under the terms of the GNU Lesser General Public License as
       
    35 -# published by the Free Software Foundation; either version 2 of the
       
    36 -# License.
       
    37 +# published by the Free Software Foundation; version 2 of the License.
       
    38  #   
       
    39  # This program is distributed in the hope that it will be useful, but
       
    40  # WITHOUT ANY WARRANTY; without even the implied warranty of
       
    41 @@ -29,7 +28,8 @@ being transferred is XML.  It is up to t
       
    42  data and interpret the result.
       
    43  '''
       
    44  
       
    45 -import sys, string, re, os, socket, pwd
       
    46 +from M2Crypto import SSL, Err
       
    47 +import sys, string, re, os, socket, getpass
       
    48  from stat import S_ISSOCK
       
    49  import cim_obj
       
    50  from types import StringTypes
       
    51 @@ -59,6 +59,15 @@ def parse_url(url):
       
    52      if m:
       
    53          host = url[len(m.group(0)):]
       
    54  
       
    55 +    # IPv6 with/without port
       
    56 +    m = re.match("^\[?([0-9A-Fa-f:]*)\]?(:([0-9]*))?$", host)
       
    57 +    if m:
       
    58 +        host = m.group(1)
       
    59 +        port_tmp = m.group(3)
       
    60 +        if port_tmp:
       
    61 +            port = int(port_tmp)
       
    62 +        return host, port, ssl
       
    63 +
       
    64      s = string.split(host, ":")         # Set port number
       
    65      if len(s) != 1:
       
    66          host = s[0]
       
    67 @@ -66,8 +75,26 @@ def parse_url(url):
       
    68  
       
    69      return host, port, ssl
       
    70  
       
    71 +def get_default_ca_certs():
       
    72 +    """
       
    73 +    Try to find out system path with ca certificates. This path is cached and
       
    74 +    returned. If no path is found out, None is returned.
       
    75 +    """
       
    76 +    if not hasattr(get_default_ca_certs, '_path'):
       
    77 +        for path in (
       
    78 +                '/etc/pki/ca-trust/extracted/openssl/ca-bundle.trust.crt',
       
    79 +                '/etc/ssl/certs',
       
    80 +                '/etc/ssl/certificates'):
       
    81 +            if os.path.exists(path):
       
    82 +                get_default_ca_certs._path = path
       
    83 +                break
       
    84 +        else:
       
    85 +            get_default_ca_certs._path = None
       
    86 +    return get_default_ca_certs._path
       
    87 +
       
    88  def wbem_request(url, data, creds, headers = [], debug = 0, x509 = None,
       
    89 -                 verify_callback = None):
       
    90 +                 verify_callback = None, ca_certs = None,
       
    91 +                 no_verification = False):
       
    92      """Send XML data over HTTP to the specified url. Return the
       
    93      response in XML.  Uses Python's build-in httplib.  x509 may be a
       
    94      dictionary containing the location of the SSL certificate and key
       
    95 @@ -97,9 +124,48 @@ def wbem_request(url, data, creds, heade
       
    96      
       
    97      class HTTPSConnection(HTTPBaseConnection, httplib.HTTPSConnection):
       
    98          def __init__(self, host, port=None, key_file=None, cert_file=None, 
       
    99 -                     strict=None):
       
   100 +                     strict=None, ca_certs=None, verify_callback=None):
       
   101              httplib.HTTPSConnection.__init__(self, host, port, key_file, 
       
   102                                               cert_file, strict)
       
   103 +            self.ca_certs = ca_certs
       
   104 +            self.verify_callback = verify_callback
       
   105 +
       
   106 +        def connect(self):
       
   107 +            "Connect to a host on a given (SSL) port."
       
   108 +            self.sock = socket.create_connection((self.host, self.port),
       
   109 +                                            self.timeout, self.source_address)
       
   110 +            if self._tunnel_host:
       
   111 +                self.sock = sock
       
   112 +                self._tunnel()
       
   113 +            ctx = SSL.Context('sslv23')
       
   114 +            if self.cert_file:
       
   115 +                ctx.load_cert(self.cert_file, keyfile=self.key_file)
       
   116 +            if self.ca_certs:
       
   117 +                ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert,
       
   118 +                    depth=9, callback=verify_callback)
       
   119 +                if os.path.isdir(self.ca_certs):
       
   120 +                    ctx.load_verify_locations(capath=self.ca_certs)
       
   121 +                else:
       
   122 +                    ctx.load_verify_locations(cafile=self.ca_certs)
       
   123 +            try:
       
   124 +                self.sock = SSL.Connection(ctx, self.sock)
       
   125 +                # Below is a body of SSL.Connection.connect() method
       
   126 +                # except for the first line (socket connection). We want to preserve
       
   127 +                # tunneling ability.
       
   128 +                self.sock.addr = (self.host, self.port)
       
   129 +                self.sock.setup_ssl()
       
   130 +                self.sock.set_connect_state()
       
   131 +                ret = self.sock.connect_ssl()
       
   132 +                if self.ca_certs:
       
   133 +                    check = getattr(self.sock, 'postConnectionCheck',
       
   134 +                             self.sock.clientPostConnectionCheck)
       
   135 +                    if check is not None:
       
   136 +                        if not check(self.sock.get_peer_cert(), self.host):
       
   137 +                            raise Error('SSL error: post connection check failed')
       
   138 +                return ret
       
   139 +            except ( Err.SSLError, SSL.SSLError, SSL.SSLTimeoutError
       
   140 +                   , SSL.Checker.WrongHost), arg:
       
   141 +                raise Error("SSL error: %s" % arg)
       
   142      
       
   143      class FileHTTPConnection(HTTPBaseConnection, httplib.HTTPConnection):
       
   144          def __init__(self, uds_path):
       
   145 @@ -109,47 +175,36 @@ def wbem_request(url, data, creds, heade
       
   146              self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
       
   147              self.sock.connect(self.uds_path)
       
   148  
       
   149 -    host, port, ssl = parse_url(url)
       
   150 +    host, port, use_ssl = parse_url(url)
       
   151  
       
   152      key_file = None
       
   153      cert_file = None
       
   154  
       
   155 -    if ssl:
       
   156 -
       
   157 -        if x509 is not None:
       
   158 +    if use_ssl and x509 is not None:
       
   159              cert_file = x509.get('cert_file')
       
   160              key_file = x509.get('key_file')
       
   161  
       
   162 -        if verify_callback is not None:
       
   163 -            try:
       
   164 -                from OpenSSL import SSL
       
   165 -                ctx = SSL.Context(SSL.SSLv3_METHOD)
       
   166 -                ctx.set_verify(SSL.VERIFY_PEER, verify_callback)
       
   167 -                # Add the key and certificate to the session
       
   168 -                if cert_file is not None and key_file is not None:
       
   169 -                  ctx.use_certificate_file(cert_file)
       
   170 -                  ctx.use_privatekey_file(key_file)
       
   171 -                s = SSL.Connection(ctx, socket.socket(socket.AF_INET,
       
   172 -                                                      socket.SOCK_STREAM))
       
   173 -                s.connect((host, port))
       
   174 -                s.do_handshake()
       
   175 -                s.shutdown()
       
   176 -                s.close()
       
   177 -            except socket.error, arg:
       
   178 -                raise Error("Socket error: %s" % (arg,))
       
   179 -            except socket.sslerror, arg:
       
   180 -                raise Error("SSL error: %s" % (arg,))
       
   181 -
       
   182      numTries = 0
       
   183      localAuthHeader = None
       
   184      tryLimit = 5
       
   185  
       
   186 +    if isinstance(data, unicode):
       
   187 +        data = data.encode('utf-8')
       
   188      data = '<?xml version="1.0" encoding="utf-8" ?>\n' + data
       
   189  
       
   190 +    if not no_verification and ca_certs is None:
       
   191 +        ca_certs = get_default_ca_certs()
       
   192 +    elif no_verification:
       
   193 +        ca_certs = None
       
   194 +
       
   195      local = False
       
   196 -    if ssl:
       
   197 -        h = HTTPSConnection(host, port = port, key_file = key_file,
       
   198 -                                            cert_file = cert_file)
       
   199 +    if use_ssl:
       
   200 +        h = HTTPSConnection(host,
       
   201 +                port = port,
       
   202 +                key_file = key_file,
       
   203 +                cert_file = cert_file,
       
   204 +                ca_certs = ca_certs,
       
   205 +                verify_callback = verify_callback)
       
   206      else:
       
   207          if url.startswith('http'):
       
   208              h = HTTPConnection(host, port = port)
       
   209 @@ -167,12 +222,12 @@ def wbem_request(url, data, creds, heade
       
   210                  raise Error('Invalid URL')
       
   211  
       
   212      locallogin = None
       
   213 -    if host in ('localhost', '127.0.0.1'):
       
   214 +    if host in ('localhost', 'localhost6', '127.0.0.1', '::1'):
       
   215          local = True
       
   216      if local:
       
   217          uid = os.getuid()
       
   218          try:
       
   219 -            locallogin = pwd.getpwuid(uid)[0]
       
   220 +            locallogin = getpass.getuser()
       
   221          except KeyError:
       
   222              locallogin = None
       
   223      while numTries < tryLimit:
       
   224 @@ -191,6 +246,8 @@ def wbem_request(url, data, creds, heade
       
   225              h.putheader('PegasusAuthorization', 'Local "%s"' % locallogin)
       
   226  
       
   227          for hdr in headers:
       
   228 +            if isinstance(hdr, unicode):
       
   229 +                hdr = hdr.encode('utf-8')
       
   230              s = map(lambda x: string.strip(x), string.split(hdr, ":", 1))
       
   231              h.putheader(urllib.quote(s[0]), urllib.quote(s[1]))
       
   232  
       
   233 
       
   234 --- pywbem-0.7.0/cim_operations.py.orig       2008-12-12 09:40:22.000000000 -0800
       
   235 +++ pywbem-0.7.0/cim_operations.py 2014-01-17 06:11:05.000000000 -0800
       
   236 @@ -4,8 +4,7 @@
       
   237  #
       
   238  # This program is free software; you can redistribute it and/or modify
       
   239  # it under the terms of the GNU Lesser General Public License as
       
   240 -# published by the Free Software Foundation; either version 2 of the
       
   241 -# License.
       
   242 +# published by the Free Software Foundation; version 2 of the License.
       
   243  #   
       
   244  # This program is distributed in the hope that it will be useful, but
       
   245  # WITHOUT ANY WARRANTY; without even the implied warranty of
       
   246 @@ -28,7 +27,7 @@ import sys, string
       
   247  from types import StringTypes
       
   248  from xml.dom import minidom
       
   249  import cim_obj, cim_xml, cim_http, cim_types
       
   250 -from cim_obj import CIMClassName, CIMInstanceName, CIMInstance, CIMClass
       
   251 +from cim_obj import CIMClassName, CIMInstanceName, CIMInstance, CIMClass, NocaseDict
       
   252  from datetime import datetime, timedelta
       
   253  from tupletree import dom_to_tupletree, xml_to_tupletree
       
   254  from tupleparse import parse_cim
       
   255 @@ -79,12 +78,12 @@ class WBEMConnection(object):
       
   256      the request before it is sent, and the reply before it is
       
   257      unpacked.
       
   258 
       
   259 -    verify_callback is used to verify the server certificate.  
       
   260 -    It is passed to OpenSSL.SSL.set_verify, and is called during the SSL
       
   261 -    handshake.  verify_callback should take five arguments: A Connection 
       
   262 -    object, an X509 object, and three integer variables, which are in turn 
       
   263 -    potential error number, error depth and return code. verify_callback 
       
   264 -    should return True if verification passes and False otherwise.
       
   265 +    verify_callback is used to verify the server certificate.  It is passed to
       
   266 +    M2Crypto.SSL.Context.set_verify, and is called during the SSL handshake.
       
   267 +    verify_callback should take five arguments: An SSL Context object, an X509
       
   268 +    object, and three integer variables, which are in turn potential error
       
   269 +    number, error depth and return code. verify_callback should return True if
       
   270 +    verification passes and False otherwise.
       
   271 
       
   272      The value of the x509 argument is used only when the url contains
       
   273      'https'. x509 must be a dictionary containing the keys 'cert_file' 
       
   274 @@ -92,14 +91,27 @@ class WBEMConnection(object):
       
   275      filename of an certificate and the value of 'key_file' must consist 
       
   276      of a filename containing the private key belonging to the public key 
       
   277      that is part of the certificate in cert_file. 
       
   278 +
       
   279 +    ca_certs specifies where CA certificates for verification purposes are
       
   280 +    located. These are trusted certificates. Note that the certificates have to
       
   281 +    be in PEM format. Either it is a directory prepared using the c_rehash tool
       
   282 +    included with OpenSSL or an pemfile. If None, default system path will be
       
   283 +    used.
       
   284 +
       
   285 +    no_verification allows to disable peer's verification. This is insecure and
       
   286 +    should be avoided. If True, peer's certificate is not verified and ca_certs
       
   287 +    argument is ignored.
       
   288      """
       
   289      
       
   290      def __init__(self, url, creds = None, default_namespace = DEFAULT_NAMESPACE,
       
   291 -                 x509 = None, verify_callback = None):
       
   292 +                 x509 = None, verify_callback = None, ca_certs = None,
       
   293 +                 no_verification = False):
       
   294          self.url = url
       
   295          self.creds = creds
       
   296          self.x509 = x509
       
   297          self.verify_callback = verify_callback
       
   298 +        self.ca_certs = ca_certs
       
   299 +        self.no_verification = no_verification
       
   300          self.last_request = self.last_reply = ''
       
   301          self.default_namespace = default_namespace
       
   302          self.debug = False
       
   303 @@ -165,7 +177,9 @@ class WBEMConnection(object):
       
   304              resp_xml = cim_http.wbem_request(self.url, req_xml.toxml(),
       
   305                                               self.creds, headers,
       
   306                                               x509 = self.x509,
       
   307 -                                             verify_callback = self.verify_callback)
       
   308 +                                             verify_callback = self.verify_callback,
       
   309 +                                             ca_certs = self.ca_certs,
       
   310 +                                             no_verification = self.no_verification)
       
   311          except cim_http.AuthError:
       
   312              raise
       
   313          except cim_http.Error, arg:
       
   314 @@ -322,7 +336,9 @@ class WBEMConnection(object):
       
   315              resp_xml = cim_http.wbem_request(self.url, req_xml.toxml(),
       
   316                                               self.creds, headers,
       
   317                                               x509 = self.x509,
       
   318 -                                             verify_callback = self.verify_callback)
       
   319 +                                             verify_callback = self.verify_callback,
       
   320 +                                             ca_certs = self.ca_certs,
       
   321 +                                             no_verification = self.no_verification)
       
   322          except cim_http.Error, arg:
       
   323              # Convert cim_http exceptions to CIMError exceptions
       
   324              raise CIMError(0, str(arg))
       
   325 @@ -812,7 +828,7 @@ class WBEMConnection(object):
       
   326 
       
   327          # Convert zero or more PARAMVALUE elements into dictionary
       
   328 
       
   329 -        output_params = {}
       
   330 +        output_params = NocaseDict()
       
   331 
       
   332          for p in result:
       
   333              if p[1] == 'reference':
       
   334