1 package org.average.nfcauthcr;
3 import java.io.IOException;
4 import java.lang.String;
5 import java.util.ArrayList;
6 import java.util.Arrays;
8 import android.nfc.NfcAdapter;
9 import android.nfc.Tag;
10 import android.nfc.tech.IsoDep;
12 import org.average.nfcauthcr.CRException;
16 // http://forum.yubico.com/viewtopic.php?\
17 // f=26&t=1053&sid=2a11c0f97306e4b699bf67502b7a754a
18 // CCID APDU, ISO 7816-4.
20 // Start with Select APDU:
21 // CLA INS P1 P2 Lc AID Le
22 // 00 A4 04 00 - GlobalPlatform - Application Select
25 // Yubico's RID (Registered application provider IDentifier)
27 // PIX (Proprietary application Identifier eXtension)
28 // for the Yubikey2 applet
30 private static final byte[] selectApdu =
31 {0x00, (byte) 0xA4, 0x04, 0x00, 0x07, (byte) 0xA0,
32 0x00, 0x00, 0x05, 0x27, 0x20, 0x01, 0x00};
37 // YK is the one-byte command&slot code as in the USB interface
38 // Lc + data bytes follow
40 private static final byte SLOT_CHAL_HMAC1 = 0x30;
41 private static final byte SLOT_CHAL_HMAC2 = 0x38;
43 public static ArrayList<String> doChalResp(IsoDep isoTag, int slot,
44 ArrayList<String> cset)
45 throws IOException, CRException {
46 byte[] challenge = unhex(cset.get(0));
47 if (challenge.length > 127) {
48 throw new CRException(String.format(
49 "NFC challenge size too big: %d",
52 if (slot != 1 && slot != 2) {
53 throw new CRException(String.format(
54 "NFC Yubikey slot is %d, can be 1 or 2",
57 byte[] resp = isoTag.transceive(selectApdu);
58 int length = resp.length;
59 if (resp[length - 2] != (byte)0x90 ||
60 resp[length - 1] != 0x00) {
61 throw new CRException(String.format(
62 "NFC select error code: %02x:%02x",
63 resp[length - 2], resp[length - 1]));
65 byte[] crApdu = new byte[6+challenge.length];
66 crApdu[0] = 0x00; // CLA
67 crApdu[1] = 0x01; // INS
69 case 1: crApdu[2] = SLOT_CHAL_HMAC1; break; // P1
70 case 2: crApdu[2] = SLOT_CHAL_HMAC2; break; // P1
72 crApdu[3] = 0x00; // P2
73 crApdu[4] = (byte)challenge.length; // Lc
74 System.arraycopy(challenge, 0, crApdu, 5,
75 challenge.length); // Payload
76 crApdu[5+challenge.length] = 22; // Le
77 resp = isoTag.transceive(crApdu);
79 if (resp[length - 2] != (byte)0x90 ||
80 resp[length - 1] != 0x00) {
81 throw new CRException(String.format(
82 "NFC CR error code: %02x:%02x",
83 resp[length - 2], resp[length - 1]));
86 throw new CRException(String.format(
87 "NFC wrong response size: only %d bytes",
90 ArrayList<String> rset = new ArrayList<String>();
91 rset.add(hex(Arrays.copyOf(resp, length-2)));
95 private static String hex(byte[] a) {
96 StringBuilder sb = new StringBuilder();
97 if (a == null) return "<null>";
98 for (byte b: a) sb.append(String.format("%02x", b&0xff));
102 private static byte[] unhex(String s) {
103 int len = s.length();
104 if ((len % 2) != 0) return null;
105 byte[] b = new byte[len / 2];
106 for (int i = 0; i < len; i += 2) {
107 b[i / 2] = (byte)Integer.parseInt(
108 s.substring(i,i+2), 16);