summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAnton <anton@picapica.im>2026-09-21 03:43:27 +0400
committerAnton <anton@picapica.im>2026-09-21 03:43:27 +0400
commit1f7c350a83d6a515919255dc72266fd4e143cd1d (patch)
treea651fc8c7e8e3703e6076ab83e5407fccfe1936f
parent3ef95aee732391bf31220f3c7393771f52e55427 (diff)
downloadpica-pica-1f7c350a83d6a515919255dc72266fd4e143cd1d.tar.gz
pica-pica-1f7c350a83d6a515919255dc72266fd4e143cd1d.zip
pica-client: get rid of running the openssl binary, use in-process library calls instead
-rw-r--r--Makefile.am5
-rw-r--r--configure.ac5
-rw-r--r--debian/control2
-rw-r--r--pica-client/accountswindow.cpp4
-rw-r--r--pica-client/dhparam.cpp11
-rw-r--r--pica-client/dhparam.h6
-rw-r--r--pica-client/dialogs/registeraccountdialog.cpp8
-rw-r--r--pica-client/dialogs/registeraccountdialog.h8
-rw-r--r--pica-client/dialogs/viewcertdialog.cpp4
-rw-r--r--pica-client/main.cpp2
-rw-r--r--pica-client/openssllib.cpp846
-rw-r--r--pica-client/openssllib.h81
-rw-r--r--pica-client/openssltool.cpp222
-rw-r--r--pica-client/openssltool.h45
-rw-r--r--pica-client/pica-client.pro4
-rw-r--r--pica-client/skynet.cpp4
16 files changed, 959 insertions, 298 deletions
diff --git a/Makefile.am b/Makefile.am
index ca53df6..e0ed1cd 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -78,8 +78,8 @@ pica-client/msguirouter.cpp \
pica-client/msguirouter.h \
pica-client/nodes.cpp \
pica-client/nodes.h \
-pica-client/openssltool.cpp \
-pica-client/openssltool.h \
+pica-client/openssllib.cpp \
+pica-client/openssllib.h \
pica-client/picaactioncenter.cpp \
pica-client/picaactioncenter.h \
pica-client/pica-client.pro \
@@ -152,6 +152,7 @@ moc_contactlistwidget.cpp \
moc_forgedcertdialog.cpp \
moc_mainwindow.cpp \
moc_msguirouter.cpp \
+moc_openssllib.cpp \
moc_picaactioncenter.cpp \
moc_picasystray.cpp \
moc_registeraccountdialog.cpp \
diff --git a/configure.ac b/configure.ac
index 2a4ab03..e6639bb 100644
--- a/configure.ac
+++ b/configure.ac
@@ -167,11 +167,6 @@ AC_CHECK_PROGS([MOC], [$QT_MOC_NAMES], [notfound])
AS_IF([test "x$UIC" = xnotfound],[AC_MSG_ERROR(["uic program not found. Please install Qt development package"])])
AS_IF([test "x$MOC" = xnotfound],[AC_MSG_ERROR(["moc program not found. Please install Qt development package"])])
-#check for openssl command line tool
-
-AC_CHECK_PROG([OPENSSL_TOOL], [openssl], [yes], [no])
-AS_IF([test "x$OPENSSL_TOOL" != xyes],[AC_MSG_WARN(["openssl command line tool not found. This is a runtime dependency for pica-client!"])])
-
#check for FFmpeg libraries
PKG_CHECK_MODULES([FFMPEG],["libavcodec libavdevice libavformat libavutil libswresample libswscale"])
AC_SUBST([FFMPEG_CFLAGS])
diff --git a/debian/control b/debian/control
index 54fbdbb..e9ad93a 100644
--- a/debian/control
+++ b/debian/control
@@ -29,7 +29,7 @@ Homepage: http://picapica.im
Package: pica-client
Architecture: any
-Depends: ${shlibs:Depends}, ${misc:Depends}, alsa-utils, libqt5sql5-sqlite, openssl
+Depends: ${shlibs:Depends}, ${misc:Depends}, alsa-utils, libqt5sql5-sqlite
Recommends: va-driver-all
Description: Pica Pica instant messaging client
Client for secure distributed instant messaging system
diff --git a/pica-client/accountswindow.cpp b/pica-client/accountswindow.cpp
index bf8dc76..aeae51f 100644
--- a/pica-client/accountswindow.cpp
+++ b/pica-client/accountswindow.cpp
@@ -23,7 +23,7 @@
#include "dialogs/showpicaiddialog.h"
#include "../PICA_client.h"
#include "skynet.h"
-#include "openssltool.h"
+#include "openssllib.h"
#include <QMessageBox>
#include <QApplication>
#include <cstring>
@@ -146,7 +146,7 @@ void AccountsWindow::CreateAccount(QString CertFilename, QString PkeyFilename, b
return;
}
- QString name = OpenSSLTool::NameFromCertFile(CertFilename);
+ QString name = OpenSSLLib::NameFromCertFile(CertFilename);
Accounts::AccountRecord rec;
QString certfilename, pkeyfilename;
diff --git a/pica-client/dhparam.cpp b/pica-client/dhparam.cpp
index 3708334..c2d8b56 100644
--- a/pica-client/dhparam.cpp
+++ b/pica-client/dhparam.cpp
@@ -76,7 +76,7 @@ bool DHParam::StartNewDHParamGeneration(QString *errormsg)
QFile::remove(tempdhfile);
return dhparamgenerator -> osslt.GenDHParamSignal(PICA_CLIENT_DHPARAMBITS,
- tempdhfile, dhparamgenerator, SLOT(DHParamGenFinished(int, QProcess::ExitStatus)));
+ tempdhfile, dhparamgenerator, SLOT(DHParamGenFinished(int)));
}
QString DHParam::GetDHParamFilename()
@@ -92,7 +92,7 @@ QString DHParam::GetDHParamFilename()
return current_dhparam_file;
}
-void DHParam::DHParamGenFinished(int retval, QProcess::ExitStatus)
+void DHParam::DHParamGenFinished(int retval)
{
if (retval == 0)
{
@@ -104,7 +104,12 @@ void DHParam::DHParamGenFinished(int retval, QProcess::ExitStatus)
current_dhparam_file = config_dir + QDir::separator() + PICA_CLIENT_DHPARAMFILE;
- delete dhparamgenerator;
+ /*
+ * deleteLater, not delete: we are inside a slot the generator's own
+ * OpenSSLLib member is emitting to, and it still has to return
+ * through it.
+ */
+ dhparamgenerator->deleteLater();
dhparamgenerator = NULL;
}
diff --git a/pica-client/dhparam.h b/pica-client/dhparam.h
index ce49f44..930c277 100644
--- a/pica-client/dhparam.h
+++ b/pica-client/dhparam.h
@@ -20,7 +20,7 @@
#include <QObject>
#include <QString>
#include "globals.h"
-#include "openssltool.h"
+#include "openssllib.h"
class DHParam : public QObject
{
@@ -42,10 +42,10 @@ private:
static DHParam *dhparamgenerator;
static QString current_dhparam_file;
- OpenSSLTool osslt;
+ OpenSSLLib osslt;
private slots:
- void DHParamGenFinished(int retval, QProcess::ExitStatus);
+ void DHParamGenFinished(int retval);
};
#endif // DHPARAM_H
diff --git a/pica-client/dialogs/registeraccountdialog.cpp b/pica-client/dialogs/registeraccountdialog.cpp
index ff77951..cf92a06 100644
--- a/pica-client/dialogs/registeraccountdialog.cpp
+++ b/pica-client/dialogs/registeraccountdialog.cpp
@@ -178,10 +178,10 @@ void RegisterAccountDialog::Register()
btRegister->setEnabled(false);
ost.GenRSAKeySignal(config_dir + QDir::separator() + "privkey.pem", cbSetPassword->isChecked(),
- password->text(), rand, this, SLOT(stageSignCert(int, QProcess::ExitStatus)));
+ password->text(), rand, this, SLOT(stageSignCert(int)));
}
-void RegisterAccountDialog::stageSignCert(int retval, QProcess::ExitStatus)
+void RegisterAccountDialog::stageSignCert(int retval)
{
if (retval != 0)
{
@@ -196,10 +196,10 @@ void RegisterAccountDialog::stageSignCert(int retval, QProcess::ExitStatus)
ost.GenCertSignal(config_dir + QDir::separator() + "cert.pem",
config_dir + QDir::separator() + "privkey.pem",
password->text(), nickname->text(),
- this, SLOT(stageFinished(int, QProcess::ExitStatus)));
+ this, SLOT(stageFinished(int)));
}
-void RegisterAccountDialog::stageFinished(int retval, QProcess::ExitStatus)
+void RegisterAccountDialog::stageFinished(int retval)
{
if (retval != 0)
{
diff --git a/pica-client/dialogs/registeraccountdialog.h b/pica-client/dialogs/registeraccountdialog.h
index 447d181..2e4fe22 100644
--- a/pica-client/dialogs/registeraccountdialog.h
+++ b/pica-client/dialogs/registeraccountdialog.h
@@ -23,7 +23,7 @@
#include <QLabel>
#include <QCheckBox>
#include <QRegExpValidator>
-#include "../openssltool.h"
+#include "../openssllib.h"
class RegisterAccountDialog : public QDialog
{
@@ -46,7 +46,7 @@ private:
QCheckBox *cbUseDevRandom;
QRegExpValidator *vld;
- OpenSSLTool ost;
+ OpenSSLLib ost;
QByteArray cert_buf;
QString CertFilename_;
@@ -59,8 +59,8 @@ private slots:
void setPasswordClick();
void useDevRandomClick();
- void stageSignCert(int retval, QProcess::ExitStatus);
- void stageFinished(int retval, QProcess::ExitStatus);
+ void stageSignCert(int retval);
+ void stageFinished(int retval);
};
#endif // REGISTERACCOUNTDIALOG_H
diff --git a/pica-client/dialogs/viewcertdialog.cpp b/pica-client/dialogs/viewcertdialog.cpp
index f2cc918..6237ac4 100644
--- a/pica-client/dialogs/viewcertdialog.cpp
+++ b/pica-client/dialogs/viewcertdialog.cpp
@@ -15,7 +15,7 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "viewcertdialog.h"
-#include "../openssltool.h"
+#include "../openssllib.h"
#include <QVBoxLayout>
#include <QMessageBox>
#include <QFile>
@@ -53,7 +53,7 @@ ViewCertDialog::ViewCertDialog(QWidget *parent) :
void ViewCertDialog::SetCert(QString cert_pem)
{
- cert_text->setPlainText(OpenSSLTool::CertTextFromString(cert_pem));
+ cert_text->setPlainText(OpenSSLLib::CertTextFromString(cert_pem));
}
void ViewCertDialog::OK()
diff --git a/pica-client/main.cpp b/pica-client/main.cpp
index 3972ab8..3693276 100644
--- a/pica-client/main.cpp
+++ b/pica-client/main.cpp
@@ -466,7 +466,7 @@ static bool create_config_dir()
/*if (!QFile::exists(config_dir + QDir::separator() + PICA_CLIENT_DHPARAMFILE))
{
- OpenSSLTool osslt;
+ OpenSSLLib osslt;
msgBox.setText(QString(QObject::tr("Diffie-Hellman parameters will be generated. Please wait. This process can take several minutes...")));
msgBox.exec();
diff --git a/pica-client/openssllib.cpp b/pica-client/openssllib.cpp
new file mode 100644
index 0000000..1dc5ad1
--- /dev/null
+++ b/pica-client/openssllib.cpp
@@ -0,0 +1,846 @@
+/*
+ (c) Copyright 2012 - 2018 Anton Sviridenko
+ https://picapica.im
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, version 3.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+#include "openssllib.h"
+#include "../PICA_security.h"
+
+#include <openssl/asn1.h>
+#include <openssl/bio.h>
+#include <openssl/bn.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/pem.h>
+#include <openssl/rand.h>
+#include <openssl/rsa.h>
+#include <openssl/x509.h>
+#include <openssl/x509v3.h>
+#include <openssl/dh.h>
+
+#include <QFile>
+#include <QRegExp>
+#include <QThread>
+
+#define REGEXOLD "CN = [0-9]+\\#([^\\#\\$&\"\'=\\(\\)\\\\/\\|`!<>\\{\\}\\[\\]\\+\r\n]+)"
+#define REGEXNEW "CN = ([^\\#\\$&\"\'=\\(\\)\\\\/\\|`!<>\\{\\}\\[\\]\\+\r\n]+)"
+
+/* "-days 3680", as it was spelled on the openssl command line. */
+#define PICA_CERT_VALIDITY_DAYS 3680
+
+/*
+ * The serial number width "openssl req -x509" picks when it is not given one
+ * (SERIAL_RAND_BITS in apps/lib/apps.c).
+ */
+#define PICA_CERT_SERIAL_BITS 159
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+#define X509_getm_notBefore(x) X509_get_notBefore(x)
+#define X509_getm_notAfter(x) X509_get_notAfter(x)
+#endif
+
+/*
+ * "oneline,-esc_msb,utf8" and "multiline,-esc_msb,utf8", the two -nameopt
+ * arguments OpenSSLTool used, worked out the way the command line program's
+ * option parser works them out: "oneline" and "multiline" clear every other
+ * bit before setting their own, the two that follow set and clear a single
+ * bit each.
+ */
+static const unsigned long ONELINE_NAMEOPT =
+ (XN_FLAG_ONELINE & ~ASN1_STRFLGS_ESC_MSB) | ASN1_STRFLGS_UTF8_CONVERT;
+
+static const unsigned long MULTILINE_NAMEOPT =
+ (XN_FLAG_MULTILINE & ~ASN1_STRFLGS_ESC_MSB) | ASN1_STRFLGS_UTF8_CONVERT;
+
+static QString BioToString(BIO *bio)
+{
+ char *data = 0;
+ long len;
+
+ len = BIO_get_mem_data(bio, &data);
+
+ if (len <= 0 || !data)
+ return QString();
+
+ return QString::fromUtf8(data, (int)len);
+}
+
+static QByteArray BioToByteArray(BIO *bio)
+{
+ char *data = 0;
+ long len;
+
+ len = BIO_get_mem_data(bio, &data);
+
+ if (len <= 0 || !data)
+ return QByteArray();
+
+ return QByteArray(data, (int)len);
+}
+
+static X509 *CertFromPEM(const QByteArray &cert_pem)
+{
+ BIO *mem;
+ X509 *cert;
+
+ if (cert_pem.isEmpty())
+ return 0;
+
+ mem = BIO_new_mem_buf((void*)cert_pem.constData(), cert_pem.size());
+
+ if (!mem)
+ return 0;
+
+ cert = PEM_read_bio_X509(mem, NULL, NULL, NULL);
+
+ BIO_free(mem);
+ ERR_clear_error();
+
+ return cert;
+}
+
+/*
+ * The whole file is read through QFile rather than handed to BIO_new_file():
+ * config_dir can hold characters that do not survive the trip through the
+ * local 8 bit encoding a C file name would have to be, and Qt already knows
+ * how to open those paths on every platform we build for.
+ */
+static QByteArray ReadWholeFile(const QString &name)
+{
+ QFile f(name);
+
+ if (!f.open(QIODevice::ReadOnly))
+ return QByteArray();
+
+ return f.readAll();
+}
+
+/*
+ * The name as the "-subject -nameopt oneline,-esc_msb,utf8" output of the
+ * command line program spelled it, prefix and trailing newline included, so
+ * that the two regular expressions below see exactly what they used to see.
+ */
+static QString SubjectLine(X509 *cert)
+{
+ BIO *mem;
+ QString line;
+
+ mem = BIO_new(BIO_s_mem());
+
+ if (!mem)
+ return QString();
+
+ BIO_puts(mem, "subject=");
+ X509_NAME_print_ex(mem, X509_get_subject_name(cert), 0, ONELINE_NAMEOPT);
+ BIO_puts(mem, "\n");
+
+ line = BioToString(mem);
+
+ BIO_free(mem);
+
+ return line;
+}
+
+static QString NameFromSubjectLine(QString subject_line)
+{
+ QRegExp rxOld(REGEXOLD);
+ QRegExp rx(REGEXNEW);
+
+ QString name = subject_line;
+
+ if (name.contains(rxOld))
+ {
+ name = rxOld.cap(1);
+ }
+ else if (name.contains(rx))
+ {
+ name = rx.cap(1);
+ }
+
+ return name;
+}
+
+/*
+ * The generating half of the class. RSA key generation takes seconds and DH
+ * parameter generation at PICA_CLIENT_DHPARAMBITS takes minutes, and the
+ * thread calling is the one drawing the dialog that shows the progress, so
+ * the work happens here instead.
+ *
+ * No Q_OBJECT: the only signal needed is QThread::finished(), which QThread
+ * already declares.
+ */
+class OpenSSLLibWorker : public QThread
+{
+public:
+ enum Job
+ {
+ JobGenRSAKey,
+ JobGenCert,
+ JobGenDHParam
+ };
+
+ OpenSSLLibWorker(Job job, QObject *parent)
+ : QThread(parent), setpassword_(false), numbits_(0), job_(job), retval_(1)
+ {
+ }
+
+ /* Set by the caller before start(), read by it after finished(). */
+ QString keyfile_;
+ QString certfile_;
+ QString outfile_;
+ QString password_;
+ QString rand_;
+ QString subject_;
+ bool setpassword_;
+ quint32 numbits_;
+
+ int retval() const
+ {
+ return retval_;
+ }
+
+ QString errors() const
+ {
+ return errors_;
+ }
+
+protected:
+ void run();
+
+private:
+ void RunGenRSAKey();
+ void RunGenCert();
+ void RunGenDHParam();
+
+ bool WriteFile(const QString &name, const QByteArray &data);
+ void Fail(const char *what);
+
+ Job job_;
+ QString errors_;
+ int retval_;
+};
+
+void OpenSSLLibWorker::Fail(const char *what)
+{
+ BIO *mem = BIO_new(BIO_s_mem());
+
+ errors_ += QString::fromLatin1(what);
+ errors_ += QLatin1String("\n");
+
+ if (mem)
+ {
+ ERR_print_errors(mem);
+ errors_ += BioToString(mem);
+ BIO_free(mem);
+ }
+
+ ERR_clear_error();
+
+ retval_ = 1;
+}
+
+bool OpenSSLLibWorker::WriteFile(const QString &name, const QByteArray &data)
+{
+ QFile f(name);
+
+ if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate))
+ {
+ errors_ += QString("Failed to open %1 for writing: %2\n").arg(name).arg(f.errorString());
+ retval_ = 1;
+ return false;
+ }
+
+ if (f.write(data) != data.size() || !f.flush())
+ {
+ errors_ += QString("Failed to write %1: %2\n").arg(name).arg(f.errorString());
+ retval_ = 1;
+ return false;
+ }
+
+ return true;
+}
+
+void OpenSSLLibWorker::run()
+{
+ retval_ = 1;
+
+ switch (job_)
+ {
+ case JobGenRSAKey:
+ RunGenRSAKey();
+ break;
+
+ case JobGenCert:
+ RunGenCert();
+ break;
+
+ case JobGenDHParam:
+ RunGenDHParam();
+ break;
+ }
+}
+
+void OpenSSLLibWorker::RunGenRSAKey()
+{
+ EVP_PKEY_CTX *ctx = 0;
+ EVP_PKEY *pkey = 0;
+ BIO *mem = 0;
+ const EVP_CIPHER *cipher = 0;
+ QByteArray password;
+ unsigned char rndchar;
+ quint32 numbits;
+
+ /*
+ * Generate random-sized key with min size 4096 and multiply of 8.
+ * Max size is limited to 4096 + 99*8.
+ * See https://blog.josefsson.org/2016/11/03/why-i-dont-use-2048-or-4096-rsa-key-sizes/
+ * "Why I don’t Use 2048 or 4096 RSA Key Sizes"
+ */
+ numbits = PICA_RSA_MINKEYSIZE;
+
+ if (RAND_bytes(&rndchar, 1) != 1)
+ {
+ Fail("Random number generator failure");
+ return;
+ }
+
+ numbits += 8 * (rndchar % 100);
+
+ /*
+ * What "-rand <file>" did: an extra seeding source, /dev/random when the
+ * account dialog offers it and the user wants it. RAND_load_file() reads
+ * a small fixed amount from anything that is not a regular file, so this
+ * does not sit on the device draining it.
+ */
+ if (!rand_.isEmpty())
+ {
+ if (RAND_load_file(QFile::encodeName(rand_).constData(), -1) <= 0)
+ {
+ Fail("Failed to seed the random number generator from the requested source");
+ return;
+ }
+ }
+
+ if (setpassword_)
+ {
+ cipher = EVP_get_cipherbyname(PICA_PRIVKEYENCALGO + 1); /* skip the leading '-' */
+
+ if (!cipher)
+ {
+ Fail("Unknown private key encryption algorithm " PICA_PRIVKEYENCALGO);
+ return;
+ }
+
+ /*
+ * UTF-8 rather than the Latin-1 OpenSSLTool wrote down the pipe:
+ * AskPassword hands the passphrase to OpenSSL as UTF-8 when the
+ * account is opened later, so anything outside ASCII produced a key
+ * that could be created but never unlocked again.
+ */
+ password = password_.toUtf8();
+ }
+
+ ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
+
+ if (!ctx)
+ {
+ Fail("Failed to create an RSA key generation context");
+ goto end;
+ }
+
+ if (EVP_PKEY_keygen_init(ctx) <= 0)
+ {
+ Fail("Failed to initialize RSA key generation");
+ goto end;
+ }
+
+ if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, (int)numbits) <= 0)
+ {
+ Fail("Failed to set the RSA key size");
+ goto end;
+ }
+
+ if (EVP_PKEY_keygen(ctx, &pkey) <= 0)
+ {
+ Fail("Failed to generate the RSA keypair");
+ goto end;
+ }
+
+ mem = BIO_new(BIO_s_mem());
+
+ if (!mem)
+ {
+ Fail("Out of memory");
+ goto end;
+ }
+
+ if (!PEM_write_bio_PrivateKey(mem, pkey, cipher, NULL, 0, NULL,
+ setpassword_ ? (void*)password.constData() : NULL))
+ {
+ Fail("Failed to encode the private key");
+ goto end;
+ }
+
+ if (WriteFile(keyfile_, BioToByteArray(mem)))
+ retval_ = 0;
+
+end:
+
+ if (mem)
+ BIO_free(mem);
+
+ if (pkey)
+ EVP_PKEY_free(pkey);
+
+ if (ctx)
+ EVP_PKEY_CTX_free(ctx);
+}
+
+void OpenSSLLibWorker::RunGenCert()
+{
+ EVP_PKEY *pkey = 0;
+ X509 *cert = 0;
+ X509_NAME *name = 0;
+ BIGNUM *serial = 0;
+ BIO *keymem = 0;
+ BIO *mem = 0;
+ const EVP_MD *digest;
+ X509V3_CTX extctx;
+ QByteArray keydata;
+ QByteArray password;
+ QByteArray subject;
+
+ /*
+ * The extensions "openssl req -x509" picked up from the [ v3_ca ] section
+ * of openssl.cnf. Spelled out here so that a certificate no longer
+ * depends on which openssl.cnf the machine happens to have - the Windows
+ * build used to have to ship one of its own next to the executable.
+ *
+ * Order matters: "keyid:always" reads the subject key identifier of the
+ * issuer, which for a self signed certificate is the extension added just
+ * above it.
+ */
+ static const struct
+ {
+ int nid;
+ const char *value;
+ } extensions[] =
+ {
+ { NID_subject_key_identifier, "hash" },
+ { NID_authority_key_identifier, "keyid:always,issuer" },
+ { NID_basic_constraints, "critical,CA:true" }
+ };
+
+ unsigned int i;
+
+ digest = EVP_get_digestbyname(PICA_CERTDIGESTALGO + 1); /* skip the leading '-' */
+
+ if (!digest)
+ {
+ Fail("Unknown certificate digest algorithm " PICA_CERTDIGESTALGO);
+ return;
+ }
+
+ keydata = ReadWholeFile(keyfile_);
+
+ if (keydata.isEmpty())
+ {
+ errors_ += QString("Failed to read the private key file %1\n").arg(keyfile_);
+ retval_ = 1;
+ return;
+ }
+
+ keymem = BIO_new_mem_buf((void*)keydata.constData(), keydata.size());
+
+ if (!keymem)
+ {
+ Fail("Out of memory");
+ goto end;
+ }
+
+ password = password_.toUtf8();
+
+ pkey = PEM_read_bio_PrivateKey(keymem, NULL, NULL, (void*)password.constData());
+
+ if (!pkey)
+ {
+ Fail("Failed to read the private key");
+ goto end;
+ }
+
+ cert = X509_new();
+
+ if (!cert)
+ {
+ Fail("Out of memory");
+ goto end;
+ }
+
+ if (!X509_set_version(cert, 2)) /* X.509 v3 */
+ {
+ Fail("Failed to set the certificate version");
+ goto end;
+ }
+
+ serial = BN_new();
+
+ if (!serial
+ || !BN_rand(serial, PICA_CERT_SERIAL_BITS, BN_RAND_TOP_ANY, BN_RAND_BOTTOM_ANY)
+ || !BN_to_ASN1_INTEGER(serial, X509_get_serialNumber(cert)))
+ {
+ Fail("Failed to generate the certificate serial number");
+ goto end;
+ }
+
+ name = X509_NAME_new();
+
+ subject = subject_.toUtf8();
+
+ if (!name
+ || !X509_NAME_add_entry_by_NID(name, NID_commonName, MBSTRING_UTF8,
+ (unsigned char*)subject.data(), subject.size(), -1, 0))
+ {
+ Fail("Failed to build the certificate subject name");
+ goto end;
+ }
+
+ if (!X509_set_subject_name(cert, name) || !X509_set_issuer_name(cert, name))
+ {
+ Fail("Failed to set the certificate subject name");
+ goto end;
+ }
+
+ if (!X509_gmtime_adj(X509_getm_notBefore(cert), 0)
+ || !X509_time_adj_ex(X509_getm_notAfter(cert), PICA_CERT_VALIDITY_DAYS, 0, NULL))
+ {
+ Fail("Failed to set the certificate validity period");
+ goto end;
+ }
+
+ if (!X509_set_pubkey(cert, pkey))
+ {
+ Fail("Failed to set the certificate public key");
+ goto end;
+ }
+
+ X509V3_set_ctx_nodb(&extctx);
+ X509V3_set_ctx(&extctx, cert, cert, NULL, NULL, 0);
+
+ for (i = 0; i < sizeof(extensions) / sizeof(extensions[0]); i++)
+ {
+ X509_EXTENSION *ext;
+ QByteArray value(extensions[i].value);
+
+ ext = X509V3_EXT_conf_nid(NULL, &extctx, extensions[i].nid, value.data());
+
+ if (!ext)
+ {
+ Fail("Failed to build a certificate extension");
+ goto end;
+ }
+
+ if (!X509_add_ext(cert, ext, -1))
+ {
+ X509_EXTENSION_free(ext);
+ Fail("Failed to add a certificate extension");
+ goto end;
+ }
+
+ X509_EXTENSION_free(ext);
+ }
+
+ if (!X509_sign(cert, pkey, digest))
+ {
+ Fail("Failed to sign the certificate");
+ goto end;
+ }
+
+ mem = BIO_new(BIO_s_mem());
+
+ if (!mem)
+ {
+ Fail("Out of memory");
+ goto end;
+ }
+
+ if (!PEM_write_bio_X509(mem, cert))
+ {
+ Fail("Failed to encode the certificate");
+ goto end;
+ }
+
+ if (WriteFile(certfile_, BioToByteArray(mem)))
+ retval_ = 0;
+
+end:
+
+ if (mem)
+ BIO_free(mem);
+
+ if (name)
+ X509_NAME_free(name);
+
+ if (serial)
+ BN_free(serial);
+
+ if (cert)
+ X509_free(cert);
+
+ if (pkey)
+ EVP_PKEY_free(pkey);
+
+ if (keymem)
+ BIO_free(keymem);
+}
+
+void OpenSSLLibWorker::RunGenDHParam()
+{
+ EVP_PKEY_CTX *ctx = 0;
+ EVP_PKEY *pkey = 0;
+ BIO *mem = 0;
+
+ ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_DH, NULL);
+
+ if (!ctx)
+ {
+ Fail("Failed to create a Diffie-Hellman parameter generation context");
+ goto end;
+ }
+
+ if (EVP_PKEY_paramgen_init(ctx) <= 0)
+ {
+ Fail("Failed to initialize Diffie-Hellman parameter generation");
+ goto end;
+ }
+
+ if (EVP_PKEY_CTX_set_dh_paramgen_prime_len(ctx, (int)numbits_) <= 0)
+ {
+ Fail("Failed to set the Diffie-Hellman prime length");
+ goto end;
+ }
+
+ /* "-5", the generator OpenSSLTool asked the command line program for. */
+ if (EVP_PKEY_CTX_set_dh_paramgen_generator(ctx, 5) <= 0)
+ {
+ Fail("Failed to set the Diffie-Hellman generator");
+ goto end;
+ }
+
+ if (EVP_PKEY_paramgen(ctx, &pkey) <= 0)
+ {
+ Fail("Failed to generate the Diffie-Hellman parameters");
+ goto end;
+ }
+
+ mem = BIO_new(BIO_s_mem());
+
+ if (!mem)
+ {
+ Fail("Out of memory");
+ goto end;
+ }
+
+ if (!PEM_write_bio_Parameters(mem, pkey))
+ {
+ Fail("Failed to encode the Diffie-Hellman parameters");
+ goto end;
+ }
+
+ if (WriteFile(outfile_, BioToByteArray(mem)))
+ retval_ = 0;
+
+end:
+
+ if (mem)
+ BIO_free(mem);
+
+ if (pkey)
+ EVP_PKEY_free(pkey);
+
+ if (ctx)
+ EVP_PKEY_CTX_free(ctx);
+}
+
+OpenSSLLib::OpenSSLLib(QObject *parent) :
+ QObject(parent), worker_(0)
+{
+}
+
+OpenSSLLib::~OpenSSLLib()
+{
+ if (worker_)
+ {
+ worker_->disconnect(this);
+ worker_->wait();
+ }
+}
+
+static QString NameFromCertBytes(const QByteArray &cert_pem)
+{
+ X509 *cert;
+ QString name;
+
+ cert = CertFromPEM(cert_pem);
+
+ if (!cert)
+ return QString();
+
+ name = NameFromSubjectLine(SubjectLine(cert));
+
+ X509_free(cert);
+
+ return name;
+}
+
+QString OpenSSLLib::NameFromCertFile(QString cert_file)
+{
+ return NameFromCertBytes(ReadWholeFile(cert_file));
+}
+
+QString OpenSSLLib::NameFromCertString(QString cert_pem)
+{
+ return NameFromCertBytes(cert_pem.toLatin1());
+}
+
+QString OpenSSLLib::CertTextFromString(QString cert_pem)
+{
+ X509 *cert;
+ BIO *mem;
+ QString text;
+
+ cert = CertFromPEM(cert_pem.toLatin1());
+
+ if (!cert)
+ return QString();
+
+ mem = BIO_new(BIO_s_mem());
+
+ if (!mem)
+ {
+ X509_free(cert);
+ return QString();
+ }
+
+ if (X509_print_ex(mem, cert, MULTILINE_NAMEOPT, 0))
+ text = BioToString(mem);
+
+ BIO_free(mem);
+ X509_free(cert);
+ ERR_clear_error();
+
+ return text;
+}
+
+bool OpenSSLLib::StartWorker(OpenSSLLibWorker *worker, QObject *receiver, const char *finished_slot)
+{
+ if (worker_)
+ {
+ delete worker;
+ return false;
+ }
+
+ worker_ = worker;
+
+ errors_.clear();
+ output_.clear();
+
+ disconnect(this, SIGNAL(finished(int)), 0, 0);
+ connect(this, SIGNAL(finished(int)), receiver, finished_slot);
+
+ /*
+ * Queued, because QThread::finished() is emitted on the worker thread and
+ * this object lives on the one that called - so WorkerFinished(), and the
+ * receiver's slot after it, run where the caller expects them to.
+ */
+ connect(worker_, SIGNAL(finished()), this, SLOT(WorkerFinished()));
+
+ worker_->start();
+
+ return true;
+}
+
+void OpenSSLLib::WorkerFinished()
+{
+ OpenSSLLibWorker *worker = worker_;
+ int retval;
+
+ if (!worker)
+ return;
+
+ retval = worker->retval();
+ errors_ = worker->errors();
+
+ worker_ = 0;
+ worker->wait(); /* returns at once - run() is over by the time this is delivered */
+ worker->deleteLater();
+
+ /*
+ * Last thing done here: the receiver is allowed to delete us from its
+ * slot, the way DHParam disposes of its generator.
+ */
+ emit finished(retval);
+}
+
+bool OpenSSLLib::GenRSAKeySignal(QString keyfile, bool setpassword, QString password, QString rand, QObject *receiver, const char *finished_slot)
+{
+ OpenSSLLibWorker *worker = new OpenSSLLibWorker(OpenSSLLibWorker::JobGenRSAKey, this);
+
+ worker->keyfile_ = keyfile;
+ worker->setpassword_ = setpassword;
+ worker->password_ = password;
+ worker->rand_ = rand;
+
+ return StartWorker(worker, receiver, finished_slot);
+}
+
+bool OpenSSLLib::GenCertSignal(QString cert_file, QString keyfile, QString keypassword,
+ QString subject, QObject *receiver, const char *finished_slot)
+{
+ OpenSSLLibWorker *worker = new OpenSSLLibWorker(OpenSSLLibWorker::JobGenCert, this);
+
+ worker->certfile_ = cert_file;
+ worker->keyfile_ = keyfile;
+ worker->password_ = keypassword;
+ worker->subject_ = subject;
+
+ return StartWorker(worker, receiver, finished_slot);
+}
+
+bool OpenSSLLib::GenDHParamSignal(quint32 numbits, QString output_file, QObject *receiver, const char *finished_slot)
+{
+ OpenSSLLibWorker *worker = new OpenSSLLibWorker(OpenSSLLibWorker::JobGenDHParam, this);
+
+ worker->numbits_ = numbits;
+ worker->outfile_ = output_file;
+
+ return StartWorker(worker, receiver, finished_slot);
+}
+
+QString OpenSSLLib::ReadStdErr()
+{
+ QString errors = errors_;
+
+ errors_.clear();
+
+ return errors;
+}
+
+QString OpenSSLLib::ReadStdOut()
+{
+ QString output = output_;
+
+ output_.clear();
+
+ return output;
+}
diff --git a/pica-client/openssllib.h b/pica-client/openssllib.h
new file mode 100644
index 0000000..2c58820
--- /dev/null
+++ b/pica-client/openssllib.h
@@ -0,0 +1,81 @@
+/*
+ (c) Copyright 2012 - 2018 Anton Sviridenko
+ https://picapica.im
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, version 3.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+#ifndef OPENSSLLIB_H
+#define OPENSSLLIB_H
+
+#include <QObject>
+#include <QString>
+
+/*
+ * Key, certificate and Diffie-Hellman parameter generation, and the reading
+ * of certificates for display, done through libcrypto.
+ *
+ * This is the replacement for OpenSSLTool, which drove the "openssl" command
+ * line program through QProcess and parsed its output. The results are meant
+ * to be the same down to the encoding of the files produced - see
+ * tests/test_openssllib.cpp, which compares them against the command line
+ * program - but the binary is no longer a runtime dependency, there is no
+ * openssl.cnf to ship next to the executable, and passphrases no longer
+ * travel through a pipe.
+ *
+ * The three generating methods keep the shape of their OpenSSLTool
+ * counterparts: they return immediately and call the receiver's slot when the
+ * work is over. The work itself happens on a worker thread, because it is the
+ * GUI thread calling - RSA key generation takes seconds and DH parameter
+ * generation at PICA_CLIENT_DHPARAMBITS takes minutes. The slot signature is
+ * the one difference from OpenSSLTool: there is no process any more, so it is
+ * finished(int retval) rather than finished(int, QProcess::ExitStatus).
+ * retval keeps its old meaning - zero on success.
+ */
+
+class OpenSSLLibWorker;
+
+class OpenSSLLib : public QObject
+{
+ Q_OBJECT
+public:
+ explicit OpenSSLLib(QObject *parent = 0);
+ ~OpenSSLLib();
+
+ static QString NameFromCertFile(QString cert_file);
+ static QString NameFromCertString(QString cert_pem);
+ static QString CertTextFromString(QString cert_pem);
+
+ bool GenRSAKeySignal(QString keyfile, bool setpassword,
+ QString password, QString rand, QObject *receiver, const char *finished_slot);
+ bool GenCertSignal(QString cert_file, QString keyfile, QString keypassword, QString subject, QObject *receiver, const char *finished_slot);
+
+ bool GenDHParamSignal(quint32 numbits, QString output_file, QObject *receiver, const char *finished_slot);
+
+ QString ReadStdErr();
+ QString ReadStdOut();
+
+signals:
+ void finished(int retval);
+
+private slots:
+ void WorkerFinished();
+
+private:
+ bool StartWorker(OpenSSLLibWorker *worker, QObject *receiver, const char *finished_slot);
+
+ OpenSSLLibWorker *worker_;
+ QString errors_;
+ QString output_;
+};
+
+#endif // OPENSSLLIB_H
diff --git a/pica-client/openssltool.cpp b/pica-client/openssltool.cpp
deleted file mode 100644
index ec7a547..0000000
--- a/pica-client/openssltool.cpp
+++ /dev/null
@@ -1,222 +0,0 @@
-/*
- (c) Copyright 2012 - 2018 Anton Sviridenko
- https://picapica.im
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, version 3.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-*/
-#include "openssltool.h"
-#include "../PICA_security.h"
-#include <openssl/rand.h>
-#include <QProcess>
-#include <QRegExp>
-#include <QMessageBox>
-
-#define REGEXOLD "CN = [0-9]+\\#([^\\#\\$&\"\'=\\(\\)\\\\/\\|`!<>\\{\\}\\[\\]\\+\r\n]+)"
-#define REGEXNEW "CN = ([^\\#\\$&\"\'=\\(\\)\\\\/\\|`!<>\\{\\}\\[\\]\\+\r\n]+)"
-
-OpenSSLTool::OpenSSLTool()
-{
-}
-
-QString OpenSSLTool::NameFromCertFile(QString cert_file)
-{
- //
- QProcess openssl;
-
- openssl.start("openssl", QStringList() << "x509" << "-in" << cert_file << "-subject" << "-nameopt" << "oneline,-esc_msb,utf8" << "-noout");
- if (!openssl.waitForStarted())
- return QString();
-
- if (!openssl.waitForFinished())
- return QString();
-
- QByteArray result = openssl.readAll();
- QRegExp rxOld(REGEXOLD);
- QRegExp rx(REGEXNEW);
-
- QString name = QString::fromUtf8( result.constData());
-
- if (name.contains(rxOld))
- {
- name = rxOld.cap(1);
- }
- else if (name.contains(rx))
- {
- name = rx.cap(1);
- }
-
- //
- return name;
-}
-
-QString OpenSSLTool::NameFromCertString(QString cert_pem)
-{
- QProcess openssl;
-
- openssl.start("openssl", QStringList() << "x509" << "-subject" << "-nameopt" << "oneline,-esc_msb,utf8" << "-noout");
- if (!openssl.waitForStarted())
- return QString();
-
- openssl.write(cert_pem.toLatin1());
-
- if (!openssl.waitForFinished())
- return QString();
-
- QByteArray result = openssl.readAll();
- QRegExp rxOld(REGEXOLD);
- QRegExp rx(REGEXNEW);
-
- QString name = QString::fromUtf8( result.constData());
-
- if (name.contains(rxOld))
- {
- name = rxOld.cap(1);
- }
- else if (name.contains(rx))
- {
- name = rx.cap(1);
- }
-
- //
- return name;
-}
-
-QString OpenSSLTool::CertTextFromString(QString cert_pem)
-{
- QProcess openssl;
- qint64 ret;
- char *data;
- QByteArray ba;
-
- openssl.start("openssl", QStringList() << "x509" << "-noout" << "-text" << "-nameopt" << "multiline,-esc_msb,utf8");
-
- if (!openssl.waitForStarted())
- return QString();
-
- ba = cert_pem.toLatin1();
- ret = openssl.write(data = (char*)ba.constData());
- openssl.closeWriteChannel();
-
- if (!openssl.waitForFinished())
- return QString();
-
- return QString::fromUtf8(openssl.readAllStandardOutput().constData());
-}
-
-bool OpenSSLTool::GenRSAKeySignal(QString keyfile, bool setpassword, QString password, QString rand, QObject *receiver, const char *finished_slot)
-{
- QStringList args;
- quint32 numbits;
- unsigned char rndchar;
-
- args << "genrsa" << "-out" << keyfile;
-
- if (setpassword)
- args << "-passout" << "stdin" << PICA_PRIVKEYENCALGO;
-
- if (!rand.isEmpty())
- args << "-rand" << rand;
-
- /*
- * Generate random-sized key with min size 4096 and multiply of 8.
- * Max size is limited to 4096 + 99*8.
- * See https://blog.josefsson.org/2016/11/03/why-i-dont-use-2048-or-4096-rsa-key-sizes/
- * "Why I don’t Use 2048 or 4096 RSA Key Sizes"
- */
- numbits = PICA_RSA_MINKEYSIZE;
-
- RAND_pseudo_bytes(&rndchar, 1);
-
- numbits += 8 * (rndchar % 100);
-
- args << QString::number(numbits);
-
- openssl_.start("openssl", args);
-
-
- if (!openssl_.waitForStarted())
- return false;
-
- if (setpassword)
- {
- openssl_.write((password + "\n").toLatin1().constData());
- }
-
- openssl_.disconnect(&openssl_, SIGNAL(finished(int, QProcess::ExitStatus)), 0, 0);
- openssl_.connect(&openssl_, SIGNAL(finished(int, QProcess::ExitStatus)), receiver, finished_slot);
-
- return true;
-}
-
-bool OpenSSLTool::GenCertSignal(QString cert_file, QString keyfile, QString keypassword,
- QString subject, QObject *receiver, const char *finished_slot)
-{
- openssl_.start("openssl",
- QStringList()
- << "req"
-#ifdef WIN32
- << "-config" << "openssl.cnf"
-#endif
- << "-out" << cert_file
- << "-key" << keyfile
- << "-subj" << "/CN=" + subject
- << "-new"
- << "-x509"
- << "-days" << "3680"
- << "-utf8"
- << "-batch"
- << PICA_CERTDIGESTALGO
- << "-passin"
- << "stdin");
-
- if (!openssl_.waitForStarted())
- return false;
-
- openssl_.write((keypassword + "\n").toLatin1().constData());
-
- openssl_.disconnect(&openssl_, SIGNAL(finished(int, QProcess::ExitStatus)), 0, 0);
- openssl_.connect(&openssl_, SIGNAL(finished(int, QProcess::ExitStatus)), receiver, finished_slot);
-
- return true;
-}
-
-QString OpenSSLTool::ReadStdErr()
-{
- return QString::fromUtf8(openssl_.readAllStandardError().constData());
-}
-
-QString OpenSSLTool::ReadStdOut()
-{
- return QString::fromUtf8(openssl_.readAllStandardOutput().constData());
-}
-
-bool OpenSSLTool::GenDHParamSignal(quint32 numbits, QString output_file, QObject *receiver, const char *finished_slot)
-{
- //openssl dhparam -5 4096
- openssl_.start("openssl",
- QStringList()
- << "dhparam"
- << "-out" << output_file
- << "-5"
- << QString::number(numbits));
-
- if (!openssl_.waitForStarted())
- return false;
-
- openssl_.disconnect(&openssl_, SIGNAL(finished(int, QProcess::ExitStatus)), 0, 0);
- openssl_.connect(&openssl_, SIGNAL(finished(int, QProcess::ExitStatus)), receiver, finished_slot);
-
- return true;
-}
-
-
diff --git a/pica-client/openssltool.h b/pica-client/openssltool.h
deleted file mode 100644
index 8d59860..0000000
--- a/pica-client/openssltool.h
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- (c) Copyright 2012 - 2018 Anton Sviridenko
- https://picapica.im
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, version 3.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-*/
-#ifndef OPENSSLTOOL_H
-#define OPENSSLTOOL_H
-
-#include <QString>
-#include <QProcess>
-
-class OpenSSLTool
-{
-public:
- OpenSSLTool();
-
- static QString NameFromCertFile(QString cert_file);
- static QString NameFromCertString(QString cert_pem);
- static QString CertTextFromString(QString cert_pem);
-
- bool GenRSAKeySignal(QString keyfile, bool setpassword,
- QString password, QString rand, QObject *receiver, const char *finished_slot);
- bool GenCertSignal(QString cert_file, QString keyfile, QString keypassword, QString subject, QObject *receiver, const char *finished_slot);
-
- bool GenDHParamSignal(quint32 numbits, QString output_file, QObject *receiver, const char *finished_slot);
-
- QString ReadStdErr();
- QString ReadStdOut();
-
-private:
- QProcess openssl_;
-};
-
-#endif // OPENSSLTOOL_H
diff --git a/pica-client/pica-client.pro b/pica-client/pica-client.pro
index ddbcda8..279879a 100644
--- a/pica-client/pica-client.pro
+++ b/pica-client/pica-client.pro
@@ -38,7 +38,7 @@ SOURCES += main.cpp\
dialogs/viewcertdialog.cpp \
dialogs/forgedcertdialog.cpp \
dialogs/registeraccountdialog.cpp \
- openssltool.cpp \
+ openssllib.cpp \
history.cpp \
../PICA_id.c \
dhparam.cpp \
@@ -83,7 +83,7 @@ HEADERS += mainwindow.h \
../PICA_common.h \
../PICA_proto.h \
dialogs/registeraccountdialog.h \
- openssltool.h \
+ openssllib.h \
history.h \
../PICA_id.h \
dhparam.h \
diff --git a/pica-client/skynet.cpp b/pica-client/skynet.cpp
index 7427cd4..e0196f5 100644
--- a/pica-client/skynet.cpp
+++ b/pica-client/skynet.cpp
@@ -21,7 +21,7 @@
#include <QCoreApplication>
//#include "dialogs/viewcertdialog.h"
#include "contacts.h"
-#include "openssltool.h"
+#include "openssllib.h"
//#include <QMessageBox> //debug
//#include "dialogs/forgedcertdialog.h"
#include <QDebug>
@@ -324,7 +324,7 @@ void SkyNet::verify_peer_cert(QByteArray peer_id, QString cert_pem, bool *verifi
{
cnt.SetContactCert(peer_id, cert_pem);
- QString name = OpenSSLTool::NameFromCertString(cert_pem);
+ QString name = OpenSSLLib::NameFromCertString(cert_pem);
cnt.SetContactName(peer_id, name);