nfc-pcsc icon indicating copy to clipboard operation
nfc-pcsc copied to clipboard

MIFARE Ultralight C authentication

Open Franx0 opened this issue 5 years ago β€’ 7 comments

First of all, great work @pokusew, is amazing and super usefull having resources like yours. Thank you!

I'm on firts step on authentication of MIFARE Ultraligh C tags. Related to the doc I need to use a command to have the default tag key in return. https://www.nxp.com/docs/en/data-sheet/MF0ICU2.pdf (page 15)

I'm using a block like that:

reader.on('card', async card => {
	console.log(`${reader.reader.name}  card ${card.uid} inserted`, card);
	try {
		const buffer = Buffer.from([0x1a, 0x00]);
		return resolve(await reader.transmit(buffer, 26));				
	} cache (err) {
                 console.log(err);
        };			
});

The main issue here is that always return a byteArray like that: Uint8Array(2) [99, 0] instead of the expected 8 bytes string. Maybe I am missing something but the following code: 0x1a, 0x00 is neccessary as first authentication step.

PD: Reader device: ACR1222L

Thank you all ;)

Franx0 avatar Dec 24 '18 08:12 Franx0

EDIT

Sending the following buffered command

Buffer.from([0xff, 0x00, 0x00, 0x00, 0x16, 0xd4, 0x42, 0x1a, 0x00])

I'm receiving the following:

d543019000

I'm missing something, becasuse as I read, "01" byte represent the response status as Timeout but I can't understand why. Any help will be appreciated, thank you all! ;)

Franx0 avatar Dec 30 '18 20:12 Franx0

Hi @Franx0,

I am very sorry for the late reply. Thank you for posting your issue here. πŸ™‚

I faced the similar issues when I implemented MIFARE Ultralight EV1 password authentication (PWD_AUTH).

I think, the 5th byte (0x16, the number of bytes to send) should be 0x04 as you are trying to send these 4 bytes: 0xd4 0x42 0x1a 0x00.

The following should work:

Buffer.from([0xff, 0x00, 0x00, 0x00, 0x16, 0xd4, 0x42, 0x1a, 0x00])

Explanation (might be useful for others): (also see the MIFARE Ultralight EV1 example in nfc-pcsc exmap)

As yo figured out correctly, that the only way to send the command, seems to be via the ACR reader's Direct Transmit command. It's structure is as follows:

const directTrasmit = Buffer.from([
    0xff,
    0x00,
    0x00,
    0x00,
    data.length, // number of bytes to send
    ...data, // data to send (assuming a Buffer or an array of bytes)
]);

The data sent via Direct Transmit are passed directly to the internal NFC frontend chip (in case of ACR readers, that's NXP PN533 or similar). To communicate with the NFC card you have to use the InCommunicateThru as described in PN533 API docs. It's structure is as follows:

const inCommunicateThru = Buffer.from([
    0xd4, // Data Exchange Command:
    0x42, // InCommunicateThru
    ...apdu, // data which will be sent directly to the card
]);

So the final structure for sending any direct command to the card for ACR readers might look like this:

const cmd = Buffer.from([/* the bytes of your command */]);
Buffer.from([
    0xff, // Class
    0x00, // Direct Transmit (see ACR122U docs) (INS)
    0x00, // ... (P2)
    0x00, // ... (P1)
    0x02 + cmd.length, // Lc: Length of the Direct Transmit Payload
    // Payload (2 + cmd.length) bytes
    0xd4, // Data Exchange Command (see PN533 docs)
    0x42, // InCommunicateThru
    ...cmd,
]);

The response should like:

// d5 43 00 ... ... 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, Table 15. Error code list)
// bytes 3-n: Data Exchange Command Response – response from the card
// last 2 bytes: ACR reader status code (should be always success – 90 00)

Please let me know if your able to make your command work.

Also I think, you might be interested in the MIFARE DESFire example, where the 3DES authentication is implemented. It might be similar to the 3DES authentication procedure on the Ultralight C.

Finally, I think that the MIFARE Ultralight C authentication would be great to have in nfc-pcsc examples. Once you finish it, feel free to open a PR to add it. Thanks. πŸ˜‰

Hope it helps. πŸ™‚


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

pokusew avatar Dec 31 '18 12:12 pokusew

Hi @pokusew thanks for your reply. So many usefull doc in that comment ;), thank's a lot. Unfortunately I'm still not able to make the pcsc command works. Let me show you the two command responses:

  1. Sending the following:
    let buffer = Buffer.from([0xff, 0x00, 0x00, 0x00, 0x04, 0xd4, 0x42, 0x1a, 0x00]);
    reader.transmit(buffer, buffer.byteLength);

