📜 ⬆️ ⬇️

Filling in Qt spaces - SSL key generation

Unfortunately, the Qt library, having all the necessary components for working with openSSL, does not include code for generating keys. Therefore, try to correct the situation.
First, consider the header file:
#ifndef SSLKEYGEN_H #define SSLKEYGEN_H #include <QtGui/QDialog> #include <QSslKey> #include <QMap> #include <QSslCertificate> #include <QWizard> #include <QWizardPage> #include <QProgressBar> #include <QDir> #include <QFileDialog> #include <QFileInfo> #include <QFile> #include <QLabel> bool isPrivateKeyCorrespondsToCertificate(QSslCertificate cert, QSslKey key); class CertificateGeneratorWizard : public QWizard { Q_OBJECT public: CertificateGeneratorWizard(QWidget * parent=0); QSslKey getPrivateKey(){return pkey;} QSslCertificate getCertificate(){return cert;} void accept(); private: QSslKey pkey; QSslCertificate cert; }; class RandSeeder : public QObject { Q_OBJECT public: void startSeeding(QList<QWidget *> w, int lim); void stop(); bool isCompleted() const{return (counter>=maximum);} protected: QMap<QWidget *, bool> wdgs; int counter; int maximum; bool eventFilter(QObject *obj, QEvent *event); signals: void step(); void stopped(); }; class getCertDataPage : public QWizardPage { Q_OBJECT public: getCertDataPage(QWidget *parent=0); }; class randomizePage : public QWizardPage { Q_OBJECT public: randomizePage(QWidget *parent=0); virtual void cleanupPage(); virtual void initializePage(); virtual bool isComplete() const; private: QProgressBar * progressBar; QLabel * label; RandSeeder seeder; private slots: void seedStep(); void seedStopped(); }; #endif // SSLKEYGEN_H 

The first declared function of the PrivateKeyCorrespondsToCertificate is intended, as you understand, to check the compliance of the certificate with the key - also an obvious omission of the Qt library. Here is the code from the sslkeygen.cpp file:
 bool isPrivateKeyCorrespondsToCertificate( QSslCertificate cert, QSslKey key ) { X509 *x; EVP_PKEY *k; x=(X509 *)cert.handle(); k=EVP_PKEY_new(); if(key.algorithm() == QSsl::Rsa) EVP_PKEY_assign_RSA(k, (RSA *)key.handle()); else EVP_PKEY_assign_DSA(k, (DSA *)key.handle()); if(X509_check_private_key(x,k)==1) return true; return false; } 

