Search code examples
androidauthenticationexceptionnfcmifare

NfcA.transceive(byte[] data) throws TagLostException


It all started here. The problem over there was solved, now I'm facing problems with using the transceive method.

My code looks like this:

private void writeAndProtectTag(final Intent intent, final String message) {
    // Run the entire process in its own thread as NfcA.transceive(byte[] data);
    // Should not be run in main thread according to <https://developer.android.com/reference/android/nfc/tech/NfcA.html#transceive(byte[])>
    (new Thread(new Runnable() {
        // Password has to be 4 characters
        // Password Acknowledge has to be 2 characters
        byte[] pwd      = "-_bA".getBytes();
        byte[] pack     = "cC".getBytes();

        @Override
        public void run() {
            // Store tag object for use in NfcA and Ndef
            Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
            // Using NfcA instead of MifareUltralight should make no difference in this method
            NfcA nfca = null;

            // Whole process is put into a big try-catch trying to catch the transceive's IOException
            try {
                nfca = NfcA.get(tag);

                nfca.connect();

                byte[] response;

                // Authenticate with the tag first
                // In case it's already been locked
                try {
                    response = nfca.transceive(new byte[]{
                            (byte) 0x1B, // PWD_AUTH
                            pwd[0], pwd[1], pwd[2], pwd[3]
                    });

                    // Check if PACK is matching expected PACK
                    // This is a (not that) secure method to check if tag is genuine
                    if ((response != null) && (response.length >= 2)) {
                        byte[] packResponse = Arrays.copyOf(response, 2);
                        if (!(pack[0] == packResponse[0] && pack[1] == packResponse[1])) {
                            Toast.makeText(ctx, "Tag could not be authenticated:\n" + packResponse.toString() + "≠" + pack.toString(), Toast.LENGTH_LONG).show();
                        }
                    }
                }catch(TagLostException e){
                    e.printStackTrace();
                }

                // Get Page 2Ah
                response = nfca.transceive(new byte[] {
                        (byte) 0x30, // READ
                        (byte) 0x2A  // page address
                });
                // configure tag as write-protected with unlimited authentication tries
                if ((response != null) && (response.length >= 16)) {    // read always returns 4 pages
                    boolean prot = false;                               // false = PWD_AUTH for write only, true = PWD_AUTH for read and write
                    int authlim = 0;                                    // 0 = unlimited tries
                    nfca.transceive(new byte[] {
                            (byte) 0xA2, // WRITE
                            (byte) 0x2A, // page address
                            (byte) ((response[0] & 0x078) | (prot ? 0x080 : 0x000) | (authlim & 0x007)),    // set ACCESS byte according to our settings
                            0, 0, 0                                                                         // fill rest as zeros as stated in datasheet (RFUI must be set as 0b)
                    });
                }
                // Get page 29h
                response = nfca.transceive(new byte[] {
                        (byte) 0x30, // READ
                        (byte) 0x29  // page address
                });
                // Configure tag to protect entire storage (page 0 and above)
                if ((response != null) && (response.length >= 16)) {  // read always returns 4 pages
                    int auth0 = 0;                                    // first page to be protected
                    nfca.transceive(new byte[] {
                            (byte) 0xA2, // WRITE
                            (byte) 0x29, // page address
                            response[0], 0, response[2],              // Keep old mirror values and write 0 in RFUI byte as stated in datasheet
                            (byte) (auth0 & 0x0ff)
                    });
                }

                // Send PACK and PWD
                // set PACK:
                nfca.transceive(new byte[] {
                        (byte)0xA2,
                        (byte)0x2C,
                        pack[0], pack[1], 0, 0  // Write PACK into first 2 Bytes and 0 in RFUI bytes
                });
                // set PWD:
                nfca.transceive(new byte[] {
                        (byte)0xA2,
                        (byte)0x2B,
                        pwd[0], pwd[1], pwd[2], pwd[3] // Write all 4 PWD bytes into Page 43
                });

                // Generate NdefMessage to be written onto the tag
                NdefMessage msg = null;
                try {
                    NdefRecord r1 = NdefRecord.createMime("text/plain", message.getBytes("UTF-8"));
                    NdefRecord r2 = NdefRecord.createApplicationRecord("com.example.alex.nfcapppcekunde");
                    msg = new NdefMessage(r1, r2);
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                }

                byte[] ndefMessage = msg.toByteArray();

                nfca.transceive(new byte[] {
                        (byte)0xA2, // WRITE
                        (byte)3,    // block address
                        (byte)0xE1, (byte)0x10, (byte)0x12, (byte)0x00
                });

                // wrap into TLV structure
                byte[] tlvEncodedData = null;

                tlvEncodedData = new byte[ndefMessage.length + 3];
                tlvEncodedData[0] = (byte)0x03;  // NDEF TLV tag
                tlvEncodedData[1] = (byte)(ndefMessage.length & 0x0FF);  // NDEF TLV length (1 byte)
                System.arraycopy(ndefMessage, 0, tlvEncodedData, 2, ndefMessage.length);
                tlvEncodedData[2 + ndefMessage.length] = (byte)0xFE;  // Terminator TLV tag

                // fill up with zeros to block boundary:
                tlvEncodedData = Arrays.copyOf(tlvEncodedData, (tlvEncodedData.length / 4 + 1) * 4);
                for (int i = 0; i < tlvEncodedData.length; i += 4) {
                    byte[] command = new byte[] {
                            (byte)0xA2, // WRITE
                            (byte)((4 + i / 4) & 0x0FF), // block address
                            0, 0, 0, 0
                    };
                    System.arraycopy(tlvEncodedData, i, command, 2, 4);
                    try {
                        response = nfca.transceive(command);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        //UI related things, not important for NFC
                        btn.setImageResource(R.drawable.arrow_red);
                        tv.setText("");
                    }
                });
                curAction = "handle";

                try {
                    nfca.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            } catch (IOException e) {
                //Trying to catch any ioexception that may be thrown
                e.printStackTrace();
            } catch (Exception e) {
                //Trying to catch any exception that may be thrown
                e.printStackTrace();
            }

        }
    })).start();
}

