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