Search code examples
c#credit-cardapdusmartcard-readeremv

Girocard-Maestro Smartcard reader issues, to read Card Holder Name and IBAN


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.

  1. 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).

  1. 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.


Solution

  • Why do I need to set Le to 95 instead of 0 (to get the maximum length)?

    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).

    What commands come next?

    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:

    • 9F33 (2 bytes)
    • 9F35 (1 byte)
    • 9F40 (1 byte)

    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.