How to obtain peer's QSslCertificate after successful SSL handshake - c++

When doing a HTTPS request using Qt, I try to obtain the peer's certificate after the SSL handshake, in order to track future changes in the certificate.
QNetworkAccessManager nam;
nam.get(QNetworkRequest(QUrl("https://google.com/"))); // example URL
QObject::connect(&nam, &QNetworkAccessManager::encrypted, [](QNetworkReply *reply){
qDebug() << reply->sslConfiguration().peerCertificate();
});
According to the documentation of QNetworkAccessManager::encrypted, the above code should get access to the server's certificate:
This signal is emitted when an SSL/TLS session has successfully completed the initial handshake. At this point, no user data has been transmitted. The signal can be used to perform additional checks on the certificate chain, for example to notify users when the certificate for a website has changed. If the reply does not match the expected criteria then it should be aborted by calling QNetworkReply::abort() by a slot connected to this signal. The SSL configuration in use can be inspected using the QNetworkReply::sslConfiguration() method.
Also, from the documentation of QSslConfiguration::peerCertificate():
Because the peer certificate is set during the handshake phase, it is safe to access the peer certificate from a slot connected to the QSslSocket::sslErrors() signal, QNetworkReply::sslErrors() signal, or the QSslSocket::encrypted() signal.
However, the certificate is always empty. The debug output of the above code (after entering the application's event loop) is:
QSslCertificate( "" , "" , "1B2M2Y8AsgTpgAmY7PhCfg==" , () , () , QMap() , QDateTime(" Qt::LocalTime") , QDateTime(" Qt::LocalTime") )
On the other hand, if SSL errors where encountered, and if I connect to sslErrors, I do get the certificate. For example, for a default certificate under Ubuntu / Apache, which isn't accepted by Qt because of a missing host name in the certificate, I get for "https://localhost" the following:
QSslCertificate( "3" , "95:b0:93:f2:16:bb:22:cb" , "cXB6WctE7oZsrvZLU2BWUw==" , () , () , QMap() , QDateTime("2014-07-10 23:04:06.000 UTC Qt::UTC") , QDateTime("2024-07-07 23:04:06.000 UTC Qt::UTC") )
How can I get the certificate when the SSL handshake was successful?
I tested with the QNetworkAccessManager's signals, as well as with the QNetworkReply's signals; the results are the same.
MCVE can be found at https://bitbucket.org/leemes/ssltest, feel free to clone and fiddle:
git clone https://bitbucket.org/leemes/ssltest.git
I tested with Qt 5.4.0 and with Qt 5.3.1; results are the same.

It was a bug in Qt up to 5.4.0. It has been fixed in Qt 5.4.1.
https://bugreports.qt.io/browse/QTBUG-40401

Related

OpenSSL how to request client certificate, but don't verify it

I want to setup openssl c/c++ server request certificate from client but don't verify it.
I already use this piece of code to query certificate from client:
/** Force the client-side have a certificate **/
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr);
SSL_CTX_set_verify_depth(ctx, 4);
Can anybody give me example of such server code?
You should read the manual. It states:
void SSL_CTX_set_verify(SSL_CTX *ctx, int mode, SSL_verify_cb verify_callback);
[...]
The return value of verify_callback controls the strategy of the further verification process. If verify_callback returns 0, the verification process is immediately stopped with "verification failed" state. [...] If verify_callback always returns 1, the TLS/SSL handshake will not be terminated with respect to verification failures and the connection will be established. [...]
Writing a function that always returns 1 and passing it as verify_callback should help you solve the problem.

QNetworkAccessManager ssl handshake fails

