QNetworkRequest with ssl local certificate - c++

I need to exchange data with server which requires local certificate (.crt file).
I try this:
loginRequest = QNetworkRequest(QUrl("https://somesite.com/login"));
QSslConfiguration sslConf = loginRequest.sslConfiguration();
QList<QSslCertificate> certs = QSslCertificate::fromPath(Preferences::certificatePath());
qDebug() << certs.first().issuerInfo(QSslCertificate::Organization); // prints name
sslConf.setLocalCertificate(certs.first());
qDebug() << "is valid " << sslConf.localCertificate().isValid(); // true
qDebug() << "is null " << sslConf.localCertificate().isNull(); // false
qDebug() << "protocol " << sslConf.protocol(); // 0
sslConf.setProtocol(QSsl::SslV3); // i also tried Qssl::AnyProtocol
qDebug() << "protocol " << sslConf.protocol(); // 0
// if i uncomment these i expect everithing to work
//QSslConfiguration::setDefaultConfiguration(sslConf);
//QSslSocket::addDefaultCaCertificate(certs.first());
//loginRequest.setSslConfiguration(sslConf);
QObject::connect(connectionManager, SIGNAL(sslErrors(QNetworkReply*,QList<QSslError>)), this, SLOT(printSslErrors2(QNetworkReply*,QList<QSslError>)));
m_reply = connectionManager->get(loginRequest);
QObject::connect(m_reply, SIGNAL(readyRead()), this, SLOT(getCookie()));
QObject::connect(m_reply, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(printSslErrors(QList<QSslError>)));
When this code executes i have the following messages in WireShark (filter: tcp && ssl && ip.addr == my_addr):
Client Hello
ServerHello, Certificate
Server Key Exchange, Certificate request, Server Hello Done
Alert (level: Warning, Description: no certificate), client key exchange, change cipher spec, encrypted handshake message
Alert (level: Fatal, Description: Handshake failure)
This is expected - the code to apply certificate is commented out, but the strange thing - I do not get any ssl errors from my QNetworkAccessManager and QNetworkReply (slots printSslErrors and printSslErrors2).
If i uncomment any of these 3 lines:
//QSslConfiguration::setDefaultConfiguration(sslConf);
//QSslSocket::addDefaultCaCertificate(certs.first());
//loginRequest.setSslConfiguration(sslConf);
I get NOTHING in wireshark (few SYN, ACK and FIN tcp messages, but no http or ssl traffic). Also there are still no errors from QNetworkAccessManager and QNetworkReply, so I have no idia what is going wrong.
Is there any chance to make Qt accept my local certificate or may be there is some 3d party qt-oriented lib to help me out?
P.S.: btw - ssl and https worked just fine a few days ago, before the server was changed to require client-side certificates.
P.P.S.: certificate is self signed if it makes any difference. Also I tried to 'install' it (the p12 file) into system and both Chrome and IE7 are able to use it and communicate with server.

Complete shot in the dark and goes on the assumption that Qt may in fact be reporting an error but you're not getting the signal.
You're connecting signals from your connectionManager to this have you included the Q_OBJECT macro in the header for this?
Also examine the output when you run your application as Qt may report issues connecting the signals/slots if that is indeed the case here.

SOLUTION, Part I:
I mostly solved this (the lack of connection), there were 2 reason:
1st - the apache server actually require private key (for some unknown reason, found it [here][1]), how to add private key:
QFile x(Preferences::certificateKeyPath());
x.open(QIODevice::ReadOnly);
pKey = QSslKey(x.readAll(),QSsl::Rsa);
QSslError error1(QSslError::SelfSignedCertificate, certs.first());
QSslError error2(QSslError::CertificateUntrusted, certs.first());
QList<QSslError> expectedSslErrors;
expectedSslErrors.append(error1);
expectedSslErrors.append(error2);
2d - the certificate I had was not very 'good'. I dont know what it really means or why it was not working, but when I got new certificate from server admin and added private key the handshake succeeded.
I still dont know how to catch sslErrors (for example to show user that his certificate is not working), but it is a good start
SOLUTION, Part II:
Solved the last part of the question (kina a woraround). It seems QNetworkReply not emitting SslErrors is a bug (or at least it does not work all the time or for all web-sites), found it [in Qt bug tracker][2]. And the workaround also from there: sinse we cant get SslErrors, we have to try and get smth else - [error][3], for example. It does not give detailed information about what have actually happend, but better than nothing. For me error code 6 - "the SSL/TLS handshake failed and the encrypted channel could not be established. The sslErrors() signal should have been emitted." is perfect (i dont care for anything else):
QObject::connect(m_reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(handleSslErrors(QNetworkReply::NetworkError)));
The important part: if the user has wrong certificate and/or key - the signal is emited. But it is also emited if certificate and key are correct. It seems auth might still be not perfect, but you can easily shut it down with
QObject::connect(m_reply, SIGNAL(sslErrors(QList<QSslError>)),
this, SLOT(printSslErrors(QList<QSslError>)));
Conclusion it seems they have fixed a lot of SSL bugs in Qt 4.8, so I hope release will be soon

