pokusew / nfc-pcsc

Easy reading and writing NFC tags and cards in Node.js
MIT License
535 stars 132 forks source link

NTAG 21x Authentication #36

Open bierlair opened 6 years ago

bierlair commented 6 years ago

Is there a way to password protect NTAG21X cards / Type TAG_ISO_14443_3 via ACR122U?

I was trying my luck with a previously posted thread about Mifare Ultralight cards, but I end up with 0x6300 errors.

Code:

try {
    // APDU CMD: Update Binary Block
    const packet = new Buffer([
        0xFF, // Class
        0x00,
        0x00,
        0x00,
        0x07, //Length
        0xD4, // inserted
        0x42, // inserted
        0x1B, //PWD_AUTH
        0x11, //my pass
        0x22, //my pass
        0x33, //my pass
        0x44 //my pass
    ]);
    var pwdResponse = await reader.transmit(packet, 7);
    console.log('pwd response', pwdResponse);
} catch (err) {
    console.log('error pwd_auth');
}

This works, but any read or write attempt after, does not work anymore:

0|yogaman  | 2018/02/18 19:03:08: Error when reading data: { ReadError: Read operation failed: Status code: 0x6300
0|yogaman  |     at /home/node/backend/node_modules/nfc-pcsc/dist/Reader.js:503:11
0|yogaman  |     at Generator.next (<anonymous>)
0|yogaman  |     at step (/home/node/backend/node_modules/nfc-pcsc/dist/Reader.js:18:191)
0|yogaman  |     at /home/node/backend/node_modules/nfc-pcsc/dist/Reader.js:18:361
0|yogaman  |     at <anonymous> name: 'ReadError', code: 'operation_failed' }
pokusew commented 6 years ago

Hi @bierlair,

thank you for posting your issue here.

According to the official documentation, it is possible to protect NTAG21x cards with password. To read/write (based on your card's access config) password protected cards, you need to authenticate using PWD_AUTH command first.

And all these things should be doable using ACR122U and nfc-pcsc library. πŸ˜ƒ

I don't know, where the problem might be. Could you please answer the following questions so that we can find why it doesn't work?

  1. What operating system, what reader, and card (I suppose NTAG21x) do you use?

  2. Are you able to read unprotected NTAG21x card?

  3. What's the content of pwdResponse? Can you log it and send it here? pwdResponse response should look like the following (7 bytes):

    d5 43 00 XX XX 90 00
    byte 0: d5 prefix for response of Data Exchange Command (see PN533 docs)
    byte 1: 43 prefix for response of Data Exchange Command (see PN533 docs)
    byte 2: Data Exchange Command Status 0x00 is success (see PN533 docs)
    bytes 3-4: Data Exchange Command Response – PACK (2 bytes password acknowledgment) from card
    bytes 5-6: ACR122U success code

    _Note about PACK: You can change the value. It is stored in the last configuration page of the card, see the docs (8.5 Memory organization)_

  4. Just for sure: Make sure you authenticate and read data from card in the same session. You have to authenticate the card every time you attach it.

Hope it helps. πŸ™‚

Looking forward to your reply. I believe we make it work together. πŸ™‚


To be honest, I have never tried to access protected cards before. I don't have any experience with NTAG21x cards (only with Mifare Ultralight EV1, Classic, and DESFire EV1).

_Fortunately, according to the documentation, I have been able to make it work on Mifare Ultralight EV1 (it is very similar to NTAG21x cards, it even uses same commands). After I issue PWDAUTH command, I am able to read the (password-read-protected) memory. πŸš€

Here is my code to authenticate card:

reader.on('card', async card => {

  console.log('card detected', card);

  // PASSWORD (4 bytes) (stored on card in page 18)
  const password = Buffer.from([
    0xff,
    0xff,
    0xff,
    0xff,
  ]);

  // PACK (2 bytes) (stored in page 19 as first two bytes)
  // PACK is the response from card in case of successful PWD_AUTH cmd
  const pack = Buffer.from([
    0xab,
    0xcd
  ]);

  try {

    // CMD: PWD_AUTH via Direct Transmit (ACR122U) and Data Exchange (PN533)
    const cmd = Buffer.from([
      0xFF, // Class
      0x00, // Direct Transmit (see ACR122U)
      0x00, // ...
      0x00, // ...
      0x07, // Length of Direct Transmit payload
      // Payload (7 bytes)
      0xd4, // Data Exchange Command (see PN533 docs)
      0x42, // inserted
      0x1b, // PWD_AUTH
      ...password,
    ]);

    console.log('pwd_auth cmd', cmd);

    const response = await reader.transmit(cmd, 7);

    console.log('pwd_auth response', response);
    // pwd_auth response should look like the following (7 bytes)
    // d5 43 00 ab cd 90 00
    // byte 0: d5 prefix for response of Data Exchange Command (see PN533 docs)
    // byte 1: 43 prefix for response of Data Exchange Command (see PN533 docs)
    // byte 2: Data Exchange Command Status 0x00 is success (see PN533 docs)
    // bytes 3-4: Data Exchange Command Response – our PACK (set on card in page 19, in bytes 0-1) from card
    // bytes 5-6: ACR122U success code

  } catch (err) {
    console.error('pwd_auth error', err);
    return;
  }

});

That's how I set up the password protection (don't allow to read content without authentication):