In my old code version that used Ndef class to write onto the tag I didn't have this problem. Now that I changed a few things I am getting two TagLostExceptions:

  • One from the line PWD_AUTH command that fails but its exception is caught, which is actually as intended as the tag I am scanning does not have a password set yet.
  • The other from the transceive that follows it for reading the configuration.

After the second TagLostException the method ends of course. When reading the configuration I should not get a TagLostException. Does anyone know what is going on there?

Here's the exceptions:

    android.nfc.TagLostException: Tag was lost.
                             at android.nfc.TransceiveResult.getResponseOrThrow(TransceiveResult.java:48)
                             at android.nfc.tech.BasicTagTechnology.transceive(BasicTagTechnology.java:151)
                             at android.nfc.tech.NfcA.transceive(NfcA.java:129)
                             at com.example.alex.nfcapppce.MainActivity$2.run(MainActivity.java:134)
                             at java.lang.Thread.run(Thread.java:761)
    android.nfc.TagLostException: Tag was lost.
                             at android.nfc.TransceiveResult.getResponseOrThrow(TransceiveResult.java:48)
                             at android.nfc.tech.BasicTagTechnology.transceive(BasicTagTechnology.java:151)
                             at android.nfc.tech.NfcA.transceive(NfcA.java:129)
                             at com.example.alex.nfcapppce.MainActivity$2.run(MainActivity.java:155)
                             at java.lang.Thread.run(Thread.java:761)

Solution

  • SOLVED:

    When trying to authenticate with a tag that does not have a protection yet the communication just crashes instead of throwing a proper Exception to catch, because I am not catching an exception the application continues trying to transceive data, but the tag "was lost" as the now called Exception is telling me.

    To know if the tag is already protected, check Auth0 for set protection:

    byte[] response;
    
    //Read page 41 on NTAG213, will be different for other tags
    response = mifare.transceive(new byte[] {
            (byte) 0x30, // READ
            41           // page address
    });
    // Authenticate with the tag first
    // only if the Auth0 byte is not 0xFF,
    // which is the default value meaning unprotected
    if(response[3] != (byte)0xFF) {
        try {
            response = mifare.transceive(new byte[]{
                    (byte) 0x1B, // PWD_AUTH
                    pwd[0], pwd[1], pwd[2], pwd[3]
            });
            // Check if PACK is matching expected PACK
            // This is a (not that) secure method to check if tag is genuine
            if ((response != null) && (response.length >= 2)) {
                final byte[] packResponse = Arrays.copyOf(response, 2);
                if (!(pack[0] == packResponse[0] && pack[1] == packResponse[1])) {runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(ctx, "Tag could not be authenticated:\n" + packResponse.toString() + "≠" + pack.toString(), Toast.LENGTH_LONG).show();
                    }
                });
                }else{
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Toast.makeText(ctx, "Tag successfully authenticated!", Toast.LENGTH_SHORT).show();
                        }
                    });
                }
            }
        } catch (TagLostException e) {
            e.printStackTrace();
        }
    }else{
        // Protect tag with your password in case
        // it's not protected yet
    
        // Get Page 2Ah
        response = mifare.transceive(new byte[] {
                (byte) 0x30, // READ
                (byte) 0x2A  // page address
        });
        // configure tag as write-protected with unlimited authentication tries
        if ((response != null) && (response.length >= 16)) {    // read always returns 4 pages
            boolean prot = false;                               // false = PWD_AUTH for write only, true = PWD_AUTH for read and write
            int authlim = 0;                                    // 0 = unlimited tries
            mifare.transceive(new byte[] {
                    (byte) 0xA2, // WRITE
                    (byte) 0x2A, // page address
                    (byte) ((response[0] & 0x078) | (prot ? 0x080 : 0x000) | (authlim & 0x007)),    // set ACCESS byte according to our settings
                    0, 0, 0                                                                         // fill rest as zeros as stated in datasheet (RFUI must be set as 0b)
            });
        }
        // Get page 29h
        response = mifare.transceive(new byte[] {
                (byte) 0x30, // READ
                (byte) 0x29  // page address
        });
        // Configure tag to protect entire storage (page 0 and above)
        if ((response != null) && (response.length >= 16)) {  // read always returns 4 pages
            int auth0 = 0;                                    // first page to be protected
            mifare.transceive(new byte[] {
                    (byte) 0xA2, // WRITE
                    (byte) 0x29, // page address
                    response[0], 0, response[2],              // Keep old mirror values and write 0 in RFUI byte as stated in datasheet
                    (byte) (auth0 & 0x0ff)
            });
        }
    
        // Send PACK and PWD
        // set PACK:
        mifare.transceive(new byte[] {
                (byte)0xA2,
                (byte)0x2C,
                pack[0], pack[1], 0, 0  // Write PACK into first 2 Bytes and 0 in RFUI bytes
        });
        // set PWD:
        mifare.transceive(new byte[] {
                (byte)0xA2,
                (byte)0x2B,
                pwd[0], pwd[1], pwd[2], pwd[3] // Write all 4 PWD bytes into Page 43
        });
    }