TLS hostname validation logic does not support subjectAltName with IP records

If I am running a custom certificate authority I might want to generate a TLS certificate that encodes its `subjectAltName` using an IP address (`iPAddress` in https://tools.ietf.org/html/rfc5280#section-4.2.1.6). this is useful when I do not have DNS records for servers in my application but I still want to validate identity.

Unfortunately, the hostname validation logic `requests` uses does not validate these records. It only validates DNS records within `subjectAltName`.


You are entirely correct. This likely applies a bit more broadly than requests: we pulled this algorithm out of the standard library, which means it is likely affected. Similarly, I checked `service_identity`, and I believe it may also not handle this appropriately.

Can I get some input from @reaperhulk, @hynek, and @tiran about whether that assessment is right?


Please note that I believe if you set the IP as a `dNSName` this will work correctly. This is quite a common practice, plenty of browsers have (and have had) similar limitations, so many `iPAddress` fields are duplicated in `dNSName` fields. That is technically poor practice, but it's a common work-around.


1. We filed issue with service_identity too (https://github.com/pyca/service_identity/issues/12).
2. We can't use the workaround of putting IP in `dNSName` because that blows up `service_identity` :wink:: https://github.com/pyca/service_identity/blob/master/service_identity/_common.py#L156


Yeah IP in dNSName is an (unfortunately) common thing. service_identity definitely needs to support iPAddress but probably will need to loosen its restrictions on dNSName as well...


Ok then, so I think the first priority here is working out whether or not this is a problem in the stdlib as well. @tiran?


AFAIK an IP in dNSName is invalid pre RFC?  IIRC it’s okay in commonName.


It is definitely invalid. Unfortunately, browsers have previously had problems where they allowed IP addresses in dNSName, but didn't use the iPAddress field, so you could expect to see certificates of that form.


Has anybody seen a cert with an IP address in the wild? I've never seen one that is signed by a public CA.

Yes, the stdlib doesn's support ipAddress. It only validates dNSName. It also uses server_hostname for two jobs: SNI and host name validation. That is going to make it more tricky...


Oooof. Thanks @tiran. For the moment I'll leave the stdlib in your hands, I think we should update the algorithm we've back ported to add this support. I'll take a look at this today and see if I can extend it.


I just checked how OpenSSL 1.0.2 has implemented the checks:
- OpenSSL supports multiple host names (X509_VERIFY_PARAM_add1_host() and X509_VERIFY_PARAM_set1_host()). One of the host in the host list must match. The matching host name can be accessed with X509_VERIFY_PARAM_get0_peername()
- OpenSSL has a couple of extra flags like X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT and X509_CHECK_FLAG_NO_WILDCARDS
- For IP address and S/MIME email address only one address of each is supported
- If both IP address and hosts are set, then BOTH are validated.
- If no host and no IP address is set, then the check passes.


As for s_i, adding non-RFC conform behavior that weakens the security is out of question unless I’ll get overwhelming proofs that it is a problem in real life.

If someone wants to help with pyca/service_identity#12 be my guest. :)


Oh, this is fun: we actually can't validate the iPAddress when using the stdlib `ssl` module because it doesn't return iPAddress fields from the SAN extension. That's extremely unfortunate. We can still add support for it from pyOpenSSL though, and require that people use pyOpenSSL when they want to do this validation.


Mozilla's NSS library doesn't support IP addresses in dNSName either: https://dxr.mozilla.org/mozilla-central/source/security/nss/lib/certdb/certdb.c#1427


FYI 1: http://bugs.python.org/msg244665

FYI 2: https://tools.ietf.org/html/rfc3546#page-9
Literal IPv4 and IPv6 addresses are not permitted in "HostName".


I'd be interested in seeing what a browser developer thinks is the "right thing" in this case since the reality of what is required for validation is sometimes at odds with RFCs. Unfortunately I don't know any on GitHub so I'm not sure who to CC ;)


I could try to get in contact with NSS developers at Red Hat.

And there are  Ryan Sleevi and Adam Langley from Google and the Chromium project.


@tiran the use case is only for private CAs. Can't imagine a public CA would sign an IP, yes.


@Lukasa do you think cURL handles this? Should we give Daniel a ping?


Why not. @bagder?


Ok, here's a response from the "there's no such thing as a simple answer" department:
1. I don't think I've ever actually seen such a cert used
2. curl supports roughly speaking a bazillion TLS backends and most of them feature their own name checking functions so then we use those and then we of course rely on them doing this check correctly.
3. OpenSSL is the by far mostly used TLS backend with curl and for this we of course have written our own verifying code and yes, we do check 'iPAddress' fields[1]. But please consider my point (1) again for this.

[1] = https://github.com/bagder/curl/blob/master/lib/vtls/openssl.c#L1211


@bagder thanks so much. :cake:

Ok, with Daniel's point 1 in mind, I still think we should aim to do this _if possible_. I don't believe we need to do it on both of our SSL backends (stdlib and PyOpenSSL), but if we can make it possible in our PyOpenSSL backend I think we should. That will allow users who desperately need this function to achieve it, but we won't bend over backwards to make this available to all our users.


It's worth noting that cURL's implementation doesn't do anything particularly clever on IP address checking, which bodes well for us. I do think we need to normalise the IP address though, and that may be somewhat painful (the stdlib doesn't provide any function that does that I think, though I'll check).


## If by normalize you're referring to RFC 3986's definition of normalization, we can steal code from rfc3986

Sent from my Android device with K-9 Mail. Please excuse my brevity.


Ok, having made a tweak to urllib3's `match_hostname` function I've now discovered that we don't use it if there is a match_hostname function available in the standard library. Which is unfortunate.


Actually, I think this just means that until the standard library does this, we can't. Closing until the Python stdlib is capable of doing something with iPAddress fields.