Reputation: 293
Following the routines described on this card reader project, and using this AID List , I was able to read a VISA Card without any problems forcing the AID List. Now I have a problem though with reading EC-Karten (Sparkasse Girocard) from Germany.
When I try to force the AID List to be read, using
foreach (byte[] aid in aidList)
{
byte[] atrValue = this.cardUpdater.GetAttribute(SCARD_ATTR_VALUE.ATR_STRING);
string strATR = ByteArrayToString(atrValue);
APDUCommand apduSelectEMVApl = null;
APDUResponse apdu2 = null;
apduSelectEMVApl = new APDUCommand(0x00, 0xA4, 0x04, 0x00, aid, 95);
apdu2 = this.cardUpdater.Transmit(apduSelectEMVApl);
if (apdu2.SW1 == 0x90)
{
//Label = ASCIIEncoding.ASCII.GetString(apdu2.Data, 15, apdu2.Data[14]);
//found it!
m_EMVAID = aid;
if (apdu2.Data[0] == 0x6f) //fci template
{
ExtractData(ReadTagData(apdu2.Data, 0));
}
return true;
}
}
return false;
Note: AID Successfuly read for selection is A0000003591010028001
If I don't set length parameter for the APDU Command specifically to 95 instead of the standard 0 as seen in all projects (to get the max length), it will not respond 90-00 (Success). I found this value just with an iteration to see which length was acceptable. Why?
With this procedure, I am able to read BIC and card type ("girocard"), as well as data such as PDOL:
9F33029F35019F4001
Then I try to raise the security level as per this post. But those APDU Commands to select and read don't throw 90-00 in my case (6700 instead).
I have tried to get the SFI records through
APDUCommand apduGPO = new APDUCommand(0x80, 0xa8, 0, 0, new byte[] { 0x83, 0 }, 0);
APDUResponse apdu1 = this.cardUpdater.Transmit(apduGPO);
if (apdu1.SW1 != 0x90) throw new Exception("Read GPO Data fail");
//two possible forms, 0x80 and 0x77
if (apdu1.Data[0] == 0x80)
{
for (int i = 4; i < apdu1.Data.Length; i += 4)
{
byte sfi = (byte)((apdu1.Data[i] >> 3) & 0xf);
byte lowRange = apdu1.Data[i + 1];
byte hiRange = apdu1.Data[i + 2];
byte[] records = new byte[hiRange - lowRange + 1];
for (int j = lowRange; j <= hiRange; j++)
records[j - lowRange] = (byte)j;
sfiRecords.Add(new SFIRecords(sfi, records));
}
}
else if (apdu1.Data[0] == 0x77)
{
//look for the application file locator AFL
int a, tag;
for (a = 2; (tag = ReadTag(apdu1.Data, a)) != 0x94; a = SkipTag(apdu1.Data, a)) ;
if (tag == 0x94)
{
//found it
a++;
int len = apdu1.Data[a++];
for (int i = a; i < a + len; i += 4)
{
byte sfi = (byte)((apdu1.Data[i] >> 3) & 0xf);
byte lowRange = apdu1.Data[i + 1];
byte hiRange = apdu1.Data[i + 2];
byte[] records = new byte[hiRange - lowRange + 1];
for (int j = lowRange; j <= hiRange; j++)
records[j - lowRange] = (byte)j;
sfiRecords.Add(new SFIRecords(sfi, records));
}
}
}
else
throw new Exception("Unknown GPO template");
As I read from many other sources including Openscdp's Initiate Application Process, but sending the PDOL as
APDUCommand apduGPO = new APDUCommand(0x80, 0xa8, 0, 0, pdol, 0);
APDUResponse apdu1 = this.cardUpdater.Transmit(apduGPO);
or
APDUCommand apduGPO = new APDUCommand(0x80, 0xa8, 0, 0, new byte[]{0x83, 0x00}, 0);
APDUResponse apdu1 = this.cardUpdater.Transmit(apduGPO);
doesn't go any further (error 6700 again, exception thrown Fail Reading GPO as result is not succesful) on the apduGPOResponse, so I cannot get to add the SFI Records to the list to further iterate with Data[0] and look for the records to read. No 90-00 response.
Any thoughts about what am I missing?
UPDATED
I ran this piece of code to force-read all possible values directly, without using GPO:
APDUCommand apduReadAll = null;
APDUResponse apdu1 = null;
for (var sfi = 1; sfi <= 31; sfi++)
{
for (var rec = 1; rec <= 16; rec++)
{
for (byte le = 0; le < 255; le++)
{
apduReadAll = new APDUCommand(0x00, 0xB2, (byte)rec, (byte)((sfi << 3) | 4), null, le);
apdu1 = this.cardUpdater.Transmit(apduReadAll);
if (apdu1.SW1 == 0x90)
{
Console.WriteLine("SFI " + sfi.ToString() + " record #" + rec);
if (apdu1.Data[0] == 0x70 || apdu1.Data[0] == 0x77)
{
Console.WriteLine("Chalk one here " + sfi.ToString() + " record #" + rec + " len " + le);
try
{
ExtractData(ReadTagData(apdu1.Data, 0));
}
catch
{
}
//if (!String.IsNullOrEmpty(NumberString) && !String.IsNullOrEmpty(Name) &&
// !String.IsNullOrEmpty(ExpiryString) && !String.IsNullOrEmpty(CardType) &&
// !String.IsNullOrEmpty(Label))
// return; //we have all info we need
}
}
}
}
}
foreach (TagData tag in Properties)
{
Console.WriteLine(tag.Name + " " + tag.DataString);
strAllData += tag.Name + " " + tag.DataString + "\r\n";
}
In the results I found some of the info I needed (now I can directly point to the data I need to speed up the process), as well as some other interesting info:
Application Label girocard
Application Priority Indicator 02
Application Identifier (AID) - card A0 00 00 00 59 45 43 01 00
Application Label girocard
Application Priority Indicator 04
Application Identifier (AID) - card A0 00 00 03 59 10 10 02 80 01
Application Label girocard
Application Priority Indicator 04
Application Identifier (AID) - card A0 00 00 00 04 30 60
Application Label Maestro
Application Priority Indicator 07
Application Identifier (AID) - card D2 76 00 00 25 45 50 02 00
Application Label GeldKarte
Application Identifier (AID) - card A0 00 00 04 86 01 01
Application Label girocard
Application Priority Indicator 05
I will run the same procedures with the mentioned AID values returned from the card to compare results and see what happens to better understand. Thanks for pointing me in the right direction.
Upvotes: 4
Views: 1242
Reputation: 40831
The reason is that the implementation of the Transmit()
in that project is buggy. When you pass an APDUCommand object that has Le set to 0 it will incorrectly treat that case as if Le was absent and will therefore not send an Le field. See CardNative.cs on line 446. Consequently,
...Transmit(new APDUCommand(0x00, 0xA4, 0x04, 0x00, new byte[] { 1, 2, 3, 4, 5 }, 0));
results in the following APDU to be sent to the card:
00 A4 0400 05 0102030405
However, what you actually wanted was the following APDU (which has an Le field):
00 A4 0400 05 0102030405 00
This could be fixed by distinguishing between the two cases Le is absent (indicating no response data expected, Ne = 0) and Le is 0 (indicating up to 256 bytes of response data are expected, Ne = 256).
Since you did not reveal the AID of the selected application (or even better the SELECT response) it's impossible to tell which protocol it might speak. So far all commands seem to get rejected with a wrong length error (SW = 0x6700
), which seems to be related the problem in the first question.
Since the AID list that you referenced indicates some form of EMV compliance for the Girocard application and you received a PDOL value of 9F33029F35019F4001
, you could try to issue a GET PROCESSING OPTIONS command (similar to what you are currently trying to do). As the card provided a PDOL, you need to fill the PDOL related data object in the GPO command with the expected values.
The PDOL lists the following elements:
So you could try to create a PDOL related data object that fills all these elements with zeros:
0000 00 00
Note that your card might expect some specific values here. Since the data objects in the PDOL do not match their definition in EMV (9F33 (Terminal Capabilities) would be expected to have a length of 3 and 9F40 (Additional Terminal Capabilities) would be expected to have a length of 5), I can't tell their actual meaning/coding.
The GPO command could then look like:
APDUCommand apduGPO = new APDUCommand(0x80, 0xa8, 0, 0, new byte[] { 0x83, 4, 0, 0, 0, 0 }, 0);
Again, this will only work if you fix the issue with the Le field.
Upvotes: 1