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