I'm using QNAM and QNetworkRequest to make a post request to our server. On most machines it works fine, but on some it fails. All machines are running windows and connecting to the same Ubuntu server. Between two windows 7 machines, one works and one fails. Both machines should have the same ssleay32.dll and libeay32.dll (I include them in my installation package). After installing chrome on to the "broken" machine, it can now properly perform the SSL handshake. If I remove all certificates (intermediate and trusted) relating to our CA (Thawte) the SSL handshake fails again.
manager = new QNetworkAccessManager( this );
connect( manager, SIGNAL( finished( QNetworkReply* ) ),
this, SLOT( licenseServerReply( QNetworkReply* ) ) );
connect( manager, SIGNAL(sslErrors(QNetworkReply*,QList<QSslError>)),
this, SLOT( sslErrorOccured(QNetworkReply*,QList<QSslError>))
);
request.setUrl( "https://www.myURL.com/postFromMachine/");
postData.append( "computer-name=" );
postData.append( hostInfo.hostName() );
postData.append( "&" );
manager->post( request, postData );
I've connected a slot to the sslErrors() signal of the QNetworkRequest object and I get the following when the SSL handshake fails:
Debug: "The issuer certificate of a locally looked up certificate could not be found"
Debug: "The root CA certificate is not trusted for this purpose"
In an attempt to fix the missing cert I added all certificates (intermediate and trusted) on the working machine concerning our Root Authority "Thawte" and related certificates "Thawte consulting" etc... to an QSSLSocket object and passed that object to the QNAM via QSSLCOnfiguration. There were 9 of them total and it didn't seem to fix the issue. I added the following code before manager->post().
QSslSocket *socket = new QSslSocket( this );
socket->addCaCertificates( ":/Certs/thawte1.cer" );
socket->addCaCertificates( ":/Certs/thawte2.cer" );
// Several more certs ...
socket->addCaCertificates( ":/Certs/thawte9.cer" );
QSslConfiguration conf;
conf.setCaCertificates( socket->caCertificates() );
request.setSslConfiguration( conf );
After discussion on an IRC chat I was pointed to the following command on the server to ensure it has the proper certificate chain:
openssl s_client -verify 5 -CApath /etc/ssl/certs -connect www.myurl.com:443 -showcerts
It replied with [ok] and no errors. The issuer is always the subject of the next cert in the chain, and the chain goes all the way to the root CA. Unless there is more to check with that command, it seems good to me.
I also checked the chain of certificates on the following site, and it seems like everything was good (expect possibly a weak certificate for path #2)
https://www.ssllabs.com/ssltest/analyze.html
Am at a loss of what to do, how can I prove that the server has the right certificate chain? If it does have the right certificate chain, why don't all machines perform the handshake properly (i.e. what certificates or settings do I need to add/change in my application to get them to work)? I don't want to simply ignore the SSLErrors as I want to be 100% sure that I have an encrypted connection to the proper host.
Thanks in advance for all the help!
I modified the socket->addCaCertificate to addDefaultCaCertificate and switched them from .cer to .pem ( I got the .pem files directly from our server ). Now everything works great. Note, don't copy your private key from your server, ensure it is the CA public keys.

How do I set timeout for TIdHTTPProxyServer (not connection timout)

I am using TIdHTTPProxyServer and now I want to terminate connection when it is success to connect to the target HTTP server but receive no response for a long time(i.g. 3 mins)
Currently I find no related property or event about it. And even if the client terminate the connection before the proxy server receive the response from the HTTP server. OnException Event will not be fired until the proxy server receive the response. (That is, if the proxy server still receive no response from HTTP Server, I even do not know the client has already terminate the connection...)
Any help will be appreciated.
Thanks!
Willy
Indy uses infinite timeouts by default. To do what you are asking for, you need to set the ReadTimeout property of the outbound connection to the target server. You can access that connection via the TIdHTTPProxyServerContext.OutboundClient property. Use the OnHTTPBeforeCommand event, which is triggered just before the OutboundClient connects to the target server, eg:
#include "IdTCPClient.hpp"
void __fastcall TForm1::IdHTTPProxyServer1HTTPBeforeCommand(TIdHTTPProxyServerContext *AContext)
{
static_cast<TIdTCPClient*>(AContext->OutboundClient)->ReadTimeout = ...;
}

SSLSniff error: "SSL Accept Failed"

I'm trying to use SSLSniff's tool, and I have some technical issues... I've been looking for any similar problems, but the only results are from Twitter feeds, with no public useful answer. So, here it is:
(My version of SSLSniff is 0.8) I'm launching sslsniff with args:
sslsniff -a -c cert_and_key.pem -s 12345 -w out.log
where: cert_and_key.pem file is my authority's certificate concatenate with my unencrypted private key (in PEM format of course), and 12345 is the port where I redirect traffic with my iptables rule.
So sslsniff is correctly running:
INFO sslsniff : Certificate ready: [...]
[And anytime I connect with a client, there are these 2 following lines:]
DEBUG sslsniff : SSL Accept Failed!
DEBUG sslsniff : Got exception: Error with SSL connection.
On my client' side, I've register my AC as a trusted CA (with FF). Then when I connect through SSL I'm having the error:
Secure Connection Failed.
Error code: ssl_error_bad_cert_domain
What is super strange (moreover the fact that the certificate is not automatically accepted since it should be signed by my trusted CA) is that I cannot accept the forged certificate by clicking on "Add exception..." : I am always returning to the error page asking me to add an(other) exception...
Moreover, when I try to connect to for example: https://www.google.com, SSLSniff's log is completed with a new line :
DEBUG sslsniff : Encoded Length: 7064 too big for session cache, skipping...
Does anyone know what I'm doing wrong?
-- Edit to summer up the different answers --
The problem is that SSLSniff is not taking care of alternive names when it forges certificates. Apparently, Firefox refuses any certificate as soon as the Common Name doesn't match exactly the domain name.
For example, for Google.com : CN = www.google.com and there is no alternative name. So when you connect to https://www.google.com, it's working fine.
But for Google.fr : CN = *.google.fr, with these alternative names: *.google.fr and google.fr. So when you connect to https://www.google.fr, FF is looking for alternative names and, since it obviously doesn't find any, refuses the malformed certificate.
... So a solution would be to patch/commit... I don't know if Moxie Marlinspike has intentionally forgot this functionnality because it was too complicated, or if he was just not aware of this issue. Anyway, I'll try to have a look at the code.
The session encoded length error message: When caching the SSL session fails, it means that SSL session resumption on subsequent connections will fail, resulting in degraded performance, because a full SSL handshake needs to be done on every request. However, despite using the CPU more heavily, sslsniff will still work fine. The caching fails because the serialized representation of the OpenSSL session object (SSL_SESSION) was larger than the maximum size supported by sslsniff's session cache.
As for your real problem, note that sslsniff does not support X.509v3 subjectAltNames, so if you are connecting to a site whose hostname does not match the subject common name of the certificate, but instead matches only a subjectAltName, then sslsniff will generate a forged certificate without subjectAltNames, which will cause a hostname verification mismatch on the connecting client.
If your problem happens only for some specific sites, let us know the site so we can examine the server certificate using e.g. openssl s_client -connect host:port -showcerts and openssl x509 -in servercert.pem -text. If it happens for all sites, then the above is not the explanation.
Try a straight MITM with a cert you fully control , and make sure you don't have some OCSP/Perspectives/Convergance stuff meddling with things. Other than that, maybe add the cert to the OS trusted roots. I think FF on windows uses the windows cert store (start->run->certmgr.msc). It may also be worth trying with something like Burp to see if the error is localized to SSLSniff or all MITM attempts.

SSL_connect() produces certificate verify failure

I'm currently rewriting some existing technologies that were once using RSA Security's libraries into OpenSSL, but I'm starting to run into a few issues. Currently, all of the certificate verification code appears to be running without a hitch, until that is, I call SSL_connect().
Before, the call to SSL_connect() would produce SSL_ERROR_WANT_READ.
An answer to this issue on another forum suggested to me that SSL_connect() should be called until it stops producing SSL_ERROR_WANT_READ errors. Unforunately, this only produces something more confusing:
error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed
even though SSL_CTX_load_verify_locations() succeeds. Does anyone have any idea as to why a verification error wouldn't register with certificate methods and wait until SSL_connect() is triggered?
Usually this error means that the server certificate your client received in response to SSL_connect() couldn't be verified.
This can happen for different reasons:
If the server certificate is self-signed, you'll have to authorize that on your SSL_CONTEXT.
If the server certificate was signed by a certificate authority that is not in the list of trusted CA certificates
If the server certificate is not valid yet or not valid anymore
Actually, you should set a callback for certificate verification and make it accept any certificate, so you can focus on the connection part. Once it works, just tweak your callback or check your certificates to be valid.
At any time you get a failure, you can call some SSL_get_error() function that will indicate you why the certificate was rejected.
(Unfortunately, I can't access my code base right now, so I cannot give concrete examples)
Here are some code samples from my own SSL Socket wrapper class, adapted for you:
// ctx is a SSL_CONTEXT
// internalCertificateVerificationCallback is a callback static method (or function)
ctx->setVerify(SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, internalCertificateVerificationCallback);
Here is the definition for internalCertificateVerificationCallback:
int SecureSocket::internalCertificateVerificationCallback(int preverify_ok, X509_STORE_CTX* x509_ctx)
{
//preverify_ok contains 1 if the pre-verification succeeded, 0 otherwise.
return 1; // This accepts every certificate
}