Certificate Pinning ignored?
-
Hi, I'm trying to make sure my Qt client application only connects to a server using a certain (self-signed) certificate, but it seems like QNetworkRequest/Reply ignores my restriction. The client code looks like this:
// In Client Setup phase QSslConfiguration conf = QSslConfiguration::defaultConfiguration(); // loadCertFromFile reads a .pem file QSslCertificate myCert = loadCertFromFile(); QList<QSslCertificate> certs; certs << myCert; conf.setCaCertificates(certs); conf.setPeerVerifyMode(QSslSocket::VerifyPeer); QSslConfiguration::setDefaultConfiguration(conf); // Request sending QNetworkRequest request; request.setSslConfiguration(QSslConfiguration::defaultConfiguration()); QNetworkReply* reply = nam->get(request); // Connect sslErrors signal, etc.
Althoug I'm connecting to a server using a different certificate than the one set in "setCaCertificates" the Client connects without emitting any QNetworkReply::sslErrors. Even if I pass an empty certificate list to the configuration no error is emitted:
QSslConfiguration conf = QSslConfiguration::defaultConfiguration(); conf.setCaCertificates(QList<QSslCertificate>()); conf.setPeerVerifyMode(QSslSocket::VerifyPeer); QNetworkRequest request; request.setSslConfiguration(conf);
In Wireshark I can see that the server clearly sends a different certificate than my self-signed one. Am I missing something? From my understanding "setCaCertificates" should restrict the certificates to only the certificate from my file and "setPeerVerifyMode" should make sure the servers cert is validated.
Any help is much appreciated. :)
-
(Btw. I'm using Qt 5.4.1 on Ubuntu)
After browsing through some source files (qsslsocket_openssl.cpp, qsslcontext_openssl.cpp) it seems to me that the certificates set by setCaCertificates() are only added to the trusted certificates. Even if the setCaCertificates() list is empty, Qt will still load the system wide certificates and accept those by default. This would explain the behaviour of my application, since it is talking to a server using a root-CA signed certificate.
Maybe somebody with more insight than me can confirm/negate this?
In case I'm right: is there an Application-side way not to use system wide certificates? (without removing the system certificates globally?)
edit: I tried using QSslSocket::setDefaultCaCertificates(QList<QSslCertificate>()), which doesn't seem to have any effect, the servers certificate is still accepted by the client.
edit2: I've found a workaround to my problem by connecting to the QNetworkReply::encrypted callback and checking the certificate like this (not very nice but does its job):
connect(reply, &QNetworkReply::encrypted, [reply](){ QSslCertificate peerCert = reply->sslConfiguration().peerCertificate(); QSslCertificate ourCert = QSslConfiguration::defaultConfiguration().caCertificates().first(); if(peerCert != ourCert) { reply->abort(); handleError(); } });
edit3: I think I've found the proper solution for my problem, finally.. :-)
By setting the peerVerifyDepth (QSslConfiguration::setPeerVerifyDepth) to 1 only the leaf-certificate is verified. This way I can pin the certificate exactly to the single certificate I provide to the client.
Turns out it was a layer 8 problem, as the QSslSocket doc states:
"Note: If available, root certificates on Unix (excluding OS X) will be loaded on demand from the standard certificate directories."And QSslConfiguration::setPeerVerifyDepth:
"Sets the maximum number of certificates in the peer's certificate chain to be checked during the SSL handshake phase, to depth. Setting a depth of 0 means that no maximum depth is set, indicating that the whole certificate chain should be checked.":-)
-
Nice catch and thanks for sharing your findings !
Since you have it working now, please mark the thread as solved using the "Topic Tool" button so that other forum users may know a solution has been found :)
-
Although it is possible in my case to set the peer verfify depth to 1, this may not be applicable to all scenarios. For example: a developers company has its own, fully trusted CA which the application should trust too. Thus we wouldn't use a verify depth of 1, so much more certificates, signed by our CA are valid. But setting the depth to a higher level would also include the system wide certificates..
Afaik there are cases where companies managed to create valid (signed by some high level CA) certificates for domains such as google.com ( see https://security.googleblog.com/2015/09/improved-digital-certificate-security.html ), so IMHO there should be a simple, non-workaround way to tell Qt not to use system wide certificates (like QSslConfiguration::setUseSystemCertificates(false) ).
(But maybe there is already one which I haven't found so far. :-) )
-
Although it is possible in my case to set the peer verfify depth to 1, this may not be applicable to all scenarios. For example: a developers company has its own, fully trusted CA which the application should trust too. Thus we wouldn't use a verify depth of 1, so much more certificates, signed by our CA are valid. But setting the depth to a higher level would also include the system wide certificates..
Afaik there are cases where companies managed to create valid (signed by some high level CA) certificates for domains such as google.com ( see https://security.googleblog.com/2015/09/improved-digital-certificate-security.html ), so IMHO there should be a simple, non-workaround way to tell Qt not to use system wide certificates (like QSslConfiguration::setUseSystemCertificates(false) ).
(But maybe there is already one which I haven't found so far. :-) )
@yetanotherbender
That's the point.
I think using depth to 1 you can't be sure at 100% . Your application is always looking in system certs and some certs have "depth == 1".