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.
287 lines
10 KiB
287 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
|
|
|