In that case the response is the tag uid, not the 8byte token.

  1. Sending this one:
    let buffer = Buffer.from([0xff, 0x00, 0x00, 0x00, 0x16, 0xd4, 0x42, 0x1a, 0x00]);
    reader.transmit(buffer, buffer.byteLength);

The response is "d543019000" as my previous comment. I can not figure out what I'm missing or what can be wrong. I debugged deep in order to see nothing weird in the response except this 01 byte that I assume it's an internal code from reader telling me that might exists a timeout issue.

Franx0 avatar Jan 02 '19 10:01 Franx0

EDIT

After multiple tests and having in mind @pokusew advices, I'm able to tell all of you that @pokusew command were right, but I was having and error in the data length send in the transmit command, so in order to send the command you need something like this:

    let buffer = Buffer.from([0xff, 0x00, 0x00, 0x00, 0x04, 0xd4, 0x42, 0x1a, 0x00]);
    return reader.transmit(buffer, 16);

Thank's a lot @pokusew, I'll keep on working in the auth, let you know ;)

Franx0 avatar Jan 02 '19 10:01 Franx0

Hi @Franx0,

I've just noticed a very dangerous mistake in your previous code snippets. Therefore, I'd like to clarify the usage of reader.transmit() method. It might be useful for other people who come across this, as well. πŸ™‚

The reader.transmit(data, responseMaxLength) method accepts two arguments:

  1. data – a Buffer instance containing the data (bytes) to transmit
  2. responseMaxLength – an integer specifying the maximum length (in bytes) of the expected response
    In case you specify too small value and the response exceeds the given maximum length, the transmit fails and an error is thrown. If you specify much bigger value than needed, nothing happens – but don't do that for performance reasons.

So, this is the ☠️INCORRECT usage:

// DO NOT USE THIS CODE ☠️
let buffer = Buffer.from([0xff, 0x00, 0x00, 0x00, 0x04, 0xd4, 0x42, 0x1a, 0x00]);
reader.transmit(buffer, buffer.byteLength); // <- this is INCORRECT usage ☠️

And this is the βœ…CORRECT usage:

let buffer = Buffer.from([0xff, 0x00, 0x00, 0x00, 0x04, 0xd4, 0x42, 0x1a, 0x00]);
reader.transmit(buffer, responseMaxLength); // <- fill appropriate expected length of the response

In your case (the first part of AUTHENTICATE command), the successful response should look like this:

d5 43 00 ... ... ... ... ... ... ... ... 90 00
  • byte 0: 0xd5 prefix for response of Data Exchange Command (see PN533 docs)
  • byte 1: 0x43 prefix for response of Data Exchange Command (see PN533 docs)
  • byte 2: 0x00 Data Exchange Command Status – 0x00 is success (see PN533 docs, Table 15. Error code list)
  • byte 3-n:. Data Exchange Command Response – response from the card – in your case, the 8bytes ek(RndB)
  • last 2 bytes: ACR reader status code (should be always success – 0x9000)
  • Total: 3 + 8 + 2 bytes = 13 bytes – that's the value you should use as the responseMaxLength in reader.transmit

In practice, when developing an implementation of any commands, I recommend trying the bigger value for responseMaxLength as first, then checking the layout and the length of the received response and finally if the real length matches the calculated value, use the calculated and verified-is-correct length as the final responseMaxLength.

Hope it helps. πŸ™‚


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

pokusew avatar Jan 03 '19 15:01 pokusew

Thank's a lot @pokusew, you're right. The code I posted was wrong. In my case was 16 bytes of response length but it was wrong too so I changed to 13 as you suggested. You're right, the better approach is to check that response length match with expected in order to control the correct transmit flow. Thank you a lot ;)

Franx0 avatar Jan 04 '19 10:01 Franx0

Wow, thank you @pokusew, you saved me a lot of time on this :)

carol-braileanu avatar Sep 25 '19 17:09 carol-braileanu