feat(rfid): Implement multi-protocol RFID system with 4 protocols

Implement comprehensive multi-protocol RFID system with deterministic
card_id-based generation, MIFARE key attacks, and protocol-specific UI.

## New Protocol System (4 protocols):
- EM4100 (low security) - Instant clone, already implemented
- MIFARE_Classic_Weak_Defaults (low) - Dictionary attack succeeds (95%)
- MIFARE_Classic_Custom_Keys (medium) - Requires Darkside attack (30s)
- MIFARE_DESFire (high) - UID only, forces physical theft

## Key Features:

### 1. Protocol Foundation
- Created rfid-protocols.js with protocol definitions
- Added protocol detection, capabilities, security levels
- Defined attack durations and common MIFARE keys

### 2. Deterministic Card Generation
- Updated rfid-data.js with card_id-based generation
- Same card_id always produces same hex/UID (deterministic)
- Simplified scenario format - no manual hex/UID needed
- getCardDisplayData() supports all protocols

### 3. MIFARE Attack System
- Created rfid-attacks.js with MIFAREAttackManager
- Dictionary Attack: Instant, 95% success on weak defaults
- Darkside Attack: 30 sec (10s on weak), cracks all keys
- Nested Attack: 10 sec, uses known key to crack rest
- Protocol-aware attack behavior

### 4. UI Enhancements
- Updated rfid-ui.js with protocol-specific displays
- showProtocolInfo() with color-coded security badges
- showAttackProgress() and updateAttackProgress()
- Protocol headers with icons and frequency info
- Updated showCardDataScreen() and showEmulationScreen()

### 5. Unlock System Integration
- Updated unlock-system.js for card_id matching
- Support multiple valid cards per door (array)
- Added acceptsUIDOnly flag for DESFire UID emulation
- Backward compatible with legacy key_id format

### 6. Minigame Integration
- Updated rfid-minigame.js with attack methods
- startKeyAttack() triggers dictionary/darkside/nested
- handleCardTap() and handleEmulate() use card_id arrays
- UID-only emulation validation for DESFire
- Attack manager cleanup on minigame exit

### 7. Styling
- Added CSS for protocol headers and security badges
- Color-coded security levels (red=low, teal=medium, green=high)
- Attack progress styling with smooth transitions
- Dimmed menu items for unlikely attack options

## Scenario Format Changes:

Before (manual technical data):
```json
{
  "type": "keycard",
  "rfid_hex": "01AB34CD56",
  "rfid_facility": 1,
  "key_id": "employee_badge"
}
```

After (simplified with card_id):
```json
{
  "type": "keycard",
  "card_id": "employee_badge",
  "rfid_protocol": "MIFARE_Classic_Weak_Defaults",
  "name": "Employee Badge"
}
```

Technical data (hex/UID) generated automatically from card_id.

## Door Configuration:

Multiple valid cards per door:
```json
{
  "lockType": "rfid",
  "requires": ["employee_badge", "contractor_badge", "master_card"],
  "acceptsUIDOnly": false
}
```

## Files Modified:
- js/minigames/rfid/rfid-protocols.js (NEW)
- js/minigames/rfid/rfid-attacks.js (NEW)
- js/minigames/rfid/rfid-data.js
- js/minigames/rfid/rfid-ui.js
- js/minigames/rfid/rfid-minigame.js
- js/systems/unlock-system.js
- css/rfid-minigame.css
- planning_notes/rfid_keycard/protocols_and_interactions/03_UPDATES_SUMMARY.md (NEW)

## Next Steps:
- Phase 5: Ink integration (syncCardProtocolsToInk)
- Test with scenarios for each protocol
- Add Ink variable documentation

Estimated implementation time: ~12 hours (Phases 1-4 complete)
This commit is contained in:
Z. Cliffe Schreuders
2025-11-15 23:48:15 +00:00
parent d697eef3ca
commit 7ecda9d39d
9 changed files with 2318 additions and 878 deletions

View File

