#include "CACToken.h"
#include "Adornment.h"
#include "AttributeCoder.h"
#include "CACError.h"
#include "CACRecord.h"
#include "CACSchema.h"
#include <security_cdsa_client/aclclient.h>
#include <map>
#include <vector>
using CssmClient::AclFactory;
#define CLA_STANDARD 0x00
#define INS_SELECT_FILE 0xA4
#define INS_GET_DATA 0xCA
#define SELECT_APPLET CLA_STANDARD, INS_SELECT_FILE, 0x04, 0x00
#define SELECT_CAC_APPLET SELECT_APPLET, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x79
#define SELECT_CAC_APPLET_PKI SELECT_CAC_APPLET, 0x01
#define SELECT_CAC_APPLET_TLB SELECT_CAC_APPLET, 0x02
#define SELECT_CAC_APPLET_PIN SELECT_CAC_APPLET, 0x03
static const unsigned char kSelectCardManagerApplet[] =
{ SELECT_APPLET, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00 };
static const unsigned char kSelectCACAppletPKIID[] =
{ SELECT_CAC_APPLET_PKI, 0x00 };
static const unsigned char kSelectCACAppletPKIESig[] =
{ SELECT_CAC_APPLET_PKI, 0x01 };
static const unsigned char kSelectCACAppletPKIECry[] =
{ SELECT_CAC_APPLET_PKI, 0x02 };
static const unsigned char kSelectCACAppletPN[] =
{ SELECT_CAC_APPLET_TLB, 0x00 };
static const unsigned char kSelectCACAppletPL[] =
{ SELECT_CAC_APPLET_TLB, 0x01 };
static const unsigned char kSelectCACAppletBS[] =
{ SELECT_CAC_APPLET_TLB, 0x02 };
static const unsigned char kSelectCACAppletOB[] =
{ SELECT_CAC_APPLET_TLB, 0x03 };
static const unsigned char kSelectCACAppletPIN[] =
{ SELECT_CAC_APPLET_PIN, 0x00 };
CACToken::CACToken() :
mCurrentApplet(NULL),
mPinStatus(0)
{
mTokenContext = this;
mSession.open();
}
CACToken::~CACToken()
{
delete mSchema;
}
bool CACToken::identify()
{
try
{
select(kSelectCACAppletPKIID);
return true;
}
catch (const PCSC::Error &error)
{
if (error.error == SCARD_E_PROTO_MISMATCH)
return false;
throw;
}
}
void CACToken::select(const unsigned char *applet)
{
if (isInTransaction() && mCurrentApplet == applet)
return;
size_t applet_length = sizeof(kSelectCACAppletPKIID);
unsigned char result[2];
size_t resultLength = sizeof(result);
transmit(applet, applet_length, result, resultLength);
if (resultLength != 2 || result[0] != 0x61 )
PCSC::Error::throwMe(SCARD_E_PROTO_MISMATCH);
if (isInTransaction())
mCurrentApplet = applet;
}
uint32_t CACToken::exchangeAPDU(const unsigned char *apdu, size_t apduLength,
unsigned char *result, size_t &resultLength)
{
size_t savedLength = resultLength;
transmit(apdu, apduLength, result, resultLength);
if (resultLength == 2 && result[0] == 0x61)
{
resultLength = savedLength;
uint8 expectedLength = result[1];
unsigned char getResult[] = { 0x00, 0xC0, 0x00, 0x00, expectedLength };
transmit(getResult, sizeof(getResult), result, resultLength);
if (resultLength - 2 != expectedLength)
{
if (resultLength < 2)
PCSC::Error::throwMe(SCARD_E_PROTO_MISMATCH);
else
CACError::throwMe((result[resultLength - 2] << 8)
+ result[resultLength - 1]);
}
}
if (resultLength < 2)
PCSC::Error::throwMe(SCARD_E_PROTO_MISMATCH);
return (result[resultLength - 2] << 8) + result[resultLength - 1];
}
void CACToken::didDisconnect()
{
PCSC::Card::didDisconnect();
mCurrentApplet = NULL;
mPinStatus = 0;
}
void CACToken::didEnd()
{
PCSC::Card::didEnd();
mCurrentApplet = NULL;
mPinStatus = 0;
}
void CACToken::changePIN(int pinNum,
const unsigned char *oldPin, size_t oldPinLength,
const unsigned char *newPin, size_t newPinLength)
{
if (pinNum != 1)
CssmError::throwMe(CSSM_ERRCODE_SAMPLE_VALUE_NOT_SUPPORTED);
if (oldPinLength < 4 || oldPinLength > 8 ||
newPinLength < 4 || newPinLength > 8)
CssmError::throwMe(CSSM_ERRCODE_INVALID_SAMPLE_VALUE);
PCSC::Transaction _(*this);
select(kSelectCACAppletPIN);
unsigned char apdu[] =
{
0x80, 0x24, 0x01, 0x00, 0x10,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
memcpy(apdu + 5, oldPin, oldPinLength);
memcpy(apdu + 13, newPin, newPinLength);
unsigned char result[2];
size_t resultLength = sizeof(result);
mPinStatus = exchangeAPDU(apdu, sizeof(apdu), result, resultLength);
memset(apdu + 5, 0, 16);
CACError::check(mPinStatus);
}
uint32_t CACToken::pinStatus(int pinNum)
{
if (pinNum != 1)
CssmError::throwMe(CSSM_ERRCODE_SAMPLE_VALUE_NOT_SUPPORTED);
if (mPinStatus && isInTransaction())
return mPinStatus;
PCSC::Transaction _(*this);
if (mCurrentApplet != kSelectCACAppletPKIID
&& mCurrentApplet != kSelectCACAppletPKIESig
&& mCurrentApplet != kSelectCACAppletPKIECry
&& mCurrentApplet != kSelectCACAppletPN
&& mCurrentApplet != kSelectCACAppletPL
&& mCurrentApplet != kSelectCACAppletBS
&& mCurrentApplet != kSelectCACAppletOB
&& mCurrentApplet != kSelectCACAppletPIN)
{
select(kSelectCACAppletPKIESig);
}
unsigned char result[2];
size_t resultLength = sizeof(result);
unsigned char apdu[] = { 0x80, 0x20, 0x00, 0x00 };
mPinStatus = exchangeAPDU(apdu, 4, result, resultLength);
if ((mPinStatus & 0xFF00) != 0x6300
&& mPinStatus != SCARD_AUTHENTICATION_BLOCKED)
CACError::check(mPinStatus);
return mPinStatus;
}
void CACToken::verifyPIN(int pinNum,
const unsigned char *pin, size_t pinLength)
{
if (pinNum != 1)
CssmError::throwMe(CSSM_ERRCODE_SAMPLE_VALUE_NOT_SUPPORTED);
if (pinLength < 4 || pinLength > 8)
CssmError::throwMe(CSSM_ERRCODE_INVALID_SAMPLE_VALUE);
PCSC::Transaction _(*this);
if (mCurrentApplet != kSelectCACAppletPKIID
&& mCurrentApplet != kSelectCACAppletPKIESig
&& mCurrentApplet != kSelectCACAppletPKIECry
&& mCurrentApplet != kSelectCACAppletPN
&& mCurrentApplet != kSelectCACAppletPL
&& mCurrentApplet != kSelectCACAppletBS
&& mCurrentApplet != kSelectCACAppletOB
&& mCurrentApplet != kSelectCACAppletPIN)
{
select(kSelectCACAppletPKIESig);
}
unsigned char apdu[] =
{
0x80, 0x20, 0x00, 0x00, 0x08,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
#if defined(CAC_PROTECTED_MODE)
memcpy(apdu + 5, "77777777", 8);
#else
memcpy(apdu + 5, pin, pinLength);
#endif
unsigned char result[2];
size_t resultLength = sizeof(result);
mPinStatus = exchangeAPDU(apdu, sizeof(apdu), result, resultLength);
memset(apdu + 5, 0, 8);
CACError::check(mPinStatus);
begin();
}
void CACToken::unverifyPIN(int pinNum)
{
if (pinNum != -1)
CssmError::throwMe(CSSM_ERRCODE_SAMPLE_VALUE_NOT_SUPPORTED);
end(SCARD_RESET_CARD);
}
uint32_t CACToken::getData(unsigned char *result, size_t &resultLength)
{
PCSC::Transaction _(*this);
try
{
select(kSelectCardManagerApplet);
}
catch (const PCSC::Error &error)
{
return error.error;
}
unsigned char apdu[] = { 0x80, INS_GET_DATA, 0x9F, 0x7F, 0x2D };
return exchangeAPDU(apdu, sizeof(apdu), result, resultLength);
}
uint32 CACToken::probe(SecTokendProbeFlags flags,
char tokenUid[TOKEND_MAX_UID])
{
uint32 score = Tokend::ISO7816Token::probe(flags, tokenUid);
bool doDisconnect = false;
try
{
if (!identify())
doDisconnect = true;
else
{
unsigned char result[0x2F];
size_t resultLength = sizeof(result);
uint32_t cacreturn = getData(result, resultLength);
if (cacreturn != SCARD_SUCCESS || resultLength != 0x2F)
{
score = 100;
}
else
{
score = 200;
for (uint32_t ix = 0x00; ix < 0x0A; ++ix)
{
sprintf(tokenUid,
"CAC-%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X",
result[3], result[4], result[5], result[6], result[19],
result[20], result[15], result[16], result[17],
result[18]);
}
Tokend::ISO7816Token::name(tokenUid);
secdebug("probe", "recognized %s", tokenUid);
}
}
}
catch (...)
{
doDisconnect = true;
score = 0;
}
if (doDisconnect)
disconnect();
return score;
}
void CACToken::establish(const CSSM_GUID *guid, uint32 subserviceId,
SecTokendEstablishFlags flags, const char *cacheDirectory,
const char *workDirectory, char mdsDirectory[PATH_MAX],
char printName[PATH_MAX])
{
Tokend::ISO7816Token::establish(guid, subserviceId, flags,
cacheDirectory, workDirectory, mdsDirectory, printName);
mSchema = new CACSchema();
mSchema->create();
populate();
}
void CACToken::getOwner(AclOwnerPrototype &owner)
{
if (!mAclOwner)
{
mAclOwner.allocator(Allocator::standard());
mAclOwner = AclFactory::PinSubject(Allocator::standard(), 0);
}
owner = mAclOwner;
}
void CACToken::getAcl(const char *tag, uint32 &count, AclEntryInfo *&acls)
{
Allocator &alloc = Allocator::standard();
if (!mAclEntries) {
mAclEntries.allocator(alloc);
mAclEntries.add(CssmClient::AclFactory::AnySubject(
mAclEntries.allocator()),
AclAuthorizationSet(CSSM_ACL_AUTHORIZATION_DB_READ, 0));
mAclEntries.addPin(AclFactory::PWSubject(alloc), 1);
mAclEntries.addPin(AclFactory::PromptPWSubject(alloc, CssmData()), 1);
}
count = mAclEntries.size();
acls = mAclEntries.entries();
}
#pragma mark ---------------- CAC Specific --------------
void CACToken::populate()
{
secdebug("populate", "CACToken::populate() begin");
Tokend::Relation &certRelation =
mSchema->findRelation(CSSM_DL_DB_RECORD_X509_CERTIFICATE);
Tokend::Relation &privateKeyRelation =
mSchema->findRelation(CSSM_DL_DB_RECORD_PRIVATE_KEY);
Tokend::Relation &dataRelation =
mSchema->findRelation(CSSM_DL_DB_RECORD_GENERIC);
RefPointer<Tokend::Record> idCert(new CACCertificateRecord(
kSelectCACAppletPKIID, "Identity Certificate"));
RefPointer<Tokend::Record> eSigCert(new CACCertificateRecord(
kSelectCACAppletPKIESig, "Email Signing Certificate"));
RefPointer<Tokend::Record> eCryCert(new CACCertificateRecord(
kSelectCACAppletPKIECry, "Email Encryption Certificate"));
certRelation.insertRecord(idCert);
certRelation.insertRecord(eSigCert);
certRelation.insertRecord(eCryCert);
RefPointer<Tokend::Record> idKey(new CACKeyRecord(
kSelectCACAppletPKIID, "Identity Private Key",
privateKeyRelation.metaRecord(), true));
RefPointer<Tokend::Record> eSigKey(new CACKeyRecord(
kSelectCACAppletPKIESig, "Email Signing Private Key",
privateKeyRelation.metaRecord(), true));
RefPointer<Tokend::Record> eCryKey(new CACKeyRecord(
kSelectCACAppletPKIECry, "Email Encryption Private Key",
privateKeyRelation.metaRecord(), false));
privateKeyRelation.insertRecord(idKey);
privateKeyRelation.insertRecord(eSigKey);
privateKeyRelation.insertRecord(eCryKey);
idKey->setAdornment(mSchema->publicKeyHashCoder().certificateKey(),
new Tokend::LinkedRecordAdornment(idCert));
eSigKey->setAdornment(mSchema->publicKeyHashCoder().certificateKey(),
new Tokend::LinkedRecordAdornment(eSigCert));
eCryKey->setAdornment(mSchema->publicKeyHashCoder().certificateKey(),
new Tokend::LinkedRecordAdornment(eCryCert));
dataRelation.insertRecord(new CACTBRecord(kSelectCACAppletPN, "PNTB"));
dataRelation.insertRecord(new CACVBRecord(kSelectCACAppletPN, "PNVB"));
dataRelation.insertRecord(new CACTBRecord(kSelectCACAppletPL, "PLTB"));
dataRelation.insertRecord(new CACVBRecord(kSelectCACAppletPL, "PLVB"));
dataRelation.insertRecord(new CACTBRecord(kSelectCACAppletBS, "BSTB"));
dataRelation.insertRecord(new CACVBRecord(kSelectCACAppletBS, "BSVB"));
dataRelation.insertRecord(new CACTBRecord(kSelectCACAppletOB, "OBTB"));
dataRelation.insertRecord(new CACVBRecord(kSelectCACAppletOB, "OBVB"));
secdebug("populate", "CACToken::populate() end");
}