@@ -40,22 +40,50 @@ import (
4040 "github.com/ZaparooProject/go-pn532/transport/uart"
4141)
4242
43+ // mustPrint prints to stdout, panicking on error (for test output only)
44+ func mustPrint (args ... any ) {
45+ _ , err := fmt .Print (args ... )
46+ if err != nil {
47+ panic (err )
48+ }
49+ }
50+
51+ // mustPrintf prints formatted output to stdout, panicking on error (for test output only)
52+ func mustPrintf (format string , args ... any ) {
53+ _ , err := fmt .Printf (format , args ... )
54+ if err != nil {
55+ panic (err )
56+ }
57+ }
58+
59+ // mustPrintln prints with newline to stdout, panicking on error (for test output only)
60+ func mustPrintln (args ... any ) {
61+ _ , err := fmt .Println (args ... )
62+ if err != nil {
63+ panic (err )
64+ }
65+ }
66+
4367type config struct {
4468 devicePath * string
4569 timeout * time.Duration
4670 writeText * string
4771 debug * bool
4872 validate * bool
73+ testRobust * bool
74+ testTiming * bool
4975}
5076
5177func parseFlags () * config {
5278 cfg := & config {
5379 devicePath : flag .String ("device" , "" ,
5480 "Serial device path (e.g., /dev/ttyUSB0 or COM3). Leave empty for auto-detection." ),
55- timeout : flag .Duration ("timeout" , 30 * time .Second , "Timeout for tag detection (default: 30s)" ),
56- writeText : flag .String ("write" , "" , "Text to write to the tag (if not specified, will only read)" ),
57- debug : flag .Bool ("debug" , false , "Enable debug output" ),
58- validate : flag .Bool ("validate" , true , "Enable read/write validation (default: true)" ),
81+ timeout : flag .Duration ("timeout" , 30 * time .Second , "Timeout for tag detection (default: 30s)" ),
82+ writeText : flag .String ("write" , "" , "Text to write to the tag (if not specified, will only read)" ),
83+ debug : flag .Bool ("debug" , false , "Enable debug output" ),
84+ validate : flag .Bool ("validate" , true , "Enable read/write validation (default: true)" ),
85+ testRobust : flag .Bool ("test-robust" , false , "Test robust authentication features for Chinese clone cards" ),
86+ testTiming : flag .Bool ("test-timing" , false , "Test timing variance analysis" ),
5987 }
6088 flag .Parse ()
6189 return cfg
@@ -192,6 +220,271 @@ func writeTextIfRequested(tag pn532.Tag, writeText string) error {
192220 return nil
193221}
194222
223+ func testChineseCloneUnlock (mifareTag * pn532.MIFARETag ) {
224+ mustPrint ("\n === Testing Chinese Clone Unlock Sequences ===\n " )
225+
226+ // Test Gen1 unlock commands directly
227+ unlockCommands := []struct {
228+ name string
229+ desc string
230+ cmd byte
231+ }{
232+ {"Gen1 7-bit" , "Chinese Gen1 clone unlock (7-bit UID)" , 0x40 },
233+ {"Gen1 8-bit" , "Chinese Gen1 clone unlock (4-byte UID)" , 0x43 },
234+ }
235+
236+ foundUnlock := false
237+
238+ for _ , unlock := range unlockCommands {
239+ mustPrintf ("Trying %s (0x%02X): " , unlock .name , unlock .cmd )
240+
241+ // Access the device directly to test unlock commands
242+ device := mifareTag .GetDevice () // We'll need to add this method
243+ if device == nil {
244+ mustPrintln ("FAILED - cannot access device" )
245+ continue
246+ }
247+
248+ // Try the unlock command
249+ start := time .Now ()
250+ _ , err := device .SendDataExchange ([]byte {unlock .cmd })
251+ duration := time .Since (start )
252+
253+ if err == nil {
254+ mustPrintf ("SUCCESS (%.2fms) - %s\n " , float64 (duration .Nanoseconds ())/ 1000000 , unlock .desc )
255+ foundUnlock = true
256+
257+ // If unlock successful, try to read manufacturer block directly
258+ mustPrintln (" Attempting direct block 0 read (no authentication needed)..." )
259+ if data , readErr := mifareTag .ReadBlockDirect (0 ); readErr == nil {
260+ mustPrintf (" Block 0 (UID): %02X %02X %02X %02X %02X %02X %02X %02X...\n " ,
261+ data [0 ], data [1 ], data [2 ], data [3 ], data [4 ], data [5 ], data [6 ], data [7 ])
262+ mustPrintln (" → This is a Gen1 Chinese clone with backdoor access!" )
263+ } else {
264+ mustPrintf (" Block 0 read failed: %v\n " , readErr )
265+ }
266+ } else {
267+ mustPrintf ("FAILED (%.2fms) - %v\n " , float64 (duration .Nanoseconds ())/ 1000000 , err )
268+ }
269+ }
270+
271+ if ! foundUnlock {
272+ mustPrintln ("\n No Chinese clone unlock sequences successful." )
273+ mustPrintln ("This may be a Gen2/CUID/FUID clone or genuine card." )
274+
275+ // Test FM11RF08S universal backdoor key
276+ mustPrint ("\n Testing FM11RF08S universal backdoor key: " )
277+ backdoorKey := []byte {0xA3 , 0x96 , 0xEF , 0xA4 , 0xE2 , 0x4F }
278+
279+ start := time .Now ()
280+ err := mifareTag .AuthenticateRobust (1 , pn532 .MIFAREKeyA , backdoorKey )
281+ duration := time .Since (start )
282+
283+ if err == nil {
284+ mustPrintf ("SUCCESS (%.2fms)\n " , float64 (duration .Nanoseconds ())/ 1000000 )
285+ mustPrintln ("→ This is likely an FM11RF08S clone with universal backdoor!" )
286+ } else {
287+ mustPrintf ("FAILED (%.2fms) - %v\n " , float64 (duration .Nanoseconds ())/ 1000000 , err )
288+ mustPrintln ("→ Backdoor key authentication failed" )
289+ }
290+ }
291+ }
292+
293+ // tryKeyOnSector attempts to authenticate with a given key and sector
294+ func tryKeyOnSector (
295+ mifareTag * pn532.MIFARETag ,
296+ sector uint8 ,
297+ key []byte ,
298+ keyType byte ,
299+ keyName string ,
300+ ) (success bool , duration time.Duration ) {
301+ mustPrintf (" Trying Key %s [%02X %02X %02X %02X %02X %02X]: " ,
302+ keyName , key [0 ], key [1 ], key [2 ], key [3 ], key [4 ], key [5 ])
303+
304+ start := time .Now ()
305+ err := mifareTag .AuthenticateRobust (sector , keyType , key )
306+ duration = time .Since (start )
307+
308+ if err == nil {
309+ mustPrintf ("SUCCESS (%.2fms)\n " , float64 (duration .Nanoseconds ())/ 1000000 )
310+ testSectorRead (mifareTag , sector )
311+ return true , duration
312+ }
313+
314+ mustPrintf ("FAILED (%.2fms) - %v\n " , float64 (duration .Nanoseconds ())/ 1000000 , err )
315+ analysis := mifareTag .AnalyzeLastError (err )
316+ mustPrintf (" Error analysis: %s\n " , analysis )
317+ return false , duration
318+ }
319+
320+ // testSectorRead attempts to read a block from the authenticated sector
321+ func testSectorRead (mifareTag * pn532.MIFARETag , sector uint8 ) {
322+ block := sector * 4 // First block of sector
323+ if data , readErr := mifareTag .ReadBlock (block ); readErr == nil {
324+ mustPrintf (" Block %d read: %02X %02X %02X ... (16 bytes)\n " ,
325+ block , data [0 ], data [1 ], data [2 ])
326+ } else {
327+ mustPrintf (" Block %d read failed: %v\n " , block , readErr )
328+ analysis := mifareTag .AnalyzeLastError (readErr )
329+ mustPrintf (" Error analysis: %s\n " , analysis )
330+ }
331+ }
332+
333+ // testSectorAuthentication tests all keys for a given sector
334+ func testSectorAuthentication (
335+ mifareTag * pn532.MIFARETag ,
336+ sector uint8 ,
337+ testKeys [][]byte ,
338+ ) (success bool , authCount int ) {
339+ mustPrintf ("\n Testing sector %d:\n " , sector )
340+
341+ for _ , key := range testKeys {
342+ for _ , keyType := range []byte {pn532 .MIFAREKeyA , pn532 .MIFAREKeyB } {
343+ keyName := "A"
344+ if keyType == pn532 .MIFAREKeyB {
345+ keyName = "B"
346+ }
347+
348+ if keySuccess , _ := tryKeyOnSector (mifareTag , sector , key , keyType , keyName ); keySuccess {
349+ return true , 1
350+ }
351+ authCount ++
352+ }
353+ }
354+
355+ mustPrintf (" No working keys found for sector %d\n " , sector )
356+ return false , authCount
357+ }
358+
359+ func testRobustAuthentication (tag pn532.Tag ) {
360+ mifareTag , ok := tag .(* pn532.MIFARETag )
361+ if ! ok {
362+ mustPrintln ("Tag is not a MIFARE tag, skipping robust authentication test" )
363+ return
364+ }
365+
366+ mustPrint ("\n === Testing Robust Authentication ===\n " )
367+
368+ testChineseCloneUnlock (mifareTag )
369+
370+ testKeys := [][]byte {
371+ {0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF }, // Default transport key
372+ {0xD3 , 0xF7 , 0xD3 , 0xF7 , 0xD3 , 0xF7 }, // NDEF key
373+ {0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 }, // All zeros
374+ {0xA3 , 0x96 , 0xEF , 0xA4 , 0xE2 , 0x4F }, // FM11RF08S universal backdoor key
375+ }
376+
377+ successfulAuths := 0
378+ totalAttempts := 0
379+
380+ for sector := uint8 (1 ); sector < 4 ; sector ++ {
381+ success , attempts := testSectorAuthentication (mifareTag , sector , testKeys )
382+ if success {
383+ successfulAuths ++
384+ }
385+ totalAttempts += attempts
386+ }
387+
388+ mustPrint ("\n Authentication Summary:\n " )
389+ mustPrintf (" Successful: %d/%d (%.1f%%)\n " ,
390+ successfulAuths , totalAttempts , float64 (successfulAuths * 100 )/ float64 (totalAttempts ))
391+
392+ variance := mifareTag .GetTimingVariance ()
393+ mustPrintf (" Timing variance: %.2fms\n " , float64 (variance .Nanoseconds ())/ 1000000 )
394+
395+ if mifareTag .IsTimingUnstable () {
396+ mustPrintln (" WARNING: High timing variance detected - possible hardware issues" )
397+ } else {
398+ mustPrintln (" Timing appears stable" )
399+ }
400+ }
401+
402+ // performTimingAttempts runs authentication attempts and collects timing data
403+ func performTimingAttempts (
404+ mifareTag * pn532.MIFARETag ,
405+ sector uint8 ,
406+ key []byte ,
407+ attempts int ,
408+ ) (timings []time.Duration , successCount int ) {
409+ timings = make ([]time.Duration , 0 , attempts )
410+ successCount = 0
411+
412+ for i := 0 ; i < attempts ; i ++ {
413+ start := time .Now ()
414+ err := mifareTag .AuthenticateRobust (sector , pn532 .MIFAREKeyA , key )
415+ duration := time .Since (start )
416+ timings = append (timings , duration )
417+
418+ if err == nil {
419+ successCount ++
420+ mustPrintf (" Attempt %2d: SUCCESS (%.2fms)\n " , i + 1 , float64 (duration .Nanoseconds ())/ 1000000 )
421+ } else {
422+ mustPrintf (" Attempt %2d: FAILED (%.2fms) - %v\n " , i + 1 , float64 (duration .Nanoseconds ())/ 1000000 , err )
423+ }
424+
425+ time .Sleep (100 * time .Millisecond )
426+ }
427+
428+ return timings , successCount
429+ }
430+
431+ // calculateTimingStats computes and displays timing statistics
432+ func calculateTimingStats (timings []time.Duration , successCount , attempts int ) {
433+ if len (timings ) == 0 {
434+ return
435+ }
436+
437+ var minTime , maxTime , total time.Duration = timings [0 ], timings [0 ], 0
438+ for _ , timing := range timings {
439+ if timing < minTime {
440+ minTime = timing
441+ }
442+ if timing > maxTime {
443+ maxTime = timing
444+ }
445+ total += timing
446+ }
447+
448+ avg := total / time .Duration (len (timings ))
449+ variance := maxTime - minTime
450+
451+ mustPrint ("\n Timing Statistics:\n " )
452+ mustPrintf (" Success rate: %d/%d (%.1f%%)\n " ,
453+ successCount , attempts , float64 (successCount * 100 )/ float64 (attempts ))
454+ mustPrintf (" Min time: %.2fms\n " , float64 (minTime .Nanoseconds ())/ 1000000 )
455+ mustPrintf (" Max time: %.2fms\n " , float64 (maxTime .Nanoseconds ())/ 1000000 )
456+ mustPrintf (" Avg time: %.2fms\n " , float64 (avg .Nanoseconds ())/ 1000000 )
457+ mustPrintf (" Variance: %.2fms\n " , float64 (variance .Nanoseconds ())/ 1000000 )
458+
459+ switch {
460+ case variance > 1000 * time .Millisecond :
461+ mustPrintln (" WARNING: High variance (>1000ms) indicates possible hardware issues" )
462+ case variance > 500 * time .Millisecond :
463+ mustPrintln (" CAUTION: Moderate variance detected" )
464+ default :
465+ mustPrintln (" Timing appears stable" )
466+ }
467+ }
468+
469+ func testTimingAnalysis (tag pn532.Tag ) {
470+ mifareTag , ok := tag .(* pn532.MIFARETag )
471+ if ! ok {
472+ mustPrintln ("Tag is not a MIFARE tag, skipping timing analysis test" )
473+ return
474+ }
475+
476+ mustPrint ("\n === Testing Timing Analysis ===\n " )
477+
478+ key := []byte {0xFF , 0xFF , 0xFF , 0xFF , 0xFF , 0xFF }
479+ sector := uint8 (1 )
480+ attempts := 10
481+
482+ mustPrintf ("Performing %d authentication attempts to sector %d...\n " , attempts , sector )
483+
484+ timings , successCount := performTimingAttempts (mifareTag , sector , key , attempts )
485+ calculateTimingStats (timings , successCount , attempts )
486+ }
487+
195488func main () {
196489 cfg := parseFlags ()
197490
@@ -215,5 +508,14 @@ func main() {
215508 return
216509 }
217510
511+ // Run tests if requested
512+ if * cfg .testRobust {
513+ testRobustAuthentication (tag )
514+ }
515+
516+ if * cfg .testTiming {
517+ testTimingAnalysis (tag )
518+ }
519+
218520 _ , _ = fmt .Print (tag .DebugInfo ())
219521}
0 commit comments