Related

Widevine Session Update endless Loop

I am using libwidevinecdm.so from chrome to handle DRM protected data. I am currently successfully setting the widevine server certificate I get from the license server. I can also create a session with the pssh box of the media im trying to decode. So far everything is successful (all promises resolve fine).
(session is created like this: _cdm->CreateSessionAndGenerateRequest(promise_id, cdm::SessionType::kTemporary, cdm::InitDataType::kCenc, pssh_box.data(), static_cast<uint32_t>(pssh_box.size()));)
I am then getting a session message of type kLicenseRequest which I am forwarding to the respective license server. The license server responds with a valid response and the same amount of data as I can see in the browser when using Chrome. I am then passing this to my session like this:
_cdm->UpdateSession(promise_id, session_id.data(), static_cast<uint32_t>(session_id.size()),
license_response.data(), static_cast<uint32_t>(license_response.size()));
The problem now is that this promise never resolves. It keeps posting the kLicenseRequest message over and over again to my session without ever returning. Does this mean my response is wrong? Or is this something else?
Br
Yanick
The issue is caused by the fact, that everything in CreateSessionAndGenerateRequest is done synchronous - that means by the time CreateSessionAndGenerateRequest returns your promise will always be resolved.
The CDM will emit the kLicenseRequest inside CreateSessionAndGenerateRequest and it doesn't do so in a "fire & forget" fashion, but the function waits there until you have returned from the cdm::Host_10::OnSessionMessage. Since my implementation of OnSessionMessage was creating a synchronous HTTP Request to the license server before - also synchronously - calling the UpdateSession the entire chain ended up to be blocking.
So ultimately I was calling UpdateSession while still being inside CreateSessionAndGenerateRequest and I assume the CDM cannot handle this and reacts by creating a new session with the given ID and generating a request again, which of course triggered another UpdateSession and so on.
Ultimately the simplest way to break the cycle was to make something asynchronous. I decided to launch a separate thread when receiving kLicenseRequest, wait for a few milliseconds to make sure that CreateSessionAndGenerateRequest has time to finish (not sure if that is really required) and then issue the request to the license server.
The only change I had to do was adding the surrounding std::thread:
void WidevineSession::forward_license_request(const std::vector<uint8_t> &data) {
std::thread{
[=]() {
std::this_thread::sleep_for(std::chrono::milliseconds{100});
net::HttpRequest request{"POST", _license_server_url};
request.add_header("Authorization", fmt::format("Bearer {}", _access_token))
.byte_body(data);
const auto response = _client.execute(request);
if (response.status_code() != 200) {
log->error("Widevine license request not accepted by license server: {} {} ({})", response.status_code(), response.status_text(), utils::bytes_to_utf8(response.body()));
throw std::runtime_error{"Error requesting widevine license"};
}
log->info("Successfully requested widevine license from license server");
_adapter->update_session(this, _session_id, response.body());
}
}.detach();
}

How to know which certificate(s) QNetworkAccessManager uses during an SSL/TLS handshake?

I am using the class QNetworkAccessManager to establish an HTTPS connexion with a remote server. The connexion works well but i will need to update the certificates eventually.
I don't know much about SSL and i would like to clarify one point.
According to http://doc.qt.io/qt-5/qnetworkaccessmanager.html#encrypted, the initial SSL handshake seems to works fine because the following slot connected to the QNetworkAccessManager signal encrypted(QNetworkReply*) is called :
void MyAccessManager::onEncrypted(QNetworkReply *rep)
{
Q_UNUSED(rep)
qDebug() << "SSL/TLS handshake success";
}
This is an embedded Linux on which several .crt files are already installed in /usr/share/ca-certificates/mozilla/ and a list of .pem files in /etc/ssl/certs/
My understanding is that Qt is able to locate those files automatically because when i get the QSslConfiguration from the QNetworkRequest i can see the list of QSslCertificate in use, for example with :
QSslConfiguration conf = myNetworkRequest.sslConfiguration();
for (int i = 0; i < conf.caCertificates().size(); i++)
qDebug() << conf.caCertificates().at(i).subjectInfo(QSslCertificate::Organization);
and the output i have is :
QDEBUG : ("DigiCert Inc")
QDEBUG : ("VeriSign, Inc.")
QDEBUG : ("GlobalSign")
...
My question is how is it possible to see which of these certificates and files, were effectively used by QSslSocket for this specific connexion ?
Thank you.

How to properly use QWebSocket::sendBinaryMessage() method from a C++ client?

There are C++ Qt client & server. Following code works fine and the connection happens between the client and the server:
QWebSocket webSocket; // defined somewhere
...
QUrl url;
url.setScheme("ws"); // SSL encryption disabled
url.setHost(serverName); // "127.0.0.1" (can be "www.abc.com" too)
url.setPort(portNumber); // 2000
webSocket.open(url); // connects with the server properly
PRINT(url.toString()); // output: "ws://127.0.0.1:2000"
While sending the binary data, the function returns 0 instead of the number of bytes:
// though the message.size() is 80 bytes; the method returns 0
webSocket.sendBinaryMessage(QByteArray(message.data(), message.size()));
Note that, the QWebSocketServer works as expected.
We also have a Javascript client. That connects & sends the binary message properly. The only addition in that client is below:
webSocketJS.binaryType = "arraybuffer"; // <--- Javascript code
But such provision is not found in QWebSocket or I may have missed it.
Question: How to correctly send the binary data over the web connection?
For those interested, the server [pseudo] code is like below:
auto pWebSocket = WebServer.nextPendingConnection();
QObject::connect(pWebSocket, &QWebSocket::binaryMessageReceived,
[&] (const QByteArray& message) { DataRead(message, rManager); }); // This slot is not called as of now
It seems that there is no mention of how the QWebSocket::connected() signal is treated.
Due to internet delay and initial handshakes, the WebSocketServer may take some time to establish a connection. Ideally the binary/text message should be sent only after the connected() is received.
Before making a connection using webSocket.open(url), you should be handling this signal:
... // same code
QObject::connect(&webSocket, &QWebSocket::connected,
[&] ()
{
webSocket.sendBinaryMessage(QByteArray(message.data(), message.size()));
// ... set some internal state suggesting the established connection
}
webSocket.open(url);
Above is just a pseudo code to show that the first sendBinaryMessage() should be sent after the connect() signal. Ideally in real world code, you may want to set some state, which informs the client that the connection is established.
Similarly as mentioned in the comments, we should be checking for errors and disconnections as well.

Qt 4.8.4 how to check if file exists on http server

i need to check the if file exists on http server,
i have the full path and when i try it via browser all works
but when i try in code to do :
if(QFile::exists("http://www.foo.com/hidden/Support/myapp_1.1.2_installer.exe" ))
{
qDebug("file exists");
return true;
}
else
{
qDebug("file not exists");
}
as it writen here :
http://www.qtcentre.org/archive/index.php/t-43712.html?s=b9ae49962c9219aec93b43c514e2ba33
it allways returns me false no matter what ..
what im doing wrong and is it the right way to do this ?
The function QFile::exists is not able to create HTTP requests, which would be necessary to achieve what you are trying to do. The forum discussion you linked to works, because the guy is trying to access a network drive; this is naturally supported by the operating system.
To check whether the file exists, you will have to go the long way around - here is an explanation of how to communicate with a web server: http://developer.nokia.com/Community/Wiki/Creating_an_HTTP_network_request_in_Qt
The Qt class QFile can only deal with files on local filesystem.
You can try out using Qt Network module, probably like this:
QNetworkAccessManager *nam = new QNetworkAccessManager(this);
....
QNetworkRequest req(QUrl("http://www.foo.com/hidden/Support/myapp_1.1.2_installer.exe"));
QNetworkReply *reply = nam->get(req);
connect(reply, SIGNAL(metaDataChanged()),
this, SLOT(slotMetaDataChanged()));
connect(reply, SIGNAL(error(QNetworkReply::NetworkError)),
this, SLOT(slotNetworkError(QNetworkReply::NetworkError)));
NOTE THAT if you only want to check for the file's existence, you DON'T want to connect to the finished(QNetworkReply*) signal, because the signal will only be emitted when the network reply has finished processing. That is, the signal will only be emitted after the file is totally downloaded if the file exists.
Then,
slotMetaDataChanged() is called whenever you received new HTTP response headers, you can then check the QNetworkRequest::HttpStatusCodeAttribute for response HTTP codes like 200(OK) or 404(Not Found). In your case, if the returned HTTP code is 200, the file exists.
slotNetworkError() is called when the network request encounters an error, like "Host Not Found" or "Connection Refused", it's up to you to handle these situations in this slot.
The way you are trying to do this, is totally wrong. QFile isn't able to query a webserver. What you need to do is use the QNetworkAccessManager class. With this you can try to download your myapp_1.1.2_installer.exe. If the file does not exist, you will get an error message.
Why the poster on qtcente.org claims it worked for him... no idea. Maybe because his address was a local one. But it still smells fishy.
QNetworkAccessManager *manager = new QNetworkAccessManager(this);
connect(manager, SIGNAL(finished(QNetworkReply*)),
this, SLOT(replyFinished(QNetworkReply*)));
manager->head(QNetworkRequest(QUrl("www.foo.com/hidden/Support/myapp_1.1.2_installer.exe")));
The QNetworkReply in the replyFinished slot has the method NetworkError QNetworkReply::error() const.
You should get a QNetworkReply::ContentNotFoundError if your file does not exist.
Edit: As several comments pointed out, just to learn the existence of a file on a remote server using 'get' and connecting to replyFinished might not be the best of ideas. Might be ok for very small files, but definitely overkill for large blobs of data. I changed the 'get' request into a 'head'. Turner's solution will work, mine should now be an acceptable alternative.

How do I duplicate certificate authentication (Mumble (c/c++)) in Python?

Alright so, before I really get into this post, I am going to have to warn you that this might not be an easy fix. Whoever reads and is able to reply to this post must know a lot of c/c++, and at least some python to be able to answer the question I have above.
Basically, I have a connection method from Mumble (a VOIP client), that connects to a server and sends it an SSL certificate for authentication purposes. I also have a Python script that connects to the same Mumble VOIP server, but I don't send a certificate.
I need to modify my existing code to send a certificate, as the current Mumble client does.
--
Here is the C++ code that seems to send a certificate:
ServerHandler::ServerHandler() {
MumbleSSL::addSystemCA();
{
QList<QSslCipher> pref;
foreach(QSslCipher c, QSslSocket::defaultCiphers()) {
if (c.usedBits() < 128)
continue;
pref << c;
}
if (pref.isEmpty())
qFatal("No ciphers of at least 128 bit found");
QSslSocket::setDefaultCiphers(pref);
}
void ServerHandler::run() {
qbaDigest = QByteArray();
QSslSocket *qtsSock = new QSslSocket(this);
qtsSock->setPrivateKey(g.s.kpCertificate.second);
qtsSock->setLocalCertificate(g.s.kpCertificate.first.at(0));
QList<QSslCertificate> certs = qtsSock->caCertificates();
certs << g.s.kpCertificate.first;
qtsSock->setCaCertificates(certs);
cConnection = ConnectionPtr(new Connection(this, qtsSock));
qtsSock->setProtocol(QSsl::TlsV1);
qtsSock->connectToHostEncrypted(qsHostName, usPort);
void ServerHandler::serverConnectionConnected() {
tConnectionTimeoutTimer->stop();
qscCert = cConnection->peerCertificateChain();
qscCipher = cConnection->sessionCipher();
if (! qscCert.isEmpty()) {
const QSslCertificate &qsc = qscCert.last();
qbaDigest = sha1(qsc.publicKey().toDer());
bUdp = Database::getUdp(qbaDigest);
} else {
bUdp = true;
}
QStringList tokens = Database::getTokens(qbaDigest);
foreach(const QString &qs, tokens)
mpa.add_tokens(u8(qs));
QMap<int, CELTCodec *>::const_iterator i;
for (i=g.qmCodecs.constBegin(); i != g.qmCodecs.constEnd(); ++i)
mpa.add_celt_versions(i.key());
sendMessage(mpa);
--
And alas, this is what I do to connect to it right now (in python):
try:
self.socket.connect(self.host)
except:
print self.threadName,"Couldn't connect to server"
return
self.socket.setblocking(0)
print self.threadName,"connected to server"
--
Soo... what do I need to do more to my Python source to connect to servers that require a certificate? Because my source currently connects just fine to any mumble server with requirecert set to false. I need it to work on all servers, as this will be used on my own server (which ironically enough, has requirecerts on.)
I can pregenerate the certificate as a .p12 or w/e type file, so I don't need the program to generate the cert. I just need it to send the cert as the server wants it (as is done in the c++ I posted).
Please help me really soon! If you need more info, message me again.
Stripped out all irrelevant code, now it's just the code that deals with ssl.
From the C++ code it looks like you simply need to have ssl support and negotiate with the correct certification file and encrypt the payload with the correct private key. Those certifications and privates keys are most likely stored in your original program somewhere. If there are non-standard Authorities that the C++ might be loading up you'll need to find out where to put those root authorities in your python installation, or make sure python simply ignores those issues, which is less secure.
In python you can create a socket, like above, except with urllib. This library has support for HTTPs and providing the certification and private keys. URLOpener
Example Usage:
opener = urllib.URLopener(key_file = 'mykey.key', cert_file = 'mycert.cer')
self.socket = opener.open(url)
You'll probably need to make it more robust with the appropriate error checking and such, but hopefully this info will help you out.