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.

288 lines
10 KiB

#ifndef SMARTCARDAUTH_H
#define SMARTCARDAUTH_H
#include <QtCore/QMutex>
#include <QtNetwork/QSslSocket>
#include <QtNetwork/QSslConfiguration>
#include <QtNetwork/QSslCertificate>
#include <QtNetwork/QSslKey>
#include <QtGui/QMessageBox>
#include <pinentry.hxx>
#include <cryptoki.hxx>
#include <pcsc.hxx>
#include <openssl-engine.hxx>
#include <openssl.hxx>
#include <memory>
class CryptokiEngine: public QObject, public openssl::Engine {
Q_OBJECT;
Q_SIGNALS:
void certRequired();
public:
CryptokiEngine(std::string lib):
_cryptoki(lib) {
OPENSSL_LOG("log");
}
operator bool() {
OPENSSL_LOG("Status of CryptokiEngine: "
<<(_privateKey.get()
?"privateKey defined, ":"privateKey undefined"));
return _privateKey.get();
}
cryptoki::Init& cryptoki() {
return _cryptoki;
}
void cert(cryptoki::Object& privateKey, const std::string& certVal) {
OPENSSL_LOG("log");
_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);
assert(localcert.isValid());
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(0);
do {
rsa = ((RSA*)privkey.handle());
RSA_free(rsa); //→ occasional crashes?
rsa = RSA_new_method(_e);
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());
} while (rsa!=(RSA*)privkey.handle());
sslConfig.setPrivateKey(privkey);
QSslConfiguration::setDefaultConfiguration(sslConfig);
} catch (const std::exception& e) {
OPENSSL_LOG("SETUP ERROR: "<<e.what());
}
}
protected:
void set(BIGNUM*& num, cryptoki::Object& key, int type, std::string name) {
try {
std::string value(key.attribute(type).value);
num = BN_bin2bn((const unsigned char*)value.data(),
value.size(), num);
} catch (const std::exception& x) {
qDebug()<<"**** ERROR: key attribute missing:"<<name.c_str()<<x.what();
}
}
virtual const char* id() {
OPENSSL_LOG("log");
return "CryptokiEngine_ID";
}
virtual const char* name() {
OPENSSL_LOG("log");
return "CryptokiEngine_NAME";
}
virtual std::string rsaSign(const std::string& in, unsigned int type) {
OPENSSL_LOG("log; type="<<type<<"; size="<<in.size());
if (type != NID_md5_sha1) throw std::runtime_error("wrong sign type");
if (in.size() != 36) throw std::runtime_error("wrong msg size to sign");
OPENSSL_LOG("ready to sign");
try {
return _privateKey->sign(in, CKM_RSA_PKCS);
} catch (const std::exception& x) {
certRequired(); // get new certificate
return _privateKey->sign(in, CKM_RSA_PKCS); // try again
}
}
private:
cryptoki::Init _cryptoki;
std::auto_ptr<cryptoki::Object> _privateKey;
};
class SmartCardAuth: public QObject {
Q_OBJECT;
public:
SmartCardAuth(const QString& lib, QWidget* p=0, bool loginAtStart=true):
_parent(p), _e(new CryptokiEngine(lib.toStdString())), _reg(_e) {
qDebug()<<__PRETTY_FUNCTION__;
if (loginAtStart) login();
assert(connect(_e, SIGNAL(certRequired()), SLOT(login())));
}
public Q_SLOTS:
void login(bool force=true) {
qDebug()<<__PRETTY_FUNCTION__;
QMutexLocker lock(&_mutex);
if (!_e || (!force && *_e)) return; // no smartcard or already logged in
try {
QList<CertInfo> authcerts;
QList<CertInfo> allcerts;
QSslConfiguration sslConfig(QSslConfiguration::defaultConfiguration());
_slots = _e->cryptoki().slotList();
// look for login certificates ----------------------------------------
for (cryptoki::SlotList::iterator slot(_slots.begin());
slot!=_slots.end(); ++slot) {
_session =
std::auto_ptr<cryptoki::Session>(new cryptoki::Session(*slot));
cryptoki::ObjectList certs(_session->find
(cryptoki::Attribute(CKA_CLASS)
.from<CK_OBJECT_CLASS>(CKO_CERTIFICATE)));
for (cryptoki::ObjectList::iterator cert(certs.begin());
cert!=certs.end(); ++cert) {
cryptoki::Attribute label(cert->attribute(CKA_LABEL));
cryptoki::Attribute id(cert->attribute(CKA_ID));
OPENSSL_LOG("**** FOUND CERTIFICATE: "<<label.value);
cryptoki::ObjectList keys
(_session->find(cryptoki::Attribute(CKA_CLASS)
.from<CK_OBJECT_CLASS>(CKO_PUBLIC_KEY),
id));
OPENSSL_LOG("**** with keys: "<<keys.size());
std::string data(cert->attribute(CKA_VALUE).value);
if (!keys.size()) { // add CA-certificate
OPENSSL_LOG("**** add to CA-certificates");
} else {
OPENSSL_LOG("**** user cert, check for authentictaion");
if (label.value.find("auth")==0 ||
label.value.find("Authentication")!=std::string::npos) {
OPENSSL_LOG("**** it's our authentication cert");
authcerts.push_back(CertInfo(data, slot, id));
} else {
OPENSSL_LOG("**** it's an unknown cert");
allcerts.push_back(CertInfo(data, slot, id));
}
}
}
}
// get pin and install client certificate ------------------------------
if (!authcerts.isEmpty() || !allcerts.isEmpty()) {
CertInfo c(authcerts.size()?authcerts[0]:allcerts[0]);
PinEntry pinEntry(QSslCertificate(QByteArray(c.data.data(),
c.data.size()),
QSsl::Der), _parent);
while (pinEntry.retries(retries(c.slot->slotinfo().slotDescription))
.exec()==PinEntry::Accepted)
try {
_session = // session login with pin
std::auto_ptr<cryptoki::Session>
(new cryptoki::Session(*c.slot));
_session->login(pinEntry.pin().toStdString());
cryptoki::ObjectList keys
(_session->find(cryptoki::Attribute(CKA_CLASS)
.from<CK_OBJECT_CLASS>(CKO_PRIVATE_KEY),
c.id));
if (keys.size()==1) {
OPENSSL_LOG("**** found one private key");
_e->cert(keys[0], c.data); // install client cert
break;
}
} catch (std::exception& x) {
pinEntry.pin().clear();
OPENSSL_LOG("**** ERROR"<<x.what());
QMessageBox::critical(0, QMessageBox::tr("Wrong PIN"),
QMessageBox::tr("Authentication failed,"
" please try again."));
}
}
} catch (...) {
throw;
}
}
private:
int retries(const std::string& name) try {
qDebug()<<__PRETTY_FUNCTION__<<name.c_str();
pcsc::Connection pcsc;
pcsc::Connection::Reader& reader(pcsc.reader(name));
#ifndef Q_OS_MAC
pcsc::Connection::Reader::Transaction lock(reader);
#endif
// first try to read version info
if (reader.transmit(0x00, 0xA4, 0x08, 0x0C, "\x3f\x00\x56\x49", 4)
!= std::string("\x90\x00", 2) || !reader) return -2;
std::string res(reader.transmit(0x00, 0xB0, 0x00, 0x00));
qDebug()<<" T E X T I S : "<<res.substr(6, res[5]).c_str();
if (res.substr(0, 2)!=std::string("\x90\x00", 2) ||
res.substr(6, res[5]) == "PZ2007") return -2;
if (retCode(reader.transmit(0x00, 0xA4, 0x00, 0x0C)) == 0x9000) {
int value(retCode(reader.transmit(0x00, 0x20, 0x00, 0x81)));
if ((value&0x63C0)==0x63C0) return value&0x0F;
} else {
qDebug()<<"**** ERROR in select MF while reading pin status";
}
return -1; // locked
} catch (const std::exception& x) {
qDebug()<<"**** ERROR while reading pin status: "<<x.what();
return -2;
}
int retCode(const std::string& res) {
if (res.size()>=2)
return ((((unsigned int)(unsigned char)res[res.size()-2])*256)
+((unsigned int)(unsigned char)res[res.size()-1]));
else
return -1;
}
private:
struct CertInfo {
CertInfo(std::string d, cryptoki::SlotList::iterator s,
cryptoki::Attribute i):
data(d), slot(s), id(i) {
}
std::string data;
cryptoki::SlotList::iterator slot;
cryptoki::Attribute id;
};
private:
QWidget* _parent;
CryptokiEngine* _e;
openssl::RegisterEngine<CryptokiEngine> _reg;
cryptoki::SlotList _slots;
std::auto_ptr<cryptoki::Session> _session;
QMutex _mutex;
//std::map<ssl_ctx_st*, QSslSocket*> sockets;
// std::list<std::string> _cacerts;
};
#endif // SMARTCARDAUTH_H