@@ -0,0 +1,466 @@
# RFID Protocols - Key Updates Summary
**Date**: Latest Revision
**Status**: Supersedes portions of 01_TECHNICAL_DESIGN.md and 02_IMPLEMENTATION_PLAN.md
This document summarizes the critical updates made after the initial planning review.
## Major Changes
### 1. Four Protocols Instead of Three
**Original Plan**: 3 protocols (EM4100, MIFARE_Classic, MIFARE_DESFire)
**Updated Plan**: 4 protocols by splitting MIFARE Classic:
```javascript
'EM4100' // Low - instant clone
'MIFARE_Classic_Weak_Defaults' // Low - instant dictionary attack
'MIFARE_Classic_Custom_Keys' // Medium - 30sec Darkside attack
'MIFARE_DESFire' // High - UID only
```
**Rationale**: MIFARE Classic security depends entirely on configuration. A card with default keys (FFFFFFFFFFFF) is as weak as EM4100, while one with custom keys requires real effort to crack.
### 2. Simplified Card Data Format
**Original Plan**: Manual hex/UID specification in scenarios:
```json
{
"type": "keycard",
"rfid_hex": "01AB34CD56",
"rfid_facility": 1,
"rfid_card_number": 43981,
"rfid_protocol": "EM4100",
"key_id": "employee_badge"
}
```
**Updated Plan**: card_id with automatic generation:
```json
{
"type": "keycard",
"card_id": "employee_badge",
"rfid_protocol": "EM4100",
"name": "Employee Badge"
}
```
**Benefits**:
- Matches existing key system pattern
- No manual hex/UID needed - generated deterministically from card_id
- Multiple cards can share same card_id (like keys)
- Cleaner scenarios
### 3. Protocol-Specific Attack Behavior
**Dictionary Attack**:
- `MIFARE_Classic_Weak_Defaults`: 95% success rate (most sectors use FFFFFFFFFFFF)
- `MIFARE_Classic_Custom_Keys`: 0% success rate (no default keys)
**Darkside Attack**:
- `MIFARE_Classic_Weak_Defaults`: 10 seconds (weak crypto)
- `MIFARE_Classic_Custom_Keys`: 30 seconds (normal)
### 4. Door Lock Configuration
**Original**: Single card requirement
```json
{
"lockType": "rfid",
"requires": "employee_badge"
}
```
**Updated**: Multiple valid cards (like key system)
```json
{
"lockType": "rfid",
"requires": ["employee_badge", "contractor_badge", "security_badge"],
"acceptsUIDOnly": false
}
```
## Implementation Updates
### Protocol Definitions
```javascript
// js/minigames/rfid/rfid-protocols.js
export const RFID_PROTOCOLS = {
'EM4100': {
name: 'EM-Micro EM4100',
security: 'low',
color: '#FF6B6B',
icon: '⚠️'
},
'MIFARE_Classic_Weak_Defaults': {
name: 'MIFARE Classic 1K (Default Keys)',
security: 'low', // Same as EM4100
color: '#FF6B6B', // Same color - equally weak
icon: '⚠️',
attackTime: 'instant'
},
'MIFARE_Classic_Custom_Keys': {
name: 'MIFARE Classic 1K (Custom Keys)',
security: 'medium',
color: '#4ECDC4',
icon: '🔐',
attackTime: '30sec'
},
'MIFARE_DESFire': {
name: 'MIFARE DESFire EV2',
security: 'high',
color: '#95E1D3',
icon: '🔒'
}
};
```
### Deterministic Data Generation
```javascript
// js/minigames/rfid/rfid-data.js
export class RFIDDataManager {
/**
* Generate RFID data from card_id (deterministic)
* Same card_id always produces same hex/UID
*/
generateRFIDDataFromCardId(cardId, protocol) {
const seed = this.hashCardId(cardId);
const data = { cardId: cardId };
switch (protocol) {
case 'EM4100':
data.hex = this.generateHexFromSeed(seed, 10);
data.facility = (seed % 256);
data.cardNumber = (seed % 65536);
break;
case 'MIFARE_Classic_Weak_Defaults':
case 'MIFARE_Classic_Custom_Keys':
data.uid = this.generateHexFromSeed(seed, 8);
data.sectors = {};
break;
case 'MIFARE_DESFire':
data.uid = this.generateHexFromSeed(seed, 14);
data.masterKeyKnown = false;
break;
}
return data;
}
hashCardId(cardId) {
let hash = 0;
for (let i = 0; i < cardId.length; i++) {
const char = cardId.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash;
}
return Math.abs(hash);
}
generateHexFromSeed(seed, length) {
let hex = '';
let currentSeed = seed;
for (let i = 0; i < length; i++) {
// Linear congruential generator
currentSeed = (currentSeed * 1103515245 + 12345) & 0x7fffffff;
hex += (currentSeed % 16).toString(16).toUpperCase();
}
return hex;
}
}
```
### Protocol-Aware Attacks
```javascript
// js/minigames/rfid/rfid-attacks.js
export class MIFAREAttackManager {
dictionaryAttack(uid, existingKeys = {}, protocol) {
const foundKeys = { ...existingKeys };
let newKeysFound = 0;
// Success rate depends on protocol
const successRate = protocol === 'MIFARE_Classic_Weak_Defaults' ? 0.95 : 0.0;
for (let sector = 0; sector < 16; sector++) {
if (foundKeys[sector]) continue;
if (Math.random() < successRate) {
foundKeys[sector] = {
keyA: 'FFFFFFFFFFFF', // Factory default
keyB: 'FFFFFFFFFFFF'
};
newKeysFound++;
}
}
return {
success: newKeysFound > 0,
foundKeys: foundKeys,
message: this.getDictionaryMessage(newKeysFound, protocol)
};
}
async startDarksideAttack(uid, progressCallback, protocol) {
// Weak defaults crack faster (10 sec vs 30 sec)
const duration = protocol === 'MIFARE_Classic_Weak_Defaults' ?
10000 : 30000;
// ... attack implementation with variable duration
}
}
```
### Unlock System Changes
```javascript
// js/systems/unlock-system.js
case 'rfid':
// Support multiple valid cards
const requiredCardIds = Array.isArray(lockRequirements.requires) ?
lockRequirements.requires : [lockRequirements.requires];
const acceptsUIDOnly = lockRequirements.acceptsUIDOnly || false;
// Check if any physical card matches
const hasValidCard = keycards.some(card =>
requiredCardIds.includes(card.scenarioData.card_id) // Match by card_id
);
// Check cloner saved cards
const hasValidClone = cloner?.scenarioData?.saved_cards?.some(card =>
requiredCardIds.includes(card.card_id) // Match by card_id
);
// Pass array of valid IDs to minigame
window.startRFIDMinigame(lockable, type, {
mode: 'unlock',
requiredCardIds: requiredCardIds, // Array
acceptsUIDOnly: acceptsUIDOnly
});
break;
```
### Ink Variables
```javascript
// js/minigames/person-chat/person-chat-conversation.js
syncCardProtocolsToInk() {
const keycards = this.npc.itemsHeld.filter(item => item.type === 'keycard');
keycards.forEach((card, index) => {
const protocol = card.rfid_protocol || 'EM4100';
const prefix = index === 0 ? 'card' : `card${index + 1}`;
// Ensure rfid_data exists (generate if needed)
if (!card.rfid_data && card.card_id) {
card.rfid_data = window.rfidDataManager.generateRFIDDataFromCardId(
card.card_id,
protocol
);
}
// Set simplified boolean variables
const isInstantClone = protocol === 'EM4100' ||
protocol === 'MIFARE_Classic_Weak_Defaults';
this.inkEngine.setVariable(`${prefix}_instant_clone`, isInstantClone);
const needsAttack = protocol === 'MIFARE_Classic_Custom_Keys';
this.inkEngine.setVariable(`${prefix}_needs_attack`, needsAttack);
const isUIDOnly = protocol === 'MIFARE_DESFire';
this.inkEngine.setVariable(`${prefix}_uid_only`, isUIDOnly);
this.inkEngine.setVariable(`${prefix}_protocol`, protocol);
this.inkEngine.setVariable(`${prefix}_card_id`, card.card_id);
this.inkEngine.setVariable(`${prefix}_security`, protocolInfo.security);
});
}
```
## Scenario Examples
### Hotel (Weak MIFARE)
```json
{
"objects": [
{
"type": "keycard",
"card_id": "room_301",
"rfid_protocol": "MIFARE_Classic_Weak_Defaults",
"name": "Room 301 Keycard"
},
{
"type": "keycard",
"card_id": "master_hotel",
"rfid_protocol": "MIFARE_Classic_Weak_Defaults",
"name": "Hotel Master Key"
}
],
"doors": [{
"locked": true,
"lockType": "rfid",
"requires": ["room_301", "master_hotel"]
}]
}
```
**Player experience**: Dictionary attack instantly finds all default keys → clone → use
### Corporate (Custom Keys)
```json
{
"npcs": [{
"id": "guard",
"itemsHeld": [{
"type": "keycard",
"card_id": "security_access",
"rfid_protocol": "MIFARE_Classic_Custom_Keys",
"name": "Security Badge"
}]
}],
"doors": [{
"locked": true,
"lockType": "rfid",
"requires": "security_access"
}]
}
```
**Player experience**: Clone from NPC → Dictionary fails → Darkside 30 sec → clone → use
### Bank (DESFire)
```json
{
"objects": [{
"type": "keycard",
"card_id": "executive_access",
"rfid_protocol": "MIFARE_DESFire",
"name": "Executive Card"
}],
"doors": [
{
"locked": true,
"lockType": "rfid",
"requires": "executive_access",
"acceptsUIDOnly": false,
"description": "Vault - requires full auth"
},
{
"locked": true,
"lockType": "rfid",
"requires": "executive_access",
"acceptsUIDOnly": true,
"description": "Office - accepts UID only"
}
]
}
```
**Player experience**: Can only save UID → Works on poorly-configured readers → Doesn't work on secure vault
## Ink Usage Examples
### Required Variables
```ink
VAR card_protocol = ""
VAR card_card_id = ""
VAR card_instant_clone = false
VAR card_needs_attack = false
VAR card_uid_only = false
```
### EM4100
```ink
{card_instant_clone && card_protocol == "EM4100":
+ [Scan their badge]
# clone_keycard:{card_card_id}
You quickly scan their badge.
-> cloned
}
```
### MIFARE Weak Defaults
```ink
{card_instant_clone && card_protocol == "MIFARE_Classic_Weak_Defaults":
+ [Scan their badge]
# clone_keycard:{card_card_id}
Your Flipper finds all the default keys instantly!
-> cloned
}
```
### MIFARE Custom Keys
```ink
{card_needs_attack:
+ [Try to scan]
The card is encrypted with custom keys.
# save_uid_only:{card_card_id}|{card_uid}
You'll need to run a Darkside attack to clone it fully.
-> uid_saved
}
```
### MIFARE DESFire
```ink
{card_uid_only:
+ [Try to scan]
# save_uid_only:{card_card_id}|{card_uid}
High security encryption - you can only save the UID.
-> uid_only
}
```
## Key Takeaways
1. **Four protocols** give meaningful gameplay progression:
- Instant (EM4100, weak MIFARE)
- Quick challenge (custom MIFARE with 30sec attack)
- Impossible/UID-only (DESFire)
2. **card_id system** simplifies scenarios dramatically:
- No need to specify technical details
- Multiple cards can share access
- Deterministic generation prevents conflicts
3. **Protocol awareness** makes attacks realistic:
- Dictionary succeeds on weak configs, fails on strong
- Darkside faster on weak keys
- DESFire can't be attacked at all
4. **Door flexibility** matches key system:
- Multiple valid cards per door
- UID-only acceptance flag for poorly-configured readers
## Next Steps
Refer to `00_IMPLEMENTATION_SUMMARY.md` for complete implementation guide with all code examples and checklists.
The original `01_TECHNICAL_DESIGN.md` and `02_IMPLEMENTATION_PLAN.md` are still valid for overall architecture and file organization, but use this document for:
- Protocol definitions (4 instead of 3)
- Card data format (card_id approach)
- Attack behavior (protocol-specific)
- Scenario structure (simplified JSON)