Everything is quite simple, if you know the OpenSSL library (it is documented, frankly, it doesn't matter - everything seems to be described, but somehow sparingly and without explanation)
The following declarations define a wizard generating a key and its page. The RandSeeder class stands somewhat apart, but about it later.
In order not to overload the article with unnecessary signs, we immediately proceed to the code. Below is a helper function, which, I confess honestly, I steal directly from Qt sources (forgive me Trolltech-Nokia):
 QByteArray QByteArray_from_X509(X509 *x509) { if (!x509) return QByteArray(); // Use i2d_X509 to convert the X509 to an array. int length = i2d_X509(x509, 0); QByteArray array; array.resize(length); char *data = array.data(); char **dataP = &data; unsigned char **dataPu = (unsigned char **)dataP; if (i2d_X509(x509, dataPu) < 0) return QByteArray(); // Convert to Base64 - wrap at 64 characters. array = array.toBase64(); QByteArray tmp; for (int i = 0; i < array.size() - 64; i += 64) { tmp += QByteArray::fromRawData(array.data() + i, 64); tmp += "\n"; } if (int remainder = array.size() % 64) { tmp += QByteArray::fromRawData(array.data() + array.size() - remainder, remainder); tmp += "\n"; } return "-----BEGIN CERTIFICATE-----\n" + tmp + "-----END CERTIFICATE-----\n"; } 

The function converts the X509 certificate store from OpenSSL's internal representation to QByteArray, from where we can easily get QSslCertificate.
Now, actually, as for the key generation wizard. First we will see the page requesting data to fill out the certificate. All the class code for this page consists only of the constructor:
 getCertDataPage::getCertDataPage( QWidget *parent ) : QWizardPage(parent) { setTitle(" "); QVBoxLayout * verticalLayout = new QVBoxLayout(); QLabel * label = new QLabel(tr("   .   .")); label->setWordWrap(true); verticalLayout->addWidget(label); QHBoxLayout * horizontalLayout = new QHBoxLayout(); label = new QLabel(tr("&:")); label->setMinimumSize(QSize(80, 0)); horizontalLayout->addWidget(label); QLineEdit * lineEdit = new QLineEdit(); horizontalLayout->addWidget(lineEdit); label->setBuddy(lineEdit); verticalLayout->addLayout(horizontalLayout); registerField("certSubject*",lineEdit); horizontalLayout = new QHBoxLayout(); label = new QLabel(tr("&:")); label->setMinimumSize(QSize(80, 0)); horizontalLayout->addWidget(label); lineEdit = new QLineEdit(); horizontalLayout->addWidget(lineEdit); label->setBuddy(lineEdit); verticalLayout->addLayout(horizontalLayout); registerField("certOrganization*",lineEdit); horizontalLayout = new QHBoxLayout(); label = new QLabel(tr("&E-Mail:")); label->setMinimumSize(QSize(80, 0)); horizontalLayout->addWidget(label); lineEdit = new QLineEdit(); horizontalLayout->addWidget(lineEdit); label->setBuddy(lineEdit); verticalLayout->addLayout(horizontalLayout); registerField("certEMail*",lineEdit); setLayout(verticalLayout); } 

This is a simple page constructor that creates three fields for entering certificate data. We use only three, but there may be other fields in your implementation.
Further, our wizard must somehow generate a good entropy. I will not explain what it means here, but the reliability of the key depends on how random it is (how good statistical indicators of randomness are). We use the mouse for this move, asking the user to move the mouse from side to side. This is where we will use the RandSeeder class. Here is its implementation:
 bool RandSeeder::eventFilter( QObject *obj, QEvent *event ) { if(event->type() == QEvent::MouseMove) { uint addition=QDateTime::currentDateTime().toTime_t(); QMouseEvent *mEvent = static_cast<QMouseEvent *>(event); addition*=mEvent->globalX(); addition*=mEvent->globalY(); RAND_seed(&addition,sizeof(uint)); counter++; emit step(); if(counter>maximum) stop(); return true; } else return QObject::eventFilter(obj, event); } void RandSeeder::startSeeding( QList<QWidget *> w, int lim ) { counter=0; if(lim<512) lim=512; maximum=lim; int i; for(i=0;i<w.count();i++) { wdgs.insert(w.at(i),w.at(i)->hasMouseTracking()); w.at(i)->installEventFilter(this); w.at(i)->setMouseTracking(true); } } void RandSeeder::stop() { QList<QWidget *> w=wdgs.keys(); int i; bool mt; for(i=0;i<w.count();i++) { mt=wdgs.value(w.at(i)); wdgs.remove(w.at(i)); w.at(i)->setMouseTracking(mt); w.at(i)->removeEventFilter(this); } emit stopped(); } 

Now let's see how the RandSeeder class is used on the second page of our wizard:
 randomizePage::randomizePage( QWidget *parent ) : QWizardPage(parent) { setTitle(" "); setFinalPage(true); QVBoxLayout * verticalLayout = new QVBoxLayout(); label = new QLabel(tr("        .")); label->setWordWrap(true); verticalLayout->addWidget(label); progressBar = new QProgressBar(); progressBar->setMaximum(1024); progressBar->setValue(0); verticalLayout->addWidget(progressBar); setLayout(verticalLayout); connect(&seeder, SIGNAL(step()), this, SLOT(seedStep())); connect(&seeder, SIGNAL(stopped()), this, SLOT(seedStopped())); } void randomizePage::seedStep() { progressBar->setValue(progressBar->value()+1); } void randomizePage::cleanupPage() { seeder.stop(); progressBar->setValue(0); } void randomizePage::initializePage() { progressBar->setValue(0); progressBar->setMaximum(1024); QList<QWidget *> wlst, chwdg; wlst << this << wizard() << progressBar << label; int i; chwdg=wizard()->findChildren<QWidget *>(); for(i=0;i<chwdg.count();i++) { if(!wlst.contains(chwdg.at(i))) wlst.append(chwdg.at(i)); } seeder.startSeeding(wlst ,1024); } void randomizePage::seedStopped() { if(seeder.isCompleted()) emit completeChanged(); } bool randomizePage::isComplete() const { if(seeder.isCompleted()) return true; return false; } 

Our RandSeeder class generates events at the beginning, end, and each step of entropy adjustment. We catch these events on the wizard page and show the user progress. The button to move to the next page becomes available only after the adjustment is completed, and the start of adjustment is performed in the function initializePage.
And here, in fact, we are ready to perform the key and certificate generation. The key generation is performed in the accept function of the wizard. But before we look at it, take a look at the wizard constructor:
 CertificateGeneratorWizard::CertificateGeneratorWizard(QWidget * parent) : QWizard(parent) { setOption(QWizard::NoCancelButton, false); setOption(QWizard::NoDefaultButton, false); setOption(QWizard::CancelButtonOnLeft, true); addPage(new getCertDataPage); addPage(new randomizePage); setWindowTitle(tr("   ")); } 

It is extremely simple - we set some options (to taste, which may be different for you), add our pages and set the title.
And finally, our long-awaited generator:
 void CertificateGeneratorWizard::accept() { EVP_PKEY *pk; RSA *rsa; X509 *x; X509_NAME *name=NULL; bool ok; //parameters int bits=4096; long serial=57; long days=1895; setCursor(Qt::WaitCursor); //create private key pk=EVP_PKEY_new(); rsa=RSA_generate_key(bits,RSA_F4,NULL,NULL); EVP_PKEY_assign_RSA(pk,rsa); { //save it to QSslKey BIO *bio = BIO_new(BIO_s_mem()); PEM_write_bio_RSAPrivateKey(bio, rsa, (const EVP_CIPHER *)0, NULL, 0, 0, 0); QByteArray pem; char *data; long size = BIO_get_mem_data(bio, &data); pem = QByteArray(data, size); BIO_free(bio); pkey=QSslKey(pem,QSsl::Rsa); ok=!pkey.isNull(); } x=X509_new(); X509_set_version(x,2); ASN1_INTEGER_set(X509_get_serialNumber(x),serial); X509_gmtime_adj(X509_get_notBefore(x),(long)60*60*24*(-2)); X509_gmtime_adj(X509_get_notAfter(x),(long)60*60*24*days); X509_set_pubkey(x,pk); name=X509_get_subject_name(x); QVariant fldVal=field("certSubject"); X509_NAME_add_entry_by_txt(name,"CN", MBSTRING_ASC, fldVal.toString().toLatin1().constData(), -1, -1, 0); fldVal=field("certOrganization"); if(!fldVal.isNull()) X509_NAME_add_entry_by_txt(name,"O", MBSTRING_ASC, fldVal.toString().toLatin1().constData(), -1, -1, 0); fldVal=field("certEMail"); if(!fldVal.isNull()) { X509_NAME_add_entry_by_txt(name,"emailAddress", MBSTRING_ASC, fldVal.toString().toLatin1().constData(), -1, -1, 0); } X509_set_issuer_name(x,name); X509_sign(x,pk,EVP_md5()); { QByteArray crt=QByteArray_from_X509(x); cert=QSslCertificate(crt); ok=cert.isValid(); } ok=isPrivateKeyCorrespondsToCertificate(cert,pkey); QWizard::accept(); } 

First we generate a private key and then a certificate. Note the certificate field names. If you want to generate certificates with a large number of fields, here you will need to fill them. See the OpenSSL documentation, there is somewhere a list of possible fields.
Well that's all.


Source: https://habr.com/ru/post/104036/

All Articles