diff --git a/scenarios/RFID_SCENARIO_PATTERNS.md b/scenarios/RFID_SCENARIO_PATTERNS.md new file mode 100644 index 0000000..e686a50 --- /dev/null +++ b/scenarios/RFID_SCENARIO_PATTERNS.md @@ -0,0 +1,264 @@ +# RFID Scenario Patterns - Correct Usage + +This document shows the **correct patterns** for creating RFID scenarios and Ink scripts in this project. + +## ✅ Correct Ink Pattern + +### Hub Structure with `#exit_conversation` + +```ink +VAR has_keycard = false +VAR has_rfid_cloner = false + +// Card protocol variables (auto-synced from NPC itemsHeld) +VAR card_protocol = "" +VAR card_instant_clone = false +VAR card_card_id = "" + +=== start === +# speaker:npc +Hello! I'm a guard. +-> hub + +=== hub === +// Main conversation hub + ++ [Option 1] + -> some_knot + ++ [Leave] #exit_conversation + # speaker:npc + Goodbye! + -> hub // Return to hub, not END + +=== some_knot === +# speaker:npc +Some dialogue here. +-> hub // Always return to hub +``` + +### Key Rules: + +1. **Use `#exit_conversation` tag** - NOT `-> END` +2. **Tag goes on the choice line** - `+ [Leave] #exit_conversation` +3. **After exit, return to hub** - `-> hub` (not `-> END`) +4. **Always have a hub knot** - Central return point for all conversations +5. **All paths return to hub** - Enables conversation to resume + +## ✅ Correct Scenario JSON Pattern + +### Use `card_id` Format (New) + +```json +{ + "npcId": "security_guard", + "itemsHeld": [ + { + "type": "keycard", + "card_id": "employee_badge", + "rfid_protocol": "EM4100", + "name": "Employee Badge" + } + ] +} +``` + +**Benefits:** +- No manual hex/UID entry needed +- Deterministic generation from card_id +- Simpler for scenario designers +- Supports all 4 protocols + +### Door Configuration + +```json +{ + "lockType": "rfid", + "requires": ["employee_badge", "master_card"], + "acceptsUIDOnly": false +} +``` + +**Key Points:** +- `requires` is an **array** of card_ids +- `acceptsUIDOnly` for DESFire UID emulation +- Use card_id, not key_id + +## ❌ Incorrect Patterns (DO NOT USE) + +### ❌ Wrong Ink Pattern + +```ink +=== hub === ++ [Leave] + # speaker:npc + Goodbye! + -> END // ❌ WRONG - breaks conversation state +``` + +**Problems:** +- Uses `-> END` - conversation can't be resumed +- Doesn't return to hub +- State isn't saved properly + +### ❌ Wrong Scenario Pattern (Legacy) + +```json +{ + "type": "keycard", + "rfid_hex": "FF4A7B9C21", + "rfid_facility": 255, + "rfid_card_number": 18811, + "key_id": "master_keycard" +} +``` + +**Problems:** +- Manual hex entry required +- Uses old `key_id` instead of `card_id` +- Doesn't work with new protocol system +- More complex for scenario designers + +## Protocol-Specific Examples + +### EM4100 (Instant Clone) + +```ink +{has_keycard and card_instant_clone and card_protocol == "EM4100": + + [Scan badge] #clone_keycard:{card_card_id} + # speaker:player + Quick scan - card cloned! + -> success +} +``` + +### MIFARE Classic - Weak Defaults (Dictionary Attack) + +```ink +{card_instant_clone and card_protocol == "MIFARE_Classic_Weak_Defaults": + + [Scan badge] #clone_keycard:{card_card_id} + # speaker:player + Dictionary attack succeeded instantly! + -> success +} +``` + +### MIFARE Classic - Custom Keys (Needs Attack) + +```ink +{card_needs_attack: + + [Try to scan] + # speaker:player + Encrypted - need Darkside attack (~30 sec) + -> needs_time +} +``` + +### MIFARE DESFire (UID Only) + +```ink +{card_uid_only: + + [Save UID only] + # speaker:player + Saved UID - only works on weak readers + -> uid_saved +} +``` + +## Complete Working Example + +See these files for complete examples: +- `scenarios/test-rfid-multiprotocol.json` - All 4 protocols +- `scenarios/ink/rfid-guard-low.ink` - EM4100 example +- `scenarios/ink/rfid-guard-custom.ink` - Custom keys example +- `scenarios/ink/rfid-security-guard-fixed.ink` - Full featured example + +## Common Mistakes + +### ❌ Mistake 1: Using `-> END` for exits +```ink ++ [Leave] + Goodbye! + -> END // ❌ WRONG +``` + +**✅ Correct:** +```ink ++ [Leave] #exit_conversation + # speaker:npc + Goodbye! + -> hub // ✅ RIGHT +``` + +### ❌ Mistake 2: Not returning to hub +```ink +=== some_knot === +Dialog here. +// ❌ Falls off end of knot +``` + +**✅ Correct:** +```ink +=== some_knot === +Dialog here. +-> hub // ✅ Always return to hub +``` + +### ❌ Mistake 3: Using legacy card format +```json +{ + "rfid_hex": "01AB34CD56", + "key_id": "employee_badge" +} +``` + +**✅ Correct:** +```json +{ + "card_id": "employee_badge", + "rfid_protocol": "EM4100" +} +``` + +### ❌ Mistake 4: Single card_id instead of array +```json +{ + "requires": "employee_badge" // ❌ Works but not preferred +} +``` + +**✅ Correct:** +```json +{ + "requires": ["employee_badge", "master_card"] // ✅ Array format +} +``` + +## Protocol Names (Use These Exact Strings) + +``` +"EM4100" // Low security, instant +"MIFARE_Classic_Weak_Defaults" // Low security, instant dictionary +"MIFARE_Classic_Custom_Keys" // Medium security, 30sec Darkside +"MIFARE_DESFire" // High security, UID only +``` + +## Testing Checklist + +When creating RFID scenarios, verify: + +- [ ] Ink file uses `#exit_conversation` tag +- [ ] All knots return to hub +- [ ] Card variables declared at top +- [ ] Uses `card_id` format in JSON +- [ ] Uses `rfid_protocol` with correct protocol name +- [ ] Door `requires` is an array +- [ ] No manual hex/UID entry +- [ ] Hub pattern implemented correctly +- [ ] #clone_keycard tag uses `{card_card_id}` variable + +## Reference Documentation + +For more details see: +- `scenarios/ink/README_RFID_VARIABLES.md` - Complete Ink variable reference +- `planning_notes/rfid_keycard/protocols_and_interactions/00_IMPLEMENTATION_SUMMARY.md` - Technical implementation details diff --git a/scenarios/ink/rfid-guard-custom.ink b/scenarios/ink/rfid-guard-custom.ink new file mode 100644 index 0000000..145ba27 --- /dev/null +++ b/scenarios/ink/rfid-guard-custom.ink @@ -0,0 +1,105 @@ +// rfid-guard-custom.ink +// Guard with MIFARE Classic (Custom Keys) - requires Darkside attack +// Demonstrates attack requirement pattern + +VAR has_keycard = false +VAR has_rfid_cloner = false + +// Card protocol variables (auto-synced) +VAR card_protocol = "" +VAR card_name = "" +VAR card_card_id = "" +VAR card_needs_attack = false +VAR card_uid = "" + +=== start === +# speaker:npc +Hey. I'm in charge of corporate security. + +{has_keycard: + This badge uses MIFARE Classic with custom encryption keys. + + Much more secure than those old EM4100 cards. +} + +-> hub + +=== hub === + +{has_keycard: + + [Ask about badge security] + -> ask_security +} + +{has_keycard and has_rfid_cloner and card_needs_attack: + + [Try to scan their badge] + # speaker:player + You try to scan, but it's encrypted... + -> needs_attack +} + ++ [Chat about security] + -> chat_security + ++ [Leave] #exit_conversation + # speaker:npc + Stay safe. + -> hub + +=== ask_security === +# speaker:npc +This badge? It's a MIFARE Classic 1K with custom encryption keys. + +Much better than the factory defaults some companies use. + +Can't just clone these with a quick scan. The crypto is... well, it's broken technically, but it takes time to crack. + ++ [How long to crack?] + # speaker:npc + With the right tools? Maybe 30 seconds using a Darkside attack. + + But most people don't have those tools. + -> hub + ++ [Interesting...] + -> hub + +=== chat_security === +# speaker:npc +Corporate security is no joke. We take access control seriously. + +All our badges use custom keys. Random generation, changed quarterly. + +The CEO even has a DESFire card - that's military-grade encryption. + +-> hub + +=== needs_attack === +# speaker:npc +What are you doing? + +# speaker:player +Oh, just... checking my phone! + +# speaker:npc +That looked like you were trying to scan my badge. + +You'd need to run a proper attack to get this one. Can't just quick-clone it. + +* [Play it cool] + # speaker:player + Sorry, my device sometimes picks up NFC signals by accident. + # speaker:npc + Uh huh. Sure. + -> hub + +* [Tell the truth] + # speaker:player + You're right - I was trying to clone it. But it's encrypted. + # speaker:npc + Yeah, that's the point of custom keys. + + You'd need to be close for about 30 seconds to run a Darkside attack. + + Good luck with that while I'm watching! + -> hub diff --git a/scenarios/ink/rfid-guard-low.ink b/scenarios/ink/rfid-guard-low.ink new file mode 100644 index 0000000..ac92296 --- /dev/null +++ b/scenarios/ink/rfid-guard-low.ink @@ -0,0 +1,76 @@ +// rfid-guard-low.ink +// Guard with EM4100 card (instant clone) +// Demonstrates proper hub pattern with #exit_conversation + +VAR has_keycard = false +VAR has_rfid_cloner = false + +// Card protocol variables (auto-synced) +VAR card_protocol = "" +VAR card_name = "" +VAR card_card_id = "" +VAR card_instant_clone = false + +=== start === +# speaker:npc +Hi! I work security here at the building. + +{has_keycard: + This badge on my belt? Just a basic EM4100 card. Nothing fancy. +} + +-> hub + +=== hub === + +{has_keycard: + + [Ask about the badge] + -> ask_badge +} + +{has_keycard and has_rfid_cloner and card_instant_clone: + + [Casually scan their badge] #clone_keycard:{card_card_id} + # speaker:player + You position your Flipper Zero near their badge while chatting... + # speaker:npc + ...and that's when I realized I'd left my lunch at home! + -> cloned +} + ++ [Chat about the job] + -> chat_job + ++ [Leave] #exit_conversation + # speaker:npc + See you around! + -> hub + +=== ask_badge === +# speaker:npc +Oh, this old thing? Yeah, it's one of those 125kHz proximity cards. + +Pretty basic technology. I just wave it at the reader and it opens. + +No PIN or anything - just the card itself. + +-> hub + +=== chat_job === +# speaker:npc +The job's not bad. Mostly just sitting at the desk and checking people in. + +I get to read a lot during my shifts. The night shift especially is pretty quiet. + +-> hub + +=== cloned === +# speaker:player +[You've successfully cloned the {card_name}!] + +# speaker:npc +Anyway, I should probably get back to my post. + ++ [Thanks for chatting] #exit_conversation + # speaker:npc + No problem! Have a good day! + -> hub diff --git a/scenarios/ink/rfid-security-guard-fixed.ink b/scenarios/ink/rfid-security-guard-fixed.ink new file mode 100644 index 0000000..8f9497f --- /dev/null +++ b/scenarios/ink/rfid-security-guard-fixed.ink @@ -0,0 +1,133 @@ +// rfid-security-guard.ink (FIXED) +// Security Guard NPC for RFID test scenario +// Demonstrates proper hub pattern and #exit_conversation usage + +VAR has_keycard = false +VAR has_rfid_cloner = false +VAR conversation_count = 0 + +// Card protocol variables (auto-synced from NPC itemsHeld) +VAR card_protocol = "" +VAR card_name = "" +VAR card_card_id = "" +VAR card_security = "" +VAR card_instant_clone = false +VAR card_needs_attack = false +VAR card_uid_only = false +VAR card_uid = "" +VAR card_hex = "" + +=== start === +~ conversation_count += 1 +# speaker:npc +Hey there. I'm the security guard for this facility. + +{has_keycard: + I've got the master keycard that opens the secure room. + {card_protocol == "EM4100": + It's a basic EM4100 card - nothing fancy. + } + {card_security == "medium": + This one's got proper encryption. Corporate security. + } +} + +-> hub + +=== hub === +// Main conversation hub + +{has_keycard and not card_instant_clone: + + [Ask about the keycard security] + -> ask_security +} + +{has_keycard: + + [Ask about the keycard] + -> ask_keycard +} + +{has_keycard and has_rfid_cloner and card_instant_clone: + + [Subtly scan their badge] #clone_keycard:{card_card_id} + # speaker:player + You casually position your Flipper Zero near their badge... + -> cloned_success +} + +{has_keycard and has_rfid_cloner and card_needs_attack: + + [Scan badge (requires attack)] + # speaker:player + You try to scan their badge, but it's encrypted. + # speaker:player + You'll need to run a Darkside attack - this will take about 30 seconds. + -> needs_attack +} + ++ [Just browsing] #exit_conversation + # speaker:npc + Alright, let me know if you need anything. + -> hub + +=== ask_security === +# speaker:npc +{card_security == "low": + Honestly? It's just a basic proximity card. Nothing special. + + The company's been meaning to upgrade for years... +- else: + This card uses {card_protocol} with custom encryption. + + Pretty secure stuff. Can't just clone these easily. +} +-> hub + +=== ask_keycard === +# speaker:npc +This keycard? Yeah, it's the master access card. Opens everything in the building. + +I can't just hand it to you though - security policy and all that. + ++ [Offer to buy it] + # speaker:npc + Ha! Nice try, but I can't sell company property. I'd lose my job. + -> hub + ++ [Ask if you can borrow it] + # speaker:npc + Sorry, no can do. This thing never leaves my person. + -> hub + ++ [Back] + -> hub + +=== cloned_success === +# speaker:npc +...So anyway, that's why I love working nights. Much quieter, you know? + +The pay's better too. Plus I get to catch up on my podcasts. + ++ [Thanks for the chat!] #exit_conversation + # speaker:npc + No problem! Stay safe out there. + -> hub + ++ [Any other secure areas?] + # speaker:npc + Well, there's the CEO's office, but that's on a different floor entirely. + + This master card works for most areas on this level though. + -> hub + +=== needs_attack === +# speaker:npc +Hey, what are you doing with that device? + +# speaker:player +Oh, just... checking the time! + +# speaker:npc +That didn't look like checking the time... + +You'll need to be more subtle. Or find a way to get the card when they're not looking. + +-> hub diff --git a/scenarios/test-rfid-multiprotocol.json b/scenarios/test-rfid-multiprotocol.json new file mode 100644 index 0000000..99ea3a7 --- /dev/null +++ b/scenarios/test-rfid-multiprotocol.json @@ -0,0 +1,267 @@ +{ + "name": "RFID Multi-Protocol Test", + "description": "Comprehensive test for all 4 RFID protocols with attacks", + "scenario_brief": "Test all RFID protocols: EM4100, MIFARE Classic (weak/custom), and DESFire", + "startRoom": "lobby", + "globalVariables": {}, + "player": { + "id": "player", + "displayName": "Agent 0x00", + "spriteSheet": "hacker", + "startX": 200, + "startY": 200 + }, + "startItemsInInventory": [ + { + "type": "rfid_cloner", + "name": "Flipper Zero", + "saved_cards": [] + } + ], + "rooms": { + "lobby": { + "name": "Test Lobby", + "type": "room_reception", + "connections": { + "north": "low_security", + "east": "medium_security", + "south": "high_security" + }, + "npcs": [ + { + "id": "guard_low", + "displayName": "Guard (Low Security)", + "npcType": "person", + "position": { "x": 3, "y": 3 }, + "spriteSheet": "hacker-red", + "spriteConfig": { + "idleFrameStart": 20, + "idleFrameEnd": 23 + }, + "storyPath": "scenarios/ink/rfid-guard-low.json", + "currentKnot": "start", + "itemsHeld": [ + { + "type": "keycard", + "card_id": "employee_badge", + "rfid_protocol": "EM4100", + "name": "Employee Badge" + } + ] + }, + { + "id": "guard_weak", + "displayName": "Guard (Weak MIFARE)", + "npcType": "person", + "position": { "x": 6, "y": 3 }, + "spriteSheet": "hacker-blue", + "spriteConfig": { + "idleFrameStart": 20, + "idleFrameEnd": 23 + }, + "storyPath": "scenarios/ink/rfid-guard-weak.json", + "currentKnot": "start", + "itemsHeld": [ + { + "type": "keycard", + "card_id": "hotel_keycard", + "rfid_protocol": "MIFARE_Classic_Weak_Defaults", + "name": "Hotel Keycard" + } + ] + }, + { + "id": "guard_custom", + "displayName": "Guard (Custom Keys)", + "npcType": "person", + "position": { "x": 9, "y": 3 }, + "spriteSheet": "hacker-green", + "spriteConfig": { + "idleFrameStart": 20, + "idleFrameEnd": 23 + }, + "storyPath": "scenarios/ink/rfid-guard-custom.json", + "currentKnot": "start", + "itemsHeld": [ + { + "type": "keycard", + "card_id": "corporate_badge", + "rfid_protocol": "MIFARE_Classic_Custom_Keys", + "name": "Corporate Badge" + } + ] + }, + { + "id": "guard_high", + "displayName": "Guard (DESFire)", + "npcType": "person", + "position": { "x": 12, "y": 3 }, + "spriteSheet": "hacker-yellow", + "spriteConfig": { + "idleFrameStart": 20, + "idleFrameEnd": 23 + }, + "storyPath": "scenarios/ink/rfid-guard-desfire.json", + "currentKnot": "start", + "itemsHeld": [ + { + "type": "keycard", + "card_id": "executive_card", + "rfid_protocol": "MIFARE_DESFire", + "name": "Executive Card" + } + ] + } + ], + "objects": [ + { + "type": "notes", + "name": "Protocol Guide", + "x": 250, + "y": 250, + "takeable": true, + "readable": true, + "note_title": "RFID Protocol Security Levels", + "note_content": "🟥 LOW SECURITY (Instant Clone):\n • EM4100 (125kHz) - Old tech, no encryption\n • MIFARE Classic (Default Keys) - Factory defaults\n\n🟦 MEDIUM SECURITY (Requires Attack):\n • MIFARE Classic (Custom Keys) - 30 sec Darkside attack\n\n🟩 HIGH SECURITY (UID Only):\n • MIFARE DESFire - Strong encryption, can't crack\n • Only UID emulation works on weak readers", + "observations": "Guide to the 4 RFID protocols" + } + ], + "doors": [ + { + "roomId": "lobby", + "connectedRoom": "low_security", + "direction": "north", + "x": 300, + "y": 100, + "locked": true, + "lockType": "rfid", + "requires": ["employee_badge"] + }, + { + "roomId": "lobby", + "connectedRoom": "medium_security", + "direction": "east", + "x": 600, + "y": 300, + "locked": true, + "lockType": "rfid", + "requires": ["hotel_keycard"] + }, + { + "roomId": "lobby", + "connectedRoom": "high_security", + "direction": "south", + "x": 300, + "y": 500, + "locked": true, + "lockType": "rfid", + "requires": ["corporate_badge", "executive_card"], + "acceptsUIDOnly": false + } + ] + }, + "low_security": { + "name": "Low Security Room (EM4100)", + "type": "room_office", + "connections": { + "south": "lobby" + }, + "npcs": [], + "objects": [ + { + "type": "notes", + "name": "Success - EM4100", + "x": 300, + "y": 300, + "takeable": true, + "readable": true, + "note_title": "✓ EM4100 Test Passed", + "note_content": "You successfully cloned an EM4100 card!\n\nThis protocol:\n• 125kHz frequency\n• No encryption\n• Instant clone\n• Used in: Old hotels, parking garages", + "observations": "EM4100 test passed" + } + ], + "doors": [ + { + "roomId": "low_security", + "connectedRoom": "lobby", + "direction": "south", + "x": 300, + "y": 500, + "locked": false + } + ] + }, + "medium_security": { + "name": "Medium Security Room (Weak MIFARE)", + "type": "room_office", + "connections": { + "west": "lobby" + }, + "npcs": [], + "objects": [ + { + "type": "notes", + "name": "Success - MIFARE Weak", + "x": 300, + "y": 300, + "takeable": true, + "readable": true, + "note_title": "✓ MIFARE Weak Defaults Test Passed", + "note_content": "You cloned a MIFARE Classic with default keys!\n\nThis protocol:\n• 13.56MHz NFC\n• Encrypted but uses FFFFFFFFFFFF keys\n• Dictionary attack succeeds instantly (~95%)\n• Used in: Cheap hotels, old transit cards", + "observations": "MIFARE weak defaults test passed" + } + ], + "doors": [ + { + "roomId": "medium_security", + "connectedRoom": "lobby", + "direction": "west", + "x": 100, + "y": 300, + "locked": false + } + ] + }, + "high_security": { + "name": "High Security Room (Custom MIFARE / DESFire)", + "type": "room_server", + "connections": { + "north": "lobby" + }, + "npcs": [], + "objects": [ + { + "type": "notes", + "name": "Success - High Security", + "x": 300, + "y": 300, + "takeable": true, + "readable": true, + "note_title": "✓ High Security Protocols Test Passed", + "note_content": "You accessed high security!\n\nMIFARE Classic (Custom Keys):\n• Requires 30-second Darkside attack\n• Used in: Corporate badges, banks\n\nMIFARE DESFire:\n• Can't be cracked - UID only\n• Only works on poorly-configured readers\n• Used in: Government, military", + "observations": "High security test passed" + }, + { + "type": "keycard", + "card_id": "master_override", + "rfid_protocol": "EM4100", + "name": "Master Override Card", + "x": 350, + "y": 300, + "takeable": true, + "observations": "A master override card that opens all doors" + } + ], + "doors": [ + { + "roomId": "high_security", + "connectedRoom": "lobby", + "direction": "north", + "x": 300, + "y": 100, + "locked": false + } + ] + } + } +}