mirror of
https://github.com/QuasarApp/qca.git
synced 2025-04-27 12:04:31 +00:00
Retrieve plugin paths. It consists of: 1. QCA_PLUGIN_PATH environment if set. 2. QCoreApplication::libraryPaths() . 3. Directory where plugins were installed. QCA_PLUGIN_PATH is paths list like PATH or QT_PLUGIN_PATH. It uses system path separator. ";" on Windows and ":" on Unix. Also qcatool now uses this function.
4591 lines
105 KiB
C++
4591 lines
105 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 <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(1)
|
|
{
|
|
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
|
|
{
|
|
public:
|
|
StreamLogger(QTextStream &stream) : QCA::AbstractLogDevice( "Stream logger" ), _stream(stream)
|
|
{
|
|
QCA::logger()->registerLogDevice (this);
|
|
}
|
|
|
|
~StreamLogger()
|
|
{
|
|
QCA::logger()->unregisterLogDevice (name ());
|
|
}
|
|
|
|
void logTextMessage( const QString &message, enum QCA::Logger::Severity severity )
|
|
{
|
|
_stream << now () << " " << severityName (severity) << " " << message << endl;
|
|
}
|
|
|
|
void logBinaryMessage( const QByteArray &blob, enum QCA::Logger::Severity severity )
|
|
{
|
|
Q_UNUSED(blob);
|
|
_stream << now () << " " << severityName (severity) << " " << "Binary blob not implemented yet" << 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 = "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] == '\n')
|
|
str.truncate(str.length()-1);
|
|
QStringList lines = str.split('\n', QString::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] == '\n')
|
|
str.truncate(str.length()-1);
|
|
QStringList lines = str.split('\n', QString::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] == '\n')
|
|
str.truncate(str.length()-1);
|
|
QStringList lines = str.split('\n', QString::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, SLOT(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, SIGNAL(finished()), SLOT(gen_finished()));
|
|
connect(&t, SIGNAL(timeout()), SLOT(t_timeout()));
|
|
}
|
|
|
|
private 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, SLOT(start()));
|
|
eventLoop.exec();
|
|
}
|
|
|
|
private:
|
|
QEventLoop *eventLoop;
|
|
QCA::KeyStoreManager *ksm;
|
|
QList<QCA::KeyStore*> keyStores;
|
|
QCA::ConsolePrompt *prompt;
|
|
|
|
private 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, SIGNAL(finished()), SLOT(prompt_finished()));
|
|
prompt->getChar();
|
|
|
|
// kick off the subsystem
|
|
QCA::KeyStoreManager::start();
|
|
|
|
// setup keystore manager for monitoring
|
|
ksm = new QCA::KeyStoreManager(this);
|
|
connect(ksm, SIGNAL(keyStoreAvailable(const QString &)), SLOT(ks_available(const QString &)));
|
|
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, SIGNAL(updated()), SLOT(ks_updated()));
|
|
connect(ks, SIGNAL(unavailable()), SLOT(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 == 'q' || c == '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 = 0;
|
|
|
|
connect(&handler, SIGNAL(eventReady(int, const QCA::Event &)), SLOT(ph_eventReady(int, const QCA::Event &)));
|
|
handler.start();
|
|
|
|
connect(&ksm, SIGNAL(keyStoreAvailable(const QString &)), SLOT(ks_available(const QString &)));
|
|
foreach(const QString &keyStoreId, ksm.keyStores())
|
|
ks_available(keyStoreId);
|
|
}
|
|
|
|
~PassphrasePrompt()
|
|
{
|
|
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 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 = "password";
|
|
if(e.passwordStyle() == QCA::Event::StylePassphrase)
|
|
type = "passphrase";
|
|
else if(e.passwordStyle() == QCA::Event::StylePIN)
|
|
type = "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 = QString("the '") + e.keyStoreInfo().name() + "' token";
|
|
else
|
|
name = e.keyStoreInfo().name();
|
|
}
|
|
str = QString("Enter %1 for %2").arg(type).arg(name);
|
|
}
|
|
else if(!e.fileName().isEmpty())
|
|
str = QString("Enter %1 for %2").arg(type).arg(e.fileName());
|
|
else
|
|
str = QString("Enter %1").arg(type);
|
|
|
|
if(!prompt)
|
|
{
|
|
prompt = new QCA::ConsolePrompt(this);
|
|
connect(prompt, SIGNAL(finished()), SLOT(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 = 0;
|
|
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 = QString("Please make ") + entry.name() + " (of " + entry.storeName() + ") available";
|
|
}
|
|
else
|
|
{
|
|
name = QString("Please insert the '") + e.keyStoreInfo().name() + "' token";
|
|
}
|
|
|
|
QString str = QString("%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, SIGNAL(finished()), SLOT(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 == '\r' || c == '\n')
|
|
handler.tokenOkay(prompt_id);
|
|
else if(c == 'q' || c == '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 = 0;
|
|
}
|
|
}
|
|
|
|
void ks_available(const QString &keyStoreId)
|
|
{
|
|
QCA::KeyStore *ks = new QCA::KeyStore(keyStoreId, &ksm);
|
|
connect(ks, SIGNAL(updated()), SLOT(ks_updated()));
|
|
connect(ks, SIGNAL(unavailable()), SLOT(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
|
|
{
|
|
public:
|
|
PassphrasePrompt *pp;
|
|
|
|
PassphrasePromptThread()
|
|
{
|
|
start();
|
|
}
|
|
|
|
~PassphrasePromptThread()
|
|
{
|
|
stop();
|
|
}
|
|
|
|
protected:
|
|
virtual void atStart()
|
|
{
|
|
pp = new PassphrasePrompt;
|
|
}
|
|
|
|
virtual void atEnd()
|
|
{
|
|
delete pp;
|
|
}
|
|
};
|
|
|
|
static bool promptForNewPassphrase(QCA::SecureArray *result)
|
|
{
|
|
QCA::ConsolePrompt prompt;
|
|
prompt.getHidden("Enter new passphrase");
|
|
prompt.waitForFinished();
|
|
QCA::SecureArray out = prompt.result();
|
|
|
|
prompt.getHidden("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(int n = 0; n < in.length(); ++n)
|
|
{
|
|
if(in[n] == '\\')
|
|
out += "\\\\";
|
|
else if(in[n] == '\n')
|
|
out += "\\n";
|
|
else
|
|
out += in[n];
|
|
}
|
|
return out;
|
|
}
|
|
|
|
static QString line_decode(const QString &in)
|
|
{
|
|
QString out;
|
|
for(int n = 0; n < in.length(); ++n)
|
|
{
|
|
if(in[n] == '\\')
|
|
{
|
|
if(n + 1 < in.length())
|
|
{
|
|
if(in[n + 1] == '\\')
|
|
out += '\\';
|
|
else if(in[n + 1] == 'n')
|
|
out += '\n';
|
|
++n;
|
|
}
|
|
}
|
|
else
|
|
out += in[n];
|
|
}
|
|
return out;
|
|
}
|
|
|
|
static QString make_ksentry_string(const QString &id)
|
|
{
|
|
QString out;
|
|
out += "QCATOOL_KEYSTOREENTRY_1\n";
|
|
out += line_encode(id) + '\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 != "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("-----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(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, "CommonName", "CN", "Common Name (CN)", "Full name, domain, anything");
|
|
out += InfoType(QCA::Email, "Email", "", "Email Address", "");
|
|
if(legacyEmail)
|
|
out += InfoType(QCA::EmailLegacy, "EmailLegacy", "", "PKCS#9 Email Address", "");
|
|
out += InfoType(QCA::Organization, "Organization", "O", "Organization (O)", "Company, group, etc");
|
|
out += InfoType(QCA::OrganizationalUnit, "OrganizationalUnit", "OU", "Organizational Unit (OU)", "Division/branch of organization");
|
|
out += InfoType(QCA::Locality, "Locality", "", "Locality (L)", "City, shire, part of a state");
|
|
out += InfoType(QCA::State, "State", "", "State (ST)", "State within the country");
|
|
out += InfoType(QCA::Country, "Country", "C", "Country Code (C)", "2-letter code");
|
|
out += InfoType(QCA::IncorporationLocality, "IncorporationLocality", "", "Incorporation Locality", "For EV certificates");
|
|
out += InfoType(QCA::IncorporationState, "IncorporationState", "", "Incorporation State", "For EV certificates");
|
|
out += InfoType(QCA::IncorporationCountry, "IncorporationCountry", "", "Incorporation Country", "For EV certificates");
|
|
out += InfoType(QCA::URI, "URI", "", "URI", "");
|
|
out += InfoType(QCA::DNS, "DNS", "", "Domain Name", "Domain (dnsName)");
|
|
out += InfoType(QCA::IPAddress, "IPAddress", "", "IP Adddress", "");
|
|
out += InfoType(QCA::XMPP, "XMPP", "", "XMPP Address (JID)", "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, "DigitalSignature", "Digital Signature", "Can be used for signing");
|
|
out += MyConstraintType(QCA::NonRepudiation, "NonRepudiation", "Non-Repudiation", "Usage is legally binding");
|
|
out += MyConstraintType(QCA::KeyEncipherment, "KeyEncipherment", "Key Encipherment", "Can encrypt other keys");
|
|
out += MyConstraintType(QCA::DataEncipherment, "DataEncipherment", "Data Encipherment", "Can encrypt arbitrary data");
|
|
out += MyConstraintType(QCA::KeyAgreement, "KeyAgreement", "Key Agreement", "Can perform key agreement (DH)");
|
|
out += MyConstraintType(QCA::KeyCertificateSign, "KeyCertificateSign", "Certificate Sign", "Can sign other certificates");
|
|
out += MyConstraintType(QCA::CRLSign, "CRLSign", "CRL Sign", "Can sign CRLs");
|
|
out += MyConstraintType(QCA::EncipherOnly, "EncipherOnly", "Encipher Only", "Can be used for encrypting");
|
|
out += MyConstraintType(QCA::DecipherOnly, "DecipherOnly", "Decipher Only", "Can be used for decrypting");
|
|
out += MyConstraintType(QCA::ServerAuth, "ServerAuth", "Server Authentication", "TLS Server");
|
|
out += MyConstraintType(QCA::ClientAuth, "ClientAuth", "Client Authentication", "TLS Client");
|
|
out += MyConstraintType(QCA::CodeSigning, "CodeSigning", "Code Signing", "");
|
|
out += MyConstraintType(QCA::EmailProtection, "EmailProtection", "Email Protection", "S/MIME");
|
|
out += MyConstraintType(QCA::IPSecEndSystem, "IPSecEndSystem", "IPSec End-System", "");
|
|
out += MyConstraintType(QCA::IPSecTunnel, "IPSecTunnel", "IPSec Tunnel", "");
|
|
out += MyConstraintType(QCA::IPSecUser, "IPSecUser", "IPSec User", "");
|
|
out += MyConstraintType(QCA::TimeStamping, "TimeStamping", "Time Stamping", "");
|
|
out += MyConstraintType(QCA::OCSPSigning, "OCSPSigning", "OCSP Signing", "");
|
|
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(int n = 0; n < in.length(); ++n)
|
|
{
|
|
if(!in[n].isDigit() && in[n] != '.')
|
|
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(1)
|
|
{
|
|
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(1)
|
|
{
|
|
// 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 == "y")
|
|
{
|
|
if(vl->years != -1)
|
|
return false;
|
|
vl->years = x;
|
|
}
|
|
if(str == "m")
|
|
{
|
|
if(vl->months != -1)
|
|
return false;
|
|
vl->months = x;
|
|
}
|
|
if(str == "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(1)
|
|
{
|
|
QString str = prompt_for("Create an end user ('user') certificate or a CA ('ca') certificate? [user]");
|
|
if(str.isEmpty())
|
|
str = "user";
|
|
if(str != "user" && str != "ca")
|
|
{
|
|
printf("'%s' is not a valid entry.\n", qPrintable(str));
|
|
continue;
|
|
}
|
|
|
|
if(str == "ca")
|
|
opts.setAsCA();
|
|
break;
|
|
}
|
|
printf("\n");
|
|
|
|
while(1)
|
|
{
|
|
QString str = prompt_for("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(1)
|
|
{
|
|
int index;
|
|
while(1)
|
|
{
|
|
QString str = prompt_for("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(1)
|
|
{
|
|
int index;
|
|
while(1)
|
|
{
|
|
QString str = prompt_for("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(1)
|
|
{
|
|
QString str = prompt_for("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("Common Name"));
|
|
info.insert(QCA::Country, prompt_for("Country Code (2 letters)"));
|
|
info.insert(QCA::Organization, prompt_for("Organization"));
|
|
info.insert(QCA::Email, prompt_for("Email"));
|
|
opts.setInfo(info);
|
|
|
|
printf("\n");
|
|
}
|
|
|
|
if(!req)
|
|
{
|
|
while(1)
|
|
{
|
|
QString str = prompt_for("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::currentDateTime().toUTC();
|
|
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 += QString("%1 year(s)").arg(vl.years);
|
|
if(vl.months > 0)
|
|
parts += QString("%1 month(s)").arg(vl.months);
|
|
if(vl.days > 0)
|
|
parts += QString("%1 day(s)").arg(vl.days);
|
|
QString out;
|
|
if(parts.count() == 1)
|
|
out = parts[0];
|
|
else if(parts.count() == 2)
|
|
out = parts[0] + " and " + parts[1];
|
|
else if(parts.count() == 3)
|
|
out = parts[0] + ", " + parts[1] + ", 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 == "true" || lc == "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 QT_VERSION >= 0x050000
|
|
if(in.canConvert<QString>() && string_is_bool(in.toString()))
|
|
#else
|
|
if(qVariantCanConvert<QString>(in) && string_is_bool(in.toString()))
|
|
#endif
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
static bool variant_is_int(const QVariant &in)
|
|
{
|
|
#if QT_VERSION >= 0x050000
|
|
if(in.canConvert<QString>() && string_is_int(in.toString()))
|
|
#else
|
|
if(qVariantCanConvert<QString>(in) && string_is_int(in.toString()))
|
|
#endif
|
|
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(1)
|
|
{
|
|
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(1)
|
|
{
|
|
QString str = prompt_for_string(prompt);
|
|
if(str.isEmpty())
|
|
return def;
|
|
if(partial_compare_nocase(str, "true"))
|
|
return true;
|
|
else if(partial_compare_nocase(str, "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(1)
|
|
{
|
|
QString str = prompt_for_string(prompt);
|
|
if(str.isEmpty())
|
|
return def;
|
|
if(partial_compare_nocase(str, "yes"))
|
|
return true;
|
|
else if(partial_compare_nocase(str, "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(1)
|
|
{
|
|
QString str = prompt_for_string(prompt);
|
|
if(str.isEmpty())
|
|
return def;
|
|
if(partial_compare_nocase(str, "auto"))
|
|
return "auto";
|
|
else if(partial_compare_nocase(str, "trigger"))
|
|
return "trigger";
|
|
else if(partial_compare_nocase(str, "poll"))
|
|
return "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 == "formtype")
|
|
continue;
|
|
QVariant val = it.value();
|
|
|
|
// fields must be bool, int, or string
|
|
QVariant newval;
|
|
QString prompt = QString("%1: [%2] ").arg(var).arg(val.toString());
|
|
if(variant_is_bool(val))
|
|
newval = prompt_for_bool(QString("bool ") + prompt, val.toBool());
|
|
else if(variant_is_int(val))
|
|
newval = prompt_for_int(QString("int ") + prompt, val.toInt());
|
|
#if QT_VERSION >= 0x050000
|
|
else if(val.canConvert<QString>())
|
|
#else
|
|
else if(qVariantCanConvert<QString>(val))
|
|
#endif
|
|
newval = prompt_for_string(QString("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("auto"),
|
|
slotevent_timeout(0)
|
|
{
|
|
}
|
|
|
|
QVariantMap toVariantMap() const
|
|
{
|
|
QVariantMap out;
|
|
out["allow_protected_authentication"] = allow_protected_authentication;
|
|
out["cert_private"] = cert_private;
|
|
out["enabled"] = enabled;
|
|
out["library"] = library;
|
|
out["name"] = name;
|
|
out["private_mask"] = private_mask;
|
|
out["slotevent_method"] = slotevent_method;
|
|
out["slotevent_timeout"] = slotevent_timeout;
|
|
return out;
|
|
}
|
|
|
|
bool fromVariantMap(const QVariantMap &in)
|
|
{
|
|
allow_protected_authentication = in["allow_protected_authentication"].toBool();
|
|
cert_private = in["cert_private"].toBool();
|
|
enabled = in["enabled"].toBool();
|
|
library = in["library"].toString();
|
|
name = in["name"].toString();
|
|
private_mask = in["private_mask"].toInt();
|
|
slotevent_method = in["slotevent_method"].toString();
|
|
slotevent_timeout = in["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["formtype"] = "http://affinix.com/qca/forms/qca-pkcs11#1.0";
|
|
|
|
// base settings
|
|
out["allow_load_rootca"] = allow_load_rootca;
|
|
out["allow_protected_authentication"] = allow_protected_authentication;
|
|
out["log_level"] = log_level;
|
|
out["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().sprintf("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["formtype"] != "http://affinix.com/qca/forms/qca-pkcs11#1.0")
|
|
return false;
|
|
|
|
allow_load_rootca = in["allow_load_rootca"].toBool();
|
|
allow_protected_authentication = in["allow_protected_authentication"].toBool();
|
|
log_level = in["log_level"].toInt();
|
|
pin_cache = in["pin_cache"].toInt();
|
|
|
|
for(int n = 0;; ++n)
|
|
{
|
|
QString prefix = QString().sprintf("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(1)
|
|
{
|
|
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 = "No limit";
|
|
else
|
|
str = QString("%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(1)
|
|
{
|
|
QString str = prompt_for("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 = QString("Allow loading of root CAs: [%1] ").arg(config.allow_load_rootca ? "Yes" : "No");
|
|
config.allow_load_rootca = prompt_for_yesno(prompt, config.allow_load_rootca);
|
|
prompt = QString("Allow protected authentication: [%1] ").arg(config.allow_protected_authentication ? "Yes" : "No");
|
|
config.allow_protected_authentication = prompt_for_yesno(prompt, config.allow_protected_authentication);
|
|
prompt = QString("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 = QString("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(1)
|
|
{
|
|
QString str = prompt_for("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(1)
|
|
{
|
|
if(index == 1)
|
|
prompt = QString("Unique friendly name: ");
|
|
else
|
|
prompt = QString("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(1)
|
|
{
|
|
if(index == 1)
|
|
prompt = QString("Library filename: ");
|
|
else
|
|
prompt = QString("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 = QString("Allow protected authentication: [%1] ").arg(provider.allow_protected_authentication ? "Yes" : "No");
|
|
provider.allow_protected_authentication = prompt_for_yesno(prompt, provider.allow_protected_authentication);
|
|
prompt = QString("Provider stores certificates as private objects: [%1] ").arg(provider.cert_private ? "Yes" : "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 = QString("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 = QString("Method value: [%1] ").arg(provider.slotevent_method);
|
|
provider.slotevent_method = prompt_for_slotevent_method(prompt, provider.slotevent_method);
|
|
if(provider.slotevent_method == "poll")
|
|
{
|
|
prompt = QString("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["formtype"] == "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 += ':';
|
|
out += hex[n];
|
|
}
|
|
return out;
|
|
}
|
|
|
|
static QString kstype_to_string(QCA::KeyStore::Type _type)
|
|
{
|
|
QString type;
|
|
switch(_type)
|
|
{
|
|
case QCA::KeyStore::System: type = "Sys "; break;
|
|
case QCA::KeyStore::User: type = "User"; break;
|
|
case QCA::KeyStore::Application: type = "App "; break;
|
|
case QCA::KeyStore::SmartCard: type = "Card"; break;
|
|
case QCA::KeyStore::PGPKeyring: type = "PGP "; break;
|
|
default: type = "XXXX"; break;
|
|
}
|
|
return type;
|
|
}
|
|
|
|
static QString ksentrytype_to_string(QCA::KeyStoreEntry::Type _type)
|
|
{
|
|
QString type;
|
|
switch(_type)
|
|
{
|
|
case QCA::KeyStoreEntry::TypeKeyBundle: type = "Key "; break;
|
|
case QCA::KeyStoreEntry::TypeCertificate: type = "Cert"; break;
|
|
case QCA::KeyStoreEntry::TypeCRL: type = "CRL "; break;
|
|
case QCA::KeyStoreEntry::TypePGPSecretKey: type = "PSec"; break;
|
|
case QCA::KeyStoreEntry::TypePGPPublicKey: type = "PPub"; break;
|
|
default: type = "XXXX"; break;
|
|
}
|
|
return type;
|
|
}
|
|
|
|
static void try_print_info(const QString &name, const QStringList &values)
|
|
{
|
|
if(!values.isEmpty())
|
|
{
|
|
QString value = values.join(", ");
|
|
printf(" %s: %s\n", qPrintable(name), value.toUtf8().data());
|
|
}
|
|
}
|
|
|
|
static void print_info(const QString &title, const QCA::CertificateInfo &info)
|
|
{
|
|
QList<InfoType> list = makeInfoTypeList();
|
|
printf("%s\n", title.toLatin1().data());
|
|
foreach(const InfoType &t, list)
|
|
try_print_info(t.name, info.values(t.type));
|
|
}
|
|
|
|
static void print_info_ordered(const QString &title, const QCA::CertificateInfoOrdered &info)
|
|
{
|
|
QList<InfoType> list = makeInfoTypeList(true);
|
|
printf("%s\n", title.toLatin1().data());
|
|
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 = QString("DN:") + pair.type().id();
|
|
else
|
|
name = QString("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 = "EMSA1(SHA1)"; break;
|
|
case QCA::EMSA3_SHA1: str = "EMSA3(SHA1)"; break;
|
|
case QCA::EMSA3_MD5: str = "EMSA3(MD5)"; break;
|
|
case QCA::EMSA3_MD2: str = "EMSA3(MD2)"; break;
|
|
case QCA::EMSA3_RIPEMD160: str = "EMSA3(RIPEMD160)"; break;
|
|
case QCA::EMSA3_Raw: str = "EMSA3(raw)"; break;
|
|
default: str = "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, "sha1")));
|
|
printf("MD5 Fingerprint: %s\n", qPrintable(get_fingerprint(cert, "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 += ' ';
|
|
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 = "Validated";
|
|
break;
|
|
case QCA::ErrorRejected:
|
|
s = "Root CA is marked to reject the specified purpose";
|
|
break;
|
|
case QCA::ErrorUntrusted:
|
|
s = "Certificate not trusted for the required purpose";
|
|
break;
|
|
case QCA::ErrorSignatureFailed:
|
|
s = "Invalid signature";
|
|
break;
|
|
case QCA::ErrorInvalidCA:
|
|
s = "Invalid CA certificate";
|
|
break;
|
|
case QCA::ErrorInvalidPurpose:
|
|
s = "Invalid certificate purpose";
|
|
break;
|
|
case QCA::ErrorSelfSigned:
|
|
s = "Certificate is self-signed";
|
|
break;
|
|
case QCA::ErrorRevoked:
|
|
s = "Certificate has been revoked";
|
|
break;
|
|
case QCA::ErrorPathLengthExceeded:
|
|
s = "Maximum certificate chain length exceeded";
|
|
break;
|
|
case QCA::ErrorExpired:
|
|
s = "Certificate has expired";
|
|
break;
|
|
case QCA::ErrorExpiredCA:
|
|
s = "CA has expired";
|
|
break;
|
|
case QCA::ErrorValidityUnknown:
|
|
default:
|
|
s = "General certificate validation error";
|
|
break;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
static QString smIdentityResultToString(QCA::SecureMessageSignature::IdentityResult r)
|
|
{
|
|
QString str;
|
|
switch(r)
|
|
{
|
|
case QCA::SecureMessageSignature::Valid: str = "Valid"; break;
|
|
case QCA::SecureMessageSignature::InvalidSignature: str = "InvalidSignature"; break;
|
|
case QCA::SecureMessageSignature::InvalidKey: str = "InvalidKey"; break;
|
|
case QCA::SecureMessageSignature::NoKey: str = "NoKey"; break;
|
|
default: str = "Unknown";
|
|
}
|
|
return str;
|
|
}
|
|
|
|
static QString smErrorToString(QCA::SecureMessage::Error e)
|
|
{
|
|
QMap<QCA::SecureMessage::Error,QString> map;
|
|
map[QCA::SecureMessage::ErrorPassphrase] = "ErrorPassphrase";
|
|
map[QCA::SecureMessage::ErrorFormat] = "ErrorFormat";
|
|
map[QCA::SecureMessage::ErrorSignerExpired] = "ErrorSignerExpired";
|
|
map[QCA::SecureMessage::ErrorSignerInvalid] = "ErrorSignerInvalid";
|
|
map[QCA::SecureMessage::ErrorEncryptExpired] = "ErrorEncryptExpired";
|
|
map[QCA::SecureMessage::ErrorEncryptUntrusted] = "ErrorEncryptUntrusted";
|
|
map[QCA::SecureMessage::ErrorEncryptInvalid] = "ErrorEncryptInvalid";
|
|
map[QCA::SecureMessage::ErrorNeedCard] = "ErrorNeedCard";
|
|
map[QCA::SecureMessage::ErrorCertKeyMismatch] = "ErrorCertKeyMismatch";
|
|
map[QCA::SecureMessage::ErrorUnknown] = "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 = QString(" (%1)").arg(info.value(QCA::Email));
|
|
fprintf(stderr, "From: %s%s\n", qPrintable(cert.commonName()), qPrintable(emailStr));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const char *mime_signpart =
|
|
"Content-Type: text/plain; charset=UTF-8\r\n"
|
|
"Content-Transfer-Encoding: 8bit\r\n"
|
|
"\r\n"
|
|
"%1";
|
|
|
|
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";
|
|
|
|
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(1)
|
|
{
|
|
at = out.indexOf('\n', at);
|
|
if(at == -1)
|
|
break;
|
|
if(at - 1 >= 0 && out[at - 1] != '\r')
|
|
{
|
|
out.insert(at, '\r');
|
|
++at;
|
|
}
|
|
++at;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
static QString rem_cr(const QString &in)
|
|
{
|
|
QString out = in;
|
|
out.replace("\r\n", "\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] == '\r' && in[n + 1] == '\n')
|
|
return n;
|
|
if(in[n] == '\n')
|
|
return n;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static int indexOf_doublenewline(const QString &in, int offset = 0)
|
|
{
|
|
int at = -1;
|
|
while(1)
|
|
{
|
|
int n = indexOf_newline(in, offset);
|
|
if(n == -1)
|
|
return -1;
|
|
|
|
if(at != -1)
|
|
{
|
|
if(n == offset)
|
|
break;
|
|
}
|
|
|
|
at = n;
|
|
if(in[n] == '\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] == '\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("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(';');
|
|
if(n != -1)
|
|
boundary = bregion.mid(0, n);
|
|
else
|
|
boundary = bregion;
|
|
|
|
if(boundary[0] == '\"')
|
|
boundary.remove(0, 1);
|
|
if(boundary[boundary.length() - 1] == '\"')
|
|
boundary.remove(boundary.length() - 1, 1);
|
|
//printf("boundary: [%s]\n", qPrintable(boundary));
|
|
QString boundary_end = QString("--") + boundary;
|
|
boundary = QString("--") + 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) == "\r\n")
|
|
tmp_data.truncate(tmp_data.length() - 2);
|
|
else if(tmp_data.right(1) == "\n")
|
|
tmp_data.truncate(tmp_data.length() - 1);
|
|
if(tmp_sig.right(2) == "\r\n")
|
|
tmp_sig.truncate(tmp_sig.length() - 2);
|
|
else if(tmp_sig.right(1) == "\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("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(':');
|
|
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(':');
|
|
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("--"))
|
|
continue;
|
|
QString var;
|
|
QString val;
|
|
int x = s.indexOf('=');
|
|
if(x != -1)
|
|
{
|
|
var = s.mid(2, x - 2);
|
|
val = s.mid(x + 1);
|
|
}
|
|
else
|
|
{
|
|
var = s.mid(2);
|
|
}
|
|
|
|
bool known = true;
|
|
|
|
if(var == "pass")
|
|
{
|
|
have_pass = true;
|
|
pass = val.toUtf8();
|
|
}
|
|
else if(var == "newpass")
|
|
{
|
|
have_newpass = true;
|
|
newpass = val.toUtf8();
|
|
}
|
|
else if(var == "log-file")
|
|
{
|
|
logFile.setFileName (val);
|
|
logFile.open (QIODevice::Append | QIODevice::Text | QIODevice::Unbuffered);
|
|
logStream.setDevice (&logFile);
|
|
}
|
|
else if(var == "log-level")
|
|
{
|
|
QCA::logger ()->setLevel ((QCA::Logger::Severity)val.toInt ());
|
|
}
|
|
else if(var == "noprompt")
|
|
allowprompt = false;
|
|
else if(var == "ordered")
|
|
ordered = true;
|
|
else if(var == "debug")
|
|
debug = true;
|
|
else if(var == "roots")
|
|
rootsFile = val;
|
|
else if(var == "nonroots")
|
|
nonRootsFile = val;
|
|
else if(var == "nosys")
|
|
nosys = true;
|
|
else if(var == "nobundle")
|
|
nobundle = true;
|
|
else
|
|
known = false;
|
|
|
|
if(known)
|
|
{
|
|
args.removeAt(n);
|
|
--n; // adjust position
|
|
}
|
|
}
|
|
|
|
// help
|
|
if(args.isEmpty() || args[0] == "help" || args[0] == "--help" || args[0] == "-h")
|
|
{
|
|
usage();
|
|
return 0;
|
|
}
|
|
|
|
// version
|
|
if(args[0] == "version" || args[0] == "--version" || args[0] == "-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] == "plugins")
|
|
{
|
|
QStringList paths = QCA::pluginPaths();
|
|
if(!paths.isEmpty())
|
|
{
|
|
for(int n = 0; n < paths.count(); ++n)
|
|
{
|
|
printf(" %s\n", qPrintable(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] == "config")
|
|
{
|
|
if(args.count() < 2)
|
|
{
|
|
usage();
|
|
return 1;
|
|
}
|
|
|
|
if(args[1] == "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] == "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] == "key")
|
|
{
|
|
if(args.count() < 2)
|
|
{
|
|
usage();
|
|
return 1;
|
|
}
|
|
|
|
if(args[1] == "make")
|
|
{
|
|
if(args.count() < 4)
|
|
{
|
|
usage();
|
|
return 1;
|
|
}
|
|
|
|
bool genrsa;
|
|
int bits;
|
|
|
|
if(args[2] == "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] == "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 = "rsapub.pem";
|
|
privFileName = "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 = "dsapub.pem";
|
|
privFileName = "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] == "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] == "cert")
|
|
{
|
|
if(args.count() < 2)
|
|
{
|
|
usage();
|
|
return 1;
|
|
}
|
|
|
|
if(args[1] == "makereq" || args[1] == "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] == "makereqadv") ? true: false;
|
|
|
|
QCA::CertificateOptions opts = promptForCertAttributes(advanced, true);
|
|
QCA::CertificateRequest req(opts, priv);
|
|
|
|
QString reqname = "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] == "makeself" || args[1] == "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] == "makeselfadv") ? true: false;
|
|
|
|
QCA::CertificateOptions opts = promptForCertAttributes(advanced, false);
|
|
QCA::Certificate cert(opts, priv);
|
|
|
|
QString certname = "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] == "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] == "keybundle")
|
|
{
|
|
if(args.count() < 2)
|
|
{
|
|
usage();
|
|
return 1;
|
|
}
|
|
|
|
if(args[1] == "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 = "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] == "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] == "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() + "_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] == "keystore")
|
|
{
|
|
if(args.count() < 2)
|
|
{
|
|
usage();
|
|
return 1;
|
|
}
|
|
|
|
if(args[1] == "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] == "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] == "monitor")
|
|
{
|
|
KeyStoreMonitor::monitor();
|
|
|
|
if(debug)
|
|
output_keystore_diagnostic_text();
|
|
}
|
|
else if(args[1] == "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] == "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] == "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] == "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] == "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] == "show")
|
|
{
|
|
if(args.count() < 2)
|
|
{
|
|
usage();
|
|
return 1;
|
|
}
|
|
|
|
if(args[1] == "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] == "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] == "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] == "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", key.certificateChain().count());
|
|
print_cert(key.certificateChain().primary(), ordered);
|
|
}
|
|
else if(args[1] == "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] == "message")
|
|
{
|
|
if(args.count() < 2)
|
|
{
|
|
usage();
|
|
return 1;
|
|
}
|
|
|
|
if(args[1] == "sign")
|
|
{
|
|
if(args.count() < 4)
|
|
{
|
|
usage();
|
|
return 1;
|
|
}
|
|
|
|
QCA::SecureMessageSystem *sms;
|
|
QCA::SecureMessageKey skey;
|
|
QCA::SecureMessage::SignMode mode;
|
|
bool pgp = false;
|
|
|
|
if(args[2] == "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] == "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] == "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(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(mime_signed).arg(hashName).arg(QString::fromUtf8(plain)).arg(sigtext);
|
|
output = str.toUtf8();
|
|
}
|
|
|
|
printf("%s", output.data());
|
|
}
|
|
else if(args[1] == "encrypt")
|
|
{
|
|
if(args.count() < 4)
|
|
{
|
|
usage();
|
|
return 1;
|
|
}
|
|
|
|
QCA::SecureMessageSystem *sms;
|
|
QCA::SecureMessageKey skey;
|
|
bool pgp = false;
|
|
|
|
if(args[2] == "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] == "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(mime_enveloped).arg(enctext);
|
|
output = str.toUtf8();
|
|
}
|
|
|
|
printf("%s", output.data());
|
|
}
|
|
else if(args[1] == "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] == "verify")
|
|
{
|
|
if(args.count() < 3)
|
|
{
|
|
usage();
|
|
return 1;
|
|
}
|
|
|
|
QCA::SecureMessageSystem *sms;
|
|
bool pgp = false;
|
|
|
|
if(args[2] == "pgp")
|
|
{
|
|
if(!QCA::isSupported("openpgp"))
|
|
{
|
|
fprintf(stderr, "Error: need 'openpgp' feature.\n");
|
|
return 1;
|
|
}
|
|
|
|
sms = new QCA::OpenPGP;
|
|
pgp = true;
|
|
}
|
|
else if(args[2] == "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-----"))
|
|
{
|
|
QString 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] == "decrypt")
|
|
{
|
|
if(args.count() < 3)
|
|
{
|
|
usage();
|
|
return 1;
|
|
}
|
|
|
|
QCA::SecureMessageSystem *sms;
|
|
bool pgp = false;
|
|
|
|
if(args[2] == "pgp")
|
|
{
|
|
if(!QCA::isSupported("openpgp"))
|
|
{
|
|
fprintf(stderr, "Error: need 'openpgp' feature.\n");
|
|
return 1;
|
|
}
|
|
|
|
sms = new QCA::OpenPGP;
|
|
pgp = true;
|
|
}
|
|
else if(args[2] == "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] == "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"
|