reader.on('card', async card => {

  try {

    // set custom PASSWORD (4 bytes) (stored in page 18)
    await reader.write(19, password);

    // set custom PACK (2 bytes) (stored in page 19 as first two bytes)
    const packPage = await reader.read(19, 4);
    packPage[0] = pack[0];
    packPage[1] = pack[1];
    await reader.write(19, packPage);

    // read current configuration
    const config = await reader.read(16, 8);

    // Configuration page 16
    console.log(config[0]);
    console.log(config[1]);
    console.log(config[2]);
    console.log(config[3]); // AUTH0 (default: 0xff)

    // Configuration page 17
    console.log(config[4]); // ACCESS
    console.log(config[5]); // VCTID (default: 0x05)
    console.log(config[6]);
    console.log(config[7]);

    // Protect everything (start with first data page)
    config[3] = 0x04;

    // set ACCESS bits
    // bit 7: PROT One bit inside the ACCESS byte defining the memory protection
    //          0b ... write access is protected by the password verification
    //          1b ... read and write access is protected by the password verification
    // bit 6: CFGLCK Write locking bit for the user configuration
    //        - 0b ... user configuration open to write access
    //        - 1b ... user configuration permanently locked against write access
    // bits 5-3: reserved
    // bits 2-0: AUTHLIM
    // bit number-76543210
    //            ||||||||
    config[4] = 0b10000000;

    // set custom access rules
    await reader.write(16, config);

  } catch (err) {
    console.error(err);
  }

});
bierlair commented 6 years ago

Awesome, this is exactly what I needed. I was pretty close to completing this myself by reading the technical docs of NTAG21x, but your code snippet accelerated things.

Since I am working with NTAG216, I needed to adjust the page addresses.

The try/catch block for setting the password now looks something like this:

try {

    // set custom PASSWORD (4 bytes) (stored in page 229)
    await reader.write(229, password);

    // set custom PACK (2 bytes) (stored in page 230 as first two bytes)
    const packPage = await reader.read(230, 4);
    packPage[0] = pack[0];
    packPage[1] = pack[1];
    await reader.write(230, packPage);

    // read current configuration
    const config = await reader.read(227, 8);

    // Configuration page 16
    console.log(config[0]);
    console.log(config[1]);
    console.log(config[2]);
    console.log(config[3]); // AUTH0 (default: 0xff)

    // Configuration page 17
    console.log(config[4]); // ACCESS
    console.log(config[5]); // VCTID (default: 0x05)
    console.log(config[6]);
    console.log(config[7]);

    // Protect everything (start with first data page)
    config[3] = 0x04;

    // set ACCESS bits
    // bit 7: PROT One bit inside the ACCESS byte defining the memory protection
    //          0b ... write access is protected by the password verification
    //          1b ... read and write access is protected by the password verification
    // bit 6: CFGLCK Write locking bit for the user configuration
    //        - 0b ... user configuration open to write access
    //        - 1b ... user configuration permanently locked against write access
    // bits 5-3: reserved
    // bits 2-0: AUTHLIM
    // bit number-76543210
    //            ||||||||
    config[4] = 0b10000000;

    // set custom access rules
    await reader.write(227, config);

} catch (err) {
    console.error(err);
}

Thanks again πŸ‘

pokusew commented 6 years ago

@bierlair That's great. πŸ‘

So everything works as expected? Can we close the issue?

BTW I think, it would be useful to have this in the examples. Thank you very much for pointing it out to me. In future, the examples should explain the basic usage and commands of the most popular cards and tags to help people to get started easily. From my experience, I can say, that regarding NFC standards and cards, there is a lack of documentation (or it is poor, hard to find and understand, especially for beginners).


PS Don't forget to star ⭐️ my library, if you find it useful. πŸ˜ƒ Thanks.

bierlair commented 6 years ago

Everything works as expected. With this I was actually able to break it down even further. I would like to detect which kind of NTAG chip I am using, as they all come with different pages for storing passwords and configs.

