Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.Sign up
WinSSL sends client certificate automatically #2262
The following issue was reported in the repo for the libcurl bindings for the R programming language.
Earlier this year we switched the R curl package from openssl to winssl on windows. Several Windows users have since then complained about getting an error
The issue is difficult to reproduce but it seems to be caused by libcurl using an inappropriate client certificate when connecting over https to a server that has not requested a client certificate at all. Most httpd servers will simply ignore the client cert, but some servers (such as vault) will actually refuse the connection if they fail to validate the (unneeded) client cert.
changed the title
WinSSL uses incorrect client cert - schannel: next InitializeSecurityContext failed
Jan 25, 2018
That thread is two issues which as far as I can tell are unrelated.
The first issue is schannel (aka WinSSL) returning SEC_E_INVALID_TOKEN. The reporter in that thread failed to follow up in that case so I'm ignoring it. Also as I mentioned in the thread I had a similar issue that I think was a bug in schannel, as it was reproducible without libcurl.
The second issue is, to sum it up, that the reporter's server requests a client certificate and schannel then automatically locates and responds with a certificate, and then the server replies with fatal alert TLS1_ALERT_BAD_CERTIFICATE, aka SEC_E_CERT_UNKNOWN. (You can read the full map of alerts -> schannel errors here).
It is default behavior that schannel chooses the client cert to send automatically:
"When the server requests client authentication, the client must send the server one of its certificates. By default, Schannel will, with no notification to the client, attempt to locate a client certificate and send it to the server. To disable this feature, clients specify ISC_REQ_USE_SUPPLIED_CREDS when calling the InitializeSecurityContext (Schannel) function. When this flag is specified, Schannel will return SEC_I_INCOMPLETE_CREDENTIALS to the client when the server requests authentication and the client has not previously supplied a certificate."
We already use ISC_REQ_USE_SUPPLIED_CREDS but only if the server returns incomplete credentials:
Also of note here is their assumption that the client must send one of its certificates. That may or may not be true, someone needs to check the RFC. I've seen replies with certificate count 0 so maybe it must send it only if one is available?
Anyway, in this case one client certificate is available so that is sent to the server in response to its certificate request. The certificate sent doesn't seem to have anything to do with the server, according to one of the reporters in that thread @weshinsley.
We may be able to offer a way to disable this behavior for example a CURLOPT_SSL_OPTIONS flag like CURLSSLOPT_NO_DEFAULT_CREDS that if on we use to set flag ISC_REQ_USE_SUPPLIED_CREDS at some point before schannel is notified of the client certificate request.
I'm not sure if we are responsible here. If the server (vault - being used as an intranet server by the reporter) rejects an unknown client certificate fatally even if one is not required then one could argue that's the server's fault.
@jay I agree with one could argue that's the server's fault, but the problem does not seem to appear for any browser (including IE) or any other libcurl SSL back-end on the same machine. So it would be nice if the default behavior were a bit more robust against picky servers.
Perhaps in case of
I took a look at RFC 5246 - The Transport Layer Security (TLS) Protocol Version 1.2.
Section 7.4.4 says if the CA list provided by the server is empty (which is what's happening) then the client MAY send any certificate of the appropriate type (which is what's happening).
So we can add an "external arrangement", ie the flag NO_DEFAULT_CREDS.
Section 7.4.6 allows the server to send a fatal alert or continue the handshake in response to the client's certificate message.
So we have a server that is asking for any certificate and then fatally terminating the handshake by saying the certificate is bad... I'm thinking the server should be at fault here.
You could check and see if you can spot where the certificate is rejected. And then maybe you can walk that back to something in the configuration. I don't suggest posting the entire log here because it may contain sensitive information.
Based on the info we have right now I'm hesitant to support this. IE is probably over compensating for server problems. We already have code to continue the connection, and that could probably be improved for other alerts such as unknown certificate. But in this case it's a fatal error. I'm just not convinced that we should retry on fatal error.
Thanks for looking up the appropriate RFC sections.
Hmm the way I read it is that the server only lets the client know it MAY authenticate, if it wishes to identify itself. I don't think the intention here is to send a random cert.
The current default behavior is actually a privacy concern. I do not expect curl to reveal my personal identity to any random server by default. For example in some countries you need to install a client cert to do your taxes online, but I certainly do not want this cert to be used for any other website.
I don't understand how the user should set
Is there something similar for Windows to identify a client cert from the certificate store?
Whose intention. With an empty CA list given by the server the client MAY send any certificate for authentication in reply and that is what's happening:
"By default, Schannel will, with no notification to the client, attempt to locate a client certificate and send it to the server."
So I think the schannel intention is clear. The issue here is should we defer to that. Obviously the MS wording and the RFC differ in that the MS says the client must reply with one of its certificates whereas the RFC says the client must reply to the certificate request but that reply can be 0 certificates. If a cert is sent but the server doesn't recognize it then the server can continue without client auth or respond with a fatal alert which is why I think the onus is on the server.
Yes I agree but I also think it would be a breaking change now if we override the schannel default behavior. I'm not 100% against it I just think it's going to break transfers of people who have come to expect that the same way they do in IE.
There is no setting right now, if there's a cert it's sent automatically.
@ is not valid in PRINTABLESTRING so technically they're correct the string is invalid. As noted in that wikipedia and elsewhere on the web e-mails are put in commonName even though that is not correct.
In case it's useful, I did some more testing today. I requested and got a new personal client certificate from Imperial - this one I'll call the GOOD certificate - it doesn't have @ in the PRINTABLESTRING, and the certificate path reports OK as far as the Imperial College Root. It doesn't have anything specifically to do with support.montagu.dide.ic.ac.uk:8200 - but the root certificate authority is .ic.ac.uk. I'll call my old Lyncpool cert the "BAD" one, because of problems with both those things. (If I only I had an UGLY certificate too...)
Testing takes some care, as after a successful connection, some caching happens somewhere and I seem to get success no matter what I change. I found a local reboot cleared that. Also, some drag and drop procedures in certmgr.msc seem to not take effect until you push F5 to refresh, even when the result of the drag-drop is visible on screen. That one confused me a few times. So in the end, for each test, I set up the certificates, refresh, reboot, and do the R call... The variable is: which certificates are in my "Personal Certificates" folder, on my Win 10 desktop. Four tests:
BAD certificate only: SEC_E_CERT_UNKNOWN
This last one: does this means that all (well, both!) my client certs get sent to the server, and then a fatal error gets thrown if only one of them is bad?
Edit - I tried the GOOD & BAD a few times to try and see if it was picking just one to send, but got consistent SEC_E_CERT_UNKNOWN. There's no "ordering" as far as I can tell in certmgr.msc - F5 sorts alphabetically. I also tried chronologically both orders to see if "most recently added"... but no difference. So it's either both get sent and it fails, or it consistently chooses to send my BAD certificate, via a criteria I don't know. (Or I've just been very unlucky each time)
It can send any certificate since the server is not requesting a specific CA. Run Wireshark and look at the certificate details to see which certificate(s) it's sending. Check the certificate that is sent, for example from your older wireshark capture in the other thread:
Ok. We can assume MS doesn't think of the cert as bad if it's sending it. Though surely we would like to be able to stop it from doing that, but short of disabling auto credentials I don't know what else we can do. I think the way forward is one of:
As far as I know this is only an issue with WinSSL, no other backend is automatically choosing client certs is it?
If we apply the principle of least surprise, I would expect the behavior to be as much as possible consistent with other libcurl tls back-ends. So that means disabling the auto credential behavior and making it opt-in via
If the Schannel API does not provide a mechanism to use a particular cert (which I find very odd) perhaps it can support