qca/tools/qcatool/main.cpp
Tobias Junghans e6a15466ae Make retval of size()/count() match format string
Starting with Qt 6 qsizetype is returned (equals size_t).
2021-05-29 22:28:27 +00:00

4262 lines
139 KiB
C++

/*
* Copyright (C) 2005-2007 Justin Karneges <justin@affinix.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*
*/
#include <QtCrypto>
#include <QCoreApplication>
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QTextStream>
#include <QTimer>
#ifdef QT_STATICPLUGIN
#include "import_plugins.h"
#endif
const char *const APPNAME = "qcatool";
const char *const EXENAME = "qcatool";
const char *const VERSION = QCA_VERSION_STR;
static QStringList wrapstring(const QString &str, int width)
{
QStringList out;
QString simp = str.simplified();
QString rest = simp;
while (true) {
int lastSpace = -1;
int n;
for (n = 0; n < rest.length(); ++n) {
if (rest[n].isSpace())
lastSpace = n;
if (n == width)
break;
}
if (n == rest.length()) {
out += rest;
break;
}
QString line;
if (lastSpace != -1) {
line = rest.mid(0, lastSpace);
rest = rest.mid(lastSpace + 1);
} else {
line = rest.mid(0, n);
rest = rest.mid(n);
}
out += line;
}
return out;
}
class StreamLogger : public QCA::AbstractLogDevice
{
Q_OBJECT
public:
StreamLogger(QTextStream &stream)
: QCA::AbstractLogDevice(QStringLiteral("Stream logger"))
, _stream(stream)
{
QCA::logger()->registerLogDevice(this);
}
~StreamLogger() override
{
QCA::logger()->unregisterLogDevice(name());
}
void logTextMessage(const QString &message, enum QCA::Logger::Severity severity) override
{
_stream << now() << " " << severityName(severity) << " " << message << Qt::endl;
}
void logBinaryMessage(const QByteArray &blob, enum QCA::Logger::Severity severity) override
{
Q_UNUSED(blob);
_stream << now() << " " << severityName(severity) << " "
<< "Binary blob not implemented yet" << Qt::endl;
}
private:
inline const char *severityName(enum QCA::Logger::Severity severity)
{
if (severity <= QCA::Logger::Debug) {
return s_severityNames[severity];
} else {
return s_severityNames[QCA::Logger::Debug + 1];
}
}
inline QString now()
{
static QString format = QStringLiteral("yyyy-MM-dd hh:mm:ss");
return QDateTime::currentDateTime().toString(format);
}
private:
static const char *s_severityNames[];
QTextStream & _stream;
};
const char *StreamLogger::s_severityNames[] = {"Q", "M", "A", "C", "E", "W", "N", "I", "D", "U"};
static void output_plugin_diagnostic_text()
{
QString str = QCA::pluginDiagnosticText();
QCA::clearPluginDiagnosticText();
if (str[str.length() - 1] == QLatin1Char('\n'))
str.truncate(str.length() - 1);
const QStringList lines = str.split(QLatin1Char('\n'), Qt::KeepEmptyParts);
for (int n = 0; n < lines.count(); ++n)
fprintf(stderr, "plugin: %s\n", qPrintable(lines[n]));
}
static void output_keystore_diagnostic_text()
{
QString str = QCA::KeyStoreManager::diagnosticText();
QCA::KeyStoreManager::clearDiagnosticText();
if (str[str.length() - 1] == QLatin1Char('\n'))
str.truncate(str.length() - 1);
const QStringList lines = str.split(QLatin1Char('\n'), Qt::KeepEmptyParts);
for (int n = 0; n < lines.count(); ++n)
fprintf(stderr, "keystore: %s\n", qPrintable(lines[n]));
}
static void output_message_diagnostic_text(QCA::SecureMessage *msg)
{
QString str = msg->diagnosticText();
if (str[str.length() - 1] == QLatin1Char('\n'))
str.truncate(str.length() - 1);
const QStringList lines = str.split(QLatin1Char('\n'), Qt::KeepEmptyParts);
for (int n = 0; n < lines.count(); ++n)
fprintf(stderr, "message: %s\n", qPrintable(lines[n]));
}
class AnimatedKeyGen : public QObject
{
Q_OBJECT
public:
static QCA::PrivateKey makeKey(QCA::PKey::Type type, int bits, QCA::DLGroupSet set)
{
AnimatedKeyGen kg;
kg.type = type;
kg.bits = bits;
kg.set = set;
QEventLoop eventLoop;
kg.eventLoop = &eventLoop;
QTimer::singleShot(0, &kg, &AnimatedKeyGen::start);
eventLoop.exec();
QCA::PrivateKey key = kg.key;
return key;
}
private:
QCA::PKey::Type type;
int bits;
QCA::DLGroupSet set;
QEventLoop * eventLoop;
QCA::KeyGenerator gen;
QCA::DLGroup group;
QCA::PrivateKey key;
QTimer t;
int x;
AnimatedKeyGen()
{
gen.setBlockingEnabled(false);
connect(&gen, &QCA::KeyGenerator::finished, this, &AnimatedKeyGen::gen_finished);
connect(&t, &QTimer::timeout, this, &AnimatedKeyGen::t_timeout);
}
private Q_SLOTS:
void start()
{
printf("Generating Key ... ");
fflush(stdout);
x = 0;
t.start(125);
if (type == QCA::PKey::RSA)
gen.createRSA(bits);
else
gen.createDLGroup(set);
}
void gen_finished()
{
if (type == QCA::PKey::DSA || type == QCA::PKey::DH) {
if (group.isNull()) {
group = gen.dlGroup();
if (type == QCA::PKey::DSA)
gen.createDSA(group);
else
gen.createDH(group);
return;
}
}
key = gen.key();
printf("\b");
if (!key.isNull())
printf("Done\n");
else
printf("Error\n");
eventLoop->exit();
}
void t_timeout()
{
if (x == 0)
printf("\b/");
else if (x == 1)
printf("\b-");
else if (x == 2)
printf("\b\\");
else if (x == 3)
printf("\b|");
fflush(stdout);
++x;
x %= 4;
}
};
class KeyStoreMonitor : public QObject
{
Q_OBJECT
public:
static void monitor()
{
KeyStoreMonitor monitor;
QEventLoop eventLoop;
monitor.eventLoop = &eventLoop;
QTimer::singleShot(0, &monitor, &KeyStoreMonitor::start);
eventLoop.exec();
}
private:
QEventLoop * eventLoop;
QCA::KeyStoreManager * ksm;
QList<QCA::KeyStore *> keyStores;
QCA::ConsolePrompt * prompt;
private Q_SLOTS:
void start()
{
// user can quit the monitoring by pressing enter
printf("Monitoring keystores, press 'q' to quit.\n");
prompt = new QCA::ConsolePrompt(this);
connect(prompt, &QCA::ConsolePrompt::finished, this, &KeyStoreMonitor::prompt_finished);
prompt->getChar();
// kick off the subsystem
QCA::KeyStoreManager::start();
// setup keystore manager for monitoring
ksm = new QCA::KeyStoreManager(this);
connect(ksm, &QCA::KeyStoreManager::keyStoreAvailable, this, &KeyStoreMonitor::ks_available);
foreach (const QString &keyStoreId, ksm->keyStores())
ks_available(keyStoreId);
}
void ks_available(const QString &keyStoreId)
{
QCA::KeyStore *ks = new QCA::KeyStore(keyStoreId, ksm);
connect(ks, &QCA::KeyStore::updated, this, &KeyStoreMonitor::ks_updated);
connect(ks, &QCA::KeyStore::unavailable, this, &KeyStoreMonitor::ks_unavailable);
keyStores += ks;
printf(" available: %s\n", qPrintable(ks->name()));
}
void ks_updated()
{
QCA::KeyStore *ks = (QCA::KeyStore *)sender();
printf(" updated: %s\n", qPrintable(ks->name()));
}
void ks_unavailable()
{
QCA::KeyStore *ks = (QCA::KeyStore *)sender();
printf(" unavailable: %s\n", qPrintable(ks->name()));
keyStores.removeAll(ks);
delete ks;
}
void prompt_finished()
{
QChar c = prompt->resultChar();
if (c == QLatin1Char('q') || c == QLatin1Char('Q')) {
eventLoop->exit();
return;
}
prompt->getChar();
}
};
class PassphrasePrompt : public QObject
{
Q_OBJECT
public:
class Item
{
public:
QString promptStr;
int id;
QCA::Event event;
};
QCA::EventHandler handler;
bool allowPrompt;
bool warned;
bool have_pass;
bool used_pass;
QCA::SecureArray pass;
QCA::ConsolePrompt *prompt;
int prompt_id;
QCA::Event prompt_event;
QList<Item> pending;
bool auto_accept;
QCA::KeyStoreManager ksm;
QList<QCA::KeyStore *> keyStores;
PassphrasePrompt()
: handler(this)
, ksm(this)
{
allowPrompt = true;
warned = false;
have_pass = false;
auto_accept = false;
prompt = nullptr;
connect(&handler, &QCA::EventHandler::eventReady, this, &PassphrasePrompt::ph_eventReady);
handler.start();
connect(&ksm, &QCA::KeyStoreManager::keyStoreAvailable, this, &PassphrasePrompt::ks_available);
foreach (const QString &keyStoreId, ksm.keyStores())
ks_available(keyStoreId);
}
~PassphrasePrompt() override
{
qDeleteAll(keyStores);
if (prompt) {
handler.reject(prompt_id);
delete prompt;
}
while (!pending.isEmpty())
handler.reject(pending.takeFirst().id);
}
void setExplicitPassword(const QCA::SecureArray &_pass)
{
have_pass = true;
used_pass = false;
pass = _pass;
}
private Q_SLOTS:
void ph_eventReady(int id, const QCA::Event &e)
{
if (have_pass) {
// only allow using an explicit passphrase once
if (used_pass) {
handler.reject(id);
return;
}
used_pass = true;
handler.submitPassword(id, pass);
return;
}
if (!allowPrompt) {
if (!have_pass && !warned) {
warned = true;
fprintf(stderr, "Error: no passphrase specified (use '--pass=' for none).\n");
}
handler.reject(id);
return;
}
if (e.type() == QCA::Event::Password) {
QString type = QStringLiteral("password");
if (e.passwordStyle() == QCA::Event::StylePassphrase)
type = QStringLiteral("passphrase");
else if (e.passwordStyle() == QCA::Event::StylePIN)
type = QStringLiteral("PIN");
QString str;
if (e.source() == QCA::Event::KeyStore) {
QString name;
QCA::KeyStoreEntry entry = e.keyStoreEntry();
if (!entry.isNull()) {
name = entry.name();
} else {
if (e.keyStoreInfo().type() == QCA::KeyStore::SmartCard)
name = QStringLiteral("the '") + e.keyStoreInfo().name() + QStringLiteral("' token");
else
name = e.keyStoreInfo().name();
}
str = QStringLiteral("Enter %1 for %2").arg(type, name);
} else if (!e.fileName().isEmpty())
str = QStringLiteral("Enter %1 for %2").arg(type, e.fileName());
else
str = QStringLiteral("Enter %1").arg(type);
if (!prompt) {
prompt = new QCA::ConsolePrompt(this);
connect(prompt, &QCA::ConsolePrompt::finished, this, &PassphrasePrompt::prompt_finished);
prompt_id = id;
prompt_event = e;
prompt->getHidden(str);
} else {
Item i;
i.promptStr = str;
i.id = id;
i.event = e;
pending += i;
}
} else if (e.type() == QCA::Event::Token) {
// even though we're being prompted for a missing token,
// we should still check if the token is present, due to
// a possible race between insert and token request.
bool found = false;
// token-only
if (e.keyStoreEntry().isNull()) {
foreach (QCA::KeyStore *ks, keyStores) {
if (ks->id() == e.keyStoreInfo().id()) {
found = true;
break;
}
}
}
// token-entry
else {
QCA::KeyStoreEntry kse = e.keyStoreEntry();
QCA::KeyStore *ks = nullptr;
foreach (QCA::KeyStore *i, keyStores) {
if (i->id() == e.keyStoreInfo().id()) {
ks = i;
break;
}
}
if (ks) {
QList<QCA::KeyStoreEntry> list = ks->entryList();
foreach (const QCA::KeyStoreEntry &e, list) {
if (e.id() == kse.id() && kse.isAvailable()) {
found = true;
break;
}
}
}
}
if (found) {
// auto-accept
handler.tokenOkay(id);
return;
}
QCA::KeyStoreEntry entry = e.keyStoreEntry();
QString name;
if (!entry.isNull()) {
name = QStringLiteral("Please make ") + entry.name() + QStringLiteral(" (of ") + entry.storeName() +
QStringLiteral(") available");
} else {
name = QStringLiteral("Please insert the '") + e.keyStoreInfo().name() + QStringLiteral("' token");
}
QString str = QStringLiteral("%1 and press Enter (or 'q' to cancel) ...").arg(name);
if (!prompt) {
fprintf(stderr, "%s\n", qPrintable(str));
prompt = new QCA::ConsolePrompt(this);
connect(prompt, &QCA::ConsolePrompt::finished, this, &PassphrasePrompt::prompt_finished);
prompt_id = id;
prompt_event = e;
prompt->getChar();
} else {
Item i;
i.promptStr = str;
i.id = id;
i.event = e;
pending += i;
}
} else
handler.reject(id);
}
void prompt_finished()
{
if (prompt_event.type() == QCA::Event::Password) {
handler.submitPassword(prompt_id, prompt->result());
} else {
if (auto_accept) {
auto_accept = false;
handler.tokenOkay(prompt_id);
} else {
QChar c = prompt->resultChar();
if (c == QLatin1Char('\r') || c == QLatin1Char('\n'))
handler.tokenOkay(prompt_id);
else if (c == QLatin1Char('q') || c == QLatin1Char('Q'))
handler.reject(prompt_id);
else {
// retry
prompt->getChar();
return;
}
}
}
if (!pending.isEmpty()) {
Item i = pending.takeFirst();
prompt_id = i.id;
prompt_event = i.event;
if (i.event.type() == QCA::Event::Password) {
prompt->getHidden(i.promptStr);
} else // Token
{
fprintf(stderr, "%s\n", qPrintable(i.promptStr));
prompt->getChar();
}
} else {
delete prompt;
prompt = nullptr;
}
}
void ks_available(const QString &keyStoreId)
{
QCA::KeyStore *ks = new QCA::KeyStore(keyStoreId, &ksm);
connect(ks, &QCA::KeyStore::updated, this, &PassphrasePrompt::ks_updated);
connect(ks, &QCA::KeyStore::unavailable, this, &PassphrasePrompt::ks_unavailable);
keyStores += ks;
ks->startAsynchronousMode();
// are we currently in a token-only prompt?
if (prompt && prompt_event.type() == QCA::Event::Token && prompt_event.keyStoreEntry().isNull()) {
// was the token we're looking for just inserted?
if (prompt_event.keyStoreInfo().id() == keyStoreId) {
fprintf(stderr, "Token inserted! Continuing...\n");
// auto-accept
auto_accept = true;
prompt_finished();
}
}
}
void ks_unavailable()
{
QCA::KeyStore *ks = (QCA::KeyStore *)sender();
keyStores.removeAll(ks);
delete ks;
}
void ks_updated()
{
QCA::KeyStore *ks = (QCA::KeyStore *)sender();
// are we currently in a token-entry prompt?
if (prompt && prompt_event.type() == QCA::Event::Token && !prompt_event.keyStoreEntry().isNull()) {
QCA::KeyStoreEntry kse = prompt_event.keyStoreEntry();
// was the token of the entry we're looking for updated?
if (prompt_event.keyStoreInfo().id() == ks->id()) {
// is the entry available?
bool avail = false;
QList<QCA::KeyStoreEntry> list = ks->entryList();
foreach (const QCA::KeyStoreEntry &e, list) {
if (e.id() == kse.id()) {
avail = kse.isAvailable();
break;
}
}
if (avail) {
fprintf(stderr, "Entry available! Continuing...\n");
// auto-accept
auto_accept = true;
prompt_finished();
}
}
}
}
};
class PassphrasePromptThread : public QCA::SyncThread
{
Q_OBJECT
public:
PassphrasePrompt *pp;
PassphrasePromptThread()
{
start();
}
~PassphrasePromptThread() override
{
stop();
}
protected:
void atStart() override
{
pp = new PassphrasePrompt;
}
void atEnd() override
{
delete pp;
}
};
static bool promptForNewPassphrase(QCA::SecureArray *result)
{
QCA::ConsolePrompt prompt;
prompt.getHidden(QStringLiteral("Enter new passphrase"));
prompt.waitForFinished();
QCA::SecureArray out = prompt.result();
prompt.getHidden(QStringLiteral("Confirm new passphrase"));
prompt.waitForFinished();
if (prompt.result() != out) {
fprintf(stderr, "Error: confirmation does not match original entry.\n");
return false;
}
*result = out;
return true;
}
static void ksm_start_and_wait()
{
// activate the KeyStoreManager and block until ready
QCA::KeyStoreManager::start();
{
QCA::KeyStoreManager ksm;
ksm.waitForBusyFinished();
}
}
static QString line_encode(const QString &in)
{
QString out;
for (const QChar &c : in) {
if (c == QLatin1Char('\\'))
out += QStringLiteral("\\\\");
else if (c == QLatin1Char('\n'))
out += QStringLiteral("\\n");
else
out += c;
}
return out;
}
static QString line_decode(const QString &in)
{
QString out;
for (int n = 0; n < in.length(); ++n) {
if (in[n] == QLatin1Char('\\')) {
if (n + 1 < in.length()) {
if (in[n + 1] == QLatin1Char('\\'))
out += QLatin1Char('\\');
else if (in[n + 1] == QLatin1Char('n'))
out += QLatin1Char('\n');
++n;
}
} else
out += in[n];
}
return out;
}
static QString make_ksentry_string(const QString &id)
{
QString out;
out += QStringLiteral("QCATOOL_KEYSTOREENTRY_1\n");
out += line_encode(id) + QLatin1Char('\n');
return out;
}
/*static bool write_ksentry_file(const QString &id, const QString &fileName)
{
QFile f(fileName);
if(!f.open(QFile::WriteOnly | QFile::Truncate))
return false;
f.write(make_ksentry_string(id).toUtf8());
return true;
}*/
static QString read_ksentry_file(const QString &fileName)
{
QString out;
QFile f(fileName);
if (!f.open(QFile::ReadOnly))
return out;
QTextStream ts(&f);
int linenum = 0;
while (!ts.atEnd()) {
QString line = ts.readLine();
if (linenum == 0) {
if (line != QLatin1String("QCATOOL_KEYSTOREENTRY_1"))
return out;
} else {
out = line_decode(line);
break;
}
++linenum;
}
return out;
}
static bool is_pem_file(const QString &fileName)
{
QFile f(fileName);
if (!f.open(QFile::ReadOnly))
return false;
QTextStream ts(&f);
if (!ts.atEnd()) {
QString line = ts.readLine();
if (line.startsWith(QLatin1String("-----BEGIN")))
return true;
}
return false;
}
static QByteArray read_der_file(const QString &fileName)
{
QFile f(fileName);
if (!f.open(QFile::ReadOnly))
return QByteArray();
return f.readAll();
}
class InfoType
{
public:
QCA::CertificateInfoType type;
QString varname;
QString shortname;
QString name;
QString desc;
InfoType()
{
}
InfoType(const QCA::CertificateInfoType &_type,
const QString & _varname,
const QString & _shortname,
const QString & _name,
const QString & _desc)
: type(_type)
, varname(_varname)
, shortname(_shortname)
, name(_name)
, desc(_desc)
{
}
};
static QList<InfoType> makeInfoTypeList(bool legacyEmail = false)
{
QList<InfoType> out;
out += InfoType(QCA::CommonName,
QStringLiteral("CommonName"),
QStringLiteral("CN"),
QStringLiteral("Common Name (CN)"),
QStringLiteral("Full name, domain, anything"));
out += InfoType(
QCA::Email, QStringLiteral("Email"), QLatin1String(""), QStringLiteral("Email Address"), QLatin1String(""));
if (legacyEmail)
out += InfoType(QCA::EmailLegacy,
QStringLiteral("EmailLegacy"),
QLatin1String(""),
QStringLiteral("PKCS#9 Email Address"),
QLatin1String(""));
out += InfoType(QCA::Organization,
QStringLiteral("Organization"),
QStringLiteral("O"),
QStringLiteral("Organization (O)"),
QStringLiteral("Company, group, etc"));
out += InfoType(QCA::OrganizationalUnit,
QStringLiteral("OrganizationalUnit"),
QStringLiteral("OU"),
QStringLiteral("Organizational Unit (OU)"),
QStringLiteral("Division/branch of organization"));
out += InfoType(QCA::Locality,
QStringLiteral("Locality"),
QLatin1String(""),
QStringLiteral("Locality (L)"),
QStringLiteral("City, shire, part of a state"));
out += InfoType(QCA::State,
QStringLiteral("State"),
QLatin1String(""),
QStringLiteral("State (ST)"),
QStringLiteral("State within the country"));
out += InfoType(QCA::Country,
QStringLiteral("Country"),
QStringLiteral("C"),
QStringLiteral("Country Code (C)"),
QStringLiteral("2-letter code"));
out += InfoType(QCA::IncorporationLocality,
QStringLiteral("IncorporationLocality"),
QLatin1String(""),
QStringLiteral("Incorporation Locality"),
QStringLiteral("For EV certificates"));
out += InfoType(QCA::IncorporationState,
QStringLiteral("IncorporationState"),
QLatin1String(""),
QStringLiteral("Incorporation State"),
QStringLiteral("For EV certificates"));
out += InfoType(QCA::IncorporationCountry,
QStringLiteral("IncorporationCountry"),
QLatin1String(""),
QStringLiteral("Incorporation Country"),
QStringLiteral("For EV certificates"));
out += InfoType(QCA::URI, QStringLiteral("URI"), QLatin1String(""), QStringLiteral("URI"), QLatin1String(""));
out += InfoType(QCA::DNS,
QStringLiteral("DNS"),
QLatin1String(""),
QStringLiteral("Domain Name"),
QStringLiteral("Domain (dnsName)"));
out += InfoType(QCA::IPAddress,
QStringLiteral("IPAddress"),
QLatin1String(""),
QStringLiteral("IP Adddress"),
QLatin1String(""));
out += InfoType(QCA::XMPP,
QStringLiteral("XMPP"),
QLatin1String(""),
QStringLiteral("XMPP Address (JID)"),
QStringLiteral("From RFC 3920 (id-on-xmppAddr)"));
return out;
}
class MyConstraintType
{
public:
QCA::ConstraintType type;
QString varname;
QString name;
QString desc;
MyConstraintType()
{
}
MyConstraintType(const QCA::ConstraintType &_type,
const QString & _varname,
const QString & _name,
const QString & _desc)
: type(_type)
, varname(_varname)
, name(_name)
, desc(_desc)
{
}
};
static QList<MyConstraintType> makeConstraintTypeList()
{
QList<MyConstraintType> out;
out += MyConstraintType(QCA::DigitalSignature,
QStringLiteral("DigitalSignature"),
QStringLiteral("Digital Signature"),
QStringLiteral("Can be used for signing"));
out += MyConstraintType(QCA::NonRepudiation,
QStringLiteral("NonRepudiation"),
QStringLiteral("Non-Repudiation"),
QStringLiteral("Usage is legally binding"));
out += MyConstraintType(QCA::KeyEncipherment,
QStringLiteral("KeyEncipherment"),
QStringLiteral("Key Encipherment"),
QStringLiteral("Can encrypt other keys"));
out += MyConstraintType(QCA::DataEncipherment,
QStringLiteral("DataEncipherment"),
QStringLiteral("Data Encipherment"),
QStringLiteral("Can encrypt arbitrary data"));
out += MyConstraintType(QCA::KeyAgreement,
QStringLiteral("KeyAgreement"),
QStringLiteral("Key Agreement"),
QStringLiteral("Can perform key agreement (DH)"));
out += MyConstraintType(QCA::KeyCertificateSign,
QStringLiteral("KeyCertificateSign"),
QStringLiteral("Certificate Sign"),
QStringLiteral("Can sign other certificates"));
out += MyConstraintType(
QCA::CRLSign, QStringLiteral("CRLSign"), QStringLiteral("CRL Sign"), QStringLiteral("Can sign CRLs"));
out += MyConstraintType(QCA::EncipherOnly,
QStringLiteral("EncipherOnly"),
QStringLiteral("Encipher Only"),
QStringLiteral("Can be used for encrypting"));
out += MyConstraintType(QCA::DecipherOnly,
QStringLiteral("DecipherOnly"),
QStringLiteral("Decipher Only"),
QStringLiteral("Can be used for decrypting"));
out += MyConstraintType(QCA::ServerAuth,
QStringLiteral("ServerAuth"),
QStringLiteral("Server Authentication"),
QStringLiteral("TLS Server"));
out += MyConstraintType(QCA::ClientAuth,
QStringLiteral("ClientAuth"),
QStringLiteral("Client Authentication"),
QStringLiteral("TLS Client"));
out += MyConstraintType(
QCA::CodeSigning, QStringLiteral("CodeSigning"), QStringLiteral("Code Signing"), QLatin1String(""));
out += MyConstraintType(QCA::EmailProtection,
QStringLiteral("EmailProtection"),
QStringLiteral("Email Protection"),
QStringLiteral("S/MIME"));
out += MyConstraintType(
QCA::IPSecEndSystem, QStringLiteral("IPSecEndSystem"), QStringLiteral("IPSec End-System"), QLatin1String(""));
out += MyConstraintType(
QCA::IPSecTunnel, QStringLiteral("IPSecTunnel"), QStringLiteral("IPSec Tunnel"), QLatin1String(""));
out +=
MyConstraintType(QCA::IPSecUser, QStringLiteral("IPSecUser"), QStringLiteral("IPSec User"), QLatin1String(""));
out += MyConstraintType(
QCA::TimeStamping, QStringLiteral("TimeStamping"), QStringLiteral("Time Stamping"), QLatin1String(""));
out += MyConstraintType(
QCA::OCSPSigning, QStringLiteral("OCSPSigning"), QStringLiteral("OCSP Signing"), QLatin1String(""));
return out;
}
const char *crlEntryReasonToString(QCA::CRLEntry::Reason r)
{
switch (r) {
case QCA::CRLEntry::Unspecified:
return "Unspecified";
case QCA::CRLEntry::KeyCompromise:
return "KeyCompromise";
case QCA::CRLEntry::CACompromise:
return "CACompromise";
case QCA::CRLEntry::AffiliationChanged:
return "AffiliationChanged";
case QCA::CRLEntry::Superseded:
return "Superseded";
case QCA::CRLEntry::CessationOfOperation:
return "CessationOfOperation";
case QCA::CRLEntry::CertificateHold:
return "CertificateHold";
case QCA::CRLEntry::RemoveFromCRL:
return "RemoveFromCRL";
case QCA::CRLEntry::PrivilegeWithdrawn:
return "PrivilegeWithdrawn";
case QCA::CRLEntry::AACompromise:
return "AACompromise";
default:
return "Unknown";
}
}
static bool validOid(const QString &in)
{
for (const QChar &c : in) {
if (!c.isDigit() && c != QLatin1Char('.'))
return false;
}
return true;
}
class ValidityLength
{
public:
int years, months, days;
};
static int vl_getnext(const QString &in, int offset = 0)
{
if (offset >= in.length())
return in.length();
int n = offset;
bool lookForNonDigit;
if (in[n].isDigit())
lookForNonDigit = true;
else
lookForNonDigit = false;
for (++n; n < in.length(); ++n) {
if (in[n].isDigit() != lookForNonDigit)
break;
}
return n;
}
static QStringList vl_getparts(const QString &in)
{
QStringList out;
int offset = 0;
while (true) {
int n = vl_getnext(in, offset);
if (n == offset)
break;
out += in.mid(offset, n - offset);
offset = n;
}
return out;
}
static bool parseValidityLength(const QString &in, ValidityLength *vl)
{
vl->years = -1;
vl->months = -1;
vl->days = -1;
QStringList parts = vl_getparts(in);
while (true) {
// first part should be a number
if (parts.count() < 1)
break;
QString str = parts.takeFirst();
bool ok;
int x = str.toInt(&ok);
if (!ok)
return false;
// next part should be 1 letter plus any amount of space
if (parts.count() < 1)
return false;
str = parts.takeFirst();
if (!str[0].isLetter())
return false;
str = str.trimmed(); // remove space
if (str == QLatin1String("y")) {
if (vl->years != -1)
return false;
vl->years = x;
}
if (str == QLatin1String("m")) {
if (vl->months != -1)
return false;
vl->months = x;
}
if (str == QLatin1String("d")) {
if (vl->days != -1)
return false;
vl->days = x;
}
}
if (vl->years == -1)
vl->years = 0;
if (vl->months == -1)
vl->months = 0;
if (vl->days == -1)
vl->days = 0;
return true;
}
static QString prompt_for(const QString &prompt)
{
printf("%s: ", prompt.toLatin1().data());
fflush(stdout);
QByteArray result(256, 0);
if (fgets((char *)result.data(), result.size(), stdin))
return QString::fromLocal8Bit(result).trimmed();
else
return QString();
}
static QCA::CertificateOptions promptForCertAttributes(bool advanced, bool req)
{
QCA::CertificateOptions opts;
if (advanced) {
if (!req) {
while (true) {
QString str = prompt_for(
QStringLiteral("Create an end user ('user') certificate or a CA ('ca') certificate? [user]"));
if (str.isEmpty())
str = QStringLiteral("user");
if (str != QLatin1String("user") && str != QLatin1String("ca")) {
printf("'%s' is not a valid entry.\n", qPrintable(str));
continue;
}
if (str == QLatin1String("ca"))
opts.setAsCA();
break;
}
printf("\n");
while (true) {
QString str = prompt_for(QStringLiteral("Serial Number"));
QCA::BigInteger num;
if (str.isEmpty() || !num.fromString(str)) {
printf("'%s' is not a valid entry.\n", qPrintable(str));
continue;
}
opts.setSerialNumber(num);
break;
}
printf("\n");
}
{
QCA::CertificateInfoOrdered info;
printf(
"Choose the information attributes to add to the certificate. They will be\n"
"added in the order they are entered.\n\n");
printf("Available information attributes:\n");
QList<InfoType> list = makeInfoTypeList();
for (int n = 0; n < list.count(); ++n) {
const InfoType &i = list[n];
char c = 'a' + n;
printf(" %c) %-32s %s\n", c, qPrintable(i.name), qPrintable(i.desc));
}
printf("\n");
while (true) {
int index;
while (true) {
QString str = prompt_for(QStringLiteral("Select an attribute to add, or enter to move on"));
if (str.isEmpty()) {
index = -1;
break;
}
if (str.length() == 1) {
index = str[0].toLatin1() - 'a';
if (index >= 0 && index < list.count())
break;
}
printf("'%s' is not a valid entry.\n", qPrintable(str));
}
if (index == -1)
break;
QString val = prompt_for(list[index].name);
info += QCA::CertificateInfoPair(list[index].type, val);
printf("Added attribute.\n\n");
}
opts.setInfoOrdered(info);
}
{
QCA::Constraints constraints;
printf("\n");
printf("Choose the constraint attributes to add to the certificate.\n\n");
printf("Available attributes:\n");
QList<MyConstraintType> list = makeConstraintTypeList();
for (int n = 0; n < list.count(); ++n) {
const MyConstraintType &i = list[n];
char c = 'a' + n;
printf(" %c) %-32s %s\n", c, qPrintable(i.name), qPrintable(i.desc));
}
printf("\n");
printf("If no constraints are added, then the certificate may be used for any purpose.\n\n");
while (true) {
int index;
while (true) {
QString str = prompt_for(QStringLiteral("Select an attribute to add, or enter to move on"));
if (str.isEmpty()) {
index = -1;
break;
}
if (str.length() == 1) {
index = str[0].toLatin1() - 'a';
if (index >= 0 && index < list.count())
break;
}
printf("'%s' is not a valid entry.\n\n", qPrintable(str));
}
if (index == -1)
break;
if (constraints.contains(list[index].type)) {
printf("You have already added '%s'.\n\n", qPrintable(list[index].name));
continue;
}
constraints += list[index].type;
printf("Added attribute.\n\n");
}
opts.setConstraints(constraints);
}
{
QStringList policies;
printf("\n");
printf(
"Are there any policy OID attributes that you wish to add? Use the dotted\n"
"string format.\n\n");
while (true) {
QString str = prompt_for(QStringLiteral("Enter a policy OID to add, or enter to move on"));
if (str.isEmpty())
break;
if (!validOid(str)) {
printf("'%s' is not a valid entry.\n\n", qPrintable(str));
continue;
}
if (policies.contains(str)) {
printf("You have already added '%s'.\n\n", qPrintable(str));
continue;
}
policies += str;
printf("Added attribute.\n\n");
}
opts.setPolicies(policies);
}
printf("\n");
} else {
QCA::CertificateInfo info;
info.insert(QCA::CommonName, prompt_for(QStringLiteral("Common Name")));
info.insert(QCA::Country, prompt_for(QStringLiteral("Country Code (2 letters)")));
info.insert(QCA::Organization, prompt_for(QStringLiteral("Organization")));
info.insert(QCA::Email, prompt_for(QStringLiteral("Email")));
opts.setInfo(info);
printf("\n");
}
if (!req) {
while (true) {
QString str = prompt_for(QStringLiteral("How long should the certificate be valid? (e.g. '1y2m3d')"));
ValidityLength vl;
if (!parseValidityLength(str, &vl)) {
printf("'%s' is not a valid entry.\n\n", qPrintable(str));
continue;
}
if (vl.years == 0 && vl.months == 0 && vl.days == 0) {
printf("The certificate must be valid for at least one day.\n\n");
continue;
}
QDateTime start = QDateTime::currentDateTimeUtc();
QDateTime end = start;
if (vl.years > 0)
end = end.addYears(vl.years);
if (vl.months > 0)
end = end.addMonths(vl.months);
if (vl.days > 0)
end = end.addDays(vl.days);
opts.setValidityPeriod(start, end);
QStringList parts;
if (vl.years > 0)
parts += QStringLiteral("%1 year(s)").arg(vl.years);
if (vl.months > 0)
parts += QStringLiteral("%1 month(s)").arg(vl.months);
if (vl.days > 0)
parts += QStringLiteral("%1 day(s)").arg(vl.days);
QString out;
if (parts.count() == 1)
out = parts[0];
else if (parts.count() == 2)
out = parts[0] + QStringLiteral(" and ") + parts[1];
else if (parts.count() == 3)
out = parts[0] + QStringLiteral(", ") + parts[1] + QStringLiteral(", and ") + parts[2];
printf("Certificate will be valid for %s.\n", qPrintable(out));
break;
}
printf("\n");
}
return opts;
}
// qsettings seems to give us a string type for both bool and int (and
// possibly others, but those are the only two we care about here).
// in order to figure out what is actually a bool or an int, we need
// to examine the string. so for the functions below, we convert
// the variant to a string, and then inspect it to see if it looks
// like a bool or an int.
static bool string_is_bool(const QString &in)
{
QString lc = in.toLower();
if (lc == QLatin1String("true") || lc == QLatin1String("false"))
return true;
return false;
}
static bool string_is_int(const QString &in)
{
bool ok;
in.toInt(&ok);
return ok;
}
static bool variant_is_bool(const QVariant &in)
{
if (in.canConvert<QString>() && string_is_bool(in.toString()))
return true;
return false;
}
static bool variant_is_int(const QVariant &in)
{
if (in.canConvert<QString>() && string_is_int(in.toString()))
return true;
return false;
}
static QString prompt_for_string(const QString &prompt, const QString &def = QString())
{
printf("%s", prompt.toLatin1().data());
fflush(stdout);
QByteArray result(256, 0);
if (!fgets((char *)result.data(), result.size(), stdin))
return QString();
if (result[result.length() - 1] == '\n')
result.truncate(result.length() - 1);
// empty input -> use default
if (result.isEmpty())
return def;
// trimmed input could result in an empty value, but in that case
// it is treated as if the user wishes to submit an empty value.
return QString::fromLocal8Bit(result).trimmed();
}
static int prompt_for_int(const QString &prompt, int def = 0)
{
while (true) {
QString str = prompt_for_string(prompt);
if (str.isEmpty())
return def;
bool ok;
int x = str.toInt(&ok);
if (ok)
return x;
printf("'%s' is not a valid entry.\n\n", qPrintable(str));
}
}
static bool partial_compare_nocase(const QString &in, const QString &target, int min = 1)
{
if (in.length() >= min && in.length() <= target.length() && target.mid(0, in.length()).toLower() == in.toLower())
return true;
return false;
}
static bool prompt_for_bool(const QString &prompt, bool def = false)
{
while (true) {
QString str = prompt_for_string(prompt);
if (str.isEmpty())
return def;
if (partial_compare_nocase(str, QStringLiteral("true")))
return true;
else if (partial_compare_nocase(str, QStringLiteral("false")))
return false;
printf("'%s' is not a valid entry.\n\n", qPrintable(str));
}
}
static bool prompt_for_yesno(const QString &prompt, bool def = false)
{
while (true) {
QString str = prompt_for_string(prompt);
if (str.isEmpty())
return def;
if (partial_compare_nocase(str, QStringLiteral("yes")))
return true;
else if (partial_compare_nocase(str, QStringLiteral("no")))
return false;
printf("'%s' is not a valid entry.\n\n", qPrintable(str));
}
}
static QString prompt_for_slotevent_method(const QString &prompt, const QString &def = QString())
{
while (true) {
QString str = prompt_for_string(prompt);
if (str.isEmpty())
return def;
if (partial_compare_nocase(str, QStringLiteral("auto")))
return QStringLiteral("auto");
else if (partial_compare_nocase(str, QStringLiteral("trigger")))
return QStringLiteral("trigger");
else if (partial_compare_nocase(str, QStringLiteral("poll")))
return QStringLiteral("poll");
printf("'%s' is not a valid entry.\n\n", qPrintable(str));
}
}
static QVariantMap provider_config_edit_generic(const QVariantMap &in)
{
QVariantMap config = in;
QMutableMapIterator<QString, QVariant> it(config);
while (it.hasNext()) {
it.next();
QString var = it.key();
if (var == QLatin1String("formtype"))
continue;
QVariant val = it.value();
// fields must be bool, int, or string
QVariant newval;
QString prompt = QStringLiteral("%1: [%2] ").arg(var, val.toString());
if (variant_is_bool(val))
newval = prompt_for_bool(QStringLiteral("bool ") + prompt, val.toBool());
else if (variant_is_int(val))
newval = prompt_for_int(QStringLiteral("int ") + prompt, val.toInt());
else if (val.canConvert<QString>())
newval = prompt_for_string(QStringLiteral("string ") + prompt, val.toString());
else
continue; // skip bogus fields
it.setValue(newval);
}
return config;
}
class Pkcs11ProviderConfig
{
public:
bool allow_protected_authentication;
bool cert_private;
bool enabled;
QString library;
QString name;
int private_mask;
QString slotevent_method;
int slotevent_timeout;
Pkcs11ProviderConfig()
: allow_protected_authentication(true)
, cert_private(false)
, enabled(false)
, private_mask(0)
, slotevent_method(QStringLiteral("auto"))
, slotevent_timeout(0)
{
}
QVariantMap toVariantMap() const
{
QVariantMap out;
out[QStringLiteral("allow_protected_authentication")] = allow_protected_authentication;
out[QStringLiteral("cert_private")] = cert_private;
out[QStringLiteral("enabled")] = enabled;
out[QStringLiteral("library")] = library;
out[QStringLiteral("name")] = name;
out[QStringLiteral("private_mask")] = private_mask;
out[QStringLiteral("slotevent_method")] = slotevent_method;
out[QStringLiteral("slotevent_timeout")] = slotevent_timeout;
return out;
}
bool fromVariantMap(const QVariantMap &in)
{
allow_protected_authentication = in[QStringLiteral("allow_protected_authentication")].toBool();
cert_private = in[QStringLiteral("cert_private")].toBool();
enabled = in[QStringLiteral("enabled")].toBool();
library = in[QStringLiteral("library")].toString();
name = in[QStringLiteral("name")].toString();
private_mask = in[QStringLiteral("private_mask")].toInt();
slotevent_method = in[QStringLiteral("slotevent_method")].toString();
slotevent_timeout = in[QStringLiteral("slotevent_timeout")].toInt();
return true;
}
};
class Pkcs11Config
{
public:
bool allow_load_rootca;
bool allow_protected_authentication;
int log_level;
int pin_cache;
QList<Pkcs11ProviderConfig> providers;
QVariantMap orig_config;
Pkcs11Config()
: allow_load_rootca(false)
, allow_protected_authentication(true)
, log_level(0)
, pin_cache(-1)
{
}
QVariantMap toVariantMap() const
{
QVariantMap out = orig_config;
// form type
out[QStringLiteral("formtype")] = QLatin1String("http://affinix.com/qca/forms/qca-pkcs11#1.0");
// base settings
out[QStringLiteral("allow_load_rootca")] = allow_load_rootca;
out[QStringLiteral("allow_protected_authentication")] = allow_protected_authentication;
out[QStringLiteral("log_level")] = log_level;
out[QStringLiteral("pin_cache")] = pin_cache;
// provider settings (always write at least 10 providers)
for (int n = 0; n < 10 || n < providers.count(); ++n) {
QString prefix = QString::asprintf("provider_%02d_", n);
Pkcs11ProviderConfig provider;
if (n < providers.count())
provider = providers[n];
QVariantMap subconfig = provider.toVariantMap();
QMapIterator<QString, QVariant> it(subconfig);
while (it.hasNext()) {
it.next();
out.insert(prefix + it.key(), it.value());
}
}
return out;
}
bool fromVariantMap(const QVariantMap &in)
{
if (in[QStringLiteral("formtype")] != QLatin1String("http://affinix.com/qca/forms/qca-pkcs11#1.0"))
return false;
allow_load_rootca = in[QStringLiteral("allow_load_rootca")].toBool();
allow_protected_authentication = in[QStringLiteral("allow_protected_authentication")].toBool();
log_level = in[QStringLiteral("log_level")].toInt();
pin_cache = in[QStringLiteral("pin_cache")].toInt();
for (int n = 0;; ++n) {
QString prefix = QString::asprintf("provider_%02d_", n);
// collect all key/values with this prefix into a
// a separate container, leaving out the prefix
// from the keys.
QVariantMap subconfig;
QMapIterator<QString, QVariant> it(in);
while (it.hasNext()) {
it.next();
if (it.key().startsWith(prefix))
subconfig.insert(it.key().mid(prefix.length()), it.value());
}
// if there are no config items with this prefix, we're done
if (subconfig.isEmpty())
break;
Pkcs11ProviderConfig provider;
if (!provider.fromVariantMap(subconfig))
return false;
// skip unnamed entries
if (provider.name.isEmpty())
continue;
// skip duplicate entries
bool have_name_already = false;
foreach (const Pkcs11ProviderConfig &i, providers) {
if (i.name == provider.name) {
have_name_already = true;
break;
}
}
if (have_name_already)
continue;
providers += provider;
}
orig_config = in;
return true;
}
};
static QVariantMap provider_config_edit_pkcs11(const QVariantMap &in)
{
Pkcs11Config config;
if (!config.fromVariantMap(in)) {
fprintf(stderr, "Error: unable to parse PKCS#11 provider configuration.\n");
return QVariantMap();
}
while (true) {
printf("\n");
printf("Global settings:\n");
printf(" Allow loading of root CAs: %s\n", config.allow_load_rootca ? "Yes" : "No");
printf(" Allow protected authentication: %s\n", config.allow_protected_authentication ? "Yes" : "No");
QString str;
if (config.pin_cache == -1)
str = QStringLiteral("No limit");
else
str = QStringLiteral("%1 seconds").arg(config.pin_cache);
printf(" Maximum PIN cache time: %s\n", qPrintable(str));
printf(" Log level: %d\n", config.log_level);
printf("\n");
printf("PKCS#11 modules:\n");
if (!config.providers.isEmpty()) {
foreach (const Pkcs11ProviderConfig &provider, config.providers)
printf(" %s\n", qPrintable(provider.name));
} else
printf(" (None)\n");
printf("\n");
printf("Actions:\n");
printf(" a) Edit global settings\n");
printf(" b) Add PKCS#11 module\n");
printf(" c) Edit PKCS#11 module\n");
printf(" d) Remove PKCS#11 module\n");
printf("\n");
int index;
while (true) {
QString str = prompt_for(QStringLiteral("Select an action, or enter to quit"));
if (str.isEmpty()) {
index = -1;
break;
}
if (str.length() == 1) {
index = str[0].toLatin1() - 'a';
if (index >= 0 && index < 4)
break;
}
printf("'%s' is not a valid entry.\n\n", qPrintable(str));
}
if (index == -1)
break;
if (index == 0) {
printf("\n");
QString prompt;
prompt = QStringLiteral("Allow loading of root CAs: [%1] ")
.arg(config.allow_load_rootca ? QStringLiteral("Yes") : QStringLiteral("No"));
config.allow_load_rootca = prompt_for_yesno(prompt, config.allow_load_rootca);
prompt = QStringLiteral("Allow protected authentication: [%1] ")
.arg(config.allow_protected_authentication ? QStringLiteral("Yes") : QStringLiteral("No"));
config.allow_protected_authentication = prompt_for_yesno(prompt, config.allow_protected_authentication);
prompt = QStringLiteral("Maximum PIN cache time in seconds (-1 for no limit): [%1] ").arg(config.pin_cache);
config.pin_cache = prompt_for_int(prompt, config.pin_cache);
prompt = QStringLiteral("Log level: [%1] ").arg(config.log_level);
config.log_level = prompt_for_int(prompt, config.log_level);
} else // 1, 2, 3
{
int at = -1;
// for edit/remove, need to select provider
if (index == 2 || index == 3) {
printf("\nWhich PKCS#11 module?\n");
for (int n = 0; n < config.providers.count(); ++n) {
const Pkcs11ProviderConfig &provider = config.providers[n];
char c = 'a' + n;
printf(" %c) %s\n", c, qPrintable(provider.name));
}
printf("\n");
int index;
while (true) {
QString str = prompt_for(QStringLiteral("Select a module, or enter to go back"));
if (str.isEmpty()) {
index = -1;
break;
}
if (str.length() == 1) {
index = str[0].toLatin1() - 'a';
if (index >= 0 && index < config.providers.count())
break;
}
printf("'%s' is not a valid entry.\n", qPrintable(str));
}
// exit?
if (index == -1)
continue;
at = index;
}
// edit the entry
if (index == 1 || index == 2) {
Pkcs11ProviderConfig provider;
if (index == 2) // edit
provider = config.providers[at];
provider.enabled = true;
printf("\n");
QString prompt;
// prompt for unique name
while (true) {
if (index == 1)
prompt = QStringLiteral("Unique friendly name: ");
else
prompt = QStringLiteral("Unique friendly name: [%1] ").arg(provider.name);
provider.name = prompt_for_string(prompt, provider.name);
if (provider.name.isEmpty()) {
printf("The friendly name cannot be blank.\n\n");
continue;
}
bool have_name_already = false;
for (int n = 0; n < config.providers.count(); ++n) {
const Pkcs11ProviderConfig &i = config.providers[n];
// skip checking against the entry we are editing
if (at != -1 && n == at)
continue;
if (i.name == provider.name) {
have_name_already = true;
break;
}
}
if (have_name_already) {
printf("This name is already used by another module.\n\n");
continue;
}
break;
}
// prompt for library file
QString last;
while (true) {
if (index == 1)
prompt = QStringLiteral("Library filename: ");
else
prompt = QStringLiteral("Library filename: [%1] ").arg(provider.library);
provider.library = prompt_for_string(prompt, provider.library);
if (provider.library.isEmpty()) {
printf("The library filename cannot be blank.\n\n");
continue;
}
if (last != provider.library && !QFile::exists(provider.library)) {
last = provider.library;
printf("'%s' does not exist.\nPress enter again if you really want this.\n\n",
qPrintable(provider.library));
continue;
}
break;
}
prompt =
QStringLiteral("Allow protected authentication: [%1] ")
.arg(provider.allow_protected_authentication ? QStringLiteral("Yes") : QStringLiteral("No"));
provider.allow_protected_authentication =
prompt_for_yesno(prompt, provider.allow_protected_authentication);
prompt = QStringLiteral("Provider stores certificates as private objects: [%1] ")
.arg(provider.cert_private ? QStringLiteral("Yes") : QStringLiteral("No"));
provider.cert_private = prompt_for_yesno(prompt, provider.cert_private);
printf("\n");
printf("Provider private key mask:\n");
printf(" 0 Determine automatically.\n");
printf(" 1 Use sign.\n");
printf(" 2 Use sign recover.\n");
printf(" 4 Use decrypt.\n");
printf(" 8 Use unwrap.\n");
prompt = QStringLiteral("Mask value: [%1] ").arg(provider.private_mask);
provider.private_mask = prompt_for_int(prompt, provider.private_mask);
printf("\n");
printf("Slot event method:\n");
printf(" auto Determine automatically.\n");
printf(" trigger Use trigger.\n");
printf(" poll Use poll.\n");
prompt = QStringLiteral("Method value: [%1] ").arg(provider.slotevent_method);
provider.slotevent_method = prompt_for_slotevent_method(prompt, provider.slotevent_method);
if (provider.slotevent_method == QLatin1String("poll")) {
prompt =
QStringLiteral("Poll timeout (0 for no preference): [%1] ").arg(provider.slotevent_timeout);
provider.slotevent_timeout = prompt_for_int(prompt, provider.slotevent_timeout);
} else
provider.slotevent_timeout = 0;
if (index == 1)
config.providers += provider;
else // 2
config.providers[at] = provider;
}
// remove the entry
else // 3
{
config.providers.removeAt(at);
}
}
}
return config.toVariantMap();
}
static QVariantMap provider_config_edit(const QVariantMap &in)
{
// see if we have a configurator for a known form type
if (in[QStringLiteral("formtype")] == QLatin1String("http://affinix.com/qca/forms/qca-pkcs11#1.0"))
return provider_config_edit_pkcs11(in);
// otherwise, use the generic configurator
return provider_config_edit_generic(in);
}
static QString get_fingerprint(const QCA::Certificate &cert, const QString &hashType)
{
QString hex = QCA::Hash(hashType).hashToString(cert.toDER());
QString out;
for (int n = 0; n < hex.count(); ++n) {
if (n != 0 && n % 2 == 0)
out += QLatin1Char(':');
out += hex[n];
}
return out;
}
static QString kstype_to_string(QCA::KeyStore::Type _type)
{
QString type;
switch (_type) {
case QCA::KeyStore::System:
type = QStringLiteral("Sys ");
break;
case QCA::KeyStore::User:
type = QStringLiteral("User");
break;
case QCA::KeyStore::Application:
type = QStringLiteral("App ");
break;
case QCA::KeyStore::SmartCard:
type = QStringLiteral("Card");
break;
case QCA::KeyStore::PGPKeyring:
type = QStringLiteral("PGP ");
break;
default:
type = QStringLiteral("XXXX");
break;
}
return type;
}
static QString ksentrytype_to_string(QCA::KeyStoreEntry::Type _type)
{
QString type;
switch (_type) {
case QCA::KeyStoreEntry::TypeKeyBundle:
type = QStringLiteral("Key ");
break;
case QCA::KeyStoreEntry::TypeCertificate:
type = QStringLiteral("Cert");
break;
case QCA::KeyStoreEntry::TypeCRL:
type = QStringLiteral("CRL ");
break;
case QCA::KeyStoreEntry::TypePGPSecretKey:
type = QStringLiteral("PSec");
break;
case QCA::KeyStoreEntry::TypePGPPublicKey:
type = QStringLiteral("PPub");
break;
default:
type = QStringLiteral("XXXX");
break;
}
return type;
}
static void try_print_info(const char *name, const QStringList &values)
{
if (!values.isEmpty()) {
QString value = values.join(QStringLiteral(", "));
printf(" %s: %s\n", name, value.toUtf8().data());
}
}
static void print_info(const char *title, const QCA::CertificateInfo &info)
{
QList<InfoType> list = makeInfoTypeList();
printf("%s\n", title);
foreach (const InfoType &t, list)
try_print_info(qPrintable(t.name), info.values(t.type));
}
static void print_info_ordered(const char *title, const QCA::CertificateInfoOrdered &info)
{
QList<InfoType> list = makeInfoTypeList(true);
printf("%s\n", title);
foreach (const QCA::CertificateInfoPair &pair, info) {
QCA::CertificateInfoType type = pair.type();
QString name;
int at = -1;
for (int n = 0; n < list.count(); ++n) {
if (list[n].type == type) {
at = n;
break;
}
}
// known type?
if (at != -1) {
name = list[at].name;
} else {
if (pair.type().section() == QCA::CertificateInfoType::DN)
name = QStringLiteral("DN:") + pair.type().id();
else
name = QStringLiteral("AN:") + pair.type().id();
}
printf(" %s: %s\n", qPrintable(name), pair.value().toUtf8().data());
}
}
static QString constraint_to_string(const QCA::ConstraintType &t)
{
QList<MyConstraintType> list = makeConstraintTypeList();
for (int n = 0; n < list.count(); ++n) {
if (list[n].type == t)
return list[n].name;
}
return t.id();
}
static QString sigalgo_to_string(QCA::SignatureAlgorithm algo)
{
QString str;
switch (algo) {
case QCA::EMSA1_SHA1:
str = QStringLiteral("EMSA1(SHA1)");
break;
case QCA::EMSA3_SHA1:
str = QStringLiteral("EMSA3(SHA1)");
break;
case QCA::EMSA3_MD5:
str = QStringLiteral("EMSA3(MD5)");
break;
case QCA::EMSA3_MD2:
str = QStringLiteral("EMSA3(MD2)");
break;
case QCA::EMSA3_RIPEMD160:
str = QStringLiteral("EMSA3(RIPEMD160)");
break;
case QCA::EMSA3_Raw:
str = QStringLiteral("EMSA3(raw)");
break;
default:
str = QStringLiteral("Unknown");
break;
}
return str;
}
static void print_cert(const QCA::Certificate &cert, bool ordered = false)
{
printf("Serial Number: %s\n", qPrintable(cert.serialNumber().toString()));
if (ordered) {
print_info_ordered("Subject", cert.subjectInfoOrdered());
print_info_ordered("Issuer", cert.issuerInfoOrdered());
} else {
print_info("Subject", cert.subjectInfo());
print_info("Issuer", cert.issuerInfo());
}
printf("Validity\n");
printf(" Not before: %s\n", qPrintable(cert.notValidBefore().toString()));
printf(" Not after: %s\n", qPrintable(cert.notValidAfter().toString()));
printf("Constraints\n");
QCA::Constraints constraints = cert.constraints();
int n;
if (!constraints.isEmpty()) {
for (n = 0; n < constraints.count(); ++n)
printf(" %s\n", qPrintable(constraint_to_string(constraints[n])));
} else
printf(" No constraints\n");
printf("Policies\n");
QStringList policies = cert.policies();
if (!policies.isEmpty()) {
for (n = 0; n < policies.count(); ++n)
printf(" %s\n", qPrintable(policies[n]));
} else
printf(" No policies\n");
QByteArray id;
printf("Issuer Key ID: ");
id = cert.issuerKeyId();
if (!id.isEmpty())
printf("%s\n", qPrintable(QCA::arrayToHex(id)));
else
printf("None\n");
printf("Subject Key ID: ");
id = cert.subjectKeyId();
if (!id.isEmpty())
printf("%s\n", qPrintable(QCA::arrayToHex(id)));
else
printf("None\n");
printf("CA: %s\n", cert.isCA() ? "Yes" : "No");
printf("Signature Algorithm: %s\n", qPrintable(sigalgo_to_string(cert.signatureAlgorithm())));
QCA::PublicKey key = cert.subjectPublicKey();
printf("Public Key:\n%s", key.toPEM().toLatin1().data());
printf("SHA1 Fingerprint: %s\n", qPrintable(get_fingerprint(cert, QStringLiteral("sha1"))));
printf("MD5 Fingerprint: %s\n", qPrintable(get_fingerprint(cert, QStringLiteral("md5"))));
}
static void print_certreq(const QCA::CertificateRequest &cert, bool ordered = false)
{
if (ordered)
print_info_ordered("Subject", cert.subjectInfoOrdered());
else
print_info("Subject", cert.subjectInfo());
printf("Constraints\n");
QCA::Constraints constraints = cert.constraints();
int n;
if (!constraints.isEmpty()) {
for (n = 0; n < constraints.count(); ++n)
printf(" %s\n", qPrintable(constraint_to_string(constraints[n])));
} else
printf(" No constraints\n");
printf("Policies\n");
QStringList policies = cert.policies();
if (!policies.isEmpty()) {
for (n = 0; n < policies.count(); ++n)
printf(" %s\n", qPrintable(policies[n]));
} else
printf(" No policies\n");
printf("CA: %s\n", cert.isCA() ? "Yes" : "No");
printf("Signature Algorithm: %s\n", qPrintable(sigalgo_to_string(cert.signatureAlgorithm())));
QCA::PublicKey key = cert.subjectPublicKey();
printf("Public Key:\n%s", key.toPEM().toLatin1().data());
}
static void print_crl(const QCA::CRL &crl, bool ordered = false)
{
if (ordered)
print_info_ordered("Issuer", crl.issuerInfoOrdered());
else
print_info("Issuer", crl.issuerInfo());
int num = crl.number();
if (num != -1)
printf("Number: %d\n", num);
printf("Validity\n");
printf(" This update: %s\n", qPrintable(crl.thisUpdate().toString()));
printf(" Next update: %s\n", qPrintable(crl.nextUpdate().toString()));
QByteArray id;
printf("Issuer Key ID: ");
id = crl.issuerKeyId();
if (!id.isEmpty())
printf("%s\n", qPrintable(QCA::arrayToHex(id)));
else
printf("None\n");
printf("Signature Algorithm: %s\n", qPrintable(sigalgo_to_string(crl.signatureAlgorithm())));
QList<QCA::CRLEntry> revokedList = crl.revoked();
foreach (const QCA::CRLEntry &entry, revokedList) {
printf(" %s: %s, %s\n",
qPrintable(entry.serialNumber().toString()),
crlEntryReasonToString(entry.reason()),
qPrintable(entry.time().toString()));
}
}
static QString format_pgp_fingerprint(const QString &in)
{
QString out;
bool first = true;
for (int n = 0; n + 3 < in.length(); n += 4) {
if (!first)
out += QLatin1Char(' ');
else
first = false;
out += in.mid(n, 4).toUpper();
}
return out;
}
static void print_pgp(const QCA::PGPKey &key)
{
printf("Key ID: %s\n", qPrintable(key.keyId()));
printf("User IDs:\n");
foreach (const QString &s, key.userIds())
printf(" %s\n", qPrintable(s));
printf("Validity\n");
printf(" Not before: %s\n", qPrintable(key.creationDate().toString()));
if (!key.expirationDate().isNull())
printf(" Not after: %s\n", qPrintable(key.expirationDate().toString()));
else
printf(" Not after: (no expiration)\n");
printf("In Keyring: %s\n", key.inKeyring() ? "Yes" : "No");
printf("Secret Key: %s\n", key.isSecret() ? "Yes" : "No");
printf("Trusted: %s\n", key.isTrusted() ? "Yes" : "No");
printf("Fingerprint: %s\n", qPrintable(format_pgp_fingerprint(key.fingerprint())));
}
static QString validityToString(QCA::Validity v)
{
QString s;
switch (v) {
case QCA::ValidityGood:
s = QStringLiteral("Validated");
break;
case QCA::ErrorRejected:
s = QStringLiteral("Root CA is marked to reject the specified purpose");
break;
case QCA::ErrorUntrusted:
s = QStringLiteral("Certificate not trusted for the required purpose");
break;
case QCA::ErrorSignatureFailed:
s = QStringLiteral("Invalid signature");
break;
case QCA::ErrorInvalidCA:
s = QStringLiteral("Invalid CA certificate");
break;
case QCA::ErrorInvalidPurpose:
s = QStringLiteral("Invalid certificate purpose");
break;
case QCA::ErrorSelfSigned:
s = QStringLiteral("Certificate is self-signed");
break;
case QCA::ErrorRevoked:
s = QStringLiteral("Certificate has been revoked");
break;
case QCA::ErrorPathLengthExceeded:
s = QStringLiteral("Maximum certificate chain length exceeded");
break;
case QCA::ErrorExpired:
s = QStringLiteral("Certificate has expired");
break;
case QCA::ErrorExpiredCA:
s = QStringLiteral("CA has expired");
break;
case QCA::ErrorValidityUnknown:
default:
s = QStringLiteral("General certificate validation error");
break;
}
return s;
}
static QString smIdentityResultToString(QCA::SecureMessageSignature::IdentityResult r)
{
QString str;
switch (r) {
case QCA::SecureMessageSignature::Valid:
str = QStringLiteral("Valid");
break;
case QCA::SecureMessageSignature::InvalidSignature:
str = QStringLiteral("InvalidSignature");
break;
case QCA::SecureMessageSignature::InvalidKey:
str = QStringLiteral("InvalidKey");
break;
case QCA::SecureMessageSignature::NoKey:
str = QStringLiteral("NoKey");
break;
default:
str = QStringLiteral("Unknown");
}
return str;
}
static QString smErrorToString(QCA::SecureMessage::Error e)
{
QMap<QCA::SecureMessage::Error, QString> map;
map[QCA::SecureMessage::ErrorPassphrase] = QStringLiteral("ErrorPassphrase");
map[QCA::SecureMessage::ErrorFormat] = QStringLiteral("ErrorFormat");
map[QCA::SecureMessage::ErrorSignerExpired] = QStringLiteral("ErrorSignerExpired");
map[QCA::SecureMessage::ErrorSignerInvalid] = QStringLiteral("ErrorSignerInvalid");
map[QCA::SecureMessage::ErrorEncryptExpired] = QStringLiteral("ErrorEncryptExpired");
map[QCA::SecureMessage::ErrorEncryptUntrusted] = QStringLiteral("ErrorEncryptUntrusted");
map[QCA::SecureMessage::ErrorEncryptInvalid] = QStringLiteral("ErrorEncryptInvalid");
map[QCA::SecureMessage::ErrorNeedCard] = QStringLiteral("ErrorNeedCard");
map[QCA::SecureMessage::ErrorCertKeyMismatch] = QStringLiteral("ErrorCertKeyMismatch");
map[QCA::SecureMessage::ErrorUnknown] = QStringLiteral("ErrorUnknown");
return map[e];
}
static void smDisplaySignatures(const QList<QCA::SecureMessageSignature> &signers)
{
foreach (const QCA::SecureMessageSignature &signer, signers) {
QCA::SecureMessageSignature::IdentityResult r = signer.identityResult();
fprintf(stderr, "IdentityResult: %s\n", qPrintable(smIdentityResultToString(r)));
QCA::SecureMessageKey key = signer.key();
if (!key.isNull()) {
if (key.type() == QCA::SecureMessageKey::PGP) {
QCA::PGPKey pub = key.pgpPublicKey();
fprintf(stderr, "From: %s (%s)\n", qPrintable(pub.primaryUserId()), qPrintable(pub.keyId()));
} else {
QCA::Certificate cert = key.x509CertificateChain().primary();
QString emailStr;
QCA::CertificateInfo info = cert.subjectInfo();
if (info.contains(QCA::Email))
emailStr = QStringLiteral(" (%1)").arg(info.value(QCA::Email));
fprintf(stderr, "From: %s%s\n", qPrintable(cert.commonName()), qPrintable(emailStr));
}
}
}
}
static const char *mime_signpart =
"Content-Type: text/plain; charset=UTF-8\r\n"
"Content-Transfer-Encoding: 8bit\r\n"
"\r\n"
"%1";
static const char *mime_signed =
"Content-Type: multipart/signed;\r\n"
" micalg=%1;\r\n"
" boundary=QCATOOL-0001;\r\n"
" protocol=\"application/pkcs7-signature\"\r\n"
"\r\n"
"\r\n"
"--QCATOOL-0001\r\n"
"%2\r\n"
"--QCATOOL-0001\r\n"
"Content-Transfer-Encoding: base64\r\n"
"Content-Type: application/pkcs7-signature;\r\n"
" name=smime.p7s\r\n"
"Content-Disposition: attachment;\r\n"
" filename=smime.p7s\r\n"
"\r\n"
"%3\r\n"
"\r\n"
"--QCATOOL-0001--\r\n";
static const char *mime_enveloped =
"Mime-Version: 1.0\r\n"
"Content-Transfer-Encoding: base64\r\n"
"Content-Type: application/pkcs7-mime;\r\n"
" name=smime.p7m;\r\n"
" smime-type=enveloped-data\r\n"
"Content-Disposition: attachment;\r\n"
" filename=smime.p7m\r\n"
"\r\n"
"%1\r\n";
static QString add_cr(const QString &in)
{
QString out = in;
int at = 0;
while (true) {
at = out.indexOf(QLatin1Char('\n'), at);
if (at == -1)
break;
if (at - 1 >= 0 && out[at - 1] != QLatin1Char('\r')) {
out.insert(at, QLatin1Char('\r'));
++at;
}
++at;
}
return out;
}
static QString rem_cr(const QString &in)
{
QString out = in;
out.replace(QLatin1String("\r\n"), QLatin1String("\n"));
return out;
}
static int indexOf_newline(const QString &in, int offset = 0)
{
for (int n = offset; n < in.length(); ++n) {
if (n + 1 < in.length() && in[n] == QLatin1Char('\r') && in[n + 1] == QLatin1Char('\n'))
return n;
if (in[n] == QLatin1Char('\n'))
return n;
}
return -1;
}
static int indexOf_doublenewline(const QString &in, int offset = 0)
{
int at = -1;
while (true) {
int n = indexOf_newline(in, offset);
if (n == -1)
return -1;
if (at != -1) {
if (n == offset)
break;
}
at = n;
if (in[n] == QLatin1Char('\n'))
offset = n + 1;
else
offset = n + 2;
}
return at;
}
// this is so gross
static int newline_len(const QString &in, int offset = 0)
{
if (in[offset] == QLatin1Char('\r'))
return 2;
else
return 1;
}
// all of this mime stuff is a total hack
static QString open_mime_envelope(const QString &in)
{
int n = indexOf_doublenewline(in);
if (n == -1)
return QString();
return in.mid(n + (newline_len(in, n) * 2)); // good lord
}
static bool open_mime_data_sig(const QString &in, QString *data, QString *sig)
{
int n = in.indexOf(QLatin1String("boundary="));
if (n == -1)
return false;
n += 9;
int i = indexOf_newline(in, n);
if (i == -1)
return false;
QString boundary;
QString bregion = in.mid(n, i - n);
n = bregion.indexOf(QLatin1Char(';'));
if (n != -1)
boundary = bregion.mid(0, n);
else
boundary = bregion;
if (boundary[0] == QLatin1Char('\"'))
boundary.remove(0, 1);
if (boundary[boundary.length() - 1] == QLatin1Char('\"'))
boundary.remove(boundary.length() - 1, 1);
// printf("boundary: [%s]\n", qPrintable(boundary));
QString boundary_end = QStringLiteral("--") + boundary;
boundary = QStringLiteral("--") + boundary;
QString work = open_mime_envelope(in);
// printf("work: [%s]\n", qPrintable(work));
n = work.indexOf(boundary);
if (n == -1)
return false;
n += boundary.length();
i = indexOf_newline(work, n);
if (i == -1)
return false;
n += newline_len(work, i);
int data_start = n;
n = work.indexOf(boundary, data_start);
if (n == -1)
return false;
int data_end = n;
n = data_end + boundary.length();
i = indexOf_newline(work, n);
if (i == -1)
return false;
n += newline_len(work, i);
int next = n;
QString tmp_data = work.mid(data_start, data_end - data_start);
n = work.indexOf(boundary_end, next);
if (n == -1)
return false;
QString tmp_sig = work.mid(next, n - next);
// nuke some newlines
if (tmp_data.right(2) == QLatin1String("\r\n"))
tmp_data.truncate(tmp_data.length() - 2);
else if (tmp_data.right(1) == QLatin1String("\n"))
tmp_data.truncate(tmp_data.length() - 1);
if (tmp_sig.right(2) == QLatin1String("\r\n"))
tmp_sig.truncate(tmp_sig.length() - 2);
else if (tmp_sig.right(1) == QLatin1String("\n"))
tmp_sig.truncate(tmp_sig.length() - 1);
tmp_sig = open_mime_envelope(tmp_sig);
*data = tmp_data;
*sig = tmp_sig;
return true;
}
static QString idHash(const QString &id)
{
// hash the id and take the rightmost 4 hex characters
return QCA::Hash(QStringLiteral("md5")).hashToString(id.toUtf8()).right(4);
}
// first = ids, second = names
static QPair<QStringList, QStringList> getKeyStoreStrings(const QStringList &list, QCA::KeyStoreManager *ksm)
{
QPair<QStringList, QStringList> out;
for (int n = 0; n < list.count(); ++n) {
QCA::KeyStore ks(list[n], ksm);
out.first.append(idHash(ks.id()));
out.second.append(ks.name());
}
return out;
}
static QPair<QStringList, QStringList> getKeyStoreEntryStrings(const QList<QCA::KeyStoreEntry> &list)
{
QPair<QStringList, QStringList> out;
for (int n = 0; n < list.count(); ++n) {
out.first.append(idHash(list[n].id()));
out.second.append(list[n].name());
}
return out;
}
static QList<int> getPartialMatches(const QStringList &list, const QString &str)
{
QList<int> out;
for (int n = 0; n < list.count(); ++n) {
if (list[n].contains(str, Qt::CaseInsensitive))
out += n;
}
return out;
}
static int findByString(const QPair<QStringList, QStringList> &in, const QString &str)
{
// exact id match
int n = in.first.indexOf(str);
if (n != -1)
return n;
// partial id match
QList<int> ret = getPartialMatches(in.first, str);
if (!ret.isEmpty())
return ret.first();
// partial name match
ret = getPartialMatches(in.second, str);
if (!ret.isEmpty())
return ret.first();
return -1;
}
static QString getKeyStore(const QString &name)
{
QCA::KeyStoreManager ksm;
QStringList storeList = ksm.keyStores();
int n = findByString(getKeyStoreStrings(storeList, &ksm), name);
if (n != -1)
return storeList[n];
return QString();
}
static QCA::KeyStoreEntry getKeyStoreEntry(QCA::KeyStore *store, const QString &name)
{
QList<QCA::KeyStoreEntry> list = store->entryList();
int n = findByString(getKeyStoreEntryStrings(list), name);
if (n != -1)
return list[n];
return QCA::KeyStoreEntry();
}
// here are a bunch of get_Foo functions for the various types
// E - generic entry
// K - private key
// C - cert
// X - keybundle
// P - pgp public key
// S - pgp secret key
// in all cases but K, the store:obj notation can be used. if there
// is no colon present, then we treat the input as a filename. we
// try the file as an exported passive entry id, and if the type
// is C or X, we'll fall back to regular files if necessary.
static QCA::KeyStoreEntry get_E(const QString &name, bool nopassiveerror = false)
{
QCA::KeyStoreEntry entry;
QCA::KeyStoreManager::start();
int n = name.indexOf(QLatin1Char(':'));
if (n != -1) {
ksm_start_and_wait();
// store:obj lookup
QString storeName = name.mid(0, n);
QString objectName = name.mid(n + 1);
QCA::KeyStoreManager ksm;
QCA::KeyStore store(getKeyStore(storeName), &ksm);
if (!store.isValid()) {
fprintf(stderr, "Error: no such store [%s].\n", qPrintable(storeName));
return entry;
}
entry = getKeyStoreEntry(&store, objectName);
if (entry.isNull()) {
fprintf(stderr, "Error: no such object [%s].\n", qPrintable(objectName));
return entry;
}
} else {
// exported id
QString serialized = read_ksentry_file(name);
entry = QCA::KeyStoreEntry(serialized);
if (entry.isNull()) {
if (!nopassiveerror)
fprintf(stderr, "Error: invalid/unknown entry [%s].\n", qPrintable(name));
return entry;
}
}
return entry;
}
static QCA::PrivateKey get_K(const QString &name)
{
QCA::PrivateKey key;
int n = name.indexOf(QLatin1Char(':'));
if (n != -1) {
fprintf(stderr, "Error: cannot use store:obj notation for raw private keys.\n");
return key;
}
if (is_pem_file(name))
key = QCA::PrivateKey::fromPEMFile(name);
else
key = QCA::PrivateKey::fromDER(read_der_file(name));
if (key.isNull()) {
fprintf(stderr, "Error: unable to read/process private key file.\n");
return key;
}
return key;
}
static QCA::Certificate get_C(const QString &name)
{
QCA::KeyStoreEntry entry = get_E(name, true);
if (!entry.isNull()) {
if (entry.type() != QCA::KeyStoreEntry::TypeCertificate) {
fprintf(stderr, "Error: entry is not a certificate.\n");
return QCA::Certificate();
}
return entry.certificate();
}
if (!QCA::isSupported("cert")) {
fprintf(stderr, "Error: need 'cert' feature.\n");
return QCA::Certificate();
}
// try file
QCA::Certificate cert;
if (is_pem_file(name))
cert = QCA::Certificate::fromPEMFile(name);
else
cert = QCA::Certificate::fromDER(read_der_file(name));
if (cert.isNull()) {
fprintf(stderr, "Error: unable to read/process certificate file.\n");
return cert;
}
return cert;
}
static QCA::KeyBundle get_X(const QString &name)
{
QCA::KeyStoreEntry entry = get_E(name, true);
if (!entry.isNull()) {
if (entry.type() != QCA::KeyStoreEntry::TypeKeyBundle) {
fprintf(stderr, "Error: entry is not a keybundle.\n");
return QCA::KeyBundle();
}
return entry.keyBundle();
}
if (!QCA::isSupported("pkcs12")) {
fprintf(stderr, "Error: need 'pkcs12' feature.\n");
return QCA::KeyBundle();
}
// try file
QCA::KeyBundle key = QCA::KeyBundle::fromFile(name);
if (key.isNull()) {
fprintf(stderr, "Error: unable to read/process keybundle file.\n");
return key;
}
return key;
}
static QCA::PGPKey get_P(const QString &name)
{
QCA::KeyStoreEntry entry = get_E(name, true);
if (!entry.isNull()) {
if (entry.type() != QCA::KeyStoreEntry::TypePGPPublicKey &&
entry.type() != QCA::KeyStoreEntry::TypePGPSecretKey) {
fprintf(stderr, "Error: entry is not a pgp public key.\n");
return QCA::PGPKey();
}
return entry.pgpPublicKey();
}
// try file
QCA::PGPKey key = QCA::PGPKey::fromFile(name);
if (key.isNull()) {
fprintf(stderr, "Error: unable to read/process pgp key file.\n");
return key;
}
return key;
}
static QPair<QCA::PGPKey, QCA::PGPKey> get_S(const QString &name, bool noerror = false)
{
QPair<QCA::PGPKey, QCA::PGPKey> key;
QCA::KeyStoreEntry entry = get_E(name, true);
if (!entry.isNull()) {
if (entry.type() != QCA::KeyStoreEntry::TypePGPSecretKey) {
if (!noerror)
fprintf(stderr, "Error: entry is not a pgp secret key.\n");
return key;
}
key.first = entry.pgpSecretKey();
key.second = entry.pgpPublicKey();
return key;
}
return key;
}
static void usage()
{
printf("%s: simple qca utility\n", APPNAME);
printf("usage: %s (options) [command]\n", EXENAME);
printf(" options: --pass=x, --newpass=x, --nonroots=x, --roots=x, --nosys,\n");
printf(" --noprompt, --ordered, --debug, --log-file=x, --log-level=n,\n");
printf(" --nobundle\n");
printf("\n");
printf(" help|--help|-h This help text\n");
printf(" version|--version|-v Print version information\n");
printf(" plugins List available plugins\n");
printf(" config [command]\n");
printf(" save [provider] Save default provider config\n");
printf(" edit [provider] Edit provider config\n");
printf(" key [command]\n");
printf(" make rsa|dsa [bits] Create a key pair\n");
printf(" changepass [K] Add/change/remove passphrase of a key\n");
printf(" cert [command]\n");
printf(" makereq [K] Create certificate request (CSR)\n");
printf(" makeself [K] Create self-signed certificate\n");
printf(" makereqadv [K] Advanced version of 'makereq'\n");
printf(" makeselfadv [K] Advanced version of 'makeself'\n");
printf(" validate [C] Validate certificate\n");
printf(" keybundle [command]\n");
printf(" make [K] [C] Create a keybundle\n");
printf(" extract [X] Extract certificate(s) and key\n");
printf(" changepass [X] Change passphrase of a keybundle\n");
printf(" keystore [command]\n");
printf(" list-stores List all available keystores\n");
printf(" list [storeName] List content of a keystore\n");
printf(" monitor Monitor for keystore availability\n");
printf(" export [E] Export a keystore entry's content\n");
printf(" exportref [E] Export a keystore entry reference\n");
printf(" addkb [storeName] [cert.p12] Add a keybundle into a keystore\n");
printf(" addpgp [storeName] [key.asc] Add a PGP key into a keystore\n");
printf(" remove [E] Remove an object from a keystore\n");
printf(" show [command]\n");
printf(" cert [C] Examine a certificate\n");
printf(" req [req.pem] Examine a certificate request (CSR)\n");
printf(" crl [crl.pem] Examine a certificate revocation list\n");
printf(" kb [X] Examine a keybundle\n");
printf(" pgp [P|S] Examine a PGP key\n");
printf(" message [command]\n");
printf(" sign pgp|pgpdetach|smime [X|S] Sign a message\n");
printf(" encrypt pgp|smime [C|P] Encrypt a message\n");
printf(" signencrypt [S] [P] PGP sign & encrypt a message\n");
printf(" verify pgp|smime Verify a message\n");
printf(" decrypt pgp|smime ((X) ...) Decrypt a message (S/MIME needs X)\n");
printf(" exportcerts Export certs from S/MIME message\n");
printf("\n");
printf("Object types: K = private key, C = certificate, X = key bundle,\n");
printf(" P = PGP public key, S = PGP secret key, E = generic entry\n");
printf("\n");
printf("An object must be either a filename or a keystore reference (\"store:obj\").\n");
printf("\n");
printf("Log level is from 0 (quiet) to 8 (debug)\n");
printf("\n");
}
int main(int argc, char **argv)
{
QCA::Initializer qcaInit;
QCoreApplication app(argc, argv);
QFile logFile;
QTextStream logStream(stderr);
StreamLogger streamLogger(logStream);
QStringList args;
for (int n = 1; n < argc; ++n)
args.append(QString::fromLocal8Bit(argv[n]));
if (args.count() < 1) {
usage();
return 1;
}
bool have_pass = false;
bool have_newpass = false;
QCA::SecureArray pass, newpass;
bool allowprompt = true;
bool ordered = false;
bool debug = false;
bool nosys = false;
bool nobundle = false;
QString rootsFile, nonRootsFile;
for (int n = 0; n < args.count(); ++n) {
QString s = args[n];
if (!s.startsWith(QLatin1String("--")))
continue;
QString var;
QString val;
int x = s.indexOf(QLatin1Char('='));
if (x != -1) {
var = s.mid(2, x - 2);
val = s.mid(x + 1);
} else {
var = s.mid(2);
}
bool known = true;
if (var == QLatin1String("pass")) {
have_pass = true;
pass = val.toUtf8();
} else if (var == QLatin1String("newpass")) {
have_newpass = true;
newpass = val.toUtf8();
} else if (var == QLatin1String("log-file")) {
logFile.setFileName(val);
logFile.open(QIODevice::Append | QIODevice::Text | QIODevice::Unbuffered);
logStream.setDevice(&logFile);
} else if (var == QLatin1String("log-level")) {
QCA::logger()->setLevel((QCA::Logger::Severity)val.toInt());
} else if (var == QLatin1String("noprompt"))
allowprompt = false;
else if (var == QLatin1String("ordered"))
ordered = true;
else if (var == QLatin1String("debug"))
debug = true;
else if (var == QLatin1String("roots"))
rootsFile = val;
else if (var == QLatin1String("nonroots"))
nonRootsFile = val;
else if (var == QLatin1String("nosys"))
nosys = true;
else if (var == QLatin1String("nobundle"))
nobundle = true;
else
known = false;
if (known) {
args.removeAt(n);
--n; // adjust position
}
}
// help
if (args.isEmpty() || args[0] == QLatin1String("help") || args[0] == QLatin1String("--help") ||
args[0] == QLatin1String("-h")) {
usage();
return 0;
}
// version
if (args[0] == QLatin1String("version") || args[0] == QLatin1String("--version") ||
args[0] == QLatin1String("-v")) {
int ver = qcaVersion();
int maj = (ver >> 16) & 0xff;
int min = (ver >> 8) & 0xff;
int bug = ver & 0xff;
printf("%s version %s by Justin Karneges\n", APPNAME, VERSION);
printf("Using QCA version %d.%d.%d\n", maj, min, bug);
return 0;
}
// show plugins
if (args[0] == QLatin1String("plugins")) {
QStringList paths = QCA::pluginPaths();
if (!paths.isEmpty()) {
for (int n = 0; n < paths.count(); ++n) {
printf(" %s\n", qPrintable(QDir::toNativeSeparators(paths[n])));
}
} else
printf(" (none)\n");
QCA::ProviderList list = QCA::providers();
if (debug)
output_plugin_diagnostic_text();
printf("Available Providers:\n");
if (!list.isEmpty()) {
for (int n = 0; n < list.count(); ++n) {
printf(" %s\n", qPrintable(list[n]->name()));
QString credit = list[n]->credit();
if (!credit.isEmpty()) {
QStringList lines = wrapstring(credit, 74);
foreach (const QString &s, lines)
printf(" %s\n", qPrintable(s));
}
if (debug) {
QStringList capabilities = list[n]->features();
foreach (const QString &capability, capabilities) {
printf(" *%s", qPrintable(capability));
if (!QCA::isSupported(qPrintable(capability), list[n]->name())) {
printf("(NOT supported) - bug");
}
printf("\n");
}
}
}
} else
printf(" (none)\n");
QCA::unloadAllPlugins();
if (debug)
output_plugin_diagnostic_text();
return 0;
}
// config stuff
if (args[0] == QLatin1String("config")) {
if (args.count() < 2) {
usage();
return 1;
}
if (args[1] == QLatin1String("save")) {
if (args.count() < 3) {
usage();
return 1;
}
QString name = args[2];
QCA::Provider *p = QCA::findProvider(name);
if (!p) {
fprintf(stderr, "Error: no such provider '%s'.\n", qPrintable(name));
return 1;
}
QVariantMap map1 = p->defaultConfig();
if (map1.isEmpty()) {
fprintf(stderr, "Error: provider does not support configuration.\n");
return 1;
}
// set and save
QCA::setProviderConfig(name, map1);
QCA::saveProviderConfig(name);
printf("Done.\n");
return 0;
} else if (args[1] == QLatin1String("edit")) {
if (args.count() < 3) {
usage();
return 1;
}
QString name = args[2];
if (!QCA::findProvider(name)) {
fprintf(stderr, "Error: no such provider '%s'.\n", qPrintable(name));
return 1;
}
QVariantMap map1 = QCA::getProviderConfig(name);
if (map1.isEmpty()) {
fprintf(stderr, "Error: provider does not support configuration.\n");
return 1;
}
printf("Editing configuration for %s ...\n", qPrintable(name));
printf("Note: to clear a string entry, type whitespace and press enter.\n");
map1 = provider_config_edit(map1);
if (map1.isEmpty())
return 1;
// set and save
QCA::setProviderConfig(name, map1);
QCA::saveProviderConfig(name);
printf("Done.\n");
return 0;
} else {
usage();
return 1;
}
}
// enable console passphrase prompt
PassphrasePromptThread passphrasePrompt;
if (!allowprompt)
passphrasePrompt.pp->allowPrompt = false;
if (have_pass)
passphrasePrompt.pp->setExplicitPassword(pass);
if (args[0] == QLatin1String("key")) {
if (args.count() < 2) {
usage();
return 1;
}
if (args[1] == QLatin1String("make")) {
if (args.count() < 4) {
usage();
return 1;
}
bool genrsa;
int bits;
if (args[2] == QLatin1String("rsa")) {
if (!QCA::isSupported("rsa")) {
fprintf(stderr, "Error: need 'rsa' feature.\n");
return 1;
}
genrsa = true;
bits = args[3].toInt();
if (bits < 512) {
fprintf(stderr, "Error: RSA bits must be at least 512.\n");
return 1;
}
} else if (args[2] == QLatin1String("dsa")) {
if (!QCA::isSupported("dsa")) {
fprintf(stderr, "Error: need 'dsa' feature.\n");
return 1;
}
if (!QCA::isSupported("dlgroup")) {
fprintf(stderr, "Error: need 'dlgroup' feature.\n");
return 1;
}
genrsa = false;
bits = args[3].toInt();
if (bits != 512 && bits != 768 && bits != 1024) {
fprintf(stderr, "Error: DSA bits must be 512, 768, or 1024.\n");
return 1;
}
} else {
usage();
return 1;
}
if (!allowprompt && !have_newpass) {
fprintf(stderr, "Error: no passphrase specified (use '--newpass=' for none).\n");
return 1;
}
QCA::PrivateKey priv;
QString pubFileName, privFileName;
if (genrsa) {
// note: third arg is bogus, doesn't apply to RSA
priv = AnimatedKeyGen::makeKey(QCA::PKey::RSA, bits, QCA::DSA_512);
pubFileName = QStringLiteral("rsapub.pem");
privFileName = QStringLiteral("rsapriv.pem");
} else // dsa
{
QCA::DLGroupSet set;
if (bits == 512)
set = QCA::DSA_512;
else if (bits == 768)
set = QCA::DSA_768;
else // 1024
set = QCA::DSA_1024;
// note: second arg is bogus, doesn't apply to DSA
priv = AnimatedKeyGen::makeKey(QCA::PKey::DSA, 0, set);
pubFileName = QStringLiteral("dsapub.pem");
privFileName = QStringLiteral("dsapriv.pem");
}
if (priv.isNull()) {
fprintf(stderr, "Error: unable to generate key.\n");
return 1;
}
QCA::PublicKey pub = priv.toPublicKey();
// prompt for new passphrase if necessary
if (!have_newpass) {
while (!promptForNewPassphrase(&newpass)) { }
have_newpass = true;
}
if (pub.toPEMFile(pubFileName))
printf("Public key saved to %s\n", qPrintable(pubFileName));
else {
fprintf(stderr, "Error: can't encode/write %s\n", qPrintable(pubFileName));
return 1;
}
bool ok;
if (!newpass.isEmpty())
ok = priv.toPEMFile(privFileName, newpass);
else
ok = priv.toPEMFile(privFileName);
if (ok)
printf("Private key saved to %s\n", qPrintable(privFileName));
else {
fprintf(stderr, "Error: can't encode/write %s\n", qPrintable(privFileName));
return 1;
}
} else if (args[1] == QLatin1String("changepass")) {
if (args.count() < 3) {
usage();
return 1;
}
QCA::PrivateKey priv = get_K(args[2]);
if (priv.isNull())
return 1;
if (!allowprompt && !have_newpass) {
fprintf(stderr, "Error: no passphrase specified (use '--newpass=' for none).\n");
return 1;
}
// prompt for new passphrase if necessary
if (!have_newpass) {
while (!promptForNewPassphrase(&newpass)) { }
have_newpass = true;
}
QString out;
if (!newpass.isEmpty())
out = priv.toPEM(newpass);
else
out = priv.toPEM();
if (!out.isEmpty())
printf("%s", qPrintable(out));
else {
fprintf(stderr, "Error: can't encode key.\n");
return 1;
}
} else {
usage();
return 1;
}
} else if (args[0] == QLatin1String("cert")) {
if (args.count() < 2) {
usage();
return 1;
}
if (args[1] == QLatin1String("makereq") || args[1] == QLatin1String("makereqadv")) {
if (args.count() < 3) {
usage();
return 1;
}
if (!QCA::isSupported("csr")) {
fprintf(stderr, "Error: need 'csr' feature.\n");
return 1;
}
QCA::PrivateKey priv = get_K(args[2]);
if (priv.isNull())
return 1;
printf("\n");
bool advanced = (args[1] == QLatin1String("makereqadv")) ? true : false;
QCA::CertificateOptions opts = promptForCertAttributes(advanced, true);
QCA::CertificateRequest req(opts, priv);
QString reqname = QStringLiteral("certreq.pem");
if (req.toPEMFile(reqname))
printf("Certificate request saved to %s\n", qPrintable(reqname));
else {
fprintf(stderr, "Error: can't encode/write %s\n", qPrintable(reqname));
return 1;
}
} else if (args[1] == QLatin1String("makeself") || args[1] == QLatin1String("makeselfadv")) {
if (args.count() < 3) {
usage();
return 1;
}
if (!QCA::isSupported("cert")) {
fprintf(stderr, "Error: need 'cert' feature.\n");
return 1;
}
QCA::PrivateKey priv = get_K(args[2]);
if (priv.isNull())
return 1;
printf("\n");
bool advanced = (args[1] == QLatin1String("makeselfadv")) ? true : false;
QCA::CertificateOptions opts = promptForCertAttributes(advanced, false);
QCA::Certificate cert(opts, priv);
QString certname = QStringLiteral("cert.pem");
if (cert.toPEMFile(certname))
printf("Certificate saved to %s\n", qPrintable(certname));
else {
fprintf(stderr, "Error: can't encode/write %s\n", qPrintable(certname));
return 1;
}
} else if (args[1] == QLatin1String("validate")) {
if (args.count() < 3) {
usage();
return 1;
}
QCA::Certificate target = get_C(args[2]);
if (target.isNull())
return 1;
// get roots
QCA::CertificateCollection roots;
if (!nosys)
roots += QCA::systemStore();
if (!rootsFile.isEmpty())
roots += QCA::CertificateCollection::fromFlatTextFile(rootsFile);
// get nonroots
QCA::CertificateCollection nonroots;
if (!nonRootsFile.isEmpty())
nonroots = QCA::CertificateCollection::fromFlatTextFile(nonRootsFile);
QCA::Validity v = target.validate(roots, nonroots);
if (v == QCA::ValidityGood)
printf("Certificate is valid\n");
else {
printf("Certificate is NOT valid: %s\n", qPrintable(validityToString(v)));
return 1;
}
} else {
usage();
return 1;
}
} else if (args[0] == QLatin1String("keybundle")) {
if (args.count() < 2) {
usage();
return 1;
}
if (args[1] == QLatin1String("make")) {
if (args.count() < 4) {
usage();
return 1;
}
if (!QCA::isSupported("pkcs12")) {
fprintf(stderr, "Error: need 'pkcs12' feature.\n");
return 1;
}
QCA::PrivateKey priv = get_K(args[2]);
if (priv.isNull())
return 1;
QCA::Certificate cert = get_C(args[3]);
if (cert.isNull())
return 1;
// get roots
QCA::CertificateCollection roots;
if (!nosys)
roots += QCA::systemStore();
if (!rootsFile.isEmpty())
roots += QCA::CertificateCollection::fromFlatTextFile(rootsFile);
// get nonroots
QCA::CertificateCollection nonroots;
if (!nonRootsFile.isEmpty())
nonroots = QCA::CertificateCollection::fromFlatTextFile(nonRootsFile);
QList<QCA::Certificate> issuer_pool = roots.certificates() + nonroots.certificates();
QCA::CertificateChain chain;
chain += cert;
chain = chain.complete(issuer_pool);
QCA::KeyBundle key;
key.setName(chain.primary().commonName());
key.setCertificateChainAndKey(chain, priv);
if (!allowprompt && !have_newpass) {
fprintf(stderr, "Error: no passphrase specified (use '--newpass=' for none).\n");
return 1;
}
// prompt for new passphrase if necessary
if (!have_newpass) {
while (!promptForNewPassphrase(&newpass)) { }
have_newpass = true;
}
if (newpass.isEmpty()) {
fprintf(stderr, "Error: keybundles cannot have empty passphrases.\n");
return 1;
}
QString newFileName = QStringLiteral("cert.p12");
if (key.toFile(newFileName, newpass))
printf("Keybundle saved to %s\n", qPrintable(newFileName));
else {
fprintf(stderr, "Error: can't encode keybundle.\n");
return 1;
}
} else if (args[1] == QLatin1String("extract")) {
if (args.count() < 3) {
usage();
return 1;
}
QCA::KeyBundle key = get_X(args[2]);
if (key.isNull())
return 1;
QCA::PrivateKey priv = key.privateKey();
bool export_priv = priv.canExport();
if (export_priv) {
fprintf(stderr, "You will need to create a passphrase for the extracted private key.\n");
if (!allowprompt && !have_newpass) {
fprintf(stderr, "Error: no passphrase specified (use '--newpass=' for none).\n");
return 1;
}
// prompt for new passphrase if necessary
if (!have_newpass) {
while (!promptForNewPassphrase(&newpass)) { }
have_newpass = true;
}
}
printf("Certs: (first is primary)\n");
QCA::CertificateChain chain = key.certificateChain();
for (int n = 0; n < chain.count(); ++n)
printf("%s", qPrintable(chain[n].toPEM()));
printf("Private Key:\n");
if (export_priv) {
QString out;
if (!newpass.isEmpty())
out = priv.toPEM(newpass);
else
out = priv.toPEM();
printf("%s", qPrintable(out));
} else {
printf("(Key is not exportable)\n");
}
} else if (args[1] == QLatin1String("changepass")) {
if (args.count() < 3) {
usage();
return 1;
}
QCA::KeyBundle key = get_X(args[2]);
if (key.isNull())
return 1;
if (!key.privateKey().canExport()) {
fprintf(stderr, "Error: private key not exportable.\n");
return 1;
}
if (!allowprompt && !have_newpass) {
fprintf(stderr, "Error: no passphrase specified (use '--newpass=' for none).\n");
return 1;
}
// prompt for new passphrase if necessary
if (!have_newpass) {
while (!promptForNewPassphrase(&newpass)) { }
have_newpass = true;
}
if (newpass.isEmpty()) {
fprintf(stderr, "Error: keybundles cannot have empty passphrases.\n");
return 1;
}
QFileInfo fi(args[2]);
QString newFileName = fi.baseName() + QStringLiteral("_new.p12");
if (key.toFile(newFileName, newpass))
printf("Keybundle saved to %s\n", qPrintable(newFileName));
else {
fprintf(stderr, "Error: can't encode keybundle.\n");
return 1;
}
} else {
usage();
return 1;
}
} else if (args[0] == QLatin1String("keystore")) {
if (args.count() < 2) {
usage();
return 1;
}
if (args[1] == QLatin1String("list-stores")) {
ksm_start_and_wait();
QCA::KeyStoreManager ksm;
QStringList storeList = ksm.keyStores();
for (int n = 0; n < storeList.count(); ++n) {
QCA::KeyStore ks(storeList[n], &ksm);
QString type = kstype_to_string(ks.type());
printf("%s %s [%s]\n", qPrintable(type), qPrintable(idHash(ks.id())), qPrintable(ks.name()));
}
if (debug)
output_keystore_diagnostic_text();
} else if (args[1] == QLatin1String("list")) {
if (args.count() < 3) {
usage();
return 1;
}
ksm_start_and_wait();
QCA::KeyStoreManager ksm;
QCA::KeyStore store(getKeyStore(args[2]), &ksm);
if (!store.isValid()) {
if (debug)
output_keystore_diagnostic_text();
fprintf(stderr, "Error: no such store\n");
return 1;
}
QList<QCA::KeyStoreEntry> list = store.entryList();
for (int n = 0; n < list.count(); ++n) {
QCA::KeyStoreEntry i = list[n];
QString type = ksentrytype_to_string(i.type());
printf("%s %s [%s]\n", qPrintable(type), qPrintable(idHash(i.id())), qPrintable(i.name()));
}
if (debug)
output_keystore_diagnostic_text();
} else if (args[1] == QLatin1String("monitor")) {
KeyStoreMonitor::monitor();
if (debug)
output_keystore_diagnostic_text();
} else if (args[1] == QLatin1String("export")) {
if (args.count() < 3) {
usage();
return 1;
}
QCA::KeyStoreEntry entry = get_E(args[2]);
if (entry.isNull())
return 1;
if (entry.type() == QCA::KeyStoreEntry::TypeCertificate)
printf("%s", qPrintable(entry.certificate().toPEM()));
else if (entry.type() == QCA::KeyStoreEntry::TypeCRL)
printf("%s", qPrintable(entry.crl().toPEM()));
else if (entry.type() == QCA::KeyStoreEntry::TypePGPPublicKey ||
entry.type() == QCA::KeyStoreEntry::TypePGPSecretKey)
printf("%s", qPrintable(entry.pgpPublicKey().toString()));
else if (entry.type() == QCA::KeyStoreEntry::TypeKeyBundle) {
fprintf(stderr, "Error: use 'keybundle extract' command instead.\n");
return 1;
} else {
fprintf(stderr, "Error: cannot export type '%d'.\n", entry.type());
return 1;
}
} else if (args[1] == QLatin1String("exportref")) {
if (args.count() < 3) {
usage();
return 1;
}
QCA::KeyStoreEntry entry = get_E(args[2]);
if (entry.isNull())
return 1;
printf("%s", make_ksentry_string(entry.toString()).toUtf8().data());
} else if (args[1] == QLatin1String("addkb")) {
if (args.count() < 4) {
usage();
return 1;
}
ksm_start_and_wait();
QCA::KeyStoreManager ksm;
QCA::KeyStore store(getKeyStore(args[2]), &ksm);
if (!store.isValid()) {
fprintf(stderr, "Error: no such store\n");
return 1;
}
QCA::KeyBundle key = get_X(args[3]);
if (key.isNull())
return 1;
if (!store.writeEntry(key).isEmpty())
printf("Entry written.\n");
else {
fprintf(stderr, "Error: unable to write entry.\n");
return 1;
}
} else if (args[1] == QLatin1String("addpgp")) {
if (args.count() < 4) {
usage();
return 1;
}
if (!QCA::isSupported("openpgp")) {
fprintf(stderr, "Error: need 'openpgp' feature.\n");
return 1;
}
ksm_start_and_wait();
QCA::KeyStoreManager ksm;
QCA::KeyStore store(getKeyStore(args[2]), &ksm);
if (!store.isValid()) {
fprintf(stderr, "Error: no such store\n");
return 1;
}
QCA::PGPKey pub = QCA::PGPKey::fromFile(args[3]);
if (pub.isNull())
return 1;
if (!store.writeEntry(pub).isEmpty())
printf("Entry written.\n");
else {
fprintf(stderr, "Error: unable to write entry.\n");
return 1;
}
} else if (args[1] == QLatin1String("remove")) {
if (args.count() < 3) {
usage();
return 1;
}
QCA::KeyStoreEntry entry = get_E(args[2]);
if (entry.isNull())
return 1;
QCA::KeyStoreManager ksm;
QCA::KeyStore store(entry.storeId(), &ksm);
if (!store.isValid()) {
fprintf(stderr, "Error: no such store\n");
return 1;
}
if (store.removeEntry(entry.id()))
printf("Entry removed.\n");
else {
fprintf(stderr, "Error: unable to remove entry.\n");
return 1;
}
} else {
usage();
return 1;
}
} else if (args[0] == QLatin1String("show")) {
if (args.count() < 2) {
usage();
return 1;
}
if (args[1] == QLatin1String("cert")) {
if (args.count() < 3) {
usage();
return 1;
}
QCA::Certificate cert = get_C(args[2]);
if (cert.isNull())
return 1;
print_cert(cert, ordered);
} else if (args[1] == QLatin1String("req")) {
if (args.count() < 3) {
usage();
return 1;
}
if (!QCA::isSupported("csr")) {
fprintf(stderr, "Error: need 'csr' feature.\n");
return 1;
}
QCA::CertificateRequest req(args[2]);
if (req.isNull()) {
fprintf(stderr, "Error: can't read/process certificate request file.\n");
return 1;
}
print_certreq(req, ordered);
} else if (args[1] == QLatin1String("crl")) {
if (args.count() < 3) {
usage();
return 1;
}
if (!QCA::isSupported("crl")) {
fprintf(stderr, "Error: need 'crl' feature.\n");
return 1;
}
QCA::CRL crl;
if (is_pem_file(args[2]))
crl = QCA::CRL::fromPEMFile(args[2]);
else
crl = QCA::CRL::fromDER(read_der_file(args[2]));
if (crl.isNull()) {
fprintf(stderr, "Error: unable to read/process CRL file.\n");
return 1;
}
print_crl(crl, ordered);
} else if (args[1] == QLatin1String("kb")) {
if (args.count() < 3) {
usage();
return 1;
}
QCA::KeyBundle key = get_X(args[2]);
if (key.isNull())
return 1;
printf("Keybundle contains %d certificates. Displaying primary:\n", int(key.certificateChain().count()));
print_cert(key.certificateChain().primary(), ordered);
} else if (args[1] == QLatin1String("pgp")) {
if (args.count() < 3) {
usage();
return 1;
}
// try for secret key, then try public key
QCA::PGPKey key = get_S(args[2], true).first;
if (key.isNull()) {
key = get_P(args[2]);
if (key.isNull())
return 1;
}
print_pgp(key);
} else {
usage();
return 1;
}
} else if (args[0] == QLatin1String("message")) {
if (args.count() < 2) {
usage();
return 1;
}
if (args[1] == QLatin1String("sign")) {
if (args.count() < 4) {
usage();
return 1;
}
QCA::SecureMessageSystem * sms;
QCA::SecureMessageKey skey;
QCA::SecureMessage::SignMode mode;
bool pgp = false;
if (args[2] == QLatin1String("pgp")) {
if (!QCA::isSupported("openpgp")) {
fprintf(stderr, "Error: need 'openpgp' feature.\n");
return 1;
}
QPair<QCA::PGPKey, QCA::PGPKey> key = get_S(args[3]);
if (key.first.isNull())
return 1;
sms = new QCA::OpenPGP;
skey.setPGPSecretKey(key.first);
mode = QCA::SecureMessage::Clearsign;
pgp = true;
} else if (args[2] == QLatin1String("pgpdetach")) {
if (!QCA::isSupported("openpgp")) {
fprintf(stderr, "Error: need 'openpgp' feature.\n");
return 1;
}
QPair<QCA::PGPKey, QCA::PGPKey> key = get_S(args[3]);
if (key.first.isNull())
return 1;
sms = new QCA::OpenPGP;
skey.setPGPSecretKey(key.first);
mode = QCA::SecureMessage::Detached;
pgp = true;
} else if (args[2] == QLatin1String("smime")) {
if (!QCA::isSupported("cms")) {
fprintf(stderr, "Error: need 'cms' feature.\n");
return 1;
}
QCA::KeyBundle key = get_X(args[3]);
if (key.isNull())
return 1;
// get nonroots
QCA::CertificateCollection nonroots;
if (!nonRootsFile.isEmpty())
nonroots = QCA::CertificateCollection::fromFlatTextFile(nonRootsFile);
QList<QCA::Certificate> issuer_pool = nonroots.certificates();
QCA::CertificateChain chain = key.certificateChain();
chain = chain.complete(issuer_pool);
sms = new QCA::CMS;
skey.setX509CertificateChain(chain);
skey.setX509PrivateKey(key.privateKey());
mode = QCA::SecureMessage::Detached;
} else {
usage();
return 1;
}
// read input data from stdin all at once
QByteArray plain;
while (!feof(stdin)) {
QByteArray block(1024, 0);
int n = fread(block.data(), 1, 1024, stdin);
if (n < 0)
break;
block.resize(n);
plain += block;
}
// smime envelope
if (!pgp) {
QString text = add_cr(QString::fromUtf8(plain));
plain = QString::fromLatin1(mime_signpart).arg(text).toUtf8();
}
QCA::SecureMessage *msg = new QCA::SecureMessage(sms);
msg->setSigner(skey);
// pgp should always be ascii
if (pgp)
msg->setFormat(QCA::SecureMessage::Ascii);
msg->setBundleSignerEnabled(!nobundle);
msg->startSign(mode);
msg->update(plain);
msg->end();
msg->waitForFinished(-1);
if (debug) {
output_keystore_diagnostic_text();
output_message_diagnostic_text(msg);
}
if (!msg->success()) {
QString errstr = smErrorToString(msg->errorCode());
delete msg;
delete sms;
fprintf(stderr, "Error: unable to sign: %s\n", qPrintable(errstr));
return 1;
}
QString hashName = msg->hashName();
QByteArray output;
if (mode == QCA::SecureMessage::Detached)
output = msg->signature();
else
output = msg->read();
delete msg;
delete sms;
// smime envelope
if (!pgp) {
QCA::Base64 enc;
enc.setLineBreaksEnabled(true);
enc.setLineBreaksColumn(76);
QString sigtext = add_cr(enc.arrayToString(output));
QString str = QString::fromLatin1(mime_signed).arg(hashName, QString::fromUtf8(plain), sigtext);
output = str.toUtf8();
}
printf("%s", output.data());
} else if (args[1] == QLatin1String("encrypt")) {
if (args.count() < 4) {
usage();
return 1;
}
QCA::SecureMessageSystem *sms;
QCA::SecureMessageKey skey;
bool pgp = false;
if (args[2] == QLatin1String("pgp")) {
if (!QCA::isSupported("openpgp")) {
fprintf(stderr, "Error: need 'openpgp' feature.\n");
return 1;
}
QCA::PGPKey key = get_P(args[3]);
if (key.isNull())
return 1;
sms = new QCA::OpenPGP;
skey.setPGPPublicKey(key);
pgp = true;
} else if (args[2] == QLatin1String("smime")) {
if (!QCA::isSupported("cms")) {
fprintf(stderr, "Error: need 'cms' feature.\n");
return 1;
}
QCA::Certificate cert = get_C(args[3]);
if (cert.isNull())
return 1;
sms = new QCA::CMS;
skey.setX509CertificateChain(cert);
} else {
usage();
return 1;
}
// read input data from stdin all at once
QByteArray plain;
while (!feof(stdin)) {
QByteArray block(1024, 0);
int n = fread(block.data(), 1, 1024, stdin);
if (n < 0)
break;
block.resize(n);
plain += block;
}
QCA::SecureMessage *msg = new QCA::SecureMessage(sms);
msg->setRecipient(skey);
// pgp should always be ascii
if (pgp)
msg->setFormat(QCA::SecureMessage::Ascii);
msg->startEncrypt();
msg->update(plain);
msg->end();
msg->waitForFinished(-1);
if (debug) {
output_keystore_diagnostic_text();
output_message_diagnostic_text(msg);
}
if (!msg->success()) {
QString errstr = smErrorToString(msg->errorCode());
delete msg;
delete sms;
fprintf(stderr, "Error: unable to encrypt: %s\n", qPrintable(errstr));
return 1;
}
QByteArray output = msg->read();
delete msg;
delete sms;
// smime envelope
if (!pgp) {
QCA::Base64 enc;
enc.setLineBreaksEnabled(true);
enc.setLineBreaksColumn(76);
QString enctext = add_cr(enc.arrayToString(output));
QString str = QString::fromLatin1(mime_enveloped).arg(enctext);
output = str.toUtf8();
}
printf("%s", output.data());
} else if (args[1] == QLatin1String("signencrypt")) {
if (args.count() < 4) {
usage();
return 1;
}
if (!QCA::isSupported("openpgp")) {
fprintf(stderr, "Error: need 'openpgp' feature.\n");
return 1;
}
QCA::SecureMessageSystem *sms;
QCA::SecureMessageKey skey;
QCA::SecureMessageKey rkey;
{
QPair<QCA::PGPKey, QCA::PGPKey> sec = get_S(args[2]);
if (sec.first.isNull())
return 1;
QCA::PGPKey pub = get_P(args[3]);
if (pub.isNull())
return 1;
sms = new QCA::OpenPGP;
skey.setPGPSecretKey(sec.first);
rkey.setPGPPublicKey(pub);
}
// read input data from stdin all at once
QByteArray plain;
while (!feof(stdin)) {
QByteArray block(1024, 0);
int n = fread(block.data(), 1, 1024, stdin);
if (n < 0)
break;
block.resize(n);
plain += block;
}
QCA::SecureMessage *msg = new QCA::SecureMessage(sms);
if (!msg->canSignAndEncrypt()) {
delete msg;
delete sms;
fprintf(stderr, "Error: cannot perform integrated sign and encrypt.\n");
return 1;
}
msg->setSigner(skey);
msg->setRecipient(rkey);
msg->setFormat(QCA::SecureMessage::Ascii);
msg->startSignAndEncrypt();
msg->update(plain);
msg->end();
msg->waitForFinished(-1);
if (debug) {
output_keystore_diagnostic_text();
output_message_diagnostic_text(msg);
}
if (!msg->success()) {
QString errstr = smErrorToString(msg->errorCode());
delete msg;
delete sms;
fprintf(stderr, "Error: unable to sign and encrypt: %s\n", qPrintable(errstr));
return 1;
}
QByteArray output = msg->read();
delete msg;
delete sms;
printf("%s", output.data());
} else if (args[1] == QLatin1String("verify")) {
if (args.count() < 3) {
usage();
return 1;
}
QCA::SecureMessageSystem *sms;
bool pgp = false;
if (args[2] == QLatin1String("pgp")) {
if (!QCA::isSupported("openpgp")) {
fprintf(stderr, "Error: need 'openpgp' feature.\n");
return 1;
}
sms = new QCA::OpenPGP;
pgp = true;
} else if (args[2] == QLatin1String("smime")) {
if (!QCA::isSupported("cms")) {
fprintf(stderr, "Error: need 'cms' feature.\n");
return 1;
}
// get roots
QCA::CertificateCollection roots;
if (!nosys)
roots += QCA::systemStore();
if (!rootsFile.isEmpty())
roots += QCA::CertificateCollection::fromFlatTextFile(rootsFile);
// get intermediates and possible signers, in case
// the message does not have them.
QCA::CertificateCollection nonroots;
if (!nonRootsFile.isEmpty())
nonroots += QCA::CertificateCollection::fromFlatTextFile(nonRootsFile);
sms = new QCA::CMS;
((QCA::CMS *)sms)->setTrustedCertificates(roots);
((QCA::CMS *)sms)->setUntrustedCertificates(nonroots);
} else {
usage();
return 1;
}
QByteArray data, sig;
QString smime_text;
{
// read input data from stdin all at once
QByteArray plain;
while (!feof(stdin)) {
QByteArray block(1024, 0);
int n = fread(block.data(), 1, 1024, stdin);
if (n < 0)
break;
block.resize(n);
plain += block;
}
if (pgp) {
// pgp can be either a detached signature followed
// by data, or an integrated message.
// detached signature?
if (plain.startsWith("-----BEGIN PGP SIGNATURE-----")) {
QByteArray footer = "-----END PGP SIGNATURE-----\n";
int n = plain.indexOf(footer);
if (n == -1) {
delete sms;
fprintf(stderr, "Error: pgp signature header, but no footer.\n");
return 1;
}
n += footer.length();
sig = plain.mid(0, n);
data = plain.mid(n);
} else {
data = plain;
}
} else {
// smime envelope
QString in = QString::fromUtf8(plain);
in = add_cr(in); // change the line endings?!
QString str, sigtext;
if (!open_mime_data_sig(in, &str, &sigtext)) {
fprintf(stderr, "Error: can't parse message file.\n");
return 1;
}
data = str.toUtf8();
smime_text = str;
QCA::Base64 dec;
dec.setLineBreaksEnabled(true);
sig = dec.stringToArray(rem_cr(sigtext)).toByteArray();
}
}
QCA::SecureMessage *msg = new QCA::SecureMessage(sms);
if (pgp)
msg->setFormat(QCA::SecureMessage::Ascii);
msg->startVerify(sig);
msg->update(data);
msg->end();
msg->waitForFinished(-1);
if (debug) {
output_keystore_diagnostic_text();
output_message_diagnostic_text(msg);
}
if (!msg->success()) {
QString errstr = smErrorToString(msg->errorCode());
delete msg;
delete sms;
fprintf(stderr, "Error: verify failed: %s\n", qPrintable(errstr));
return 1;
}
QByteArray output;
if (pgp && sig.isEmpty())
output = msg->read();
QList<QCA::SecureMessageSignature> signers = msg->signers();
delete msg;
delete sms;
// for pgp clearsign, pgp signed (non-detached), and smime,
// the signed content was inside of the message. we need
// to print that content now
if (pgp) {
printf("%s", output.data());
} else {
QString str = open_mime_envelope(smime_text);
printf("%s", str.toUtf8().data());
}
smDisplaySignatures(signers);
bool allgood = true;
foreach (const QCA::SecureMessageSignature &signer, signers) {
if (signer.identityResult() != QCA::SecureMessageSignature::Valid) {
allgood = false;
break;
}
}
if (!allgood)
return 1;
} else if (args[1] == QLatin1String("decrypt")) {
if (args.count() < 3) {
usage();
return 1;
}
QCA::SecureMessageSystem *sms;
bool pgp = false;
if (args[2] == QLatin1String("pgp")) {
if (!QCA::isSupported("openpgp")) {
fprintf(stderr, "Error: need 'openpgp' feature.\n");
return 1;
}
sms = new QCA::OpenPGP;
pgp = true;
} else if (args[2] == QLatin1String("smime")) {
if (args.count() < 4) {
usage();
return 1;
}
if (!QCA::isSupported("cms")) {
fprintf(stderr, "Error: need 'cms' feature.\n");
return 1;
}
// user can provide many possible decrypt keys
QList<QCA::KeyBundle> keys;
for (int n = 3; n < args.count(); ++n) {
QCA::KeyBundle key = get_X(args[n]);
if (key.isNull())
return 1;
keys += key;
}
sms = new QCA::CMS;
QList<QCA::SecureMessageKey> skeys;
foreach (const QCA::KeyBundle &key, keys) {
QCA::SecureMessageKey skey;
skey.setX509CertificateChain(key.certificateChain());
skey.setX509PrivateKey(key.privateKey());
skeys += skey;
}
((QCA::CMS *)sms)->setPrivateKeys(skeys);
} else {
usage();
return 1;
}
// read input data from stdin all at once
QByteArray plain;
while (!feof(stdin)) {
QByteArray block(1024, 0);
int n = fread(block.data(), 1, 1024, stdin);
if (n < 0)
break;
block.resize(n);
plain += block;
}
// smime envelope
if (!pgp) {
QString in = QString::fromUtf8(plain);
QString str = open_mime_envelope(in);
if (str.isEmpty()) {
delete sms;
fprintf(stderr, "Error: can't parse message file.\n");
return 1;
}
QCA::Base64 dec;
dec.setLineBreaksEnabled(true);
plain = dec.stringToArray(rem_cr(str)).toByteArray();
}
QCA::SecureMessage *msg = new QCA::SecureMessage(sms);
if (pgp)
msg->setFormat(QCA::SecureMessage::Ascii);
msg->startDecrypt();
msg->update(plain);
msg->end();
msg->waitForFinished(-1);
if (debug) {
output_keystore_diagnostic_text();
output_message_diagnostic_text(msg);
}
if (!msg->success()) {
QString errstr = smErrorToString(msg->errorCode());
delete msg;
delete sms;
fprintf(stderr, "Error: decrypt failed: %s\n", qPrintable(errstr));
return 1;
}
QByteArray output = msg->read();
QList<QCA::SecureMessageSignature> signers;
bool wasSigned = false;
if (msg->wasSigned()) {
signers = msg->signers();
wasSigned = true;
}
delete msg;
delete sms;
printf("%s", output.data());
if (wasSigned) {
fprintf(stderr, "Message was also signed:\n");
smDisplaySignatures(signers);
bool allgood = true;
foreach (const QCA::SecureMessageSignature &signer, signers) {
if (signer.identityResult() != QCA::SecureMessageSignature::Valid) {
allgood = false;
break;
}
}
if (!allgood)
return 1;
}
} else if (args[1] == QLatin1String("exportcerts")) {
if (!QCA::isSupported("cms")) {
fprintf(stderr, "Error: need 'cms' feature.\n");
return 1;
}
QCA::SecureMessageSystem *sms = new QCA::CMS;
QByteArray data, sig;
QString smime_text;
{
// read input data from stdin all at once
QByteArray plain;
while (!feof(stdin)) {
QByteArray block(1024, 0);
int n = fread(block.data(), 1, 1024, stdin);
if (n < 0)
break;
block.resize(n);
plain += block;
}
// smime envelope
QString in = QString::fromUtf8(plain);
QString str, sigtext;
if (!open_mime_data_sig(in, &str, &sigtext)) {
delete sms;
fprintf(stderr, "Error: can't parse message file.\n");
return 1;
}
data = str.toUtf8();
smime_text = str;
QCA::Base64 dec;
dec.setLineBreaksEnabled(true);
sig = dec.stringToArray(rem_cr(sigtext)).toByteArray();
}
QCA::SecureMessage *msg = new QCA::SecureMessage(sms);
msg->startVerify(sig);
msg->update(data);
msg->end();
msg->waitForFinished(-1);
if (debug)
output_message_diagnostic_text(msg);
if (!msg->success()) {
QString errstr = smErrorToString(msg->errorCode());
delete msg;
delete sms;
fprintf(stderr, "Error: export failed: %s\n", qPrintable(errstr));
return 1;
}
QList<QCA::SecureMessageSignature> signers = msg->signers();
delete msg;
delete sms;
// print out all certs of all signers
foreach (const QCA::SecureMessageSignature &signer, signers) {
QCA::SecureMessageKey key = signer.key();
if (!key.isNull()) {
foreach (const QCA::Certificate &c, key.x509CertificateChain())
printf("%s", qPrintable(c.toPEM()));
}
}
} else {
usage();
return 1;
}
} else {
usage();
return 1;
}
return 0;
}
#include "main.moc"