return early if cannot init adapter
[YkNeoCR.git] / src / org / average / nfcauthcr / YkNeo.java
1 package org.average.ykneocr;
2
3 import java.io.IOException;
4 import java.lang.String;
5 import java.util.ArrayList;
6 import java.util.Arrays;
7 import java.util.Iterator;
8
9 import android.nfc.NfcAdapter;
10 import android.nfc.Tag;
11 import android.nfc.tech.IsoDep;
12
13 import org.average.ykneocr.CRException;
14
15 public class YkNeo {
16
17         // http://forum.yubico.com/viewtopic.php?\
18         //              f=26&t=1053&sid=2a11c0f97306e4b699bf67502b7a754a
19         // CCID APDU, ISO 7816-4.
20
21         // Start with Select APDU:
22         // CLA INS P1 P2 Lc AID Le
23         //  00  A4 04 00                - GlobalPlatform - Application Select
24         // Lc = 07
25         //   A0 00 00 05 27
26         //      Yubico's RID (Registered application provider IDentifier)
27         //   20 01
28         //      PIX (Proprietary application Identifier eXtension)
29         //      for the Yubikey2 applet
30         // Le = 00
31         private static final byte[] selectApdu =
32                 {0x00, (byte) 0xA4, 0x04, 0x00, 0x07, (byte) 0xA0,
33                  0x00, 0x00, 0x05, 0x27, 0x20, 0x01, 0x00};
34
35         // Application APDUs:
36         // CLA INS P1 P2
37         //  00  01 YK 00
38         //   YK is the one-byte command&slot code as in the USB interface
39         //   Lc + data bytes follow
40         //   Le is optional
41         private static final byte SLOT_CHAL_HMAC1 = 0x30;
42         private static final byte SLOT_CHAL_HMAC2 = 0x38;
43
44         public static ArrayList<String> doChalResp(IsoDep isoTag, int slot,
45                                         ArrayList<String> cset)
46                         throws IOException, CRException {
47                 if (slot != 1 && slot != 2) {
48                         throw new CRException(String.format(
49                         "NFC Yubikey slot is %d, can be 1 or 2",
50                         slot));
51                 }
52                 byte[] resp = isoTag.transceive(selectApdu);
53                 int length = resp.length;
54                 if (resp[length - 2] != (byte)0x90 ||
55                     resp[length - 1] != 0x00) {
56                         throw new CRException(String.format(
57                         "NFC select error code: %02x:%02x",
58                         resp[length - 2], resp[length - 1]));
59                 }
60                 ArrayList<String> rset = new ArrayList<String>();
61
62                 Iterator itr = cset.iterator();
63                 while (itr.hasNext()) {
64                         byte[] challenge = unhex((String)itr.next());
65                         if (challenge.length > 127) {
66                                 throw new CRException(String.format(
67                                 "NFC challenge size too big: %d",
68                                 challenge.length));
69                         }
70                         byte[] crApdu = new byte[6+challenge.length];
71                         crApdu[0] = 0x00; // CLA
72                         crApdu[1] = 0x01; // INS
73                         switch (slot) {
74                         case 1: crApdu[2] = SLOT_CHAL_HMAC1; break; // P1
75                         case 2: crApdu[2] = SLOT_CHAL_HMAC2; break; // P1
76                         }
77                         crApdu[3] = 0x00; // P2
78                         crApdu[4] = (byte)challenge.length; // Lc
79                         System.arraycopy(challenge, 0, crApdu, 5,
80                                                 challenge.length); // Payload
81                         crApdu[5+challenge.length] = 22; // Le
82                         resp = isoTag.transceive(crApdu);
83                         length = resp.length;
84                         if (resp[length - 2] != (byte)0x90 ||
85                             resp[length - 1] != 0x00) {
86                                 throw new CRException(String.format(
87                                 "NFC CR error code: %02x:%02x",
88                                 resp[length - 2], resp[length - 1]));
89                         }
90                         if (length <= 2) {
91                                 throw new CRException(String.format(
92                                 "NFC wrong response size: only %d bytes",
93                                 length-2));
94                         }
95                         rset.add(hex(Arrays.copyOf(resp, length-2)));
96                 }
97
98                 return rset;
99         }
100
101         private static String hex(byte[] a) {
102                 StringBuilder sb = new StringBuilder();
103                 if (a == null) return "<null>";
104                 for (byte b: a) sb.append(String.format("%02x", b&0xff));
105                 return sb.toString();
106         }
107
108         private static byte[] unhex(String s) {
109                 int len = s.length();
110                 if ((len % 2) != 0) return null;
111                 byte[] b = new byte[len / 2];
112                 for (int i = 0; i < len; i += 2) {
113                         b[i / 2] = (byte)Integer.parseInt(
114                                                 s.substring(i,i+2), 16);
115                 }
116                 return b;
117         }
118 }