handle multivalue challenges
[YkNeoCR.git] / src / org / average / nfcauthcr / YkNeo.java
index 1537459ba7e7c6ef4513370a844fc8b234194e07..96f3e0fbb53b776a02444b17f07c8608ca80935f 100644 (file)
@@ -1,41 +1,73 @@
-package org.average.nfcauthcr;
+package org.average.ykneocr;
 
 import java.io.IOException;
+import java.lang.String;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Iterator;
 
 import android.nfc.NfcAdapter;
 import android.nfc.Tag;
-import android.nfc.TagLostException;
 import android.nfc.tech.IsoDep;
 
-import org.average.nfcauthcr.CRException;
+import org.average.ykneocr.CRException;
 
 public class YkNeo {
 
-       // This is a CCID APDU, ISO 7816-4.
-       // 00 A4 04 00 xx AID - GlobalPlatform - SELECT
-       // Lc, send data = 07: A0 00 00 05 27 20 01
-       // Le, recv data = 00
+       // http://forum.yubico.com/viewtopic.php?\
+       //              f=26&t=1053&sid=2a11c0f97306e4b699bf67502b7a754a
+       // CCID APDU, ISO 7816-4.
+
+       // Start with Select APDU:
+       // CLA INS P1 P2 Lc AID Le
+       //  00  A4 04 00                - GlobalPlatform - Application Select
+       // Lc = 07
+       //   A0 00 00 05 27
+       //      Yubico's RID (Registered application provider IDentifier)
+       //   20 01
+       //      PIX (Proprietary application Identifier eXtension)
+       //      for the Yubikey2 applet
+       // Le = 00
        private static final byte[] selectApdu =
                {0x00, (byte) 0xA4, 0x04, 0x00, 0x07, (byte) 0xA0,
                 0x00, 0x00, 0x05, 0x27, 0x20, 0x01, 0x00};
 
+       // Application APDUs:
+       // CLA INS P1 P2
+       //  00  01 YK 00
+       //   YK is the one-byte command&slot code as in the USB interface
+       //   Lc + data bytes follow
+       //   Le is optional
        private static final byte SLOT_CHAL_HMAC1 = 0x30;
        private static final byte SLOT_CHAL_HMAC2 = 0x38;
 
-       public static final byte[] doChallengeYubiKey(IsoDep isoTag, int slot,
-                                               byte[] challenge)
-                               throws CRException {
-               try {
-                       isoTag.connect();
-                       byte[] resp = isoTag.transceive(selectApdu);
-                       int length = resp.length;
-                       if (resp[length - 2] != (byte)0x90 ||
-                           resp[length - 1] != 0x00) {
-                               throw new CRException(
-                               "NFC Error code in the response to select");
+       public static ArrayList<String> doChalResp(IsoDep isoTag, int slot,
+                                       ArrayList<String> cset)
+                       throws IOException, CRException {
+               if (slot != 1 && slot != 2) {
+                       throw new CRException(String.format(
+                       "NFC Yubikey slot is %d, can be 1 or 2",
+                       slot));
+               }
+               byte[] resp = isoTag.transceive(selectApdu);
+               int length = resp.length;
+               if (resp[length - 2] != (byte)0x90 ||
+                   resp[length - 1] != 0x00) {
+                       throw new CRException(String.format(
+                       "NFC select error code: %02x:%02x",
+                       resp[length - 2], resp[length - 1]));
+               }
+               ArrayList<String> rset = new ArrayList<String>();
+
+               Iterator itr = cset.iterator();
+               while (itr.hasNext()) {
+                       byte[] challenge = unhex((String)itr.next());
+                       if (challenge.length > 127) {
+                               throw new CRException(String.format(
+                               "NFC challenge size too big: %d",
+                               challenge.length));
                        }
-                       byte[] crApdu = new byte[69];
+                       byte[] crApdu = new byte[6+challenge.length];
                        crApdu[0] = 0x00; // CLA
                        crApdu[1] = 0x01; // INS
                        switch (slot) {
@@ -43,22 +75,44 @@ public class YkNeo {
                        case 2: crApdu[2] = SLOT_CHAL_HMAC2; break; // P1
                        }
                        crApdu[3] = 0x00; // P2
-                       crApdu[4] = 63;   // Lc
+                       crApdu[4] = (byte)challenge.length; // Lc
                        System.arraycopy(challenge, 0, crApdu, 5,
                                                challenge.length); // Payload
-                       crApdu[crApdu.length-1] = 22; // Le
+                       crApdu[5+challenge.length] = 22; // Le
                        resp = isoTag.transceive(crApdu);
                        length = resp.length;
                        if (resp[length - 2] != (byte)0x90 ||
                            resp[length - 1] != 0x00) {
-                               throw new CRException(
-                               "NFC Error code in the response to CR");
+                               throw new CRException(String.format(
+                               "NFC CR error code: %02x:%02x",
+                               resp[length - 2], resp[length - 1]));
+                       }
+                       if (length <= 2) {
+                               throw new CRException(String.format(
+                               "NFC wrong response size: only %d bytes",
+                               length-2));
                        }
-                       return Arrays.copyOf(resp, length-2);
-               } catch (TagLostException e) {
-                       throw new CRException("NFC connection lost", e);
-               } catch (IOException e) {
-                       throw new CRException("NFC I/O error", e);
+                       rset.add(hex(Arrays.copyOf(resp, length-2)));
+               }
+
+               return rset;
+       }
+
+       private static String hex(byte[] a) {
+               StringBuilder sb = new StringBuilder();
+               if (a == null) return "<null>";
+               for (byte b: a) sb.append(String.format("%02x", b&0xff));
+               return sb.toString();
+       }
+
+       private static byte[] unhex(String s) {
+               int len = s.length();
+               if ((len % 2) != 0) return null;
+               byte[] b = new byte[len / 2];
+               for (int i = 0; i < len; i += 2) {
+                       b[i / 2] = (byte)Integer.parseInt(
+                                               s.substring(i,i+2), 16);
                }
+               return b;
        }
 }