-
-
Notifications
You must be signed in to change notification settings - Fork 30.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
New SSL module doesn't seem to verify hostname against commonName in certificate #45930
Comments
(I hope I used the correct component for this report) http://pypi.python.org/pypi/ssl/ I used the client example shown at If I make up a hostname called something else, like "wwws", and place it cert = verisign-inc-class-3-public-primary.pem
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ssl_sock = ssl.wrap_socket(s,
ca_certs="/etc/pki/tls/rootcerts/%s" % cert,
cert_reqs=ssl.CERT_REQUIRED)
ssl_sock.connect(('wwws', 443))
print repr(ssl_sock.getpeername()) output: If I now open, say, a firefox window and point it to "https://wwws", it I'll attach the verisign CA certificate to make it easier to reproduce |
Bill, can you respond? |
Unfortunately, hostname matching is one of those ideas that seemed |
At the least it should be made clear in the documentation that the def get_subjectAltName(cert):
if not cert.has_key('subjectAltName'):
return []
ret = []
for rdn in cert['subjectAltName']:
if rdn[0].lower() == 'dns' or rdn[0][:2].lower() == 'ip':
ret.append(rdn[1])
return ret
def get_commonName(cert):
if not cert.has_key('subject'):
return []
ret = []
for rdn in cert['subject']:
if rdn[0][0].lower() == 'commonname':
ret.append(rdn[0][1])
return ret
def verify_hostname(cert, host):
cn = get_commonName(cert)
san = get_subjectAltName(cert)
return (host in cn) or (host in san) |
Yes, I think that's reasonable. And for pseudo-standards like https, which Bill On Dec 12, 2007 4:48 AM, Andreas Hasenack <report@bugs.python.org> wrote:
|
But the current API already has this feature: So this is already taken care of with ca_certs and cert_reqs, right? |
The mechanism is there for direct use of the SSL module, yes. But the Bill On Dec 13, 2007 7:14 AM, Andreas Hasenack <report@bugs.python.org> wrote:
|
I would definitely recommend providing as strict as possible hostname M2Crypto (and TLS Lite, from which I copied the approach to M2Crypto), |
Nope. Hostname verification was never a good idea -- the "hostname" is |
I would think most people/applications want to know to which host they A TLS extension also allows SSL vhosts, so static IPs are no longer |
checking hostnames is false security, not real security. On 8/20/08, Heikki Toivonen <report@bugs.python.org> wrote:
|
Could you clarify your comment regarding hostname check being false Just about all SSL texts I have read say you must do that, and that is |
Sorry to be so brief there -- I was off on vacation. Verifying hostnames is a prescription that someone (well, OK, Eric So what's wrong with it? There are two problems. The first is that But the larger problem is that hostnames are a DNS construct for humans, This is all exacerbated by the fact that HTTP isn't what it was when |
Ok, thank you for clarifications. Now I understand why the hostname But still, I think dealing with email servers is another common use case If you want to continue the discussion, we should maybe take this to |
I think that, where it's appropriate, you can do that. Just don't put it in Bill On Wed, Sep 10, 2008 at 11:24 PM, Heikki Toivonen <report@bugs.python.org>wrote:
|
Reopening. I think it would be nice to provide the appropriate convenience function(s) as part of the ssl module, even if the user has to call them explicitly. |
Removed this message by mistake. Author ahasenack Ups, typo in the script: |
Welcome to 2010. Today they are still getting it wrong and are still vulnerable to mitm attacks against https on the client side. I have an example in fairly large open source project: Less large: I would *very* much like to see these methods fixed by default. I can keep on looking at python projects and reporting these issues but it is really easy, just look at anything that says and is important that mitm isn't possible against it -> then check the deps. in ubuntu /debian and pick the ones that don't use pycurl, check they don't validate the common name etc. and then you have a bunch of mitm'able apps probably ;) |
This appears to be a concern for some people. Maybe the builtin ssl module should be deprecated if there isn't a lot of manpower to maintain it and instead the well-maintained pyOpenSSL package should become the recommended tool? Here is a letter that I just received, in my role as a developer of Tahoe-LAFS, from a concerned coder who doesn't know much about Python:
Here is my reply to him:
|
That sounds like an inventively outrageous kind of FUD. It's the first By the way, if "businesses" are really concerned about the security
Correct me if I'm wrong, but the "well-maintained pyOpenSSL package" |
I'm pretty sure it's just a wrapper around the openssl library, which does not include it. That was Bill Janssen's argument for why the ssl module shouldn't do that verification. Well, that and the fact that there's no finalized standard for it yet. I believe this is the latest draft: |
On Wed, Sep 29, 2010 at 11:34 AM, Antoine Pitrou <report@bugs.python.org> wrote:
Not to add fuel to the fire, but I've had a user report this behavior
What would the approximate cost on that be, do you think? My Geremy Condra |
Well, to be clear, it shouldn't be done *automatically*. But providing a (openssl may not provide such a function, but gnutls does, by the way) |
To err on the safe side and account for integration work (unit tests, (but, don't assume that urllib will then be secure by default; Python |
imho it would be nice to be 'secure by default' in say the next python stable releases... (or perhaps only 3.X ? ). |
I added some extra verification to Mercurial (http://www.selenic.com/hg/rev/f2937d6492c5). Feel free to use the following under the Python license in Python or elsewhere. It could be a separate method/function or it could integrated in wrap_socket and controlled by a keyword. I would appreciate if you find the implementation insufficient or incorrect. The purpose with this function is to verify if the received and validated certificate matches the host we intended to connect to. I try to keep it simple and to fail to the safe side. "Correct" subjectAltName handling seems not to be feasible. Are CRLs checked by the SSL module? Otherwise it deserves a big fat warning. (I now assume that notBefore is handled by the SSL module and shouldn't be checked here.) def _verifycert(cert, hostname):
'''Verify that cert (in socket.getpeercert() format) matches
hostname and is valid at this time. CRLs and subjectAltName are
not handled.
Returns error message if any problems are found and None on success.
'''
if not cert:
return _('no certificate received')
notafter = cert.get('notAfter')
if notafter and time.time() > ssl.cert_time_to_seconds(notafter):
return _('certificate expired %s') % notafter
dnsname = hostname.lower()
for s in cert.get('subject', []):
key, value = s[0]
if key == 'commonName':
certname = value.lower()
if (certname == dnsname or
'.' in dnsname and
certname == '*.' + dnsname.split('.', 1)[1]):
return None
return _('certificate is for %s') % certname
return _('no commonName found in certificate')
def check(a, b):
if a != b:
print (a, b) # Test non-wildcard certificates # Test wildcard certificates # Avoid some pitfalls import time
lastyear = time.gmtime().tm_year - 1
nextyear = time.gmtime().tm_year + 1
check(_verifycert({'notAfter': 'May 9 00:00:00 %s GMT' % lastyear}, 'example.com'),
'certificate expired May 9 00:00:00 %s GMT' % lastyear)
check(_verifycert({'notAfter': 'Sep 29 15:29:48 %s GMT' % nextyear, 'subject': ()}, 'example.com'),
'no commonName found in certificate')
check(_verifycert(None, 'example.com'),
'no certificate received') |
Hello,
Thank you, I'll take a look!
They are not, but AFAIK most browsers don't check CRLs either...
I can't say for sure, but OpenSSL seems to handle both notBefore and |
Here is a patch against py3k. It adds a single ssl.match_hostname method, with rules from RFC 2818 (that is, tailored for HTTPS). Review welcome. |
I think it looks good except for the wildcard checking. According to the latest draft of that TLS id-checking RFC, you aren't supposed to allow the wildcard as part of a fragment. Of course this contradicts RFC 2818. http://tools.ietf.org/html/draft-saintandre-tls-server-id-check-09#section-4.4.3 If this gets accepted, I'll submit a patch to http.client and urllib that makes use of it. |
Well, since it is then an "error" (according to the id-checking draft) I'm also assuming RFC 2818 is in wider use than the id-checking draft; |
Yeah, since RFC 2818 has been accepted since 2000 and the id-checking draft was started in 2009, I'd say it's a safe bet. I'm in no way authoritative though. |
If nobody objects, I will commit this (with docs) soon. Then I will open a separate issue for the http.client / urllib.request integration, since the discussion is already quite long here. |
I'm sorry to make the discussion longer ... From a Python user/programmers point of view it would be nice if http://docs.python.org/library/ssl.html also clarified what "validation" means (apparently that the cert chain all the way from one of ca_certs is valid and with valid dates, except that CRLs not are checked?). It could perhaps be mentioned next to the ca_certs description. It would also be nice to see an example with subjectAltName, both with DNS and IP entries. Has it been tested that the way Python uses OpenSSL really checks both notBefore and notAfter? Some comments to the patch. Some of them are just thoughts that can be ignored, but I think some of them are relevant. _dnsname_to_pat: AFAICS * shouldn't match the empty string. I would expect "fail(cert, '.a.com')". I would prefer to fail to the safe side and only allow a left-most wildcard and thus not allow multiple or f* wildcards, just like draft-saintandre-tls-server-id-check-09 suggests. I would prefer to not use re for such an important task where clarity and correctness is so important. If we only allow left-most wildcards it shouldn't be necessary. match_hostname: I don't understand "IP addresses are not accepted for hostname". I assume that if commonName specifies an IP address then a hostname with this address is valid. So isn't it more that "subjectAltName iPAddress isn't supported"? But wouldn't it be better and simpler to simply support iPAddress - either as the only check iff hostname "looks" like an IP address, alternatively in all cases check against both DNS and IP entries? "dnsnames" doesn't say much about what it is. Perhaps "unmatched"? "if san: ... else: ..." would perhaps be a bit clearer. "doesn't match with either of (%s)" ... isn't the paranthesis around the list elements too pythonic for a message intended for end users? Separate error messages for subjectAltName and commonName could be helpful. I assume it should be "no appropriate _commonName_" to match "subjectAltName". test: cert for example.com is defined twice. Finally: How about unicode and/or IDN hostnames? |
As mentioned in the patch, IP entries are not supported.
I just checked and, yes, it does (but only if you specify CERT_OPTIONAL
Good point.
Well, RFC 2818 allows them, and I see no point in being stricter.
I'm not convinced that manual parsing is really more readable than
Indeed. But, strictly speaking, there are no tests for IPs, so it
Well, that's additional logic to code. I'm not sure it's worth it,
Hmm, perhaps.
That depends if you're an end user or an SSL expert, I guess. end users
Ah, yes.
Right.
I haven't looked how these work in the context of certificate checking. |
Here is a new patch with doc updates and the corrections mentioned above. |
I don't know if there is a point or not, but some hosts are for some
"hostname" in Python usually refers to both IP addresses and DNS Perhaps it should be noted that CertificateError only is raised by |
Do you have examples? Otherwise it is difficult to implement. |
I wanted to go forward with this and so I've committed the patch in r85321. If you're concerned about the lack of support for IP addresses, you can open a new issue (and even provide a patch!). Thank you. |
Can you confirm that the exception raised both on "too early" and "too late" is something like "...SSL3_GET_SERVER_CERTIFICATE:certificate verify failed"? (If so: It would be nice if a slightly more helpful message could be given. I don't know if that is possible.) |
Le vendredi 15 octobre 2010 à 22:51 +0000, Mads Kiilerich a écrit :
Yes.
Agreed. I don't know how to do that, though. |
So I know the current patch doesn't support IP addresses but I thought I would link to what mozilla considered a security problem(just for future reference): CVE-2010-3170: http://www.mozilla.org/security/announce/2010/mfsa2010-70.html "Security researcher Richard Moore reported that when an SSL certificate was created with a common name containing a wildcard followed by a partial IP address a valid SSL connection could be established with a server whose IP address matched the wildcard range by browsing directly to the IP address. It is extremely unlikely that such a certificate would be issued by a Certificate Authority." |
Not exactly. The committed patch do not consider IP addresses - (It seems like IP in commonName isn't permitted by the RFCs, but I think
For reference, the actual report can be found on FWIW, I don't think it is critical at all. Granted, it is a deviation Further, this issue will only have relevance if one the trusted CAs |
Should we escalate this issue to CVA for Python 2.x? |
It's more of a missing feature than a security issue in itself, although |
On 11 November 2010 23:31, Antoine Pitrou <report@bugs.python.org> wrote:
Still it would be nice to see in python 2.x at some point don't you think? |
Well, lots of things would be nice to see in python 2.x, but that's not |
Just to add a couple of data points to argue in favour of a secure-by-default behaviour: 0install.net: http://secunia.com/advisories/47935 (spoofing attack due to certificate names not being validated) Mozilla is recommending people avoid using Python's built-in SSL: https://github.com/mozilla/browserid/wiki/Security-Considerations-when-Implementing-BrowserID I find it hard to believe that anyone would be able to write an SSL client in Python currently without introducing some vulnerability. There are too many traps to fall into. Here are the three I know about:
|
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: