I have a Client-Server application that was working with QTcpSocket. Now I would like to use an encrypted SSL connection instead, therefore I tried to switch to QSslSocket. But I can't establish a connection to server.
Here is the code for the client:
ConnectionHandler::ConnectionHandler(QString ip, int port, QObject *parent) : QObject(parent) {
// connect(this->socket, SIGNAL(connected()), this, SLOT(connected()));
connect(this->socket, SIGNAL(disconnected()), this, SLOT(disconnected()));
connect(this->socket, SIGNAL(readyRead()), this, SLOT(readyRead()));
connect(this->socket, SIGNAL(encrypted()), this, SLOT(encryptedReady()));
connect(this->socket, SIGNAL(sslErrors(const QList<QSslError> &)), this, SLOT(SSLerrorOccured(const QList<QSslError> &)));
connect(this->socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(socketError(QAbstractSocket::SocketError)));
connect(this->socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(socketStateChanged(QAbstractSocket::SocketState)));
this->ip = ip;
this->port = port;
}
void ConnectionHandler::socketStateChanged(QAbstractSocket::SocketState state) {
qDebug() << state;
}
void ConnectionHandler::socketError(QAbstractSocket::SocketError) {
qDebug() << this->socket->errorString();
}
void ConnectionHandler::encryptedReady() {
qDebug() << "READY";
}
void ConnectionHandler::SSLerrorOccured(const QList<QSslError> &) {
qDebug() << "EEROR";
}
void ConnectionHandler::connectToServer() {
// this->socket->connectToHost(this->ip, this->port);
this->socket->connectToHostEncrypted(this->ip, this->port);
if (!this->socket->waitForConnected(5000)) {
this->socket->close();
this->errorMsg = this->socket->errorString();
}
}
void ConnectionHandler::connected() {
qDebug() << "connected";
this->connectedHostAddress = this->socket->peerAddress().toString();
this->connectionEstablished = true;
this->localIP = this->socket->localAddress().toString();
this->localPort = this->socket->localPort();
}
Here the one for the server:
ClientHandler::ClientHandler() {
this->socket->setProtocol(QSsl::SslV3);
this->socket->setSocketOption(QAbstractSocket::KeepAliveOption, true);
}
void ClientHandler::run() {
if (!this->fd)
return;
connect(this->socket, SIGNAL(readyRead()), this, SLOT(readyRead()));
connect(this->socket, SIGNAL(disconnected()), this, SLOT(disconnected()), Qt::DirectConnection);
connect(this->socket, SIGNAL(encrypted()), this, SLOT(encryptedReady()));
connect(this->socket, SIGNAL(sslErrors(const QList<QSslError> &)), this, SLOT(sslErrorOccured(const QList<QSslError> &)));
connect(this->socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(socketError(QAbstractSocket::SocketError)));
connect(this->socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(socketStateChanged(QAbstractSocket::SocketState)));
if (!this->socket->setSocketDescriptor(this->fd)) {
emit error(socket->error());
return;
} else {
connect(this->socket, SIGNAL(encrypted()), this, SLOT(ready()));
this->socket->startServerEncryption();
}
this->peerIP = socket->peerAddress().toString();
QString tmp;
tmp.append(QString("%1").arg(socket->peerPort()));
this->peerPort = tmp;
QHostInfo::lookupHost(this->peerIP, this, SLOT(lookedUp(QHostInfo)));
}
void ClientHandler::socketStateChanged(QAbstractSocket::SocketState state) {
qDebug() << state;
}
void ClientHandler::socketError(QAbstractSocket::SocketError) {
qDebug() << this->socket->errorString();
}
void ClientHandler::setFileDescriptor(int fd) {
this->fd = fd;
}
void ClientHandler::ready() {
qDebug() << "READY";
}
void ClientHandler::sslErrorOccured(const QList<QSslError> &) {
qDebug() << "EEROR";
}
void ClientHandler::encryptedReady() {
qDebug() << "READY";
}
The output for the client I receive is:
QAbstractSocket::HostLookupState
QAbstractSocket::ConnectingState
QAbstractSocket::ConnectedState
"The remote host closed the connection"
QAbstractSocket::ClosingState
QAbstractSocket::UnconnectedState
and for the server:
QAbstractSocket::ConnectedState
"Error during SSL handshake: error:1408A0C1:SSL routines:SSL3_GET_CLIENT_HELLO:no shared cipher"
QAbstractSocket::UnconnectedState
Does anyone know how to fix this?
I assume that with non encrypted sockets everything is fine. So let's focus only on peculiarities of detailing with QSslSocket. I can share larger pieces or working code if needed. There is also a large story regarding SSL certificates that I will touch briefly here.
To start let's check you client on some external HTTP SSL servers, for example:
socket->connectToHostEncrypted("gmail.com", 443);
It should work immediately with default SSL protocol (without any setProtocol()). On the signal encrypted() you can write HTTP GET header and on the readyRead() the reply will come.
Now try to set socket->setProtocol(QSsl::SslV3); for "gmail.com". Expected result:
sslErrorOccured: ("The host name did not match any of the valid hosts for this certificate")
Note that it is not the error() signal but the sslErrors() signal notifying about certificate issues that can be ignored by client.
So, for simplicity let's work with default SSL protocol without using setProtocol().
Since the client is in working state we can move to the server. You were interrupted on the first level of SSL certification challenge. To start initializing SSL connection by the server you have to provide at least private encryption key and public certificate. That is your error:
"Error during SSL handshake: error:1408A0C1:SSL routines:SSL3_GET_CLIENT_HELLO:no shared cipher"
In the perfect case your certificate should be signed by an authentication authority company. In that case any client that has a list of such root certificates is able to check that your certificate is valid. However that service is paid and it may take few weeks. We can start with self signed certificate.
You can find various recipes for generating certificates, for example: "How to create a self-signed SSL Certificate which can be used for testing purposes or internal usage"
Short extract:
#Step 1: Generate a Private Key
openssl genrsa -des3 -out server.key 1024
#Step 2: Generate a CSR (Certificate Signing Request)
#Common Name (eg, your name or your server's hostname) []:example.com
openssl req -new -key server.key -out server.csr
#Step 3: Remove Passphrase from Key
cp server.key server.key.org
openssl rsa -in server.key.org -out server.key
#Step 4: Generating a Self-Signed Certificate
openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt
Now there are server.key and server.crt files. Those files should be passed by the server to QSslSocket before startServerEncryption():
socket->setPrivateKey("c:/test/ssl/cert/server.key", QSsl::Rsa);
socket->setLocalCertificate("c:/test/ssl/cert/server.crt");
Now the server can start SSL connection. You can test it with a browser. Typically browser will raise alarm that the connection is not trusted. That is because the certificate is self signed and it cannot protect from "man in the middle" attack. However, you can ask your browser to continue with that connection. So, on the server side you will have the HTTP GET header on the signalreadyRead().
Try to connect with the Qt client to that server. Now the error is raised by the client:
sslErrorOccured: ("The certificate is self-signed, and untrusted")
The server says in error(): "The remote host closed the connection". The client raised the SSL certificate error, but as with browsers we can continue with that connection. Put socket->ignoreSslErrors() in the sslErrors() signal handler:
void Client::sslErrorOccured(const QList<QSslError> & error) {
qDebug() << "sslErrorOccured:" << error;
socket->ignoreSslErrors(error);
}
That's it. Of course, your client should not accept all SSL certificate errors from all servers, since such errors mean that the connection is not really secure and can be hacked. It is just for testing. The object QSslError contains the certificate data, so it possible on your client side to accept only one specific self-signed certificate from your server and ignore all other such errors.
It is also possible to create your own 'authority root certificate' that you can manually write to your system. Then that certificate can be used for signing your server certificate. Your client will think that it is trusted connection, since it will be able to validate it by system root certificates.
Note that you can also have some issue with OpenSSL library. On Linux or OS X OpenSSL is in default setup, however for Windows it should be installed manually. Some compilation of OpenSSL may be already present in Windows system PATH, for example CMake has some limited OpenSSL library. However in general for Windows you should deploy OpenSSL together with your application.
Related
I compiled OpenSSL on my Windows machine and was able to do HTTPS queries with QNetworkAccessManager, but when I try to add HTTPS proxy
QNetworkAccessManager m_nm;
connect(&m_nm, &QNetworkAccessManager::proxyAuthenticationRequired, this, &BinanceReceiver::onProxyAuthenticationRequired);
connect(&m_nm, &QNetworkAccessManager::sslErrors, this, &BinanceReceiver::onSslErrors);
QNetworkProxy m_proxy(QNetworkProxy::HttpProxy, "host.com", 3129, "user", "password");
m_nm.setProxy(m_proxy);
QNetworkRequest request;
request.setUrl(MakeUrl(url));
QNetworkReply* reply = m_nm.get(request);
QObject::connect(reply, &QNetworkReply::finished, [this, reply]()
{
if (reply->error())
{
netLogger.error(reply->errorString());
}
else
{
//...
}
//Ensure it is deleted after this handler is called, but not before.
delete reply;
});
it stops working, I do not get the reply at all and neither proxyAuthenticationRequired nore sslErrors is triggered.
It is a Squid HTTPS proxy with the authentication.
Does QNetworkAccessManager support HTTPS proxy?
In FTPS, the client connect to the server, the server sending a welcome message; thus, then client send command "AUTH TLS",the server set CertificatećPrivateKey and startServerEncryption; what should the client do to finish this ssl handshake?
Server:
if ("AUTH" == command && "TLS" == commandParameters.toUpper())
{
auth();
}
void FtpControlConnection::auth()
{
reply("234 Initializing SSL connection.");
SslServer::setLocalCertificateAndPrivateKey(socket);
socket->startServerEncryption();
}
void SslServer::setLocalCertificateAndPrivateKey(QSslSocket *socket)
{
socket->setPrivateKey(":/ssl/server.key", QSsl::Rsa, QSsl::Pem, "1234");
Q_ASSERT(!socket->privateKey().isNull());
socket->setLocalCertificate(":/ssl/server.crt");
Q_ASSERT(!socket->localCertificate().isNull());
}
void SslServer::incomingConnection(PortableSocketDescriptorType socketDescriptor)
{
QSslSocket *socket = new QSslSocket(this);
if (socket->setSocketDescriptor(socketDescriptor)) {
addPendingConnection(socket);
} else {
delete socket;
}
}
client
socket->connectToHost(hostNameEdit->text(), portBox->value());
socket->write("AUTH TLS\r\n");
socket->waitForConnected();
socket->startClientEncryption();
socket->waitForEncrypted();
I wish the client finish the ssl shakehand by using startClientEncryption, and then I can use the client socket to send command or file; but it does not work; I know the client socket and the server socket already connect, but how to add ssl on this?
I am building a Web Api (using ASP.NET Web API), that connects via Secure WebSockets to an endpoint that our client exposed (wss://client-domain:4747/app/engineData). They gave me their certificates all in .pem format (root.pem and client.pem), and a private key (client_key.pem).
In order to get this done I did the following:
1) Converted client.pem and client_key.pem to a single .pfx file (used this here: Convert a CERT/PEM certificate to a PFX certificate)
2) I used the library System.Net.WebSockets, and wrote the following code:
private void InitWebSockesClient()
{
client = new ClientWebSocket();
client.Options.SetRequestHeader(HEADER_KEY, HEADER_VALUE); //Some headers I need
AddCertificatesSecurity();
}
private void AddCertificatesSecurity()
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls
| SecurityProtocolType.Tls11
| SecurityProtocolType.Tls12;
// I KNOW THIS SHOULDNT BE USED ON PROD, had to use it to make it
// work locally.
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
X509Certificate2 x509 = new X509Certificate2();
// this is the pfx I converted from client.pem and client_key
byte[] rawData = ReadFile(certificatesPath + #"\cert.pfx");
x509.Import(rawData, "123456", X509KeyStorageFlags.UserKeySet);
X509Certificate2Collection certificateCollection = new X509Certificate2Collection(x509);
client.Options.ClientCertificates = certificateCollection;
}
And when I want to connect I call:
public async Task<bool> Connect()
{
Uri uriToConnect = new Uri(URL);
await client.ConnectAsync(uriToConnect, CancellationToken.None);
return client.State == WebSocketState.Open;
}
This works fine locally. But whenever I deploy my Web Api on Azure (App Service) and make an HTTP request to it, it throws:
System.Net.WebSockets.WebSocketException - Unable to connect to the remote server.
And the inner exception:
System.Net.WebException - The request was aborted: Could not create SSL/TLS secure channel.
I enabled WebSockets on the AppService instance.
If I delete the line that always return true for the certificate validation, it doesn't work even locally, and the message says something like:
The remote certificate is invalid according to the validation procedure.
So definitely I got something wrong with the certificates, those three .pem files are being used right now in a similar [![enter image description here][1]][1]app in a node.js and work fine, the WSS connection is established properly. I don't really know what usage give to each one, so I am kind of lost here.
These are the cipher suites of the domain I want to connect: https://i.stack.imgur.com/ZFbo3.png
Inspired by Tom's comment, I finally made it work by just adding the certificate to the Web App in Azure App Service, instead of trying to use it from the filesystem. First I uploaded the .pfx file in the SSL Certificates section in Azure. Then, in the App settings, I added a setting called WEBSITE_LOAD_CERTIFICATES, with the thumbprint of the certificate I wanted (the .pfx).
After that, I modified my code to do work like this:
private void InitWebSockesClient()
{
client = new ClientWebSocket();
client.Options.SetRequestHeader(HEADER_KEY, HEADER_VALUE); //Some headers I need
AddCertificateToWebSocketsClient();
}
private void AddCertificateToWebSocketsClient()
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls11
| SecurityProtocolType.Tls12;
// this should really validate the cert
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
// reading cert from store
X509Store certStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
certStore.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certCollection =
certStore.Certificates.Find(X509FindType.FindByThumbprint,
CERTIFICATES_THUMBPRINT,
false);
if (certCollection.Count > 0)
{
client.Options.ClientCertificates = certCollection;
}
else
{
// handle error
}
certStore.Close();
}
Where CERTIFICATES_THUMBPRINT is a string (thumbsprint of your certificate, the one you saw on Azure).
In case you want to make it work locally, you just need to install the certificate on your computer, as otherwise it won't obviously find it on the store.
Reference for all this in Azure docs: https://learn.microsoft.com/en-us/azure/app-service/app-service-web-ssl-cert-load.
I'm having troubles setting the no_delay option on an asio socket. The following code runs well, except for the delay. My server receives the messages only after the 5000 ms expire.
#include <boost/asio.hpp>
#include <boost/thread.hpp>
using namespace boost::asio;
struct Client
{
io_service svc;
ip::tcp::socket sock;
Client() : svc(), sock(svc)
{
ip::tcp::resolver resolver(svc);
ip::tcp::resolver::iterator endpoint = resolver.resolve(boost::asio::ip::tcp::resolver::query("127.0.0.1", "32323"));
connect(sock, endpoint);
}
void send(std::string const& message) {
sock.send(buffer(message));
}
};
int main()
{
Client client;
client.send("hello world\n");
client.send("bye world\n");
boost::this_thread::sleep_for(boost::chrono::milliseconds(5000));
}
When trying to add a delay I have a few options:
1) Add the option before connection:
Client() : svc(), sock(svc)
{
ip::tcp::resolver resolver(svc);
ip::tcp::resolver::iterator endpoint = resolver.resolve(boost::asio::ip::tcp::resolver::query("127.0.0.1", "32323"));
sock.set_option(ip::tcp::no_delay(true));
connect(sock, endpoint);
}
However this throws set_option: Bad file descriptor
2) Add the option after the connection:
Client() : svc(), sock(svc)
{
ip::tcp::resolver resolver(svc);
ip::tcp::resolver::iterator endpoint = resolver.resolve(boost::asio::ip::tcp::resolver::query("127.0.0.1", "32323"));
connect(sock, endpoint);
sock.set_option(ip::tcp::no_delay(true));
}
However in this case, the option has no effect and I still see the delay. According to boost::asio with no_delay not possible? , I need to set the option after I've opened the socket but before I've connected the socket. So I've tried this:
Client() : svc(), sock(svc)
{
ip::tcp::endpoint endpoint( ip::address::from_string("127.0.0.1"), 32323);
sock.open(ip::tcp::v4());
sock.set_option(ip::tcp::no_delay(true));
sock.connect(endpoint);
}
However, I still see no effect. How can I set this option?
Edit: It's possible that I am not setting the option correctly on the server-side. This is the complete server code:
#include <boost/asio.hpp>
#include <iostream>
int main() {
boost::asio::io_service io_service;
boost::asio::ip::tcp::acceptor acceptor(io_service, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), 32323));
boost::asio::ip::tcp::socket socket(io_service);
acceptor.accept(socket);
socket.set_option(boost::asio::ip::tcp::no_delay(true));
boost::asio::streambuf sb;
boost::system::error_code ec;
while (boost::asio::read(socket, sb, ec)) {
std::cout << "received:\n" << &sb;
}
}
The client is properly setting the ip::tcp::no_delay option. However, the delay being observed is not the result of this option. Instead, it is the result of the server attempting to read more data than the client has sent, and when the client exits after sleeping 5000ms, the server's read operation completes with an error.
The read() operation initiated by the server will complete when either it has read streambuf.max_size() bytes or an error occurs. The streambuf's max size defaults to std::numeric_limits<std::size_t>::max() and can be configured in its constructor. In this case, the server attempts to read std::numeric_limits<std::size_t>::max() bytes, but the client only sends 22 bytes, sleeps 5000ms, then closes the socket. When the server observes that connection has closed, the read() operation completes with 22 bytes read and an error code of boost::asio::error::eof.
How can I send a custom response upon a custom request on a xampp/wamp based Apache server upon a connection to a specific port?
I'm trying to reply to the \0 a flash app is requesting in order to allow a crossdomain http GET request.
The flash policy request, is made to port 843 by default and i'd like to keep it that way.
The port should get a \0 (ending with a null char,\0 is just for the reference) and replying with something like:
<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
<site-control permitted-cross-domain-policies="master-only"/>
<allow-http-request-headers-from domain="*" headers="*" secure="true" />
<allow-access-from domain="*" to-ports="*" />
</cross-domain-policy>
As far as i know, the request should be return as a plain text, although Content-type, might be needed as well.
I've tried using the following: http://socketpolicyserver.com/ and although it listens to the port and accepts connections, it doesn't reply with the specified xml upon request.
Any methods / ways of achieving a proper reply will be appreciated,
with regards,
Mike.
!---UPDATE--->
I wrote a simple C# web server which listens to port 843, and serves the aforementioned policy - it worked out just fine, however, when using a SecureSocket connection for a secure connection (i.e opening a socket to a HTTPS/SSL protocol) - the request that is sent is encrypted using the hosts certificate. As far as i know, there's no way of listening or acquiring the servers certificate and decrypting the data via an external app hence, the only way is to somehow 'teach' Apache to respond with the crossdomain policy after a proper request is sent via an appropriate port.
Another idea i have is to read the server's certificate file stored in the Apache directory regardless of what happens on the server itself, though imo it's an overkill.
Would love to hear your comments,
Mike.
So here's how i eventually solved it:
I've used this guys code with some modifications: http://www.switchonthecode.com/tutorials/csharp-tutorial-simple-threaded-tcp-server
and created a simple multithreaded web server that listens to port 843, and provides a somewhat general policy upon the appropriate flash request.
There were several examples provided by Adobe, but for some reason, windows didn't like those.
Also note that if you're using the SecureSocket object of flash it should allegedly use the target servers (IIS'/Apaches'/Tomcats' etc..) SSL credentials and will initiate a client Authentication using the public key of the target servers certificate, then again, it might not so this code doesn't have SSL support, although i've started implementing one using C#'s SSL Streams, so far without any luck. If you can make it work via SSL, please let me know.
Hope this code will help,
Mike.
using System;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using System.Net;
using System.IO;
namespace TCPSexyServer
{
class Server
{
private TcpListener tcpListener;
private Thread listenThread;
private void ListenForClients(int p)
{
throw new NotImplementedException();
}
public Server()
{
this.tcpListener = new TcpListener(IPAddress.Any, 843);
this.listenThread = new Thread(new ThreadStart(ListenForClients));
this.listenThread.Start();
}
private void ListenForClients()
{
this.tcpListener.Start();
while (true)
{
//blocks until a client has connected to the server
TcpClient client = this.tcpListener.AcceptTcpClient();
//create a thread to handle communication
//with connected client
Thread clientThread = new Thread(new ParameterizedThreadStart(HandleClientComm));
clientThread.Start(client);
}
}
private void HandleClientComm(object client)
{
TcpClient tcpClient = (TcpClient)client;
NetworkStream clientStream = tcpClient.GetStream();
byte[] message = new byte[4096];
int bytesRead;
while (true)
{
bytesRead = 0;
try
{
//blocks until a client sends a message
bytesRead = clientStream.Read(message, 0, 4096);
}
catch
{
//a socket error has occured
break;
}
if (bytesRead == 0)
{
//the client has disconnected from the server
break;
}
//message has successfully been received
UTF8Encoding encoder = new UTF8Encoding();
string sentData = encoder.GetString(message, 0, bytesRead);
Console.WriteLine(sentData);
if (sentData == "<policy-file-request/>\0")
{
String policy = "<?xml version=\"1.0\"?>\n" +
"<!DOCTYPE cross-domain-policy SYSTEM \"/xml/dtds/cross-domain-policy.dtd\">\n" +
"<cross-domain-policy>\n" +
"<site-control permitted-cross-domain-policies=\"master-only\"/>\n" +
"<allow-http-request-headers-from domain=\"*\" headers=\"*\" secure=\"true\" />\n" +
"<allow-access-from domain=\"*\" to-ports=\"*\" />\n" +
"</cross-domain-policy>\0";
byte[] buffer = encoder.GetBytes(policy);
clientStream.Write(buffer, 0, buffer.Length);
clientStream.Flush();
Console.WriteLine(policy);
}
else
{
tcpClient.Close();
}
System.Diagnostics.Debug.WriteLine(encoder.GetString(message, 0, bytesRead));
}
tcpClient.Close();
}
public static void Main(string[] args)
{
Server blah = new Server();
}
}
}