MifareClassic.java revision b134223f91c8801d577cb72e92a37cb65fec717a
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.nfc.tech;
18
19import android.nfc.Tag;
20import android.nfc.TagLostException;
21import android.os.RemoteException;
22
23import java.io.IOException;
24import java.nio.ByteBuffer;
25import java.nio.ByteOrder;
26
27/**
28 * Technology class representing MIFARE Classic tags (also known as MIFARE Standard).
29 *
30 * <p>Support for this technology type is optional. If the NFC stack doesn't support this technology
31 * MIFARE Classic tags will still be scanned, but will only show the NfcA technology.
32 *
33 * <p>MIFARE Classic tags have sectors that each contain blocks. The block size is constant at
34 * 16 bytes, but the number of sectors and the sector size varies by product. MIFARE has encryption
35 * built in and each sector has two keys associated with it, as well as ACLs to determine what
36 * level acess each key grants. Before operating on a sector you must call either
37 * {@link #authenticateSectorWithKeyA(int, byte[])} or
38 * {@link #authenticateSectorWithKeyB(int, byte[])} to gain authorization for your request.
39 */
40public final class MifareClassic extends BasicTagTechnology {
41    /**
42     * The well-known default MIFARE read key. All keys are set to this at the factory.
43     * Using this key will effectively make the payload in the sector public.
44     */
45    public static final byte[] KEY_DEFAULT =
46            {(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF};
47    /**
48     * The well-known, default MIFARE Application Directory read key.
49     */
50    public static final byte[] KEY_MIFARE_APPLICATION_DIRECTORY =
51            {(byte)0xA0,(byte)0xA1,(byte)0xA2,(byte)0xA3,(byte)0xA4,(byte)0xA5};
52    /**
53     * The well-known, default read key for NDEF data on a MIFARE Classic
54     */
55    public static final byte[] KEY_NFC_FORUM =
56            {(byte)0xD3,(byte)0xF7,(byte)0xD3,(byte)0xF7,(byte)0xD3,(byte)0xF7};
57
58    /** A MIFARE Classic tag */
59    public static final int TYPE_CLASSIC = 0;
60    /** A MIFARE Plus tag */
61    public static final int TYPE_PLUS = 1;
62    /** A MIFARE Pro tag */
63    public static final int TYPE_PRO = 2;
64    /** A Mifare Classic compatible card that does not match the other types */
65    public static final int TYPE_OTHER = -1;
66
67    /** The tag contains 16 sectors, each holding 4 blocks. */
68    public static final int SIZE_1K = 1024;
69    /** The tag contains 32 sectors, each holding 4 blocks. */
70    public static final int SIZE_2K = 2048;
71    /**
72     * The tag contains 40 sectors. The first 32 sectors contain 4 blocks and the last 8 sectors
73     * contain 16 blocks.
74     */
75    public static final int SIZE_4K = 4096;
76    /** The tag contains 5 sectors, each holding 4 blocks. */
77    public static final int SIZE_MINI = 320;
78
79    /** Size of a Mifare Classic block (in bytes) */
80    public static final int BLOCK_SIZE = 16;
81
82    private static final int MAX_BLOCK_COUNT = 256;
83    private static final int MAX_SECTOR_COUNT = 40;
84
85    private boolean mIsEmulated;
86    private int mType;
87    private int mSize;
88
89    /**
90     * Returns an instance of this tech for the given tag. If the tag doesn't support
91     * this tech type null is returned.
92     *
93     * @param tag The tag to get the tech from
94     */
95    public static MifareClassic get(Tag tag) {
96        if (!tag.hasTech(TagTechnology.MIFARE_CLASSIC)) return null;
97        try {
98            return new MifareClassic(tag);
99        } catch (RemoteException e) {
100            return null;
101        }
102    }
103
104    /** @hide */
105    public MifareClassic(Tag tag) throws RemoteException {
106        super(tag, TagTechnology.MIFARE_CLASSIC);
107
108        NfcA a = NfcA.get(tag);  // Mifare Classic is always based on NFC a
109
110        mIsEmulated = false;
111
112        switch (a.getSak()) {
113        case 0x08:
114            mType = TYPE_CLASSIC;
115            mSize = SIZE_1K;
116            break;
117        case 0x09:
118            mType = TYPE_CLASSIC;
119            mSize = SIZE_MINI;
120            break;
121        case 0x10:
122            mType = TYPE_PLUS;
123            mSize = SIZE_2K;
124            // SecLevel = SL2
125            break;
126        case 0x11:
127            mType = TYPE_PLUS;
128            mSize = SIZE_4K;
129            // Seclevel = SL2
130            break;
131        case 0x18:
132            mType = TYPE_CLASSIC;
133            mSize = SIZE_4K;
134            break;
135        case 0x28:
136            mType = TYPE_CLASSIC;
137            mSize = SIZE_1K;
138            mIsEmulated = true;
139            break;
140        case 0x38:
141            mType = TYPE_CLASSIC;
142            mSize = SIZE_4K;
143            mIsEmulated = true;
144            break;
145        case 0x88:
146            mType = TYPE_CLASSIC;
147            mSize = SIZE_1K;
148            // NXP-tag: false
149            break;
150        case 0x98:
151        case 0xB8:
152            mType = TYPE_PRO;
153            mSize = SIZE_4K;
154            break;
155        default:
156            // Stack incorrectly reported a MifareClassic. We cannot handle this
157            // gracefully - we have no idea of the memory layout. Bail.
158            throw new RuntimeException(
159                    "Tag incorrectly enumerated as Mifare Classic, SAK = " + a.getSak());
160        }
161    }
162
163    /** Returns the type of the tag, determined at discovery time */
164    public int getType() {
165        return mType;
166    }
167
168    /** Returns the size of the tag in bytes, determined at discovery time */
169    public int getSize() {
170        return mSize;
171    }
172
173    /** Returns true if the tag is emulated, determined at discovery time.
174     * These are actually smart-cards that emulate a Mifare Classic interface.
175     * They can be treated identically to a Mifare Classic tag.
176     * @hide
177     */
178    public boolean isEmulated() {
179        return mIsEmulated;
180    }
181
182    /** Returns the number of sectors on this tag, determined at discovery time */
183    public int getSectorCount() {
184        switch (mSize) {
185        case SIZE_1K:
186            return 16;
187        case SIZE_2K:
188            return 32;
189        case SIZE_4K:
190            return 40;
191        case SIZE_MINI:
192            return 5;
193        default:
194            return 0;
195        }
196    }
197
198    /** Returns the total block count, determined at discovery time */
199    public int getBlockCount() {
200        return mSize / BLOCK_SIZE;
201    }
202
203    /** Returns the block count for the given sector, determined at discovery time */
204    public int getBlockCountInSector(int sectorIndex) {
205        validateSector(sectorIndex);
206
207        if (sectorIndex < 32) {
208            return 4;
209        } else {
210            return 16;
211        }
212    }
213
214    /** Return the sector index of a given block */
215    public int blockToSector(int blockIndex) {
216        validateBlock(blockIndex);
217
218        if (blockIndex < 32 * 4) {
219            return blockIndex / 4;
220        } else {
221            return 32 + (blockIndex - 32 * 4) / 16;
222        }
223    }
224
225    /** Return the first block of a given sector */
226    public int sectorToBlock(int sectorIndex) {
227        if (sectorIndex < 32) {
228            return sectorIndex * 4;
229        } else {
230            return 32 * 4 + (sectorIndex - 32) * 16;
231        }
232    }
233
234    // Methods that require connect()
235    /**
236     * Authenticate a sector.
237     * <p>Every sector has an A and B key with different access privileges,
238     * this method attempts to authenticate against the A key.
239     * <p>This requires a that the tag be connected.
240     */
241    public boolean authenticateSectorWithKeyA(int sectorIndex, byte[] key) throws IOException {
242        return authenticate(sectorIndex, key, true);
243    }
244
245    /**
246     * Authenticate a sector.
247     * <p>Every sector has an A and B key with different access privileges,
248     * this method attempts to authenticate against the B key.
249     * <p>This requires a that the tag be connected.
250     */
251    public boolean authenticateSectorWithKeyB(int sectorIndex, byte[] key) throws IOException {
252        return authenticate(sectorIndex, key, false);
253    }
254
255    private boolean authenticate(int sector, byte[] key, boolean keyA) throws IOException {
256        validateSector(sector);
257        checkConnected();
258
259        byte[] cmd = new byte[12];
260
261        // First byte is the command
262        if (keyA) {
263            cmd[0] = 0x60; // phHal_eMifareAuthentA
264        } else {
265            cmd[0] = 0x61; // phHal_eMifareAuthentB
266        }
267
268        // Second byte is block address
269        // Authenticate command takes a block address. Authenticating a block
270        // of a sector will authenticate the entire sector.
271        cmd[1] = (byte) sectorToBlock(sector);
272
273        // Next 4 bytes are last 4 bytes of UID
274        byte[] uid = getTag().getId();
275        System.arraycopy(uid, uid.length - 4, cmd, 2, 4);
276
277        // Next 6 bytes are key
278        System.arraycopy(key, 0, cmd, 6, 6);
279
280        try {
281            if (transceive(cmd, false) != null) {
282                return true;
283            }
284        } catch (TagLostException e) {
285            throw e;
286        } catch (IOException e) {
287            // No need to deal with, will return false anyway
288        }
289        return false;
290    }
291
292    /**
293     * Read 16-byte block.
294     * <p>This requires a that the tag be connected.
295     * @throws IOException
296     */
297    public byte[] readBlock(int blockIndex) throws IOException {
298        validateBlock(blockIndex);
299        checkConnected();
300
301        byte[] cmd = { 0x30, (byte) blockIndex };
302        return transceive(cmd, false);
303    }
304
305    /**
306     * Write 16-byte block.
307     * <p>This requires a that the tag be connected.
308     * @throws IOException
309     */
310    public void writeBlock(int blockIndex, byte[] data) throws IOException {
311        validateBlock(blockIndex);
312        checkConnected();
313        if (data.length != 16) {
314            throw new IllegalArgumentException("must write 16-bytes");
315        }
316
317        byte[] cmd = new byte[data.length + 2];
318        cmd[0] = (byte) 0xA0; // MF write command
319        cmd[1] = (byte) blockIndex;
320        System.arraycopy(data, 0, cmd, 2, data.length);
321
322        transceive(cmd, false);
323    }
324
325    /**
326     * Increment a value block, and store the result in temporary memory.
327     * @param blockIndex
328     * @throws IOException
329     */
330    public void increment(int blockIndex, int value) throws IOException {
331        validateBlock(blockIndex);
332        validateValueOperand(value);
333        checkConnected();
334
335        ByteBuffer cmd = ByteBuffer.allocate(6);
336        cmd.order(ByteOrder.LITTLE_ENDIAN);
337        cmd.put( (byte) 0xC1 );
338        cmd.put( (byte) blockIndex );
339        cmd.putInt(value);
340
341        transceive(cmd.array(), false);
342    }
343
344    /**
345     * Decrement a value block, and store the result in temporary memory.
346     * @param blockIndex
347     * @throws IOException
348     */
349    public void decrement(int blockIndex, int value) throws IOException {
350        validateBlock(blockIndex);
351        validateValueOperand(value);
352        checkConnected();
353
354        ByteBuffer cmd = ByteBuffer.allocate(6);
355        cmd.order(ByteOrder.LITTLE_ENDIAN);
356        cmd.put( (byte) 0xC0 );
357        cmd.put( (byte) blockIndex );
358        cmd.putInt(value);
359
360        transceive(cmd.array(), false);
361    }
362
363    private void validateValueOperand(int value) {
364        if (value < 0) {
365            throw new IllegalArgumentException("value operand negative");
366        }
367    }
368
369    /**
370     * Copy from temporary memory to value block.
371     * @param blockIndex
372     * @throws IOException
373     */
374    public void transfer(int blockIndex) throws IOException {
375        validateBlock(blockIndex);
376        checkConnected();
377
378        byte[] cmd = { (byte) 0xB0, (byte) blockIndex };
379
380        transceive(cmd, false);
381    }
382
383    /**
384     * Copy from value block to temporary memory.
385     * @param blockIndex
386     * @throws IOException
387     */
388    public void restore(int blockIndex) throws IOException {
389        validateBlock(blockIndex);
390        checkConnected();
391
392        byte[] cmd = { (byte) 0xC2, (byte) blockIndex };
393
394        transceive(cmd, false);
395    }
396
397    /**
398     * Send raw NfcA data to a tag and receive the response.
399     * <p>
400     * This method will block until the response is received. It can be canceled
401     * with {@link #close}.
402     * <p>Requires {@link android.Manifest.permission#NFC} permission.
403     * <p>This requires a that the tag be connected.
404     *
405     * @param data bytes to send
406     * @return bytes received in response
407     * @throws IOException if the target is lost or connection closed
408     */
409    public byte[] transceive(byte[] data) throws IOException {
410        return transceive(data, true);
411    }
412
413    private void validateSector(int sector) {
414        // Do not be too strict on upper bounds checking, since some cards
415        // have more addressable memory than they report. For example,
416        // Mifare Plus 2k cards will appear as Mifare Classic 1k cards when in
417        // Mifare Classic compatibility mode.
418        // Note that issuing a command to an out-of-bounds block is safe - the
419        // tag should report error causing IOException. This validation is a
420        // helper to guard against obvious programming mistakes.
421        if (sector < 0 || sector >= MAX_SECTOR_COUNT) {
422            throw new IndexOutOfBoundsException("sector out of bounds: " + sector);
423        }
424    }
425
426    private void validateBlock(int block) {
427        // Just looking for obvious out of bounds...
428        if (block < 0 || block >= MAX_BLOCK_COUNT) {
429            throw new IndexOutOfBoundsException("block out of bounds: " + block);
430        }
431    }
432}
433