Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add locking of tags. #6

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
<a name="1.1.0"></a>
# [1.1.0](https://github.com/RoopeHakulinen/cordova-plugin-mifare-ultralight/blob/master/CHANGELOG.md#1.1.0) (2018-06-15)

Add possibility to lock different kinds of tags with a PIN code.

<a name="1.0.4"></a>
# [1.0.4](https://github.com/RoopeHakulinen/cordova-plugin-mifare-ultralight/blob/master/CHANGELOG.md#1.0.4) (2019-02-01)

Expand Down
69 changes: 68 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
This plugins lets you interact with Mifare Ultralight NFC tags on Android devices. You can use it to
- Detect Mifare Ultralight tag near-by the device.
- Connect/disconnet with Mifare Ultralight tags.
- Unlock Mifare Ultralight tags with PIN.
- Lock/unlock Mifare Ultralight tags with PIN.
- Read Mifare Ultralight tags.
- Write Mifare Ultralight tags.
- Check if NFC is enabled on the device.
Expand Down Expand Up @@ -57,6 +57,10 @@ Available methods:
* [mifare.read](#mifareread)
* [mifare.write](#mifarewrite)
* [mifare.unlock](#mifareunlock)
* [mifare.lockNTAG212](#mifarelockntag212)
* [mifare.lockMF0UL11](#mifarelockmf0ul11)
* [mifare.lockMF0UL21](#mifarelockmf0ul21)
* [mifare.lock](#mifarelock)

#### mifare.enabled
`mifare.enabled(success, failure)`
Expand Down Expand Up @@ -129,6 +133,65 @@ Tries to unlock the tag. Parameter `pin` should be a number.
window.mifare.unlock(0x1234, (response) => alert('Unlocked successfully'), err => alert(`Couldn't unlock because ${err}`));
```

#### mifare.lockNTAG212
`mifare.lockNTAG212(firstPageToBeProtected, pin, protectAlsoReads, authenticationTryLimit, success, failure)`

Tries to lock the tag of type `NTAG212`. For other tag types see the other methods starting with word `lock`.

For parameter details see the options table under [`parameters in lock method`](Parameters).

##### Example
```javascript
window.mifare.lockMF0UL11(37, 0x1234, true, 0, () => alert('Locked successfully'), err => alert(`Couldn't lock because ${err}`));
```

#### mifare.lockMF0UL11
`mifare.lockMF0UL11(firstPageToBeProtected, pin, protectAlsoReads, authenticationTryLimit, success, failure)`

Tries to lock the tag of type `MF0UL11`. For other tag types see the other methods starting with word `lock`.

For parameter details see the options table under [`parameters in lock method`](Parameters).

##### Example
```javascript
window.mifare.lockMF0UL11(37, 0x1234, true, 0, () => alert('Locked successfully'), err => alert(`Couldn't lock because ${err}`));
```

#### mifare.lockMF0UL21
`mifare.lockMF0UL21(firstPageToBeProtected, pin, protectAlsoReads, authenticationTryLimit, success, failure)`

Tries to lock the tag of type `MF0UL21`. For other tag types see the other methods starting with word `lock`.

For parameter details see the options table under [`parameters in lock method`](Parameters).

##### Example
```javascript
window.mifare.lockMF0UL21(37, 0x1234, true, 0, () => alert('Locked successfully'), err => alert(`Couldn't lock because ${err}`));
```

#### mifare.lock
`mifare.lock(pinPage, pinAckPage, protectionPage, firstPageToBeProtectedPage, firstPageToBeProtected, pin, protectAlsoReads, authenticationTryLimit, success, failure)`

Tries to lock the tag.

*Please note that this command is the raw command used by the above vendor-specific ones. You need to know the pages you want to be using to use this method.*

##### Parameters
*pinPage*: Page number for the PIN setting page (e.g. `39`)
*pinAckPage*: Page number for the PIN acknowledgement page (e.g. `40`)
*protectionPage*: Page number for the protection page (e.g. `38`)
*firstPageToBeProtectedPage*: Page number for the first page to be protected page (e.g. `37`)

*firstPageToBeProtected*: Page number from which onwards to enable the protection (e.g. `41`)
*pin*: Pin to lock the tag with (e.g. 0x1234)
*protectAlsoReads*: Whether the reads should be protected along with writes (`false` for only protecting writes, `read` for protecting reads and writes)
*authenticationTryLimit*: How many times unlocking can be tried with invalid PIN. Value should be between 0-7. 0 means no limit is applied.

##### Example
```javascript
window.mifare.lock(21, 22, 23, 24, 37, 0x1234, true, 0, () => alert('Locked successfully'), err => alert(`Couldn't lock because ${err}`));
```

## FAQ

*Q: Why is there no version to iOS or Windows Phone?*
Expand All @@ -147,5 +210,9 @@ A: Not yet unfortunately. It is something on the todo list for sure, though.

A: I would be happy to take in a Pull Request for this. It seemed somewhat cumbersome to implement.. Maybe at some point I will find the motivation to add it.

*Q: What is the difference between the lock methods?*

A: Different tag types use different page numbers. For example `NTAG212` tags use pages numbered 39, 40, 38 and 37. These pages can all be adjusted by using the "raw" `lock` method but for your convenience there exists separate methods for different tag types that use the default page numbers.

## Kudos
This plugin is heavily influenced by the excellent work done on [PhoneGap NFC Plugin](https://github.com/chariotsolutions/phonegap-nfc). Thank you for the effort you have put into it over the years.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cordova-plugin-mifare-ultralight",
"version": "1.0.4",
"version": "1.1.0",
"description": "Provides Mifare Ultralight functionality for Cordova-based applications",
"main": "www/cordova-plugin-mifare-ultralight.js",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion plugin.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
<plugin id="cordova-plugin-mifare-ultralight" version="1.0.4"
<plugin id="cordova-plugin-mifare-ultralight" version="1.1.0"
xmlns="http://apache.org/cordova/ns/plugins/1.0" xmlns:android="http://schemas.android.com/apk/res/android">
<name>cordova-plugin-mifare-ultralight</name>
<js-module name="mifare" src="www/cordova-plugin-mifare-ultralight.js">
Expand Down
83 changes: 69 additions & 14 deletions src/android/CordovaPluginMifareUltralight.java
Original file line number Diff line number Diff line change
@@ -1,24 +1,20 @@
package fi.roopehakulinen.CordovaPluginMifareUltralight;

import org.apache.cordova.*;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.app.Activity;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.IntentFilter;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.Log;
import org.apache.cordova.*;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;

/**
* This class echoes a string called from JavaScript.
Expand Down Expand Up @@ -102,6 +98,37 @@ public boolean execute(String action, JSONArray args, final CallbackContext call
final long pin = Long.parseLong(arg0);
this.unlock(callbackContext, pin);
return true;
} else if (action.equals("lock")) {
final String arg0 = args.getString(0);
final String arg1 = args.getString(1);
final String arg2 = args.getString(2);
final String arg3 = args.getString(3);
final String arg4 = args.getString(4);
final String arg5 = args.getBoolean(5);
final String arg6 = args.getBoolean(6);
final String arg7 = args.getString(7);

final int pinPage = Integer.parseInt(arg0);
final int pinAckPage = Integer.parseInt(arg1);
final int protectionPage = Integer.parseInt(arg2);
final int firstPageToBeProtectedPage = Integer.parseInt(arg3);
final int firstPageToBeProtected = Integer.parseInt(arg4);
final long pin = Long.parseLong(arg5);
final boolean protectAlsoReads = arg6;
final int authenticationTryLimit = Integer.parseInt(arg7);

this.lock(
callbackContext,
pinPage,
pinAckPage,
protectionPage,
firstPageToBeProtectedPage,
firstPageToBeProtected,
pin,
protectAlsoReads,
authenticationTryLimit
);
return true;
}
return false;
}
Expand Down Expand Up @@ -244,6 +271,34 @@ public void run() {
});
}

private void lock(final CallbackContext callbackContext, final long pin) {
cordova.getThreadPool().execute(new Runnable() {
@Override
public void run() {
if (getIntent() == null) { // Lost Tag
clean(callbackContext, "No tag available.");
return;
}

final Tag tag = savedIntent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
if (tag == null) {
clean(callbackContext, "No tag available.");
return;
}
try {
final boolean success = mifareUltralight.lockWithPin(page, pin);
if (success) {
callbackContext.success();
} else {
callbackContext.error("Locking failed.");
}
} catch (final Exception e) {
clean(callbackContext, e);
}
}
});
}

private void fireTagEvent(Tag tag, String name) {
String command = MessageFormat.format(javaScriptEventTemplate, name, byteArrayToJSON(tag.getId()));
this.webView.sendJavascript(command);
Expand Down Expand Up @@ -362,7 +417,7 @@ private void setIntent(Intent intent) {

private String bytesToHex(final byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
for ( int j = 0; j < bytes.length; j++ ) {
for (int j = 0; j < bytes.length; j++) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
Expand All @@ -375,7 +430,7 @@ private static byte[] hexStringToByteArray(String s) {
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i+1), 16));
+ Character.digit(s.charAt(i + 1), 16));
}
return data;
}
Expand Down
54 changes: 54 additions & 0 deletions src/android/MifareUltralight.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;

public class MifareUltralight {
private android.nfc.tech.MifareUltralight mifare = null;
Expand Down Expand Up @@ -35,6 +36,59 @@ public boolean unlockWithPin(long pin) throws Exception {
return false;
}

public boolean lockWithPin(
int pinPage,
int pinAckPage,
int protectionPage,
int firstPageToBeProtectedPage,
int firstPageToBeProtected,
long pin,
boolean protectAlsoReads,
int authenticationTryLimit
) throws Exception {
byte[] pack;
// 1. Write the PIN
final byte[] pinAsByteArray = longToByteArray(pin);
byte[] response = mifare.writePage(page, pinAsByteArray);
if ((response != null) && (response.length >= 2)) {
pack = Arrays.copyOf(response, 2);
} else {
return false;
}
// 2. Write the acknowledgement
byte[] data = {pack[0], pack[1], (byte) 0, (byte) 0};
mifare.writePage(pinAckPage, data);

// 3. Read the protection page to be able to only modify certain bits and then write it again
response = mifare.readPages(protectionPage);
if ((response != null) && (response.length >= 16)) {
data = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got a an error just here at compilation:
error: illegal start of expression data = {
I'm not a Java developper so I don't know how to fix it actually :/
Maybe by modifying each entry on the array like data[0] = foo, data[1] = bar, etc .?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tried that with beta release 1.1.2.. :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got the exact same issue on line 75 ! (sorry for the delay, I'm working at Disneyland on weekends so I'm too exhausted to code after my shift :'))

Copy link
Owner Author

@RoopeHakulinen RoopeHakulinen Feb 12, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should've probably checked that too.. Fixed now in beta release 1.1.3 (I wish I understood the npm publishing as 1.1.0-beta.0 was not a valid semver according to npm).

Take your time. I don't need this feature myself nor do I use the plugin really nowadays :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here are the last errors I get: https://pastebin.com/pgX12tTf

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1.1.4 available :)

(byte) ((response[0] & 0x078) | (protectAlsoReads ? 0x080 : 0x000) | (authenticationTryLimit & 0x007)),
response[1], // Keep old value for byte 1
response[2], // Keep old value for byte 2
response[3] // Keep old value for byte 3
};
mifare.writePage(protectionPage, data);
}

// 4. Read the page to be able to only modify certain bits and then Write first page to be protected
response = mifare.readPages(firstPageToBeProtectedPage);
if ((response != null) && (response.length >= 16)) {
data = {
response[0], // Keep old value for byte 0
response[1], // Keep old value for byte 1
response[2], // Keep old value for byte 2
(byte) (firstPageToBeProtected & 0x0ff)
};
response = mifare.writePage(firstPageToBeProtectedPage, data);
if ((response != null) && (response.length >= 2)) {
return true;
}
}

return false;
}

public byte[] read(int pageOffset) throws Exception {
if (mifare == null || !mifare.isConnected()) {
throw new Exception();
Expand Down
50 changes: 43 additions & 7 deletions www/cordova-plugin-mifare-ultralight.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,66 @@
var exec = require('cordova/exec');


exports.enabled = function(success, error) {
exports.enabled = function (success, error) {
exec(success, error, "cordova-plugin-mifare-ultralight", "enabled", []);
};

exports.connect = function(success, error) {
exports.connect = function (success, error) {
exec(success, error, "cordova-plugin-mifare-ultralight", "connect", []);
};

exports.disconnect = function(success, error) {
exports.disconnect = function (success, error) {
exec(success, error, "cordova-plugin-mifare-ultralight", "disconnect", []);
};

exports.isConnected = function(success, error) {
exports.isConnected = function (success, error) {
exec(success, error, "cordova-plugin-mifare-ultralight", "isConnected", []);
};

exports.read = function(page, success, error) {
exports.read = function (page, success, error) {
exec(success, error, "cordova-plugin-mifare-ultralight", "read", [page]);
};

exports.write = function(page, data, success, error) {
exports.write = function (page, data, success, error) {
exec(success, error, "cordova-plugin-mifare-ultralight", "write", [page, data]);
};

exports.unlock = function(pin, success, error) {
exports.unlock = function (pin, success, error) {
exec(success, error, "cordova-plugin-mifare-ultralight", "unlock", [pin]);
};

exports.lock = function (
pinPage,
pinAckPage,
protectionPage,
firstPageToBeProtectedPage,
firstPageToBeProtected,
pin,
protectAlsoReads,
authenticationTryLimit,
success,
error
) {
exec(success, error, "cordova-plugin-mifare-ultralight", "lock", [
pinPage,
pinAckPage,
protectionPage,
firstPageToBeProtectedPage,
firstPageToBeProtected,
pin,
protectAlsoReads,
authenticationTryLimit
]);
};

exports.lockNTAG212 = function (firstPageToBeProtected, pin, protectAlsoReads, authenticationTryLimit, success, error) {
exports.lock(39, 40, 38, 37, firstPageToBeProtected, pin, protectAlsoReads, authenticationTryLimit, success, error);
};

exports.lockMF0UL11 = function (firstPageToBeProtected, pin, protectAlsoReads, authenticationTryLimit, success, error) {
exports.lock(12, 13, 11, 10, firstPageToBeProtected, pin, protectAlsoReads, authenticationTryLimit, success, error);
};

exports.lockMF0UL21 = function (firstPageToBeProtected, pin, protectAlsoReads, authenticationTryLimit, success, error) {
exports.lock(27, 28, 26, 25, firstPageToBeProtected, pin, protectAlsoReads, authenticationTryLimit, success, error);
};