After setting up the PACE authentication process (PACE - GENERIC MAPPING (INFORMATIVE)) in accordance with ICAO Doc 9303 Part 11, I am able to exchange tokens between the chip and the terminal (see previous question : CMac calculation in C# using BouncyCastle). I am relying on the log of the PlatinumReader smart card reader, trying to read data from a data group protected in read mode (access condition 10FFFF).
Once I receive a token from the chip, what should I do with it?
Here is a log of PlatinumReader after establishing PACE authentication : the terminal and the chip exchange tokens
Key: T_PCD - PCD's calculated authentication token (T_PCD [PACE])
**3e 93 48 fc ca 2c 5d dc**
Key: T_PICC - PICC's calculated authentication token (T_PICC [PACE])
8b 9e 28 f9 81 be 50 86
APDU: GeneralAuthenticate - Start
APDU: Request
00 86 00 00 0c 7c 0a 85 08 3e 93 48 fc ca 2c 5d dc 00
APDU: Response
7c 0a 86 08 8b 9e 28 f9 81 be 50 86 90 00
APDU: GeneralAuthenticate - End
Protocol: PACE - End
PACE processing time 00:00:00.296
And after that, the terminal is able to read DG4 file protected (access conditions 10FFFF)
Reading file DG4 in application ePassport
ReadData: ePass.DG4 - Start
APDU: Select File - Start
APDU: Request
00 a4 02 0c 02 01 04
APDU: SMRequest - 000000000000000000000000000001F1
0c a4 02 0c 1d 87 11 01 dd f3 54 5a bd f6 5a 3c
4b 8a ec c6 50 c2 48 2b 8e 08 ea d4 db 01 00 1a
ee 9a 00 ...
APDU: SMResponse - 000000000000000000000000000001F2
99 02 90 00 8e 08 12 25 06 c5 82 f8 37 23 90 00
APDU: Response
90 00
Here is my PACE authentication function, I am able to exchange the token with the chip:
public void paceGenericMapping()
{
string selectFile1 = executeCommande("00a4020c020104");
string readData2 = executeCommande("00b0000005");
// Paramètres du domaine pour la courbe ECC-NIST 256
X9ECParameters curve = SecNamedCurves.GetByName("secp256r1");
// Utilisation des paramètres déjà connus
Org.BouncyCastle.Math.BigInteger prime = new Org.BouncyCastle.Math.BigInteger("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF", 16);
Org.BouncyCastle.Math.BigInteger a = new Org.BouncyCastle.Math.BigInteger("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC", 16);
Org.BouncyCastle.Math.BigInteger order = new Org.BouncyCastle.Math.BigInteger("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551", 16);
Org.BouncyCastle.Math.BigInteger b = new Org.BouncyCastle.Math.BigInteger("5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B", 16);
Org.BouncyCastle.Math.BigInteger G_X = new Org.BouncyCastle.Math.BigInteger("6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296", 16);
Org.BouncyCastle.Math.BigInteger G_Y = new Org.BouncyCastle.Math.BigInteger("4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5", 16);
Org.BouncyCastle.Math.EC.ECPoint basePoint = curve.Curve.CreatePoint(G_X, G_Y);
ECDomainParameters domainParams = new ECDomainParameters(curve.Curve, basePoint, order, curve.H, curve.GetSeed());
// • Read EF.DIR and EF.CardAccess (applet capabilities)
string selectEF_F000 = executeCommande("00A40000023F00");
string selectEF_CardAccess = executeCommande("00a4020c02011c");
string read_binary_1 = executeCommande("00b0000005");
string read_binary_2 = executeCommande("00b000055b");
// • Select algorithm (INS 22)
string setMSE = executeCommande("0022c1a412800a04007f0007020204020283010384010c");
// • The host requests PACE for authentication, possibly giving a preference :
string paceAuthenticationREQUEST = executeCommande("10860000027c0000");
// • The card responds with a 128-bit (16 byte) random number (nonce) encrypted with PACE_nonce_AES128key : 7C 12 80 10 <encrypted_nonce>
int startIndex = paceAuthenticationREQUEST.IndexOf("7C128010") + "7C128010".Length;
// Trouver l'index de fin de la sous-chaîne
int endIndex = paceAuthenticationREQUEST.IndexOf("9000", startIndex);
// Extraire la sous-chaîne : representant Key: z - encrypted nonce (z [PACE])
string encryptedNonce = paceAuthenticationREQUEST.Substring(startIndex, endIndex - startIndex);
// the host decrypts the 128-bit nonce with PACE_nonce_AES128key derived from user input as described above
byte[] bytesEncryptedNonce = StringToByteArray(encryptedNonce);
// Key: K - derived key from shared secret : trouver la clé dérivée du PIN
string derivedKeySH = calculerSHA1("3132333400000003");
byte[] bytesDerivedKeySH = StringToByteArray(derivedKeySH);
// • [decrypt nonce nonce]
byte[] bytesDecryptedNonce = decryptAES(bytesEncryptedNonce, bytesDerivedKeySH);
string decryptedNonce = byteToHexStr(bytesDecryptedNonce);
Org.BouncyCastle.Math.BigInteger nonce = new Org.BouncyCastle.Math.BigInteger(decryptedNonce, 16);
// • [generate random number as private key SKPCD for DHKA]
SecureRandom random = new SecureRandom();
Org.BouncyCastle.Math.BigInteger SKPCD = new Org.BouncyCastle.Math.BigInteger(domainParams.Curve.Order.BitLength, random).Mod(domainParams.Curve.Order);
// • [calculate ephermeral PKPCD = BasePoint G * SKPCD]
Org.BouncyCastle.Math.EC.ECPoint PKPCD = domainParams.G.Multiply(SKPCD).Normalize();
// Convertir les coordonnées X et Y en hexadécimal
string PKPCDXHex = PKPCD.XCoord.ToBigInteger().ToString(16);
string PKPCDYHex = PKPCD.YCoord.ToBigInteger().ToString(16);
// • Send PKPCD and Receive PKPICC from card
string requestPKicc = "10860000457C43814104" + PKPCDXHex + PKPCDYHex + "00";
string reponsePKicc = executeCommande(requestPKicc);
// Définir les positions de début et de fin de la sous-chaîne
int entete = 10; // Après les 4 premiers octets
int sw = reponsePKicc.Length - 4; // Avant les 4 derniers octets
// Extraire la sous-chaîne
reponsePKicc = reponsePKicc.Substring(entete, sw - entete);
string PKICCXHex = reponsePKicc.Substring(0, reponsePKicc.Length / 2);
string PKICCYHex = reponsePKicc.Substring(reponsePKicc.Length / 2);
Org.BouncyCastle.Math.EC.ECPoint PKPICC = curve.Curve.CreatePoint(new Org.BouncyCastle.Math.BigInteger(PKICCXHex, 16), new Org.BouncyCastle.Math.BigInteger(PKICCYHex, 16));
// • [build shared secret: H = PKPICC * SKPCD = G * SKPICC * SKPCD]
Org.BouncyCastle.Math.EC.ECPoint H = PKPICC.Multiply(SKPCD).Normalize();
// • [build new base point:] : Gmap = G * s + H = G * s + G * SKPICC * SKPCD = G * (s + SKPICC * SKPCD)
Org.BouncyCastle.Math.EC.ECPoint Gmap = basePoint.Multiply(nonce); Gmap = Gmap.Add(H).Normalize();
// • [generate random number as private key SKPCD,map for DHKA]
Org.BouncyCastle.Math.BigInteger SKPCDMap = new Org.BouncyCastle.Math.BigInteger(Gmap.Curve.Order.BitLength, random).Mod(Gmap.Curve.Order);
// • [calculate ephermeral PKPCD,map = BasePoint Gmap * SKPCD,map]
Org.BouncyCastle.Math.EC.ECPoint PKPCDMap = Gmap.Multiply(SKPCDMap).Normalize();
// Convertir les coordonnées X et Y en hexadécimal
string PKPCDMapXHex = PKPCDMap.XCoord.ToBigInteger().ToString(16);
string PKPCDMapYHex = PKPCDMap.YCoord.ToBigInteger().ToString(16);
// • Send PKPCD,map and Receive PKPICC,map from card
string requestPKpcdmap = "10860000457c43834104" + PKPCDMapXHex + PKPCDMapYHex + "00";
string responsePKpcdmap = executeCommande(requestPKpcdmap);
sw = responsePKpcdmap.Length - 4;
// Extraire la sous-chaîne
if (responsePKpcdmap.Length > 4)
{
responsePKpcdmap = responsePKpcdmap.Substring(entete, sw - entete);
}
string PKICCMapXHex = responsePKpcdmap.Substring(0, responsePKpcdmap.Length / 2);
string PKICCMapYHex = responsePKpcdmap.Substring(responsePKpcdmap.Length / 2);
Org.BouncyCastle.Math.EC.ECPoint PKPICCMap = curve.Curve.CreatePoint(new Org.BouncyCastle.Math.BigInteger(PKICCMapXHex, 16), new Org.BouncyCastle.Math.BigInteger(PKICCMapYHex, 16));
// • [build Hmap = PKPICC,map * SKPCD,map = Gmap * SKPICC,map * SKPCD,map]
Org.BouncyCastle.Math.EC.ECPoint Hmap = PKPICCMap.Multiply(SKPCDMap).Normalize();
byte[] HmapBytes = StringToByteArray(Hmap.XCoord.ToBigInteger().ToString(16));
// • Derive session keys Kenc and Kmac from Hmap
string Kenc = calculerSHA1(Hmap.XCoord.ToBigInteger().ToString(16) + "00000001");
string Kmac = calculerSHA1(Hmap.XCoord.ToBigInteger().ToString(16) + "00000002");
// • Calculate token: TPICC = MAC(Kmac , PKPCD,map), TPCD = MAC(Kmac, PKPICC,map)
var prefix_hex = "7f494f060a04007f000702020402028641";
var pk_picc_map = "04" + PKICCMapXHex + PKICCMapYHex; // change
var k_mac = StringToByteArray(Kmac);
var cmac_t_pcd = StringToByteArray(prefix_hex + pk_picc_map);
var macBlock = new CMac(new AesEngine());
macBlock.Init(new KeyParameter(k_mac));
macBlock.BlockUpdate(cmac_t_pcd, 0, cmac_t_pcd.Length);
var t_pcd = new byte[16];
macBlock.DoFinal(t_pcd, 0);
addLogMsg("Kenc: " + pk_picc_map);
var segment = new ArraySegment<byte>(t_pcd, 0, 8);
string auth_token_host = BitConverter.ToString(segment.ToArray()).Replace("-", "").ToUpper();
addLogMsg(" token host: " + auth_token_host);
string tpicc = executeCommande("008600000c7c0a8508" + auth_token_host);
sw = tpicc.Length - 4;
string auth_token_tpicc = tpicc.Substring(8, sw - 8);
addLogMsg(" token picc : " + auth_token_tpicc);
// trying to read DG4 after PACE auth :
string readData1 = executeCommande("00a4040c07a0000002471001");
string selectFile2 = executeCommande("00a4020c020104");
string readData3 = executeCommande("00b0000005");
}
And here the log of my function :
Send=>00a4020c020104
Get<=6982
Send=>00b0000005
Get<=6986
Send=>00A40000023F00
Get<=9000
Send=>00a4020c02011c
Get<=9000
Send=>00b0000005
Get<=315E3012069000
Send=>00b000055b
Get<=0A04007F0007020204020202010202010C3023060904007F000702020C033003020108301103030435E08301018401048501008601103023060904007F000702020C043003020109301103030431008301018401048501008601109000
Send=>0022c1a412800a04007f0007020204020283010384010c
Get<=9000
Send=>10860000027c0000
Get<=7C1280101DCB30E6EAAAA25AA65051424A1798469000
Send=>10860000457C438141047a47647acf6dc99d19a4964edd003172a3743feff697a20aaec6812d6f71deb32151b28d0a4b727477c73b948ccf31d82814e48c3138030436cdb608246489a500
Get<=7C43824104A7D1531EDF9A74D66F59FCFC6DDD2B1F7762AC725EFCC765353A0A243239BFE139E49C88C78F8E8B02E6BCF00F55B36FD2794C3E43FCB9E5642D30D88C2308DC9000
Send=>10860000457c43834104473cd341ca28fa8d7b1ddd3b102ddb8f50b68e94c8481636560edf6df8accd5ef8a4b12457e2e13baca887a3a4a16157425d00d4706b9c545817f929262e6c3200
Get<=7C43844104D23396849FB9C28880C05B566A5018EB67AB085E6034738682D311DF5865DA8159CAB7EC25A1FA8084DCC6C9AFAF356367DE42C8C543B377ABE39F2A5C00F9BF9000
Kenc: 04D23396849FB9C28880C05B566A5018EB67AB085E6034738682D311DF5865DA8159CAB7EC25A1FA8084DCC6C9AFAF356367DE42C8C543B377ABE39F2A5C00F9BF
token host: 3CEB1726FBBD656B
Send=>008600000c7c0a85083CEB1726FBBD656B
Get<=7C0A860810ECEA6E039E39409000
token picc : 10ECEA6E039E3940
Send=>00a4040c07a0000002471001
Get<=9000
Send=>00a4020c020104
Get<=6982
Send=>00b0000005
Get<=6986
6982 mean that : SW_SECURITY_STATUS_NOT_SATISFIED Access condition not satisfied And since I cannot select DG4, when I execute the read command from the file I am in (DF), it is normal that I receive error 6986 SW_COMMAND_NOT_ALLOWED.
Does anyone know what I need to do after exchanging tokens between the terminal and the chip to be able to read the data group protected in read mode by PACE?
See : https://www.icao.int/publications/Documents/9303_p11_cons_en.pdf(ref see page 91 : D.4 SECURE MESSAGING)
First of all, congrats of implementing PACE, that's not an easy task for most devs. DG4 (iris) is usually not present. At the time the company that had IP rights to the methods required for iris authentication didn't open them up sufficiently (in time). If it is present it is protected using EAC (authentication of the reader using card verifiable certificates).
PACE only establishes reader authentication in the sense that it makes sure that the reader has access to the document or at least the CAN or MRZ. Access to protected biometric data requires the reader to have a DS certificate trusted / signed by the CVCA certificate / private key that is controlled by the country of issuance. That DS certificate & private key can then be used to create IS certificates & private key combinations to get access to DG3 and DG4.