Generating cert/key during run-time for QSslSocket
-
I have a client/server app that I'm writing, and I want to secure the communications for it with randomly-generated certs/keys for every client/server (ie, no authentication, merely for privacy). The docs imply this is possible by default in their examples (none of the examples set the cert/key/CA certs, which implies that there is a default), but in my tests I get a "no shared cipher" error when attempting to do that. Also, when running "openssl s_client -connect X:Y", I get the following:
@CONNECTED(00000003)
140555404101328:error:140790E5:SSL routines:SSL23_WRITE:ssl handshake failure:s23_lib.c:184:no peer certificate available
No client certificate CA names sent
SSL handshake has read 0 bytes and written 308 bytes
New, (NONE), Cipher is (NONE)
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
---@
... while my server app shows the following error:@Client '127.0.0.1' error: Error while reading: error:1408A0C1:SSL routines:SSL3_GET_CLIENT_HELLO:no shared cipher@
... which implies that there are no certs/keys being used, hence no encryption, which causes the handshake to fail.
Server code is fairly simple:
@void myClass::incomingConnection(qintptr handle)
QSslSocket * sock = new QSslSocket(this);
if(sock->setSocketDescriptor(handle))
{
connect(sock, &QSslSocket::encrypted, [sock, this]
{
addPendingConnection(sock);
Q_EMIT ready();
});
connect(sock, static_cast<void (QSslSocket:: *)(const QList<QSslError> &)>(&QSslSocket::sslErrors), [sock](const QList<QSslError> & errors)
{
for(const QSslError & e : errors)
{
qCritical("SSL Handshake error: %s", qPrintable(e.errorString()));
}
delete sock;
});
connect(sock, static_cast<void (QAbstractSocket:: *)(QAbstractSocket::SocketError)>(&QAbstractSocket::error), [sock](QAbstractSocket::SocketError e)
{
switch(e)
{
case QAbstractSocket::RemoteHostClosedError: return;
default:
qWarning("Client '%s' error: %s", qPrintable(sock->peerAddress().toString()), qPrintable(sock->errorString()));
sock->disconnectFromHost();
}
});
sock->ignoreSslErrors({QSslError::SelfSignedCertificate});
sock->startServerEncryption();
}
else
{
delete sock;
}
}@Interestingly, the QSslSocket::sslErrors() signal is never emitted, only the QAbstractSocket::error signal comes into play.
On a side-note, the two error signals had to be static_cast'ed to work because there is ambiguity between signals and functions (QSslSocket::sslErrors and QAbstractSocket::error are overloaded functions), and gcc 4.8 doesn't know how to resolve the ambiguity without me giving some additional hints (ie, required parameters in this case).
-
Ok, after a bit more research, I've found that Qt simply does not support randomized certificates and keys... so I've added the following to a subclass of QSslSocket to randomize a cert and key (simple enough to do since I can guarantee that OpenSSL exists if Qt supports OpenSSL on the client environment):
@sslSocket::sslSocket(QObject *parent) : QSslSocket(parent)
{
int ret;
RSA * r = nullptr;
BIGNUM * bne = nullptr;
BIO * bp_public = nullptr, * bp_private = nullptr;
long size;
char * buffer;const int bits = 2048;
unsigned long e = RSA_F4;// 1. generate rsa key
bne = BN_new();
q_check_ptr(bne);
if((ret = BN_set_word(bne, e)) != 1)
{
BN_free(bne);
qFatal("BN_set_word");
}r = RSA_new();
q_check_ptr(r);
if((ret = RSA_generate_key_ex(r, bits, bne, nullptr)) != 1)
{
BN_free(bne);
RSA_free(r);
qFatal("RSA_generate_key_ex");
}// 2. save public key
bp_public = BIO_new(BIO_s_mem());
q_check_ptr(bp_public);
if((ret = PEM_write_bio_RSAPublicKey(bp_public, r)) != 1)
{
BN_free(bne);
RSA_free(r);
BIO_free_all(bp_public);
qFatal("PEM_write_bio_RSAPublicKey");
}// 3. Save private key
bp_private = BIO_new(BIO_s_mem());
q_check_ptr(bp_private);
if((ret = PEM_write_bio_RSAPrivateKey(bp_private, r, nullptr, nullptr, 0, nullptr, nullptr)) != 1)
{
BN_free(bne);
RSA_free(r);
BIO_free_all(bp_public);
BIO_free_all(bp_private);
qFatal("PEM_write_bio_RSAPrivateKey");
}
size = BIO_get_mem_data(bp_public, &buffer);
q_check_ptr(buffer);
const QByteArray cert(buffer, size);
QSslCertificate c(cert);
if(c.isNull())
{
qFatal("Failed to generate a random client certificate");
}
setLocalCertificate(c);
size = BIO_get_mem_data(bp_private, &buffer);
q_check_ptr(buffer);
const QByteArray key(buffer, size);
setPrivateKey(QSslKey(key, QSsl::Rsa));
if(privateKey().isNull())
{
qFatal("Failed to generate a random private key");
}
BN_free(bne);
RSA_free(r);
BIO_free_all(bp_public);
BIO_free_all(bp_private);
}@This... well... doesn't appear to work, and it doesn't appear to be the fault of OpenSSL. Poking around with my debugger, the buffer is filled with the key properly, and the last byte is a '\n', which appears correct. However, the certificate ('c' in this case) is always failing the 'isNull()' check. Why? It's a valid certificate... :(
A sample of the buffer from the certificate (randomly generated cert):
"-----BEGIN RSA PUBLIC KEY-----\nMIIBCgKCAQEAxoCMMMfEGri487WOTGN0uVfXRaE7ji2IEHudOwh45X21CnMfeVtL\njo7SdBZ2QCAIEtbYx5MBSCjItiq+brquygDvohDceyA/V0dNZFhofxjYEH+xYcO1\nskuxID6UxL4SMuwOz/6HdDROsGsj5mGxD57zFPTXcPDXOyQeg1s+K0etk3kfcvj0\n3bYC+Fs/smNTd//OHOOIrN7xs4TS4PYb63ArzLDmSbK9/gEpXQ79dcxyVEEXWx+S\nSASWq+ado4SkfGE6rBC6wzOxt8t6gGLH9bNP2WcxAofsDqOFUXmpujBr8DQrws0b\n8tfjIW2lRM3CfLCERes+i2+8dv8VjM6XTwIDAQAB\n-----END RSA PUBLIC KEY-----\n"
-
Hi,
Maybe a silly question, but did you check the key QByteArray content to ensure that you have something correct in there ?
-
Yeah, I checked that in the debugger as well - the last character the QByteArray contains is a '\n', which is apparently correct.
On the other hand... I think I did this wrong. The docs don't say that QSslSocket works with an RSA key pair, they say it works with an X509 certificate/key pair... I'll need to figure out how to write something to generate something sensible.
I've written SSL apps for Qt before, but in those apps it was appropriate to use full X509 certificates (ie, a CA, signed keys and certs, etc). In this application, that would be inappropriate.
-
I can't tell you, I haven't looked yet at that part of the code.
Anyway, you should bring this question on the interest mailing list. You'll find there Qt's developers/maintainers. This forum is more oriented.
You can also try the IRC channel, you might find the ssl module guys there
-
It appears I was correct in that QSslSocket only supports X509 cert/key pairs. The following X509 generation code works properly (note: you may need to tell QSslSocket to ignore self-signed certificate errors)
@sslSocket::sslSocket(QObject *parent) : QSslSocket(parent)
{
EVP_PKEY * pkey = nullptr;
RSA * rsa = nullptr;
X509 * x509 = nullptr;
X509_NAME * name = nullptr;
BIO * bp_public = nullptr, * bp_private = nullptr;
const char * buffer = nullptr;
long size;pkey = EVP_PKEY_new();
q_check_ptr(pkey);
rsa = RSA_generate_key(2048, RSA_F4, nullptr, nullptr);
q_check_ptr(rsa);
EVP_PKEY_assign_RSA(pkey, rsa);
x509 = X509_new();
q_check_ptr(x509);
ASN1_INTEGER_set(X509_get_serialNumber(x509), 1);
X509_gmtime_adj(X509_get_notBefore(x509), 0); // not before current time
X509_gmtime_adj(X509_get_notAfter(x509), 31536000L); // not after a year from this point
X509_set_pubkey(x509, pkey);
name = X509_get_subject_name(x509);
q_check_ptr(name);
X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (unsigned char *)"US", -1, -1, 0);
X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (unsigned char *)"My Organization", -1, -1, 0);
X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char *)"My Common Name", -1, -1, 0);
X509_set_issuer_name(x509, name);
X509_sign(x509, pkey, EVP_sha1());
bp_private = BIO_new(BIO_s_mem());
q_check_ptr(bp_private);
if(PEM_write_bio_PrivateKey(bp_private, pkey, nullptr, nullptr, 0, nullptr, nullptr) != 1)
{
EVP_PKEY_free(pkey);
X509_free(x509);
BIO_free_all(bp_private);
qFatal("PEM_write_bio_PrivateKey");
}
bp_public = BIO_new(BIO_s_mem());
q_check_ptr(bp_public);
if(PEM_write_bio_X509(bp_public, x509) != 1)
{
EVP_PKEY_free(pkey);
X509_free(x509);
BIO_free_all(bp_public);
BIO_free_all(bp_private);
qFatal("PEM_write_bio_PrivateKey");
}
size = BIO_get_mem_data(bp_public, &buffer);
q_check_ptr(buffer);
setLocalCertificate(QSslCertificate(QByteArray(buffer, size)));
if(localCertificate().isNull())
{
qFatal("Failed to generate a random client certificate");
}
size = BIO_get_mem_data(bp_private, &buffer);
q_check_ptr(buffer);
setPrivateKey(QSslKey(QByteArray(buffer, size), QSsl::Rsa));
if(privateKey().isNull())
{
qFatal("Failed to generate a random private key");
}EVP_PKEY_free(pkey); // this will also free the rsa key
X509_free(x509);
BIO_free_all(bp_public);
BIO_free_all(bp_private);
}@The nice thing about the above code is that it all happens in memory, no certs/keys are stored on disk. This does not guarantee that someone cannot masquerade as a "false" server, but once a connection is established, it is guaranteed to be private.
-
Thanks for sharing ! :)
-
Hi,
I am trying to generate key and certificate in IOS using the same code.
But I am not able to create a valid QSSLKey.
In my logs I can see "Failed to generate a random private key" is printed.
Do we need some additional steps for generating key in IOS?Note: same code is working fine for android build.
Thanks,
-
Hi,
Which Qt version are you using ? There's been some changes in the backend handling for cryptography i.e. iOS doesn't provide OpenSSL. So depending on your Qt version you may have to rebuild it with your own iOS built OpenSSL or if recent enough it should use the native backend (SecureTransport).
-
Hi,
We are using Qt5.6.2.
As per Qt documentation from Qt 5.6 new SSL back-end for iOS and OS X based on Secure Transport is default SSL.
Do we need to implement some more changes in the exsisting sample code??Thanks