This library provides a simple and nice C++ wrapper around these libraries, so that programmers can concentrate on functionality. It offers general support for PCSC-lite, OpenSSL, PKCS#11, plus specific functionality for 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.
406 lines
14 KiB
406 lines
14 KiB
16 years ago
|
/*! @file
|
||
|
|
||
|
This file offers a C++ access to the PCSC library.
|
||
|
|
||
|
@id $Id$
|
||
|
*/
|
||
|
// 1 2 3 4 5 6 7 8
|
||
|
// 45678901234567890123456789012345678901234567890123456789012345678901234567890
|
||
|
|
||
|
#ifndef PCSC_HXX
|
||
|
#define PCSC_HXX
|
||
|
|
||
|
#include <PCSC/wintypes.h>
|
||
|
#include <PCSC/pcsclite.h>
|
||
|
#include <PCSC/winscard.h>
|
||
|
|
||
|
#include <boost/shared_ptr.hpp>
|
||
|
#include <string>
|
||
|
#include <vector>
|
||
|
#include <map>
|
||
|
#include <memory>
|
||
|
|
||
|
#include <sstream>
|
||
|
#include <iomanip>
|
||
|
|
||
|
//! This library is a C++ wrapper to the awful pcsc-lite interface.
|
||
|
/*! The reason for this wrapper is to get a nice object oriented
|
||
|
interface written in C++ manner and using standard types and so to
|
||
|
avoid the ugly M$-C-quirks. This interface is memory clean.
|
||
|
@todo: Not implemented, not supported:
|
||
|
- SCardGetStatusChange(hContext, 0, rgReaderStates, 1);
|
||
|
- SCardReconnect(hCard, SCARD_SHARE_SHARED,
|
||
|
SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1,
|
||
|
SCARD_LEAVE_CARD,
|
||
|
&dwActiveProtocol);
|
||
|
- several other
|
||
|
I do not need these, and I don't know the usecase where they are
|
||
|
required. If you need something that is not yet supported, please
|
||
|
let me know, what you need and why. Then I'll add it so that it
|
||
|
best fits the common needs. */
|
||
|
namespace pcsc {
|
||
|
|
||
|
std::string hex(const std::string& data) {
|
||
|
std::stringstream res;
|
||
|
for (std::string::const_iterator it(data.begin()); it!=data.end(); ++it)
|
||
|
res<<std::hex<<std::setfill('0')<<std::setw(2)<<(int)*it;
|
||
|
return res.str();
|
||
|
}
|
||
|
|
||
|
//============================================================================
|
||
|
class exception: public std::exception {
|
||
|
public:
|
||
|
exception(const std::string& reason) throw(): _what("pcsc: "+reason) {}
|
||
|
~exception() throw() {}
|
||
|
const char* what() const throw() {
|
||
|
return _what.c_str();
|
||
|
}
|
||
|
private:
|
||
|
std::string _what;
|
||
|
};
|
||
|
//----------------------------------------------------------------------------
|
||
|
class not_implemented: exception {
|
||
|
public:
|
||
|
not_implemented(const std::string& reason) throw():
|
||
|
exception("feature is not implemented:\n"+reason) {
|
||
|
}
|
||
|
};
|
||
|
//----------------------------------------------------------------------------
|
||
|
class access_error: exception {
|
||
|
public:
|
||
|
access_error(const std::string& reason) throw():
|
||
|
exception("smardcard access error:\n"+reason) {
|
||
|
}
|
||
|
};
|
||
|
|
||
|
//============================================================================
|
||
|
class Connection {
|
||
|
|
||
|
//...............................................................typedefs
|
||
|
public:
|
||
|
|
||
|
|
||
|
//------------------------------------------------------------------Reader
|
||
|
friend class Reader;
|
||
|
class Reader {
|
||
|
|
||
|
//............................................................typedefs
|
||
|
public:
|
||
|
|
||
|
friend class Transaction;
|
||
|
//! Scoped transaction.
|
||
|
/*! This is a scoped transaction. That means, the
|
||
|
transaction is started in the constructor and cancelled
|
||
|
in the destructor, unless it is committed before. That
|
||
|
means: If you commit the transaction, it is executed. If
|
||
|
you leave the code block unexpectedly, i.e. through an
|
||
|
exception, the transaction is cancelled.
|
||
|
|
||
|
@code
|
||
|
pcsc::Connection c;
|
||
|
{
|
||
|
Transaction t(c.reader("name"));
|
||
|
[...] // do some stuff, possible exceptions thrown
|
||
|
if (problem) return; // automatically cancelled
|
||
|
[...]
|
||
|
t.commit(); // commit if we reach this line
|
||
|
} // cancelled, unless commit reaced
|
||
|
@endcode */
|
||
|
class Transaction {
|
||
|
public:
|
||
|
//! Begins a transaction.
|
||
|
/*! @note Please note that the Reader is required in the
|
||
|
destructor und must therefore live longer than the
|
||
|
Transaction instance. */
|
||
|
Transaction(Reader& r): _reader(r), _running(true) {
|
||
|
_reader.beginTransaction();
|
||
|
}
|
||
|
//! Cancels the transaction if not yet finished.
|
||
|
~Transaction() try {
|
||
|
if (_running) cancel();
|
||
|
} catch (...) {
|
||
|
if (!std::uncaught_exception()) throw;
|
||
|
}
|
||
|
//! Ends (commits) the running transaction.
|
||
|
void commit() {
|
||
|
if (_running) _reader.commitTransaction();
|
||
|
_running = false;
|
||
|
}
|
||
|
//! Cancels the running transaction.
|
||
|
void cancel() {
|
||
|
if (_running) _reader.cancelTransaction();
|
||
|
_running = false;
|
||
|
}
|
||
|
private:
|
||
|
Reader& _reader;
|
||
|
bool _running;
|
||
|
};
|
||
|
|
||
|
//! State and attribute list of a reader.
|
||
|
class Status {
|
||
|
public:
|
||
|
Status(unsigned long s, const std::vector<unsigned char>& attrs):
|
||
|
state(s), attr(attrs) {
|
||
|
}
|
||
|
Status(unsigned long s, const std::string& attrs):
|
||
|
state(s), attr(convert(attrs)) {
|
||
|
}
|
||
|
const unsigned long state;
|
||
|
const std::vector<unsigned char> attr;
|
||
|
private:
|
||
|
static std::vector<unsigned char> convert(const std::string& a) {
|
||
|
std::vector<unsigned char> res;
|
||
|
for (std::string::const_iterator it(a.begin());
|
||
|
it!=a.end(); ++it)
|
||
|
res.push_back(*it);
|
||
|
return res;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
//.............................................................methods
|
||
|
public:
|
||
|
|
||
|
//! Disconnects connection.
|
||
|
~Reader() {
|
||
|
_state = SCardDisconnect(_id, SCARD_LEAVE_CARD);
|
||
|
if (!std::uncaught_exception()) _connection.check();
|
||
|
}
|
||
|
|
||
|
//! Get reader status.
|
||
|
Status status() {
|
||
|
unsigned long dummy(0);
|
||
|
unsigned long s;
|
||
|
unsigned long len(MAX_ATR_SIZE);
|
||
|
unsigned char a[len];
|
||
|
check(SCardStatus(_id, 0, &dummy, &s, &_protocol, a, &len));
|
||
|
return Status(s, std::string((char*)a, len));
|
||
|
}
|
||
|
|
||
|
//! Transmit data to reader.
|
||
|
std::string transmit(std::string in) {
|
||
|
unsigned long len(1024); // arbitrary
|
||
|
unsigned char buff[len];
|
||
|
SCARD_IO_REQUEST rPci;
|
||
|
check(SCardTransmit(_id, pci(),
|
||
|
(unsigned char*)in.c_str(), in.size(),
|
||
|
&rPci, buff, &len));
|
||
|
return std::string((char*)buff, len);
|
||
|
}
|
||
|
|
||
|
//! @c false if last operation was not successful
|
||
|
operator bool() const {
|
||
|
return _state==SCARD_S_SUCCESS;
|
||
|
}
|
||
|
|
||
|
//...........................................................variables
|
||
|
public:
|
||
|
|
||
|
const std::string name;
|
||
|
|
||
|
//.............................................................methods
|
||
|
private:
|
||
|
|
||
|
//! Use scoped transactions with @ref Transaction
|
||
|
void beginTransaction() {
|
||
|
check(SCardBeginTransaction(_id));
|
||
|
}
|
||
|
|
||
|
//! Use scoped transactions with @ref Transaction
|
||
|
void commitTransaction() {
|
||
|
check(SCardEndTransaction(_id, SCARD_LEAVE_CARD));
|
||
|
}
|
||
|
|
||
|
//! Use scoped transactions with @ref Transaction
|
||
|
void cancelTransaction() {
|
||
|
check(SCardCancelTransaction(_id));
|
||
|
}
|
||
|
|
||
|
/*! @throw not_implemented if _protocol is unknown. */
|
||
|
SCARD_IO_REQUEST* pci() {
|
||
|
switch(_protocol) {
|
||
|
case SCARD_PROTOCOL_T0: return SCARD_PCI_T0;
|
||
|
case SCARD_PROTOCOL_T1: return SCARD_PCI_T1;
|
||
|
}
|
||
|
if (_connection._exc) throw not_implemented("unknown protocol");
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
//! Sets state and throws an exception if neccessary.
|
||
|
/*! @throw access_error if it is instanciated for exceptions and
|
||
|
an error occured in the last command. */
|
||
|
bool check(long state) {
|
||
|
_state = state;
|
||
|
return _connection.check(state);
|
||
|
}
|
||
|
|
||
|
//! Only Connection is allowed to instanciate.
|
||
|
friend class Connection;
|
||
|
|
||
|
//! Establishes a connection to the given named cardreader
|
||
|
Reader(const std::string& nm, Connection& c):
|
||
|
name(nm), _connection(c) {
|
||
|
check(SCardConnect(_connection._id, name.c_str(),
|
||
|
SCARD_SHARE_SHARED,
|
||
|
SCARD_PROTOCOL_T0|SCARD_PROTOCOL_T1,
|
||
|
&_id, &_protocol));
|
||
|
}
|
||
|
|
||
|
//! forbidden
|
||
|
Reader();
|
||
|
|
||
|
//! forbidden
|
||
|
Reader(const Reader&);
|
||
|
|
||
|
//...........................................................variables
|
||
|
private:
|
||
|
|
||
|
Connection& _connection;
|
||
|
long _id;
|
||
|
long _state;
|
||
|
unsigned long _protocol;
|
||
|
|
||
|
};
|
||
|
//------------------------------------------------------------------Reader
|
||
|
|
||
|
enum Scope {
|
||
|
USER = SCARD_SCOPE_USER,
|
||
|
TERMINAL = SCARD_SCOPE_TERMINAL,
|
||
|
SYSTEM = SCARD_SCOPE_SYSTEM
|
||
|
};
|
||
|
|
||
|
typedef std::vector<std::string> Strings;
|
||
|
|
||
|
//................................................................methods
|
||
|
public:
|
||
|
|
||
|
//! Opens a connection (establishes a smartcard context)
|
||
|
/*! The errorhandling is defined with the @c exceptions flag:
|
||
|
|
||
|
Using exceptions:
|
||
|
@code
|
||
|
try {
|
||
|
pcsc::Connection c; // standard with exceptions
|
||
|
pcsc::Connection::Strings r(c.scan());
|
||
|
} catch (std::exception& x) {
|
||
|
std::cout<<"Error with message: "<<x.what()<<std::endl;
|
||
|
}
|
||
|
@endcode
|
||
|
|
||
|
Without exceptions:
|
||
|
@code
|
||
|
pcsc::Connection c(SYSTEM, false);
|
||
|
if (!c) std::cout<<"Error with message: "<<c.error()<<std::endl;
|
||
|
pcsc::Connection::Strings r(c.scan());
|
||
|
if (!c) std::cout<<"Error with message: "<<c.error()<<std::endl;
|
||
|
@endcode
|
||
|
|
||
|
Recommendation: Use exceptions!
|
||
|
|
||
|
@param s defines the scope of the connection
|
||
|
@param exceptions
|
||
|
- @c true: class throws exceptions in case of an error
|
||
|
- @c false: no exceptions, check your instance after each
|
||
|
operation */
|
||
|
Connection(Scope s=SYSTEM, bool exceptions=true):
|
||
|
_exc(exceptions), _id(0),
|
||
|
_state(SCardEstablishContext(s, 0, 0, &_id)) {
|
||
|
check();
|
||
|
}
|
||
|
|
||
|
//! Closes the connection (releases the smartcard context)
|
||
|
~Connection() {
|
||
|
_state = SCardReleaseContext(_id);
|
||
|
if (!std::uncaught_exception()) check();
|
||
|
}
|
||
|
|
||
|
//! Scans for available readers from a specified list of groups.
|
||
|
/*! Defaults to all groups. */
|
||
|
Strings scan(const Strings& groups = Strings()) {
|
||
|
Strings res;
|
||
|
std::string grp(join(groups));
|
||
|
unsigned long num(0);
|
||
|
if (!check(SCardListReaders(_id, grp.size()?grp.data():0, 0, &num)))
|
||
|
return res;
|
||
|
std::auto_ptr<char> nm(new char[num]);
|
||
|
if (!check(SCardListReaders(_id, grp.size()?grp.data():0,
|
||
|
nm.get(), &num)))
|
||
|
return res;
|
||
|
return res = split(std::string(nm.get(), num-1));
|
||
|
}
|
||
|
|
||
|
//! Get a reader, open a connection if not already open.
|
||
|
/*! First use scan() to get a list of readers, then open a
|
||
|
connection to the reader, then access it. */
|
||
|
Reader& reader(const std::string& name) {
|
||
|
if (_reader.find(name)==_reader.end())
|
||
|
_reader.insert(std::make_pair(name, new Reader(name, *this)));
|
||
|
return *_reader.find(name)->second;
|
||
|
}
|
||
|
|
||
|
//! Close connection of a named reader.
|
||
|
/*! If you access the same reader through raeder() later, the
|
||
|
connection will be reestablished. */
|
||
|
void close(const std::string& s) {
|
||
|
|
||
|
}
|
||
|
|
||
|
//! @c false if last operation was not successful
|
||
|
operator bool() const {
|
||
|
return _state==SCARD_S_SUCCESS;
|
||
|
}
|
||
|
|
||
|
//! Get the describing text of the last error
|
||
|
std::string error() const {
|
||
|
return pcsc_stringify_error(_state);
|
||
|
}
|
||
|
|
||
|
//................................................................methods
|
||
|
private:
|
||
|
|
||
|
//! Sets state and throws an exception if neccessary.
|
||
|
/*! @throw access_error if it is instanciated for exceptions and
|
||
|
an error occured in the last command. */
|
||
|
bool check(long state) {
|
||
|
_state = state;
|
||
|
return check();
|
||
|
}
|
||
|
|
||
|
//! Throws an exception if neccessary.
|
||
|
/*! @throw access_error if it is instanciated for exceptions and
|
||
|
an error occured in the last command. */
|
||
|
bool check() {
|
||
|
if (_exc&&_state!=SCARD_S_SUCCESS) throw access_error(error());
|
||
|
return _state==SCARD_S_SUCCESS;
|
||
|
}
|
||
|
|
||
|
//! Splits a buffer with 0 separators into a vector of strings.
|
||
|
Strings split(const std::string& in) {
|
||
|
Strings res;
|
||
|
for (std::string::size_type pos(0); pos<in.size();
|
||
|
pos+=res.rbegin()->size()+1)
|
||
|
res.push_back(in.substr(pos).c_str());
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
//! Joins a vector of strings into a buffer with 0 separators.
|
||
|
std::string join(const Strings& in) {
|
||
|
std::string res;
|
||
|
for (Strings::const_iterator it(in.begin());
|
||
|
it!=in.end(); ++it)
|
||
|
res+=*it+'\0';
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
//..............................................................variables
|
||
|
private:
|
||
|
|
||
|
bool _exc;
|
||
|
long _id;
|
||
|
long _state;
|
||
|
std::map<std::string, boost::shared_ptr<Reader> > _reader;
|
||
|
|
||
|
};
|
||
|
|
||
|
}
|
||
|
#endif
|