mirror of
https://github.com/QuasarApp/qca.git
synced 2025-04-30 05:24:32 +00:00
1251 lines
30 KiB
C++
1251 lines
30 KiB
C++
/*
|
|
Copyright (C) 2007 Justin Karneges <justin@affinix.com>
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in
|
|
all copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
|
AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
#include <QtCore>
|
|
#include <QtGui>
|
|
#include <QtCrypto>
|
|
|
|
#include "ui_mainwin.h"
|
|
#include "prompter.h"
|
|
#include "mylistview.h"
|
|
#include "ui_loadstore.h"
|
|
#include "pkcs11configdlg/pkcs11configdlg.h"
|
|
|
|
#define VERSION "0.0.1"
|
|
|
|
class Icons
|
|
{
|
|
public:
|
|
QPixmap cert, crl, keybundle, pgppub, pgpsec;
|
|
};
|
|
|
|
Icons *g_icons = 0;
|
|
|
|
//----------------------------------------------------------------------------
|
|
// CertItem
|
|
//----------------------------------------------------------------------------
|
|
class CertItem
|
|
{
|
|
public:
|
|
enum StorageType { File, Entry };
|
|
|
|
QString name;
|
|
QCA::CertificateChain chain;
|
|
bool havePrivate;
|
|
StorageType storageType; // private storage type
|
|
bool usable; // storage is accessible
|
|
QString fileName;
|
|
QCA::KeyStoreEntry keyStoreEntry;
|
|
|
|
CertItem();
|
|
QString toString() const;
|
|
bool fromString(const QString &in);
|
|
};
|
|
|
|
static QString escape(const QString &in)
|
|
{
|
|
QString out;
|
|
for(int n = 0; n < in.length(); ++n)
|
|
{
|
|
if(in[n] == '\\')
|
|
out += "\\\\";
|
|
else if(in[n] == ':')
|
|
out += "\\c";
|
|
else if(in[n] == '\n')
|
|
out += "\\n";
|
|
else
|
|
out += in[n];
|
|
}
|
|
return out;
|
|
}
|
|
|
|
static QString unescape(const QString &in)
|
|
{
|
|
QString out;
|
|
for(int n = 0; n < in.length(); ++n)
|
|
{
|
|
if(in[n] == '\\')
|
|
{
|
|
if(n + 1 < in.length())
|
|
{
|
|
++n;
|
|
if(in[n] == '\\')
|
|
out += '\\';
|
|
else if(in[n] == 'c')
|
|
out += ':';
|
|
else if(in[n] == 'n')
|
|
out += '\n';
|
|
}
|
|
}
|
|
else
|
|
out += in[n];
|
|
}
|
|
return out;
|
|
}
|
|
|
|
CertItem::CertItem() :
|
|
havePrivate(false),
|
|
storageType(File),
|
|
usable(false)
|
|
{
|
|
}
|
|
|
|
QString CertItem::toString() const
|
|
{
|
|
QStringList parts;
|
|
|
|
parts += name;
|
|
parts += QString::number(chain.count());
|
|
foreach(const QCA::Certificate &cert, chain)
|
|
parts += QCA::Base64().arrayToString(cert.toDER());
|
|
|
|
if(havePrivate)
|
|
{
|
|
if(storageType == File)
|
|
{
|
|
parts += "privateFile";
|
|
parts += fileName;
|
|
}
|
|
else // Entry
|
|
{
|
|
parts += "privateEntry";
|
|
parts += keyStoreEntry.toString();
|
|
}
|
|
}
|
|
|
|
for(int n = 0; n < parts.count(); ++n)
|
|
parts[n] = escape(parts[n]);
|
|
return parts.join(":");
|
|
}
|
|
|
|
bool CertItem::fromString(const QString &in)
|
|
{
|
|
QStringList parts = in.split(':');
|
|
for(int n = 0; n < parts.count(); ++n)
|
|
parts[n] = unescape(parts[n]);
|
|
|
|
if(parts.count() < 3)
|
|
return false;
|
|
|
|
name = parts[0];
|
|
int chainCount = parts[1].toInt();
|
|
if(chainCount < 1 || chainCount > parts.count() - 2)
|
|
return false;
|
|
chain.clear();
|
|
for(int n = 0; n < chainCount; ++n)
|
|
{
|
|
QCA::Certificate cert = QCA::Certificate::fromDER(QCA::Base64().stringToArray(parts[n + 2]).toByteArray());
|
|
if(cert.isNull())
|
|
return false;
|
|
chain += cert;
|
|
}
|
|
int at = chain.count() + 2;
|
|
|
|
if(at < parts.count())
|
|
{
|
|
havePrivate = true;
|
|
usable = false;
|
|
|
|
if(parts[at] == "privateFile")
|
|
{
|
|
storageType = File;
|
|
fileName = parts[at + 1];
|
|
if(QFile::exists(fileName))
|
|
usable = true;
|
|
}
|
|
else if(parts[at] == "privateEntry")
|
|
{
|
|
storageType = Entry;
|
|
keyStoreEntry = QCA::KeyStoreEntry(parts[at + 1]);
|
|
if(!keyStoreEntry.isNull())
|
|
usable = true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
// CertListModel
|
|
//----------------------------------------------------------------------------
|
|
class CertListModel : public QAbstractListModel
|
|
{
|
|
Q_OBJECT
|
|
public:
|
|
QList<CertItem> list;
|
|
|
|
CertListModel(QObject *parent = 0) :
|
|
QAbstractListModel(parent)
|
|
{
|
|
}
|
|
|
|
int rowCount(const QModelIndex &parent = QModelIndex()) const
|
|
{
|
|
Q_UNUSED(parent);
|
|
return list.count();
|
|
}
|
|
|
|
QVariant data(const QModelIndex &index, int role) const
|
|
{
|
|
if(!index.isValid())
|
|
return QVariant();
|
|
|
|
if(index.row() >= list.count())
|
|
return QVariant();
|
|
|
|
if(role == Qt::DisplayRole)
|
|
return list[index.row()].name;
|
|
else if(role == Qt::EditRole)
|
|
return list[index.row()].name;
|
|
else if(role == Qt::DecorationRole && g_icons)
|
|
{
|
|
const CertItem &i = list[index.row()];
|
|
if(i.havePrivate)
|
|
return g_icons->keybundle;
|
|
else
|
|
return g_icons->cert;
|
|
}
|
|
else
|
|
return QVariant();
|
|
}
|
|
|
|
Qt::ItemFlags flags(const QModelIndex &index) const
|
|
{
|
|
if(!index.isValid())
|
|
return Qt::ItemIsEnabled;
|
|
|
|
return QAbstractItemModel::flags(index) | Qt::ItemIsEditable;
|
|
}
|
|
|
|
bool setData(const QModelIndex &index, const QVariant &value, int role)
|
|
{
|
|
if(index.isValid() && role == Qt::EditRole)
|
|
{
|
|
QString str = value.toString();
|
|
list[index.row()].name = str;
|
|
emit dataChanged(index, index);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void addItems(const QList<CertItem> &items)
|
|
{
|
|
if(items.isEmpty())
|
|
return;
|
|
|
|
beginInsertRows(QModelIndex(), list.size(), list.size() + items.count() - 1);
|
|
list += items;
|
|
endInsertRows();
|
|
}
|
|
|
|
void addItem(const CertItem &i)
|
|
{
|
|
beginInsertRows(QModelIndex(), list.size(), list.size());
|
|
list += i;
|
|
endInsertRows();
|
|
}
|
|
|
|
void removeItem(int at)
|
|
{
|
|
beginRemoveRows(QModelIndex(), at, at);
|
|
list.removeAt(at);
|
|
endRemoveRows();
|
|
}
|
|
|
|
QString getUniqueName(const QString &name)
|
|
{
|
|
int num = 1;
|
|
while(1)
|
|
{
|
|
QString tryname;
|
|
if(num == 1)
|
|
tryname = name;
|
|
else
|
|
tryname = name + QString(" (%1)").arg(num);
|
|
|
|
bool found = false;
|
|
foreach(const CertItem &i, list)
|
|
{
|
|
if(i.name == tryname)
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if(!found)
|
|
return tryname;
|
|
|
|
++num;
|
|
}
|
|
}
|
|
};
|
|
|
|
//----------------------------------------------------------------------------
|
|
// Operation
|
|
//----------------------------------------------------------------------------
|
|
class Operation : public QObject
|
|
{
|
|
Q_OBJECT
|
|
public:
|
|
Operation(QObject *parent = 0) :
|
|
QObject(parent)
|
|
{
|
|
}
|
|
|
|
signals:
|
|
void error(const QString &str);
|
|
};
|
|
|
|
static QString validityToString(QCA::Validity v)
|
|
{
|
|
QString s;
|
|
switch(v)
|
|
{
|
|
case QCA::ValidityGood:
|
|
s = Operation::tr("Validated");
|
|
break;
|
|
case QCA::ErrorRejected:
|
|
s = Operation::tr("Root CA is marked to reject the specified purpose");
|
|
break;
|
|
case QCA::ErrorUntrusted:
|
|
s = Operation::tr("Certificate not trusted for the required purpose");
|
|
break;
|
|
case QCA::ErrorSignatureFailed:
|
|
s = Operation::tr("Invalid signature");
|
|
break;
|
|
case QCA::ErrorInvalidCA:
|
|
s = Operation::tr("Invalid CA certificate");
|
|
break;
|
|
case QCA::ErrorInvalidPurpose:
|
|
s = Operation::tr("Invalid certificate purpose");
|
|
break;
|
|
case QCA::ErrorSelfSigned:
|
|
s = Operation::tr("Certificate is self-signed");
|
|
break;
|
|
case QCA::ErrorRevoked:
|
|
s = Operation::tr("Certificate has been revoked");
|
|
break;
|
|
case QCA::ErrorPathLengthExceeded:
|
|
s = Operation::tr("Maximum certificate chain length exceeded");
|
|
break;
|
|
case QCA::ErrorExpired:
|
|
s = Operation::tr("Certificate has expired");
|
|
break;
|
|
case QCA::ErrorExpiredCA:
|
|
s = Operation::tr("CA has expired");
|
|
break;
|
|
case QCA::ErrorValidityUnknown:
|
|
default:
|
|
s = Operation::tr("General certificate validation error");
|
|
break;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
static QString smErrorToString(QCA::SecureMessage::Error e)
|
|
{
|
|
QMap<QCA::SecureMessage::Error,QString> map;
|
|
map[QCA::SecureMessage::ErrorPassphrase] = Operation::tr("Invalid passphrase");
|
|
map[QCA::SecureMessage::ErrorFormat] = Operation::tr("Bad input format");
|
|
map[QCA::SecureMessage::ErrorSignerExpired] = Operation::tr("Signer key is expired");
|
|
map[QCA::SecureMessage::ErrorSignerInvalid] = Operation::tr("Signer key is invalid");
|
|
map[QCA::SecureMessage::ErrorEncryptExpired] = Operation::tr("Encrypting key is expired");
|
|
map[QCA::SecureMessage::ErrorEncryptUntrusted] = Operation::tr("Encrypting key is untrusted");
|
|
map[QCA::SecureMessage::ErrorEncryptInvalid] = Operation::tr("Encrypting key is invalid");
|
|
map[QCA::SecureMessage::ErrorNeedCard] = Operation::tr("Card was needed but not found");
|
|
map[QCA::SecureMessage::ErrorCertKeyMismatch] = Operation::tr("Certificate and private key don't match");
|
|
map[QCA::SecureMessage::ErrorUnknown] = Operation::tr("General error");
|
|
return map[e];
|
|
}
|
|
|
|
class SignOperation : public Operation
|
|
{
|
|
Q_OBJECT
|
|
private:
|
|
QByteArray in;
|
|
CertItem *item;
|
|
QCA::CMS *cms;
|
|
QCA::KeyLoader *loader;
|
|
QCA::KeyBundle key;
|
|
QCA::SecureMessage *msg;
|
|
|
|
public:
|
|
SignOperation(const QByteArray &_in, CertItem *_item, QCA::CMS *_cms, QObject *parent = 0) :
|
|
Operation(parent),
|
|
in(_in),
|
|
item(_item),
|
|
cms(_cms),
|
|
loader(0),
|
|
msg(0)
|
|
{
|
|
if(item->storageType == CertItem::File)
|
|
{
|
|
loader = new QCA::KeyLoader(this);
|
|
connect(loader, SIGNAL(finished()), SLOT(loaded()));
|
|
loader->loadKeyBundleFromFile(item->fileName);
|
|
}
|
|
else // Entry
|
|
{
|
|
key = item->keyStoreEntry.keyBundle();
|
|
QMetaObject::invokeMethod(this, "do_sign", Qt::QueuedConnection);
|
|
}
|
|
}
|
|
|
|
~SignOperation()
|
|
{
|
|
}
|
|
|
|
signals:
|
|
void finished(const QString &sig);
|
|
|
|
private slots:
|
|
void loaded()
|
|
{
|
|
if(loader->convertResult() != QCA::ConvertGood)
|
|
{
|
|
emit error(tr("Error opening key file."));
|
|
return;
|
|
}
|
|
|
|
key = loader->keyBundle();
|
|
delete loader;
|
|
loader = 0;
|
|
|
|
do_sign();
|
|
}
|
|
|
|
void do_sign()
|
|
{
|
|
//printf("do_sign\n");
|
|
|
|
QCA::SecureMessageKey signer;
|
|
signer.setX509CertificateChain(key.certificateChain());
|
|
signer.setX509PrivateKey(key.privateKey());
|
|
|
|
msg = new QCA::SecureMessage(cms);
|
|
connect(msg, SIGNAL(finished()), SLOT(msg_finished()));
|
|
msg->setFormat(QCA::SecureMessage::Ascii);
|
|
msg->setSigner(signer);
|
|
msg->startSign(QCA::SecureMessage::Detached);
|
|
update();
|
|
}
|
|
|
|
void update()
|
|
{
|
|
//printf("update\n");
|
|
|
|
QByteArray buf = in.mid(0, 16384); // 16k chunks
|
|
in = in.mid(buf.size());
|
|
msg->update(buf);
|
|
|
|
if(in.isEmpty())
|
|
msg->end();
|
|
else
|
|
QMetaObject::invokeMethod(this, "update", Qt::QueuedConnection);
|
|
}
|
|
|
|
void msg_finished()
|
|
{
|
|
//printf("msg_finished\n");
|
|
|
|
if(!msg->success())
|
|
{
|
|
QString str = smErrorToString(msg->errorCode());
|
|
delete msg;
|
|
msg = 0;
|
|
emit error(tr("Error during sign operation.\nReason: %1").arg(str));
|
|
return;
|
|
}
|
|
|
|
QByteArray result = msg->signature();
|
|
delete msg;
|
|
msg = 0;
|
|
|
|
emit finished(QString::fromLatin1(result));
|
|
}
|
|
};
|
|
|
|
class VerifyOperation : public Operation
|
|
{
|
|
Q_OBJECT
|
|
private:
|
|
QByteArray in, sig;
|
|
QCA::CMS *cms;
|
|
QCA::SecureMessage *msg;
|
|
|
|
public:
|
|
VerifyOperation(const QByteArray &_in, const QByteArray &_sig, QCA::CMS *_cms, QObject *parent = 0) :
|
|
Operation(parent),
|
|
in(_in),
|
|
sig(_sig),
|
|
cms(_cms),
|
|
msg(0)
|
|
{
|
|
//printf("do_verify\n");
|
|
|
|
msg = new QCA::SecureMessage(cms);
|
|
connect(msg, SIGNAL(finished()), SLOT(msg_finished()));
|
|
msg->setFormat(QCA::SecureMessage::Ascii);
|
|
msg->startVerify(sig);
|
|
QMetaObject::invokeMethod(this, "update", Qt::QueuedConnection);
|
|
}
|
|
|
|
signals:
|
|
void finished();
|
|
|
|
private slots:
|
|
void update()
|
|
{
|
|
//printf("update\n");
|
|
|
|
QByteArray buf = in.mid(0, 16384); // 16k chunks
|
|
in = in.mid(buf.size());
|
|
msg->update(buf);
|
|
|
|
if(in.isEmpty())
|
|
msg->end();
|
|
else
|
|
QMetaObject::invokeMethod(this, "update", Qt::QueuedConnection);
|
|
}
|
|
|
|
void msg_finished()
|
|
{
|
|
//printf("msg_finished\n");
|
|
|
|
if(!msg->success())
|
|
{
|
|
QString str = smErrorToString(msg->errorCode());
|
|
delete msg;
|
|
msg = 0;
|
|
emit error(tr("Error during verify operation.\nReason: %1").arg(str));
|
|
return;
|
|
}
|
|
|
|
QCA::SecureMessageSignature signer = msg->signer();
|
|
QCA::SecureMessageSignature::IdentityResult r = signer.identityResult();
|
|
delete msg;
|
|
msg = 0;
|
|
|
|
if(r != QCA::SecureMessageSignature::Valid)
|
|
{
|
|
QString str;
|
|
if(r == QCA::SecureMessageSignature::InvalidSignature)
|
|
str = tr("Invalid signature");
|
|
else if(r == QCA::SecureMessageSignature::InvalidKey)
|
|
str = tr("Invalid key: %1").arg(validityToString(signer.keyValidity()));
|
|
else if(r == QCA::SecureMessageSignature::NoKey)
|
|
str = tr("Key not found");
|
|
else // unknown
|
|
str = tr("Unknown");
|
|
|
|
emit error(tr("Verification failed!\nReason: %1").arg(str));
|
|
return;
|
|
}
|
|
|
|
emit finished();
|
|
}
|
|
};
|
|
|
|
QAction *actionView, *actionRename, *actionRemove;
|
|
|
|
MyListView::MyListView(QWidget *parent) :
|
|
QListView(parent)
|
|
{
|
|
}
|
|
|
|
void MyListView::contextMenuEvent(QContextMenuEvent *event)
|
|
{
|
|
QItemSelection selection = selectionModel()->selection();
|
|
if(selection.indexes().isEmpty())
|
|
return;
|
|
|
|
QMenu menu(this);
|
|
menu.addAction(actionView);
|
|
menu.addAction(actionRename);
|
|
menu.addAction(actionRemove);
|
|
menu.exec(event->globalPos());
|
|
}
|
|
|
|
/*static QString entryTypeToString(QCA::KeyStoreEntry::Type type)
|
|
{
|
|
QString out;
|
|
switch(type)
|
|
{
|
|
case QCA::KeyStoreEntry::TypeKeyBundle: out = "X"; break;
|
|
case QCA::KeyStoreEntry::TypeCertificate: out = "C"; break;
|
|
case QCA::KeyStoreEntry::TypeCRL: out = "R"; break;
|
|
case QCA::KeyStoreEntry::TypePGPSecretKey: out = "S"; break;
|
|
case QCA::KeyStoreEntry::TypePGPPublicKey: out = "P"; break;
|
|
default: out = "U"; break;
|
|
}
|
|
return out;
|
|
}*/
|
|
|
|
static QPixmap entryTypeToIcon(QCA::KeyStoreEntry::Type type)
|
|
{
|
|
QPixmap out;
|
|
switch(type)
|
|
{
|
|
case QCA::KeyStoreEntry::TypeKeyBundle: out = g_icons->keybundle; break;
|
|
case QCA::KeyStoreEntry::TypeCertificate: out = g_icons->cert; break;
|
|
case QCA::KeyStoreEntry::TypeCRL: out = g_icons->crl; break;
|
|
case QCA::KeyStoreEntry::TypePGPSecretKey: out = g_icons->pgpsec; break;
|
|
case QCA::KeyStoreEntry::TypePGPPublicKey: out = g_icons->pgppub; break;
|
|
default: break;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
class KeyStoreModel : public QStandardItemModel
|
|
{
|
|
Q_OBJECT
|
|
public:
|
|
QCA::KeyStoreManager ksm;
|
|
QList<QCA::KeyStore*> stores;
|
|
QList<QStandardItem*> storeItems;
|
|
QList< QList<QCA::KeyStoreEntry> > storeEntries;
|
|
QList< QList<QStandardItem*> > storeEntryItems;
|
|
|
|
KeyStoreModel(QObject *parent = 0) :
|
|
QStandardItemModel(parent), ksm(this)
|
|
{
|
|
// make sure keystores are started
|
|
QCA::KeyStoreManager::start();
|
|
|
|
connect(&ksm, SIGNAL(keyStoreAvailable(const QString &)), SLOT(ks_available(const QString &)));
|
|
QStringList list = ksm.keyStores();
|
|
foreach(const QString &s, list)
|
|
ks_available(s);
|
|
}
|
|
|
|
private slots:
|
|
void ks_available(const QString &keyStoreId)
|
|
{
|
|
QCA::KeyStore *ks = new QCA::KeyStore(keyStoreId, &ksm);
|
|
|
|
// TODO: only list non-pgp identity stores
|
|
//if(!ks->holdsIdentities() || ks->type() == QCA::KeyStore::PGPKeyring)
|
|
// return;
|
|
|
|
connect(ks, SIGNAL(updated()), SLOT(ks_updated()));
|
|
connect(ks, SIGNAL(unavailable()), SLOT(ks_unavailable()));
|
|
stores += ks;
|
|
ks->startAsynchronousMode();
|
|
|
|
QStandardItem *item = new QStandardItem(ks->name());
|
|
storeItems += item;
|
|
storeEntries += QList<QCA::KeyStoreEntry>();
|
|
storeEntryItems += QList<QStandardItem*>();
|
|
appendRow(item);
|
|
}
|
|
|
|
void ks_updated()
|
|
{
|
|
QCA::KeyStore *ks = (QCA::KeyStore *)sender();
|
|
int at = stores.indexOf(ks);
|
|
QList<QCA::KeyStoreEntry> entries = ks->entryList();
|
|
|
|
// TODO: only list keybundles
|
|
/*for(int n = 0; n < entries.count(); ++n)
|
|
{
|
|
if(entries[n].type() != QCA::KeyStoreEntry::TypeKeyBundle)
|
|
{
|
|
entries.removeAt(n);
|
|
--n; // adjust position
|
|
}
|
|
}*/
|
|
|
|
storeEntries[at] = entries;
|
|
storeEntryItems[at].clear();
|
|
|
|
// fake CRL, just to show off the icon
|
|
/*if(ks->type() == QCA::KeyStore::System)
|
|
{
|
|
QStandardItem *item = new QStandardItem(entryTypeToIcon(QCA::KeyStoreEntry::TypeCRL), "Santa's Naughty List");
|
|
storeEntryItems[at] += item;
|
|
storeItems[at]->appendRow(item);
|
|
}*/
|
|
|
|
foreach(const QCA::KeyStoreEntry &entry, entries)
|
|
{
|
|
QStandardItem *item = new QStandardItem(entryTypeToIcon(entry.type()), entry.name());
|
|
storeEntryItems[at] += item;
|
|
storeItems[at]->appendRow(item);
|
|
}
|
|
}
|
|
|
|
void ks_unavailable()
|
|
{
|
|
QCA::KeyStore *ks = (QCA::KeyStore *)sender();
|
|
Q_UNUSED(ks);
|
|
|
|
// TODO: remove from internal list and display
|
|
}
|
|
};
|
|
|
|
class LoadStore : public QDialog
|
|
{
|
|
Q_OBJECT
|
|
private:
|
|
Ui_LoadStore ui;
|
|
KeyStoreModel *model;
|
|
QCA::KeyStoreEntry cur_entry;
|
|
|
|
public:
|
|
LoadStore(QWidget *parent = 0) :
|
|
QDialog(parent)
|
|
{
|
|
ui.setupUi(this);
|
|
|
|
ui.lv_stores->header()->hide();
|
|
|
|
ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
|
|
|
|
model = new KeyStoreModel(this);
|
|
ui.lv_stores->setModel(model);
|
|
connect(ui.lv_stores->selectionModel(), SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)), SLOT(stores_selectionChanged(const QItemSelection &, const QItemSelection &)));
|
|
}
|
|
|
|
signals:
|
|
void entrySelected(const QCA::KeyStoreEntry &entry);
|
|
|
|
protected slots:
|
|
virtual void accept()
|
|
{
|
|
QCA::KeyStoreEntry entry = cur_entry;
|
|
QDialog::accept();
|
|
emit entrySelected(entry);
|
|
}
|
|
|
|
private slots:
|
|
void stores_selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
|
|
{
|
|
Q_UNUSED(deselected);
|
|
|
|
bool valid = false;
|
|
QCA::KeyStoreEntry entry;
|
|
{
|
|
QModelIndex index;
|
|
if(!selected.indexes().isEmpty())
|
|
index = selected.indexes().first();
|
|
if(index.isValid())
|
|
{
|
|
QModelIndex pindex = index.parent();
|
|
// are we clicking on an entry?
|
|
if(pindex.isValid())
|
|
{
|
|
int store_at = pindex.row();
|
|
int entry_at = index.row();
|
|
|
|
entry = model->storeEntries[store_at][entry_at];
|
|
if(entry.type() == QCA::KeyStoreEntry::TypeKeyBundle)
|
|
valid = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(valid)
|
|
cur_entry = entry;
|
|
else
|
|
cur_entry = QCA::KeyStoreEntry();
|
|
|
|
QPushButton *ok = ui.buttonBox->button(QDialogButtonBox::Ok);
|
|
if(valid && !ok->isEnabled())
|
|
ok->setEnabled(true);
|
|
else if(!valid && ok->isEnabled())
|
|
ok->setEnabled(false);
|
|
}
|
|
};
|
|
|
|
class MyPrompter : public Prompter
|
|
{
|
|
Q_OBJECT
|
|
private:
|
|
QMap<QString,QCA::SecureArray> known;
|
|
|
|
public:
|
|
MyPrompter(QObject *parent = 0) :
|
|
Prompter(parent)
|
|
{
|
|
}
|
|
|
|
protected:
|
|
virtual QCA::SecureArray knownPassword(const QCA::Event &event)
|
|
{
|
|
if(event.source() == QCA::Event::Data && !event.fileName().isEmpty())
|
|
return known.value(event.fileName());
|
|
else
|
|
return QCA::SecureArray();
|
|
}
|
|
|
|
virtual void userSubmitted(const QCA::SecureArray &password, const QCA::Event &event)
|
|
{
|
|
if(event.source() == QCA::Event::Data && !event.fileName().isEmpty())
|
|
known[event.fileName()] = password;
|
|
}
|
|
};
|
|
|
|
class MainWin : public QMainWindow
|
|
{
|
|
Q_OBJECT
|
|
private:
|
|
Ui_MainWin ui;
|
|
MyPrompter *prompter;
|
|
QCA::KeyLoader *keyLoader;
|
|
QString keyLoader_fileName;
|
|
CertListModel *users, *roots;
|
|
QCA::CMS *cms;
|
|
Operation *op;
|
|
|
|
public:
|
|
MainWin(QWidget *parent = 0) :
|
|
QMainWindow(parent),
|
|
keyLoader(0),
|
|
op(0)
|
|
{
|
|
ui.setupUi(this);
|
|
|
|
g_icons = new Icons;
|
|
g_icons->cert = QPixmap(":/gfx/icons/cert16.png");
|
|
g_icons->crl = QPixmap(":/gfx/icons/crl16.png");
|
|
g_icons->keybundle = QPixmap(":/gfx/icons/keybundle16.png");
|
|
g_icons->pgppub = QPixmap(":/gfx/icons/publickey16.png");
|
|
g_icons->pgpsec = QPixmap(":/gfx/icons/keypair16.png");
|
|
if(g_icons->cert.isNull() || g_icons->crl.isNull() || g_icons->keybundle.isNull() || g_icons->pgppub.isNull() || g_icons->pgpsec.isNull())
|
|
printf("warning: not all icons loaded\n");
|
|
|
|
actionView = new QAction(tr("&View"), this);
|
|
actionRename = new QAction(tr("Re&name"), this);
|
|
actionRemove = new QAction(tr("Rem&ove"), this);
|
|
|
|
// TODO
|
|
actionView->setEnabled(false);
|
|
|
|
connect(ui.actionLoadIdentityFile, SIGNAL(triggered()), SLOT(load_file()));
|
|
connect(ui.actionLoadIdentityEntry, SIGNAL(triggered()), SLOT(load_device()));
|
|
connect(ui.actionLoadAuthority, SIGNAL(triggered()), SLOT(load_root()));
|
|
connect(ui.actionConfigurePkcs11, SIGNAL(triggered()), SLOT(mod_config()));
|
|
connect(ui.actionQuit, SIGNAL(triggered()), SLOT(close()));
|
|
connect(ui.actionAbout, SIGNAL(triggered()), SLOT(about()));
|
|
connect(ui.pb_sign, SIGNAL(clicked()), SLOT(do_sign()));
|
|
connect(ui.pb_verify, SIGNAL(clicked()), SLOT(do_verify()));
|
|
|
|
//connect(actionView, SIGNAL(triggered()), SLOT(item_view()));
|
|
connect(actionRename, SIGNAL(triggered()), SLOT(item_rename()));
|
|
connect(actionRemove, SIGNAL(triggered()), SLOT(item_remove()));
|
|
|
|
ui.pb_sign->setEnabled(false);
|
|
|
|
prompter = new MyPrompter(this);
|
|
|
|
users = new CertListModel(this);
|
|
ui.lv_users->setModel(users);
|
|
connect(ui.lv_users->selectionModel(), SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)), SLOT(users_selectionChanged(const QItemSelection &, const QItemSelection &)));
|
|
|
|
roots = new CertListModel(this);
|
|
ui.lv_authorities->setModel(roots);
|
|
|
|
// FIXME: is this redundant?
|
|
ui.lv_users->model = users;
|
|
ui.lv_authorities->model = roots;
|
|
|
|
cms = new QCA::CMS(this);
|
|
|
|
QStringList ulist, rlist;
|
|
{
|
|
QSettings settings("Affinix", "CMS Signer");
|
|
ulist = settings.value("users").toStringList();
|
|
rlist = settings.value("roots").toStringList();
|
|
}
|
|
|
|
QList<CertItem> userslist;
|
|
foreach(const QString &s, ulist)
|
|
{
|
|
CertItem i;
|
|
if(i.fromString(s))
|
|
userslist += i;
|
|
}
|
|
|
|
QList<CertItem> rootslist;
|
|
foreach(const QString &s, rlist)
|
|
{
|
|
CertItem i;
|
|
if(i.fromString(s))
|
|
rootslist += i;
|
|
}
|
|
|
|
users->addItems(userslist);
|
|
roots->addItems(rootslist);
|
|
}
|
|
|
|
~MainWin()
|
|
{
|
|
QStringList ulist;
|
|
foreach(const CertItem &i, users->list)
|
|
ulist += i.toString();
|
|
QStringList rlist;
|
|
foreach(const CertItem &i, roots->list)
|
|
rlist += i.toString();
|
|
|
|
QSettings settings("Affinix", "CMS Signer");
|
|
settings.setValue("users", ulist);
|
|
settings.setValue("roots", rlist);
|
|
|
|
delete g_icons;
|
|
g_icons = 0;
|
|
}
|
|
|
|
private slots:
|
|
void load_file()
|
|
{
|
|
QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"), QString(), tr("X.509 Identities (*.p12 *.pfx)"));
|
|
if(fileName.isEmpty())
|
|
return;
|
|
|
|
setEnabled(false);
|
|
keyLoader = new QCA::KeyLoader(this);
|
|
connect(keyLoader, SIGNAL(finished()), SLOT(load_file_finished()));
|
|
keyLoader_fileName = fileName;
|
|
keyLoader->loadKeyBundleFromFile(fileName);
|
|
}
|
|
|
|
void load_device()
|
|
{
|
|
LoadStore *w = new LoadStore(this);
|
|
w->setAttribute(Qt::WA_DeleteOnClose, true);
|
|
w->setWindowModality(Qt::WindowModal);
|
|
connect(w, SIGNAL(entrySelected(const QCA::KeyStoreEntry &)), SLOT(load_device_finished(const QCA::KeyStoreEntry &)));
|
|
w->show();
|
|
}
|
|
|
|
void load_root()
|
|
{
|
|
QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"), QString(), tr("X.509 Certificates (*.pem *.crt)"));
|
|
if(fileName.isEmpty())
|
|
return;
|
|
|
|
QCA::Certificate cert = QCA::Certificate::fromPEMFile(fileName);
|
|
if(cert.isNull())
|
|
{
|
|
QMessageBox::information(this, tr("Error"), tr("Error opening certificate file."));
|
|
return;
|
|
}
|
|
|
|
QString name = roots->getUniqueName(cert.commonName());
|
|
|
|
// TODO: check for duplicate entries?
|
|
CertItem i;
|
|
i.name = name;
|
|
i.chain += cert;
|
|
roots->addItem(i);
|
|
}
|
|
|
|
void load_file_finished()
|
|
{
|
|
// TODO: show more descriptive reason?
|
|
if(keyLoader->convertResult() != QCA::ConvertGood)
|
|
{
|
|
setEnabled(true);
|
|
QMessageBox::information(this, tr("Error"), tr("Error opening key file."));
|
|
return;
|
|
}
|
|
|
|
QCA::KeyBundle kb = keyLoader->keyBundle();
|
|
delete keyLoader;
|
|
keyLoader = 0;
|
|
|
|
QCA::CertificateChain chain = kb.certificateChain();
|
|
QCA::Certificate cert = chain.primary();
|
|
|
|
QString name = users->getUniqueName(cert.commonName());
|
|
|
|
// TODO: check for duplicate identities?
|
|
CertItem i;
|
|
i.name = name;
|
|
i.chain = chain;
|
|
i.havePrivate = true;
|
|
i.storageType = CertItem::File;
|
|
i.fileName = keyLoader_fileName;
|
|
i.usable = true;
|
|
users->addItem(i);
|
|
|
|
ui.lv_users->selectionModel()->select(users->index(users->list.count()-1), QItemSelectionModel::Clear | QItemSelectionModel::Select | QItemSelectionModel::Current);
|
|
|
|
setEnabled(true);
|
|
}
|
|
|
|
void load_device_finished(const QCA::KeyStoreEntry &entry)
|
|
{
|
|
QCA::KeyBundle kb = entry.keyBundle();
|
|
|
|
QCA::CertificateChain chain = kb.certificateChain();
|
|
QCA::Certificate cert = chain.primary();
|
|
|
|
QString name = users->getUniqueName(entry.name());
|
|
|
|
// TODO: check for duplicate identities?
|
|
CertItem i;
|
|
i.name = name;
|
|
i.chain = chain;
|
|
i.havePrivate = true;
|
|
i.storageType = CertItem::Entry;
|
|
i.keyStoreEntry = entry;
|
|
i.usable = true;
|
|
users->addItem(i);
|
|
|
|
ui.lv_users->selectionModel()->select(users->index(users->list.count()-1), QItemSelectionModel::Clear | QItemSelectionModel::Select | QItemSelectionModel::Current);
|
|
|
|
setEnabled(true);
|
|
}
|
|
|
|
void mod_config()
|
|
{
|
|
if(!Pkcs11ConfigDlg::isSupported())
|
|
{
|
|
QMessageBox::information(this, tr("Error"), tr("No provider available supporting standard PKCS#11 configuration."));
|
|
return;
|
|
}
|
|
|
|
Pkcs11ConfigDlg *w = new Pkcs11ConfigDlg(this);
|
|
w->setAttribute(Qt::WA_DeleteOnClose, true);
|
|
w->setWindowModality(Qt::WindowModal);
|
|
w->show();
|
|
}
|
|
|
|
void users_selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
|
|
{
|
|
Q_UNUSED(deselected);
|
|
|
|
if(!selected.indexes().isEmpty() && !ui.pb_sign->isEnabled())
|
|
ui.pb_sign->setEnabled(true);
|
|
else if(selected.indexes().isEmpty() && ui.pb_sign->isEnabled())
|
|
ui.pb_sign->setEnabled(false);
|
|
}
|
|
|
|
/*void item_view()
|
|
{
|
|
if(ui.lv_identities->hasFocus())
|
|
{
|
|
QItemSelection selection = ui.lv_identities->selectionModel()->selection();
|
|
if(selection.indexes().isEmpty())
|
|
return;
|
|
QModelIndex index = selection.indexes().first();
|
|
identity_view(index.row());
|
|
}
|
|
else // lv_known
|
|
{
|
|
QItemSelection selection = ui.lv_known->selectionModel()->selection();
|
|
if(selection.indexes().isEmpty())
|
|
return;
|
|
QModelIndex index = selection.indexes().first();
|
|
known_view(index.row());
|
|
}
|
|
}*/
|
|
|
|
void item_rename()
|
|
{
|
|
if(ui.lv_users->hasFocus())
|
|
{
|
|
QItemSelection selection = ui.lv_users->selectionModel()->selection();
|
|
if(selection.indexes().isEmpty())
|
|
return;
|
|
QModelIndex index = selection.indexes().first();
|
|
users_rename(index.row());
|
|
}
|
|
else // lv_authorities
|
|
{
|
|
QItemSelection selection = ui.lv_authorities->selectionModel()->selection();
|
|
if(selection.indexes().isEmpty())
|
|
return;
|
|
QModelIndex index = selection.indexes().first();
|
|
roots_rename(index.row());
|
|
}
|
|
}
|
|
|
|
void item_remove()
|
|
{
|
|
if(ui.lv_users->hasFocus())
|
|
{
|
|
QItemSelection selection = ui.lv_users->selectionModel()->selection();
|
|
if(selection.indexes().isEmpty())
|
|
return;
|
|
QModelIndex index = selection.indexes().first();
|
|
users_remove(index.row());
|
|
}
|
|
else // lv_authorities
|
|
{
|
|
QItemSelection selection = ui.lv_authorities->selectionModel()->selection();
|
|
if(selection.indexes().isEmpty())
|
|
return;
|
|
QModelIndex index = selection.indexes().first();
|
|
roots_remove(index.row());
|
|
}
|
|
}
|
|
|
|
/*void identity_view(int at)
|
|
{
|
|
printf("identity_view: %d\n", at);
|
|
}*/
|
|
|
|
void users_rename(int at)
|
|
{
|
|
QModelIndex index = users->index(at);
|
|
ui.lv_users->setFocus();
|
|
ui.lv_users->setCurrentIndex(index);
|
|
ui.lv_users->selectionModel()->select(index, QItemSelectionModel::Clear | QItemSelectionModel::Select | QItemSelectionModel::Current);
|
|
ui.lv_users->edit(index);
|
|
}
|
|
|
|
void users_remove(int at)
|
|
{
|
|
users->removeItem(at);
|
|
}
|
|
|
|
/*void known_view(int at)
|
|
{
|
|
printf("known_view: %d\n", at);
|
|
}*/
|
|
|
|
void roots_rename(int at)
|
|
{
|
|
QModelIndex index = roots->index(at);
|
|
ui.lv_authorities->setFocus();
|
|
ui.lv_authorities->setCurrentIndex(index);
|
|
ui.lv_authorities->selectionModel()->select(index, QItemSelectionModel::Clear | QItemSelectionModel::Select | QItemSelectionModel::Current);
|
|
ui.lv_authorities->edit(index);
|
|
}
|
|
|
|
void roots_remove(int at)
|
|
{
|
|
roots->removeItem(at);
|
|
}
|
|
|
|
void do_sign()
|
|
{
|
|
QItemSelection selection = ui.lv_users->selectionModel()->selection();
|
|
if(selection.indexes().isEmpty())
|
|
return;
|
|
QModelIndex index = selection.indexes().first();
|
|
int at = index.row();
|
|
|
|
op = new SignOperation(ui.te_data->toPlainText().toUtf8(), &users->list[at], cms, this);
|
|
connect(op, SIGNAL(finished(const QString &)), SLOT(sign_finished(const QString &)));
|
|
connect(op, SIGNAL(error(const QString &)), SLOT(op_error(const QString &)));
|
|
}
|
|
|
|
void do_verify()
|
|
{
|
|
// prepare root certs
|
|
QCA::CertificateCollection col;
|
|
|
|
// system store
|
|
col += QCA::systemStore();
|
|
|
|
// additional roots configured in application
|
|
foreach(const CertItem &i, roots->list)
|
|
col.addCertificate(i.chain.primary());
|
|
|
|
// consider self-signed users as roots
|
|
// (it is therefore not possible with this application to
|
|
// have people in your keyring that you don't trust)
|
|
foreach(const CertItem &i, users->list)
|
|
{
|
|
QCA::Certificate cert = i.chain.primary();
|
|
if(cert.isSelfSigned())
|
|
col.addCertificate(cert);
|
|
}
|
|
|
|
cms->setTrustedCertificates(col);
|
|
|
|
op = new VerifyOperation(ui.te_data->toPlainText().toUtf8(), ui.te_sig->toPlainText().toUtf8(), cms, this);
|
|
connect(op, SIGNAL(finished()), SLOT(verify_finished()));
|
|
connect(op, SIGNAL(error(const QString &)), SLOT(op_error(const QString &)));
|
|
}
|
|
|
|
void about()
|
|
{
|
|
int ver = qcaVersion();
|
|
int maj = (ver >> 16) & 0xff;
|
|
int min = (ver >> 8) & 0xff;
|
|
int bug = ver & 0xff;
|
|
QString verstr;
|
|
verstr.sprintf("%d.%d.%d", maj, min, bug);
|
|
|
|
QString str;
|
|
str += tr("CMS Signer version %1 by Justin Karneges").arg(VERSION) + '\n';
|
|
str += tr("A simple tool for creating and verifying digital signatures.") + '\n';
|
|
str += '\n';
|
|
str += tr("Using QCA version %1").arg(verstr) + '\n';
|
|
str += '\n';
|
|
str += tr("Icons by Jason Kim") + '\n';
|
|
|
|
QCA::ProviderList list = QCA::providers();
|
|
foreach(QCA::Provider *p, list)
|
|
{
|
|
QString credit = p->credit();
|
|
if(!credit.isEmpty())
|
|
{
|
|
str += '\n';
|
|
str += credit;
|
|
}
|
|
}
|
|
|
|
QMessageBox::about(this, tr("About CMS Signer"), str);
|
|
}
|
|
|
|
void sign_finished(const QString &sig)
|
|
{
|
|
ui.te_sig->setPlainText(sig);
|
|
}
|
|
|
|
void verify_finished()
|
|
{
|
|
QMessageBox::information(this, tr("Verify"), tr("Signature verified successfully."));
|
|
}
|
|
|
|
void op_error(const QString &msg)
|
|
{
|
|
QMessageBox::information(this, tr("Error"), msg);
|
|
delete op;
|
|
op = 0;
|
|
}
|
|
};
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
QCA::Initializer qcaInit;
|
|
QApplication qapp(argc, argv);
|
|
qapp.setApplicationName(MainWin::tr("CMS Signer"));
|
|
if(!QCA::isSupported("cms"))
|
|
{
|
|
QMessageBox::critical(0, qapp.applicationName() + ": " + MainWin::tr("Error"), MainWin::tr("No support for CMS is available. Please install an appropriate QCA plugin, such as qca-openssl."));
|
|
return 1;
|
|
}
|
|
MainWin mainWin;
|
|
mainWin.show();
|
|
return qapp.exec();
|
|
}
|
|
|
|
#include "main.moc"
|