I couldn’t find any samples to read a Belgian Electronic Identity Card in a UWP application.
With the DeviceInformation and SmartCardReader classes it is very easy to access the devices/readers connected to your PC. But how do you get information like name, addres, identification number, nationality, date of birth, birth location, gender, … from the eID card? After some research I was able to succesfully send APDU (Application Protocol Data Unit) commands to the card, read the responses and parse the requested data. Let me show you how.
TL;DR
Are you just looking for a working library or samples and not (yet) interested in the technical details? Take a look at the links at the bottom of this blogpost.
Connecting to a device
This is the easy part! UWP has very simple APIs to get your devices and to connect with them. These eID readers I tested (USB, onboard readers,…) are ‘Smart Card Readers’ to the system.
The DeviceInformation class has an interesting FindAllAsync method that accepts a AQS (Advanced Query Syntax) filter. As we are only interested in Smart Card Readers, we can use the static GetDeviceSelector method on the SmartCardReader class to get the AQS string for smart card readers.
To get a list of all smart card readers on our system, we can do the following:
string selector = SmartCardReader.GetDeviceSelector(); DeviceInformationCollection smartCardReaders = await DeviceInformation.FindAllAsync(selector);
This DeviceInformationCollection contains DeviceInformation objects which contain some generic information like Name, Id, …
Connecting to a specific device is as easy as passing the Id of the DeviceInformation class to the SmartCardReader.FromIdAsync method.
SmartCardReader reader = await SmartCardReader.FromIdAsync(device.Id); IReadOnlyList<SmartCard> cards = await reader.FindAllCardsAsync(); var card = cards.FirstOrDefault(); if (card == null) throw new NoCardFoundException(); SmartCardConnection connection = await card.ConnectAsync();
As you can see in the code above, as soon we have access to the reader, we can query the cards on that device. Once we have a card, we can create a SmartCardConnection and start communicating with it.
Before calling SmartCardReader.FromIdAsync(device.Id); you need to make sure that the Shared User Certificates capability is set in your appxmanifest! Else you will get a System.UnauthorizedAccessException!
Sending commands
Sending the commands is litteraly calling just one method! The TransmitAsync method on the SmartCardConnection class is there to do this! The ‘tricky’ part with this is that this method takes an IBuffer and returns an IBuffer. So we can basically send anything over this connection. The question is: WHAT commands to send to get the data we want?
APDU commands
Application Protocol Data Unit (APDU) commands are the things we need to send to the card via the TransmitAsync method.
After digging into the reference manual of the (Belgian) eID card, I was able to determine what APDU commands to send. It turns out the information is stored in ‘files’ on the card. In order to read a particular file, we first need to send a command to ‘select’ the file, followed by a command to ‘read’ its contents.
Select file command
The select file command is a byte array that is structured like this:
This would mean that if we would like to select the ‘Address’ file, our select command would look like this:
protected readonly byte[] SELECT_ADDRESS_APDU_COMMAND = new byte[] { 0x00, //CLA 0xA4, //INS 0x08, //P1 => absolute path 0x0C, //P2 0x06, //Length of the path //ADDRESS FILE PATH 0x3F,// directory MF "3f00" 0x00, 0xDF,// subdirectory identity DF(ID) "DF01" 0x01, 0x40,// address file EF(ID#Address) "4033" 0x33 };
The paths of the files can be deducted from the Belgian eID content document.
Similarly, for getting the ‘ID’ file, the select command would look like this:
protected readonly byte[] SELECT_ID_APDU_COMMAND = new byte[] { 0x00, //CLA 0xA4, //INS 0x08, //P1 => absolute path 0x0C, //P2 0x06, //Length of the path //ID FILE PATH 0x3F,//directory MF "3f00" 0x00, 0xDF,//subdirectory identity DF(ID) "DF01" 0x01, 0x40,//identity file EF(ID#RN) "4031" 0x31 };
As said before, sending a APDU command to the card can be done by calling the TransmitAsync method on the SmartCardConnection class:
var result = await conn.TransmitAsync(SELECT_ADDRESS_APDU_COMMAND.AsBuffer()); CryptographicBuffer.CopyToByteArray(result, out byte[] response);
According to the documentation, if the status bytes of the response are set to ’90 00′, this indicates ‘Normal ending of the command’.
That means we can easily check if we succesfuly selected the file we asked like this:
if (response?.Length == 2 && (ushort)((response[0] << 8) + response[1]) == 0x9000) { //We're good! } else { //We didn't get a OK status when selecting the file //We are unable to read the card throw new ReadCardException(); }
Read file command
The above commands show how to ‘select’ a particuale, at the time we are not accessing the file yet. To read the contents of the selected file, a subsequent read command needs to be set. The read command itself is pretty simple, but there is one catch…
The tricky part here is the ‘Le’ parameter. You don’t always know the lenght of the data you want to read. Address data for example has a fixed length, but the ID file hasn’t. But when looking closely into the documentation, there’s this status we can get back from the read command:
This means that if we specify a lenght (Le) of 0, we should get back a status where the first byte is 6C and the second byte will indicate the length of the data that is available. Thus, after getting this status, we can again do a read command passing in the correct Le parameter.
private byte[] GetReadCommand(int length = 0) { return new byte[] { 0x00, //CLA 0xB0, //Read binary command 0x00, //OFF_H higher byte of the offset (bit 8 = 0) 0x00, (byte)length //le }; } byte[] fileContents = null; result = await conn.TransmitAsync(GetReadCommand().AsBuffer()); CryptographicBuffer.CopyToByteArray(result, out byte[] readResponse); if (readResponse?.Length == 2 && readResponse[0] == 0x6C) { //Getting back 0x6C, Length is in second byte result = await conn.TransmitAsync(GetReadCommand(readResponse[1]).AsBuffer()); CryptographicBuffer.CopyToByteArray(result, out fileContents); } else { //Don't think this should occur... CryptographicBuffer.CopyToByteArray(result, out fileContents); } //Raw contents of file is in fileContents array
Now that we have the raw data of a file (as a byte array), we need to look how to parse this data into something meaningful.
Parsing raw data
In the Belgian eID content document you can find an overview of how the data is structured per file. For example the address file is structured like this:
The ID file is structured similarly with tags and length preceding each field. Now that we know this, we can write a loop that reads the data sequentially and puts it in something more useful for us:
private Dictionary<byte, string> ReadData(byte[] rawData, List<byte> tags) { var data = tags.ToDictionary(k => k, v => ""); int idx = 0; //we start at 0 :-) while (idx < rawData.Length) { byte tag = rawData[idx]; //at this location we have a Tag idx++; var length = rawData[idx]; //the next position holds the lenght of the data idx++; //start of the data if (tags.Contains(tag)) //this is a tag we are interested in { var res = new byte[length]; //create array to put data of this tag in. We know the length Array.Copy(rawData, idx, res, 0, length); //fill var value = Encoding.UTF8.GetString(res); //convert to string data[tag] = value; //put the string value we read in the data dictionary } idx += length; //moving on, skipping the length of data we just read } return data; }
Reading address data for example can then be done like this:
const byte ADDRESS_STREET_NUMBER_TAG = 0x01; const byte ADDRESS_ZIP_CODE_TAG = 0x02; const byte ADDRESS_MUNICIPALITY_TAG = 0x03; var data = ReadData(fileContents, new List<byte>() { ADDRESS_STREET_NUMBER_TAG, ADDRESS_ZIP_CODE_TAG, ADDRESS_MUNICIPALITY_TAG }); var street = data[ADDRESS_STREET_NUMBER_TAG]; var zip = data[ADDRESS_ZIP_CODE_TAG]; var muni = data[ADDRESS_MUNICIPALITY_TAG];
We get the data back in a dictionary where the keys are the tags, which we can define as constants with a meaningful name.
Success! We managed to read the contents of a Belgian eID in UWP!
NOTE: debugging it step by step can be pretty hard as the connection to the card times out rather quickly, resulting in an exception saying : ‘The smart card has been reset, so any shared state information is invalid. (Exception from HRESULT: 0x80100068)’
Links
GitHub
You can find my library on GitHub, the simplified code samples (not using the library) used in this blogpost can be found on a separate branch.
More information about using the (NuGet) library can be found on the Wiki page on GitHub.
NuGet
A library to read the contents of an eID card is available on NuGet.
February 15, 2020 at 1:36 pm
Outstanding!
This is very interesting.
December 9, 2020 at 6:59 am
You are my hero! I searched for a C# WPF solution for Austria’s e-Health System – e-card but didn’t find anything. Then I came to your Blog and it was easy to code. Many thanks for this great article and sharing your source code!
THANK YOU!
December 21, 2020 at 4:34 pm
That’s great to hear! Glad my sample could help you out!
December 28, 2020 at 9:03 pm
Thank you so much!
October 25, 2021 at 5:51 pm
Hi, thanks for the code, I tried for the Italian version, I looked on the documents and retrieved the addresses, but it tells me file not found … 6A 82 …. I know that to use it you need a pin, this is the l why?
November 29, 2021 at 2:39 pm
HI! I don’t know the ins or outs from the Italian eID cards. But if you need a pin, that’s probably why it crashes. On the other hand, some data should be publicly accessible.
May 16, 2023 at 1:15 pm
Hi,
Thanks for this effort, but I still didnt find the APDU command for getting the first name and last name from a Belgian ID.
May 30, 2023 at 10:29 am
Hi Mohamed! The Firstname and Lastname can be found in the ID file. The tags for these properties are 0x08 and 0x07