| OLD | NEW | 
| (Empty) |  | 
 |    1 # Copyright 2007,2011 Google Inc. | 
 |    2 # | 
 |    3 # Licensed under the Apache License, Version 2.0 (the "License"); | 
 |    4 # you may not use this file except in compliance with the License. | 
 |    5 # You may obtain a copy of the License at | 
 |    6 # | 
 |    7 #     http://www.apache.org/licenses/LICENSE-2.0 | 
 |    8 # | 
 |    9 # Unless required by applicable law or agreed to in writing, software | 
 |   10 # distributed under the License is distributed on an "AS IS" BASIS, | 
 |   11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
 |   12 # See the License for the specific language governing permissions and | 
 |   13 # limitations under the License. | 
 |   14 # | 
 |   15  | 
 |   16 # This file is derived from | 
 |   17 # http://googleappengine.googlecode.com/svn-history/r136/trunk/python/google/app
     engine/tools/https_wrapper.py | 
 |   18  | 
 |   19  | 
 |   20 """Extensions to allow HTTPS requests with SSL certificate validation.""" | 
 |   21  | 
 |   22 import httplib | 
 |   23 import re | 
 |   24 import socket | 
 |   25 import ssl | 
 |   26  | 
 |   27 import boto | 
 |   28  | 
 |   29 class InvalidCertificateException(httplib.HTTPException): | 
 |   30   """Raised when a certificate is provided with an invalid hostname.""" | 
 |   31  | 
 |   32   def __init__(self, host, cert, reason): | 
 |   33     """Constructor. | 
 |   34  | 
 |   35     Args: | 
 |   36       host: The hostname the connection was made to. | 
 |   37       cert: The SSL certificate (as a dictionary) the host returned. | 
 |   38     """ | 
 |   39     httplib.HTTPException.__init__(self) | 
 |   40     self.host = host | 
 |   41     self.cert = cert | 
 |   42     self.reason = reason | 
 |   43  | 
 |   44   def __str__(self): | 
 |   45     return ('Host %s returned an invalid certificate (%s): %s' % | 
 |   46             (self.host, self.reason, self.cert)) | 
 |   47  | 
 |   48 def GetValidHostsForCert(cert): | 
 |   49   """Returns a list of valid host globs for an SSL certificate. | 
 |   50  | 
 |   51   Args: | 
 |   52     cert: A dictionary representing an SSL certificate. | 
 |   53   Returns: | 
 |   54     list: A list of valid host globs. | 
 |   55   """ | 
 |   56   if 'subjectAltName' in cert: | 
 |   57     return [x[1] for x in cert['subjectAltName'] if x[0].lower() == 'dns'] | 
 |   58   else: | 
 |   59     return [x[0][1] for x in cert['subject'] | 
 |   60             if x[0][0].lower() == 'commonname'] | 
 |   61  | 
 |   62 def ValidateCertificateHostname(cert, hostname): | 
 |   63   """Validates that a given hostname is valid for an SSL certificate. | 
 |   64  | 
 |   65   Args: | 
 |   66     cert: A dictionary representing an SSL certificate. | 
 |   67     hostname: The hostname to test. | 
 |   68   Returns: | 
 |   69     bool: Whether or not the hostname is valid for this certificate. | 
 |   70   """ | 
 |   71   hosts = GetValidHostsForCert(cert) | 
 |   72   boto.log.debug( | 
 |   73       "validating server certificate: hostname=%s, certificate hosts=%s", | 
 |   74       hostname, hosts) | 
 |   75   for host in hosts: | 
 |   76     host_re = host.replace('.', '\.').replace('*', '[^.]*') | 
 |   77     if re.search('^%s$' % (host_re,), hostname, re.I): | 
 |   78       return True | 
 |   79   return False | 
 |   80  | 
 |   81  | 
 |   82 class CertValidatingHTTPSConnection(httplib.HTTPConnection): | 
 |   83   """An HTTPConnection that connects over SSL and validates certificates.""" | 
 |   84  | 
 |   85   default_port = httplib.HTTPS_PORT | 
 |   86  | 
 |   87   def __init__(self, host, port=default_port, key_file=None, cert_file=None, | 
 |   88                ca_certs=None, strict=None, **kwargs): | 
 |   89     """Constructor. | 
 |   90  | 
 |   91     Args: | 
 |   92       host: The hostname. Can be in 'host:port' form. | 
 |   93       port: The port. Defaults to 443. | 
 |   94       key_file: A file containing the client's private key | 
 |   95       cert_file: A file containing the client's certificates | 
 |   96       ca_certs: A file contianing a set of concatenated certificate authority | 
 |   97           certs for validating the server against. | 
 |   98       strict: When true, causes BadStatusLine to be raised if the status line | 
 |   99           can't be parsed as a valid HTTP/1.0 or 1.1 status line. | 
 |  100     """ | 
 |  101     httplib.HTTPConnection.__init__(self, host, port, strict, **kwargs) | 
 |  102     self.key_file = key_file | 
 |  103     self.cert_file = cert_file | 
 |  104     self.ca_certs = ca_certs | 
 |  105  | 
 |  106   def connect(self): | 
 |  107     "Connect to a host on a given (SSL) port." | 
 |  108     sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | 
 |  109     sock.connect((self.host, self.port)) | 
 |  110     boto.log.debug("wrapping ssl socket; CA certificate file=%s", | 
 |  111                    self.ca_certs) | 
 |  112     self.sock = ssl.wrap_socket(sock, keyfile=self.key_file, | 
 |  113                                 certfile=self.cert_file, | 
 |  114                                 cert_reqs=ssl.CERT_REQUIRED, | 
 |  115                                 ca_certs=self.ca_certs) | 
 |  116     cert = self.sock.getpeercert() | 
 |  117     hostname = self.host.split(':', 0)[0] | 
 |  118     if not ValidateCertificateHostname(cert, hostname): | 
 |  119       raise InvalidCertificateException(hostname, | 
 |  120                                         cert, | 
 |  121                                         'remote hostname "%s" does not match '\ | 
 |  122                                         'certificate' % hostname) | 
 |  123  | 
 |  124  | 
| OLD | NEW |