mirror of
https://github.com/Chewbaccalakis/rfid-database.git
synced 2026-09-10 00:11:56 -07:00
Accept Proxmark dump uploads, mfc v2 JSON, and key.bin
Parse object-shaped mfc v2 dumps (the format Proxmark writes today) and let the new-tag form upload dump.json/dump.bin plus an optional binary hf-mf-*-key.bin instead of only pasting text.
This commit is contained in:
+269
@@ -0,0 +1,269 @@
|
||||
{
|
||||
"Created": "proxmark3",
|
||||
"FileType": "mfc v2",
|
||||
"Card": {
|
||||
"UID": "91A2F020",
|
||||
"ATQA": "0400",
|
||||
"SAK": "08"
|
||||
},
|
||||
"blocks": {
|
||||
"0": "91A2F020E30804000497F47A359E4798",
|
||||
"1": "54C017569969134569ED95BD2C8A95FE",
|
||||
"2": "8A000400010000000000000000000000",
|
||||
"3": "A1D4FAFD4CAFFF078069FFFFFFFFFFFF",
|
||||
"4": "2200020000000000000000C10000001E",
|
||||
"5": "2200020000000000000000C10000001E",
|
||||
"6": "00000000000000000000000000000000",
|
||||
"7": "2A2C13CC242AFF078069FFFFFFFFFFFF",
|
||||
"8": "00000000000000000000000000000000",
|
||||
"9": "00000000000000000000000000000000",
|
||||
"10": "00000000000000000000000000000000",
|
||||
"11": "FFFFFFFFFFFFFF078069FFFFFFFFFFFF",
|
||||
"12": "00000000000000000000000000000000",
|
||||
"13": "00000000000000000000000000000000",
|
||||
"14": "00000000000000000000000000000000",
|
||||
"15": "A1D4FAFD4CAFFF078069FFFFFFFFFFFF",
|
||||
"16": "AE48B81460060028AE48B81C200600F0",
|
||||
"17": "00000000000000000000000000000000",
|
||||
"18": "00000000000000000000000000000000",
|
||||
"19": "A1D4FAFD4CAFFF078069FFFFFFFFFFFF",
|
||||
"20": "00000000000000000000000000000000",
|
||||
"21": "00000000000000000000000000000000",
|
||||
"22": "00000000000000000000000000000000",
|
||||
"23": "A1D4FAFD4CAFFF078069FFFFFFFFFFFF",
|
||||
"24": "00000000000000000000000000000000",
|
||||
"25": "00000000000000000000000000000000",
|
||||
"26": "00000000000000000000000000000000",
|
||||
"27": "A1D4FAFD4CAFFF078069FFFFFFFFFFFF",
|
||||
"28": "00000000000000000000000000000000",
|
||||
"29": "00000000000000000000000000000000",
|
||||
"30": "00000000000000000000000000000000",
|
||||
"31": "A1D4FAFD4CAFFF078069FFFFFFFFFFFF",
|
||||
"32": "00000000000000000000000000000000",
|
||||
"33": "00000000000000000000000000000000",
|
||||
"34": "00000000000000000000000000000000",
|
||||
"35": "A1D4FAFD4CAFFF078069FFFFFFFFFFFF",
|
||||
"36": "00000000000000000000000000000000",
|
||||
"37": "00000000000000000000000000000000",
|
||||
"38": "00000000000000000000000000000000",
|
||||
"39": "A1D4FAFD4CAFFF078069FFFFFFFFFFFF",
|
||||
"40": "00000000000000000000000000000000",
|
||||
"41": "00000000000000000000000000000000",
|
||||
"42": "00000000000000000000000000000000",
|
||||
"43": "A1D4FAFD4CAFFF078069FFFFFFFFFFFF",
|
||||
"44": "00000000000000000000000000000000",
|
||||
"45": "00000000000000000000000000000000",
|
||||
"46": "00000000000000000000000000000000",
|
||||
"47": "A1D4FAFD4CAFFF078069FFFFFFFFFFFF",
|
||||
"48": "00000000000000000000000000000000",
|
||||
"49": "00000000000000000000000000000000",
|
||||
"50": "00000000000000000000000000000000",
|
||||
"51": "A1D4FAFD4CAFFF078069FFFFFFFFFFFF",
|
||||
"52": "00000000000000000000000000000000",
|
||||
"53": "00000000000000000000000000000000",
|
||||
"54": "00000000000000000000000000000000",
|
||||
"55": "A1D4FAFD4CAFFF078069FFFFFFFFFFFF",
|
||||
"56": "00000000000000000000000000000000",
|
||||
"57": "00000000000000000000000000000000",
|
||||
"58": "00000000000000000000000000000000",
|
||||
"59": "A1D4FAFD4CAFFF078069FFFFFFFFFFFF",
|
||||
"60": "00000000000000000000000000000000",
|
||||
"61": "00000000000000000000000000000000",
|
||||
"62": "00000000000000000000000000000000",
|
||||
"63": "A1D4FAFD4CAFFF078069FFFFFFFFFFFF"
|
||||
},
|
||||
"SectorKeys": {
|
||||
"0": {
|
||||
"KeyA": "A1D4FAFD4CAF",
|
||||
"KeyB": "FFFFFFFFFFFF",
|
||||
"AccessConditions": "FF078069",
|
||||
"AccessConditionsText": {
|
||||
"block0": "read AB; write AB; increment AB; decrement transfer restore AB",
|
||||
"block1": "read AB; write AB; increment AB; decrement transfer restore AB",
|
||||
"block2": "read AB; write AB; increment AB; decrement transfer restore AB",
|
||||
"block3": "write A by A; read/write ACCESS by A; read/write B by A",
|
||||
"UserData": "69"
|
||||
}
|
||||
},
|
||||
"1": {
|
||||
"KeyA": "2A2C13CC242A",
|
||||
"KeyB": "FFFFFFFFFFFF",
|
||||
"AccessConditions": "FF078069",
|
||||
"AccessConditionsText": {
|
||||
"block4": "read AB; write AB; increment AB; decrement transfer restore AB",
|
||||
"block5": "read AB; write AB; increment AB; decrement transfer restore AB",
|
||||
"block6": "read AB; write AB; increment AB; decrement transfer restore AB",
|
||||
"block7": "write A by A; read/write ACCESS by A; read/write B by A",
|
||||
"UserData": "69"
|
||||
}
|
||||
},
|
||||
"2": {
|
||||
"KeyA": "FFFFFFFFFFFF",
|
||||
"KeyB": "FFFFFFFFFFFF",
|
||||
"AccessConditions": "FF078069",
|
||||
"AccessConditionsText": {
|
||||
"block8": "read AB; write AB; increment AB; decrement transfer restore AB",
|
||||
"block9": "read AB; write AB; increment AB; decrement transfer restore AB",
|
||||
"block10": "read AB; write AB; increment AB; decrement transfer restore AB",
|
||||
"block11": "write A by A; read/write ACCESS by A; read/write B by A",
|
||||
"UserData": "69"
|
||||
}
|
||||
},
|
||||
"3": {
|
||||
"KeyA": "A1D4FAFD4CAF",
|
||||
"KeyB": "FFFFFFFFFFFF",
|
||||
"AccessConditions": "FF078069",
|
||||
"AccessConditionsText": {
|
||||
"block12": "read AB; write AB; increment AB; decrement transfer restore AB",
|
||||
"block13": "read AB; write AB; increment AB; decrement transfer restore AB",
|
||||
"block14": "read AB; write AB; increment AB; decrement transfer restore AB",
|
||||
"block15": "write A by A; read/write ACCESS by A; read/write B by A",
|
||||
"UserData": "69"
|
||||
}
|
||||
},
|
||||
"4": {
|
||||
"KeyA": "A1D4FAFD4CAF",
|
||||
"KeyB": "FFFFFFFFFFFF",
|
||||
"AccessConditions": "FF078069",
|
||||
"AccessConditionsText": {
|
||||
"block16": "read AB; write AB; increment AB; decrement transfer restore AB",
|
||||
"block17": "read AB; write AB; increment AB; decrement transfer restore AB",
|
||||
"block18": "read AB; write AB; increment AB; decrement transfer restore AB",
|
||||
"block19": "write A by A; read/write ACCESS by A; read/write B by A",
|
||||
"UserData": "69"
|
||||
}
|
||||
},
|
||||
"5": {
|
||||
"KeyA": "A1D4FAFD4CAF",
|
||||
"KeyB": "FFFFFFFFFFFF",
|
||||
"AccessConditions": "FF078069",
|
||||
"AccessConditionsText": {
|
||||
"block20": "read AB; write AB; increment AB; decrement transfer restore AB",
|
||||
"block21": "read AB; write AB; increment AB; decrement transfer restore AB",
|
||||
"block22": "read AB; write AB; increment AB; decrement transfer restore AB",
|
||||
"block23": "write A by A; read/write ACCESS by A; read/write B by A",
|
||||
"UserData": "69"
|
||||
}
|
||||
},
|
||||
"6": {
|
||||
"KeyA": "A1D4FAFD4CAF",
|
||||
"KeyB": "FFFFFFFFFFFF",
|
||||
"AccessConditions": "FF078069",
|
||||
"AccessConditionsText": {
|
||||
"block24": "read AB; write AB; increment AB; decrement transfer restore AB",
|
||||
"block25": "read AB; write AB; increment AB; decrement transfer restore AB",
|
||||
"block26": "read AB; write AB; increment AB; decrement transfer restore AB",
|
||||
"block27": "write A by A; read/write ACCESS by A; read/write B by A",
|
||||
"UserData": "69"
|
||||
}
|
||||
},
|
||||
"7": {
|
||||
"KeyA": "A1D4FAFD4CAF",
|
||||
"KeyB": "FFFFFFFFFFFF",
|
||||
"AccessConditions": "FF078069",
|
||||
"AccessConditionsText": {
|
||||
"block28": "read AB; write AB; increment AB; decrement transfer restore AB",
|
||||
"block29": "read AB; write AB; increment AB; decrement transfer restore AB",
|
||||
"block30": "read AB; write AB; increment AB; decrement transfer restore AB",
|
||||
"block31": "write A by A; read/write ACCESS by A; read/write B by A",
|
||||
"UserData": "69"
|
||||
}
|
||||
},
|
||||
"8": {
|
||||
"KeyA": "A1D4FAFD4CAF",
|
||||
"KeyB": "FFFFFFFFFFFF",
|
||||
"AccessConditions": "FF078069",
|
||||
"AccessConditionsText": {
|
||||
"block32": "read AB; write AB; increment AB; decrement transfer restore AB",
|
||||
"block33": "read AB; write AB; increment AB; decrement transfer restore AB",
|
||||
"block34": "read AB; write AB; increment AB; decrement transfer restore AB",
|
||||
"block35": "write A by A; read/write ACCESS by A; read/write B by A",
|
||||
"UserData": "69"
|
||||
}
|
||||
},
|
||||
"9": {
|
||||
"KeyA": "A1D4FAFD4CAF",
|
||||
"KeyB": "FFFFFFFFFFFF",
|
||||
"AccessConditions": "FF078069",
|
||||
"AccessConditionsText": {
|
||||
"block36": "read AB; write AB; increment AB; decrement transfer restore AB",
|
||||
"block37": "read AB; write AB; increment AB; decrement transfer restore AB",
|
||||
"block38": "read AB; write AB; increment AB; decrement transfer restore AB",
|
||||
"block39": "write A by A; read/write ACCESS by A; read/write B by A",
|
||||
"UserData": "69"
|
||||
}
|
||||
},
|
||||
"10": {
|
||||
"KeyA": "A1D4FAFD4CAF",
|
||||
"KeyB": "FFFFFFFFFFFF",
|
||||
"AccessConditions": "FF078069",
|
||||
"AccessConditionsText": {
|
||||
"block40": "read AB; write AB; increment AB; decrement transfer restore AB",
|
||||
"block41": "read AB; write AB; increment AB; decrement transfer restore AB",
|
||||
"block42": "read AB; write AB; increment AB; decrement transfer restore AB",
|
||||
"block43": "write A by A; read/write ACCESS by A; read/write B by A",
|
||||
"UserData": "69"
|
||||
}
|
||||
},
|
||||
"11": {
|
||||
"KeyA": "A1D4FAFD4CAF",
|
||||
"KeyB": "FFFFFFFFFFFF",
|
||||
"AccessConditions": "FF078069",
|
||||
"AccessConditionsText": {
|
||||
"block44": "read AB; write AB; increment AB; decrement transfer restore AB",
|
||||
"block45": "read AB; write AB; increment AB; decrement transfer restore AB",
|
||||
"block46": "read AB; write AB; increment AB; decrement transfer restore AB",
|
||||
"block47": "write A by A; read/write ACCESS by A; read/write B by A",
|
||||
"UserData": "69"
|
||||
}
|
||||
},
|
||||
"12": {
|
||||
"KeyA": "A1D4FAFD4CAF",
|
||||
"KeyB": "FFFFFFFFFFFF",
|
||||
"AccessConditions": "FF078069",
|
||||
"AccessConditionsText": {
|
||||
"block48": "read AB; write AB; increment AB; decrement transfer restore AB",
|
||||
"block49": "read AB; write AB; increment AB; decrement transfer restore AB",
|
||||
"block50": "read AB; write AB; increment AB; decrement transfer restore AB",
|
||||
"block51": "write A by A; read/write ACCESS by A; read/write B by A",
|
||||
"UserData": "69"
|
||||
}
|
||||
},
|
||||
"13": {
|
||||
"KeyA": "A1D4FAFD4CAF",
|
||||
"KeyB": "FFFFFFFFFFFF",
|
||||
"AccessConditions": "FF078069",
|
||||
"AccessConditionsText": {
|
||||
"block52": "read AB; write AB; increment AB; decrement transfer restore AB",
|
||||
"block53": "read AB; write AB; increment AB; decrement transfer restore AB",
|
||||
"block54": "read AB; write AB; increment AB; decrement transfer restore AB",
|
||||
"block55": "write A by A; read/write ACCESS by A; read/write B by A",
|
||||
"UserData": "69"
|
||||
}
|
||||
},
|
||||
"14": {
|
||||
"KeyA": "A1D4FAFD4CAF",
|
||||
"KeyB": "FFFFFFFFFFFF",
|
||||
"AccessConditions": "FF078069",
|
||||
"AccessConditionsText": {
|
||||
"block56": "read AB; write AB; increment AB; decrement transfer restore AB",
|
||||
"block57": "read AB; write AB; increment AB; decrement transfer restore AB",
|
||||
"block58": "read AB; write AB; increment AB; decrement transfer restore AB",
|
||||
"block59": "write A by A; read/write ACCESS by A; read/write B by A",
|
||||
"UserData": "69"
|
||||
}
|
||||
},
|
||||
"15": {
|
||||
"KeyA": "A1D4FAFD4CAF",
|
||||
"KeyB": "FFFFFFFFFFFF",
|
||||
"AccessConditions": "FF078069",
|
||||
"AccessConditionsText": {
|
||||
"block60": "read AB; write AB; increment AB; decrement transfer restore AB",
|
||||
"block61": "read AB; write AB; increment AB; decrement transfer restore AB",
|
||||
"block62": "read AB; write AB; increment AB; decrement transfer restore AB",
|
||||
"block63": "write A by A; read/write ACCESS by A; read/write B by A",
|
||||
"UserData": "69"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -270,4 +270,38 @@ FFFFFFFFFFFFFF078069FFFFFFFFFFFF
|
||||
);
|
||||
expect(restore.status).toBe(200);
|
||||
});
|
||||
|
||||
it("imports Proxmark mfc v2 JSON dumps", async () => {
|
||||
const auth = { Authorization: `Bearer ${bearer}` };
|
||||
const siteRes = await postSite(
|
||||
req("http://localhost/api/v1/sites", {
|
||||
method: "POST",
|
||||
headers: { ...auth, "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ code: "PM3", name: "Proxmark" }),
|
||||
})
|
||||
);
|
||||
const site = await siteRes.json();
|
||||
const dump = fs.readFileSync(
|
||||
path.join(import.meta.dirname, "../fixtures/hf-mf-91A2F020-dump.json"),
|
||||
"utf8"
|
||||
);
|
||||
|
||||
const imported = await importTag(
|
||||
req("http://localhost/api/v1/tags/import", {
|
||||
method: "POST",
|
||||
headers: { ...auth, "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
siteId: site.id,
|
||||
label: "Proxmark v2",
|
||||
content: dump,
|
||||
filename: "hf-mf-91A2F020-dump.json",
|
||||
}),
|
||||
})
|
||||
);
|
||||
expect(imported.status).toBe(201);
|
||||
const tag = await imported.json();
|
||||
expect(tag.uid).toBe("91A2F020");
|
||||
expect(tag.protocol).toBe("MIFARE_CLASSIC_1K");
|
||||
expect(tag.keys.A[0]).toBe("A1D4FAFD4CAF");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { parseMct, parseImport } from "@/lib/rfid/parsers";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { parseMct, parseImport, parseDump, parseKeyBin } from "@/lib/rfid/parsers";
|
||||
import { exportTag } from "@/lib/rfid/exporters";
|
||||
import { mifareClassicDumpSchema } from "@/lib/validation/rfid";
|
||||
import { isMifareClassicDump } from "@/lib/rfid/dump-types";
|
||||
|
||||
const sampleMct = `+UID: 04A1B2C3
|
||||
+Sector: 0
|
||||
@@ -60,3 +63,91 @@ describe("MCT parser/exporter", () => {
|
||||
expect(tag.label).toBe("Dock");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Proxmark dump import", () => {
|
||||
const fixturePath = path.join(
|
||||
import.meta.dirname,
|
||||
"../fixtures/hf-mf-91A2F020-dump.json"
|
||||
);
|
||||
const proxmarkV2 = fs.readFileSync(fixturePath, "utf8");
|
||||
|
||||
it("parses mfc v2 JSON with object blocks and SectorKeys", () => {
|
||||
const tag = parseImport(proxmarkV2, "hf-mf-91A2F020-dump.json");
|
||||
expect(tag.uid).toBe("91A2F020");
|
||||
expect(tag.protocol).toBe("MIFARE_CLASSIC_1K");
|
||||
expect(mifareClassicDumpSchema.safeParse(tag.dumpData).success).toBe(true);
|
||||
expect(isMifareClassicDump(tag.dumpData)).toBe(true);
|
||||
if (isMifareClassicDump(tag.dumpData)) {
|
||||
expect(tag.dumpData.sectors).toHaveLength(16);
|
||||
expect(tag.dumpData.sectors[0].blocks[0]).toBe(
|
||||
"91A2F020E30804000497F47A359E4798"
|
||||
);
|
||||
}
|
||||
expect(tag.keys?.A?.[0]).toBe("A1D4FAFD4CAF");
|
||||
expect(tag.keys?.B?.[0]).toBe("FFFFFFFFFFFF");
|
||||
expect(tag.keys?.A?.[1]).toBe("2A2C13CC242A");
|
||||
expect(tag.keys?.A).toHaveLength(16);
|
||||
});
|
||||
|
||||
it("parses mfc v2 JSON pasted inside a markdown fence", () => {
|
||||
const tag = parseImport(
|
||||
"```json\n" + proxmarkV2 + "\n```",
|
||||
"paste.txt"
|
||||
);
|
||||
expect(tag.uid).toBe("91A2F020");
|
||||
expect(tag.keys?.A?.[0]).toBe("A1D4FAFD4CAF");
|
||||
});
|
||||
|
||||
it("parses a 192-byte 1K key.bin", () => {
|
||||
const bytes = new Uint8Array(192);
|
||||
bytes.set(hexToBytes("A1D4FAFD4CAF"), 0);
|
||||
bytes.set(hexToBytes("FFFFFFFFFFFF"), 6);
|
||||
bytes.set(hexToBytes("2A2C13CC242A"), 12);
|
||||
bytes.set(hexToBytes("FFFFFFFFFFFF"), 18);
|
||||
const keys = parseKeyBin(bytes, "hf-mf-91A2F020-key.bin");
|
||||
expect(keys.A).toHaveLength(16);
|
||||
expect(keys.A?.[0]).toBe("A1D4FAFD4CAF");
|
||||
expect(keys.B?.[0]).toBe("FFFFFFFFFFFF");
|
||||
expect(keys.A?.[1]).toBe("2A2C13CC242A");
|
||||
});
|
||||
|
||||
it("merges key.bin onto a dump that has no SectorKeys", () => {
|
||||
const dump = JSON.parse(proxmarkV2) as { SectorKeys?: unknown };
|
||||
delete dump.SectorKeys;
|
||||
const keys = new Uint8Array(192);
|
||||
keys.set(hexToBytes("AABBCCDDEEFF"), 0);
|
||||
keys.set(hexToBytes("112233445566"), 6);
|
||||
const tag = parseDump({
|
||||
text: JSON.stringify(dump),
|
||||
filename: "hf-mf-91A2F020-dump.json",
|
||||
keysBytes: keys,
|
||||
keysFilename: "hf-mf-91A2F020-key.bin",
|
||||
});
|
||||
expect(tag.keys?.A?.[0]).toBe("AABBCCDDEEFF");
|
||||
expect(tag.keys?.B?.[0]).toBe("112233445566");
|
||||
});
|
||||
|
||||
it("parses a 1024-byte dump.bin", () => {
|
||||
const bytes = new Uint8Array(1024);
|
||||
const block0 = hexToBytes("91A2F020E30804000497F47A359E4798");
|
||||
bytes.set(block0, 0);
|
||||
const tag = parseDump({ bytes, filename: "hf-mf-91A2F020.bin" });
|
||||
expect(tag.uid).toBe("91A2F020");
|
||||
expect(tag.protocol).toBe("MIFARE_CLASSIC_1K");
|
||||
});
|
||||
|
||||
it("rejects key.bin uploaded as the dump", () => {
|
||||
const bytes = new Uint8Array(192);
|
||||
expect(() =>
|
||||
parseDump({ bytes, filename: "hf-mf-91A2F020-key.bin" })
|
||||
).toThrow(/key\.bin/i);
|
||||
});
|
||||
});
|
||||
|
||||
function hexToBytes(hex: string): Uint8Array {
|
||||
const out = new Uint8Array(hex.length / 2);
|
||||
for (let i = 0; i < out.length; i++) {
|
||||
out[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user