admin管理员组

文章数量:1289537

Background for this question: In a project, we have some data on a shared webhosting. The DNS record for the site is with CloudFlare and is using its proxied DNS feature.

So far so good, and creating a request like

urllib.request.Request(
    '.zip', 
    headers={...}
)

and subsequent download works just fine.

Now, because of (performance) side-effects of the proxy-DNS, I wanted to access the data using the direct IP of the webhosting. I have to supply 'Host' in the headers because it is a shared webshosting.

When I do this using http, everything still works fine, i.e. like so:

urllib.request.Request(
    "http://123.456.100.1/myfile.zip",
    headers={'Host': 'www.example'}
)

The problem is now when doing the same with https I get this error:

urllib.error.URLError: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: IP address mismatch, certificate is not valid for '123.456.100.1'

Hence, finally the question:

How can I download a file using https and directly using the IP, while still using the Host argument for the certificate verification?

Background for this question: In a project, we have some data on a shared webhosting. The DNS record for the site is with CloudFlare and is using its proxied DNS feature.

So far so good, and creating a request like

urllib.request.Request(
    'https://www.example./myfile.zip', 
    headers={...}
)

and subsequent download works just fine.

Now, because of (performance) side-effects of the proxy-DNS, I wanted to access the data using the direct IP of the webhosting. I have to supply 'Host' in the headers because it is a shared webshosting.

When I do this using http, everything still works fine, i.e. like so:

urllib.request.Request(
    "http://123.456.100.1/myfile.zip",
    headers={'Host': 'www.example.'}
)

The problem is now when doing the same with https I get this error:

urllib.error.URLError: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: IP address mismatch, certificate is not valid for '123.456.100.1'

Hence, finally the question:

How can I download a file using https and directly using the IP, while still using the Host argument for the certificate verification?

Share Improve this question edited Feb 27 at 3:09 Selcuk 59.4k12 gold badges110 silver badges115 bronze badges asked Feb 20 at 22:34 Johannes HinrichsJohannes Hinrichs 17112 bronze badges 4
  • 1 Certificate validation is done on the client, the Host header is used by the server. – Barmar Commented Feb 20 at 22:45
  • 1 I don't know the full syntax, but from my searching it looks like you should be able to specify the hostname in an SSLContext object, which you pass with the context= option. – Barmar Commented Feb 20 at 22:48
  • 1 If your shared hosting setup is like mine, then you have a second, unique DNS name that your hosting provider has provided for your host. It's probably ugly, like hostname.shared.3xyz.myhosting. That DNS name should not be proxied by Cloudflare. That should be the URL you use, not an IP address. And, again, if your setup is like mine, the certificate will have both your DNS names as subject alternative names. – President James K. Polk Commented Feb 21 at 2:03
  • The hostname you use to connect must match a hostname in the certificate. So if you want to use an IP address, tit must be present as an additional hostname in the certificate. – user207421 Commented Feb 21 at 6:20
Add a comment  | 

1 Answer 1

Reset to default 0

There is no documented way to pass a hostname while using urllib. If you really need to perform the hostname check, you can use a raw socket instead:

import socket, ssl

context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
context.verify_mode = ssl.CERT_REQUIRED
context.check_hostname = True
context.load_default_certs()

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ssl_sock = context.wrap_socket(s, server_hostname="www.example.")
ssl_sock.connect(("23.211.125.73", 443))
ssl_sock.send(b"GET / HTTP/1.0\r\nHost: www.example.\r\n\r\n")
while data := ssl_sock.recv(1024):
    print(data.decode("utf-8"))
ssl_sock.close()

If you are willing to take the risk (See @PresidentJamesK.Polk's comment below), you can skip SSL hostname checking by setting check_hostname to False:

import ssl
import urllib.request

context = ssl.create_default_context()
context.check_hostname = False

request = urllib.request.Request(
    url="https://23.211.125.73",
    headers={"Host": "www.example."},
)

response = urllib.request.urlopen(request, context=context)
print(response.read().decode("utf-8"))

本文标签: pythonSSL certificate verification when accessing a server via IPStack Overflow