A simple Qt based browser with no bullshit that supports PKCS#11 tokens (such as the SuisseID).
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

273 lines
9.2 KiB

#ifndef SMARTCARDAUTH_H
#define SMARTCARDAUTH_H
#include <qbrowserlib/log.hxx>
#include <QtCore/QMutex>
#include <QtCore/QWaitCondition>
#include <QtNetwork/QSslSocket>
#include <QtNetwork/QSslConfiguration>
#include <QtNetwork/QSslCertificate>
#include <QtNetwork/QSslKey>
#include <QMessageBox>
#include <qbrowserlib/pinentry.hxx>
#include <suisseid.hxx>
#include <openssl-engine.hxx>
#include <memory>
namespace qbrowserlib {
class CryptokiEngine: public QObject, public openssl::Engine {
Q_OBJECT;
Q_SIGNALS:
void certRequired();
public:
CryptokiEngine() {
TRC;
}
operator bool() {
TRC; LOG<<"Status of CryptokiEngine: "
<<(_privateKey.get()
?"privateKey defined, ":"privateKey undefined");
return _privateKey.get();
}
void cert(cryptoki::Object& privateKey, const std::string& certVal) {
TRC;
_privateKey = std::auto_ptr<cryptoki::Object>
(new cryptoki::Object(privateKey));
try { // new
QSslConfiguration sslConfig(QSslConfiguration::defaultConfiguration());
QSslCertificate localcert(QByteArray(certVal.data(),
certVal.size()),
QSsl::Der);
sslConfig.setLocalCertificate(localcert);
//RSA_set_default_method(ENGINE_get_RSA(_e));
QByteArray pem // empty dummy key for qt object instantiation
("-----BEGIN RSA PRIVATE KEY-----\n"
"MIIBOwIBAAJBAMH2yqAGeVNPdgeZ2GoHo31m9aUxZ7QfK2Go2qLTahLpQ3UL1C8G\n"
"LkuMS8SNK0ZGfRMalIpIhv6bW5l3kjogOncCAwEAAQJABVGECtFCoGMsZFb2lSmy\n"
"dOzOzYHGSy0TnnDn1dEgNnZ8sIljElPtUzm9dyXs2P3ICL1sOd7qjpzfJeyxknDL\n"
"AQIhAO5iKdLmhyuW+EDEH19vDs1Pmqs3/ZnT5UgUiJnTJqz3AiEA0ExIfUOCnxq2\n"
"a3Z46KEivcr8JB2P9VqouBbVryiq/oECIQDj8bPCejMoiEzMSX0iWWTTB9qC/KAg\n"
"FtF4skHIrXKfEwIgPCs86Uo+Ch2aQjKHvJMHSRHAgeI0OmiEwiB+e0lhE4ECIQDd\n"
"IbUmHIXt6oHLJmoGFX46bCcfil5eE5FXfiaw7Q9iPw==\n"
"-----END RSA PRIVATE KEY-----\n");
QSslKey privkey(pem, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey);
RSA* rsa((RSA*)privkey.handle());
if (!ENGINE_init(_e)) return;
rsa->engine=_e;
rsa->meth=ENGINE_get_RSA(_e);
if (!CRYPTO_new_ex_data(CRYPTO_EX_INDEX_RSA, rsa, &rsa->ex_data)) {
ENGINE_finish(_e);
return;
}
set(rsa->n, privateKey, CKA_MODULUS, "CKA_MODULUS");
set(rsa->e, privateKey, CKA_PUBLIC_EXPONENT, "CKA_PUBLIC_EXPONENT");
set(rsa->d, privateKey, CKA_PRIVATE_EXPONENT, "CKA_PRIVATE_EXPONENT");
set(rsa->p, privateKey, CKA_PRIME_1, "CKA_PRIME_1");
set(rsa->q, privateKey, CKA_PRIME_2, "CKA_PRIME_2");
set(rsa->dmp1, privateKey, CKA_EXPONENT_1, "CKA_EXPONENT_1");
set(rsa->dmq1, privateKey, CKA_EXPONENT_2, "CKA_EXPONENT_2");
set(rsa->iqmp, privateKey, CKA_COEFFICIENT, "CKA_COEFFICIENT");
rsa->flags |= RSA_FLAG_SIGN_VER; // don't emulate with encrypt/decrypt
assert(!privkey.isNull());
LOG<<"Setup RSA finished";
sslConfig.setPrivateKey(privkey);
QSslConfiguration::setDefaultConfiguration(sslConfig);
} catch (const std::exception& e) {
LOG<<"SETUP ERROR: "<<e.what();
}
}
protected:
void set(BIGNUM*& num, cryptoki::Object& key, int type, std::string name) {
TRC;
try {
std::string value(key.attribute(type).value);
num = BN_bin2bn((const unsigned char*)value.data(),
value.size(), num);
} catch (const std::exception& x) {
LOG<<"**** ERROR: key attribute missing:"<<name.c_str()<<x.what();
}
}
virtual const char* id() {
TRC;
return "SuisseID Engine ID";
}
virtual const char* name() {
TRC;
return "SuisseID Engine NAME";
}
virtual std::string rsaSign(const std::string& in, unsigned int type) try {
TRC; LOG<<"log; type="<<type<<"; size="<<in.size();
LOG<<crypto::readable(in).c_str();
/* std::string data;
switch (type) {
case NID_sha1:
data += QString(QByteArray::fromHex("3021300906052b0e03021a05000414"))
.toStdString();
break;
case NID_sha224:
data += QString(QByteArray::fromHex("302d300d06096086480165030402040500041c"))
.toStdString();
break;
case NID_sha256:
data += QString(QByteArray::fromHex("3031300d060960864801650304020105000420"))
.toStdString();
break;
case NID_sha384:
data += QString(QByteArray::fromHex("3041300d060960864801650304020205000430"))
.toStdString();
break;
case NID_sha512:
data += QString(QByteArray::fromHex("3051300d060960864801650304020305000440"))
.toStdString();
break;
default:
break;
}
data += in; */
unsigned int algo(CKM_RSA_PKCS);
switch (type) {
case NID_md5_sha1: algo = CKM_RSA_PKCS; break;
case NID_md2: algo = CKM_MD2_RSA_PKCS; break;
case NID_md5: algo = CKM_MD5_RSA_PKCS; break;
case NID_sha1: algo = CKM_SHA1_RSA_PKCS; break;
case NID_sha256: algo = CKM_SHA256_RSA_PKCS; break;
case NID_sha384: algo = CKM_SHA384_RSA_PKCS; break;
case NID_sha512: algo = CKM_SHA512_RSA_PKCS; break;
case NID_ripemd160: algo = CKM_RIPEMD160_RSA_PKCS; break;
default: throw std::runtime_error("unknown sign mechanism");
}
LOG<<"ready to sign with algorith "<<algo;
try {
return _privateKey->sign(in, algo);
} catch (const std::exception& x) {
LOG<<"signature failed, reason: "<<x.what();
certRequired(); // get new certificate
return _privateKey->sign(in, algo); // try again
}
} catch (const std::exception& x) {
TRC; LOG<<"rsaSign failed, reason: "<<x.what();
throw;
}
private:
std::auto_ptr<cryptoki::Object> _privateKey;
};
class SmartCardAuth: public QObject {
Q_OBJECT;
public:
SmartCardAuth(suisseid::Cards cards, QWidget* p=0, bool loginAtStart=true):
_parent(p), _e(new CryptokiEngine()), _reg(_e), _cards(cards) {
TRC;
if (loginAtStart) login();
assert(connect(_e, SIGNAL(certRequired()), SLOT(login())));
}
public Q_SLOTS:
void login(bool force=true) {
TRC;
Lock lock;
LOG<<"got lock";
if (!_e || (!force && *_e)) return; // no smartcard or already logged in
LOG<<"get new certificate";
try {
for (suisseid::Cards::iterator card(_cards.begin());
card!=_cards.end(); ++card) {
suisseid::Certificate cert((*card)->authenticationCertificate());
PinEntry pinEntry(QSslCertificate(QByteArray(cert.data(),
cert.size()),
QSsl::Der), _parent);
while (true) try {
if (pinEntry
.tokeninfo((*card)->minimalPinLength(),
(*card)->maximalPinLength())
.retries((*card)->pkcs15PinRetries())
.myexec()
!=PinEntry::Accepted)
return;
_session = // session login with pin
std::shared_ptr<cryptoki::Session>
(new cryptoki::Session((*card)->session()));
_session->login(pinEntry.pin().toStdString());
cryptoki::ObjectList keys
(_session->find(cryptoki::Attribute(CKA_CLASS)
.from<CK_OBJECT_CLASS>(CKO_PRIVATE_KEY),
cert.id()));
if (keys.size()==1) {
_e->cert(keys[0], cert); // install client cert
break;
}
} catch (std::exception& x) {
pinEntry.pin().clear();
LOG<<"**** ERROR"<<x.what();
QMessageBox::critical(0, QMessageBox::tr("Wrong PIN"),
QMessageBox::tr("Authentication failed,"
" please try again."));
}
}
} catch (std::exception& x) {
LOG<<"**** ERROR"<<x.what();
throw;
}
}
private:
private:
class Lock {
public:
Lock() {
TRC; LOG<<loops().size();
loops().append(new QEventLoop); // add to queue
if (loops().size()>1) loops().back()->exec(); // wait
}
~Lock() {
TRC; LOG<<loops().size();
delete loops().front(); // mine is the first;
loops().erase(loops().begin()); // mine is the first;
if (loops().begin()!=loops().end())
loops().front()->exit(0); // wake up next
}
private:
QList<QEventLoop*>& loops() { // same as a static member variable
static QList<QEventLoop*> _loops;
return _loops;
}
};
private:
QWidget* _parent;
CryptokiEngine* _e;
openssl::RegisterEngine<CryptokiEngine> _reg;
suisseid::Cards _cards;
std::shared_ptr<cryptoki::Session> _session;
};
}
#endif // SMARTCARDAUTH_H