Here is something quick and dirty that helps identify the NTAG chip. Next step would be to return the appropriate offsets in an object property, such as model.offset = 123

    async getModel(reader) {

        try {

            let cmd = Buffer.from([
                0xFF, // Class
                0x00, // Direct Transmit (see ACR122U)
                0x00, // ...
                0x00, // ...
                0x03, // Length of Direct Transmit payload
                0xd4, // Data Exchange Command (see PN533 docs)
                0x42, // inserted
                0x60, // GET_VERSION
            ]);

            let response = await reader.transmit(cmd, 13);

            // console.log('+--- Vendor    :', response[4]);
            // console.log('+--- Type      :', response[5]);
            // console.log('+--- SubType   :', response[6]);
            // console.log('+--- Version 0 :', response[7]);
            // console.log('+--- Version 1 :', response[8]);
            // console.log('+--- Storage   :', response[9]);

            let model = {};
            model.name = null;
            model.page = null;

            if(response[4] === 4 && response[5] === 4) {
                if(response[6] === 1 && response[7] === 1 && response[8] === 0 && response[9] === 11) {
                    model.name = "NTAG210";
                } else if(response[6] === 1 && response[7] === 1 && response[8] === 0 && response[9] === 14) {
                    model.name = "NTAG212";
                } else if(response[6] === 2 && response[7] === 1 && response[8] === 0 && response[9] === 15) {
                    model.name = "NTAG213";
                } else if(response[6] === 4 && response[7] === 1 && response[8] === 0 && response[9] === 15) {
                    model.name = "NTAG213F";
                } else if(response[6] === 2 && response[7] === 1 && response[8] === 0 && response[9] === 17) {
                    model.name = "NTAG215";
                } else if(response[6] === 2 && response[7] === 1 && response[8] === 0 && response[9] === 19) {
                    model.name = "NTAG216";
                } else if(response[6] === 4 && response[7] === 1 && response[8] === 0 && response[9] === 19) {
                    model.name = "NTAG216F";
                }
            }
            return model;

        } catch (err) {
            console.error('get_version error', err);
        }

Reference guide for this:

// get version
> FF 00 00 00 03 D4 42 60
// response
< D5 43 00 00 04 04 02 01 00 13 03 90 00
Details:

            Vendor (04 = NXP)
            |
D5 43 00 00 04   04    02        01   00      13    03 90 00
                 |      |         |   |       |
+------------+------+---------+-----------+--------------+
| Chip       | Type | Subtype | Version   | Storage size |
+------------+------+---------+-----------+--------------+
| NTAG210    | 0x04 | 0x01    | 0x01 0x00 | 0x0B         |
| NTAG212    | 0x04 | 0x01    | 0x01 0x00 | 0x0E         |
| NTAG213    | 0x04 | 0x02    | 0x01 0x00 | 0x0F         |
| NTAG213F   | 0x04 | 0x04    | 0x01 0x00 | 0x0F         |
| NTAG215    | 0x04 | 0x02    | 0x01 0x00 | 0x11         |
| NTAG216    | 0x04 | 0x02    | 0x01 0x00 | 0x13         |
| NTAG216F   | 0x04 | 0x04    | 0x01 0x00 | 0x13         |
+------------+------+---------+-----------+--------------+
| NT3H1101   | 0x04 | 0x02    | 0x01 0x01 | 0x13         |
| NT3H1101W0 | 0x04 | 0x05    | 0x02 0x01 | 0x13         |
| NT3H2111W0 | 0x04 | 0x05    | 0x02 0x02 | 0x13         |
| NT3H2101   | 0x04 | 0x02    | 0x01 0x01 | 0x15         |
| NT3H1201W0 | 0x04 | 0x05    | 0x02 0x01 | 0x15         |
| NT3H2211W0 | 0x04 | 0x05    | 0x02 0x02 | 0x15         |
+------------+------+---------+-----------+--------------+
| MF0UL1101  | 0x03 | 0x01    | 0x01 0x00 | 0x0B         |
| MF0ULH1101 | 0x03 | 0x02    | 0x01 0x00 | 0x0B         |
| MF0UL2101  | 0x03 | 0x01    | 0x01 0x00 | 0x0E         |
| MF0ULH2101 | 0x03 | 0x02    | 0x01 0x00 | 0x0E         |
+------------+------+---------+-----------+--------------+

==> NTAG Technical Doc, Table 28

And yes, NFC technical guides are tough to dig through. So good code examples would help beginners to kick off faster.

Cheers :-)

lokachun commented 5 years ago

@pokusew Hello, I am currently using ACS ACR 1252 1S CL Reader PICC 0 for my Mifare Ultralight. I wish to add a password to my tag so that others can write on it if they enter the password. Could I know what platform are you guys using to code and link with the reader because I am unable to find it. Thank you!

dcr31000 commented 3 years ago

Hello, I want to add a password protection after writing data on NTAG216. I tried @bierlair code sample but the last writing command break my nfc cards...

This in my code:

// PASSWORD (4 bytes) (stored on card in page 18)
const CARD_PASSWORD = Buffer.from([
    0xff,
    0xff,
    0xff,
    0xff,
]);

// PACK (2 bytes) (stored in page 19 as first two bytes)
// PACK is the response from card in case of successful PWD_AUTH cmd
const PACK = Buffer.from([
    0xab,
    0xcd
]);

// set custom PASSWORD (4 bytes) (stored in page 229)
await reader.write(229, CARD_PASSWORD);

// set custom PACK (2 bytes) (stored in page 230 as first two bytes)
const packPage = await reader.read(230, 4);
packPage[0] = PACK[0];
packPage[1] = PACK[1];
await reader.write(230, packPage);

const config = await reader.read(227, 8);
config[3] = 0x04;
config[4] = 0b10000000;

await reader.write(227, config);

Could you please help me to find a solution ? Thanks all