KeycardAccess - Controllo accessi

Alla luce dei nuovi fatti (il costo relativamente spropositato per fare le copie della chiave di ingresso), si iniziava a pensare di creare un controllo accessi per la nostra sede. Il progetto si comporrebbe delle seguenti componenti:

Lettore Badge

Me ne sto occupando io, anche se lentamente. Pensavo usare MFRC-522 (lettore RFID) e un ESP8266 o ESP32(quando arriveranno) per il loro costo basso. Pensavo di usare il protocollo MQTT per dialogare con il server di gestione accessi… ma anche raw TCP andrebbe bene. Se si usa l’ESP32 si avrebbe abbastanza potenza per criptare la comunicazione lettore-server.
###Server di gestione accessi
Bisognerebbe realizzare un server che avverta l’apertura/chiusura della sede tramite i vari canali di comunicazione. L’idea era di usare MQTT, quindi il server diventerebbe lo “MQTT broker”. Funzioni di apertura e chiusura in intervalli di tempo prestabiliti per ogni utente non sarebbero male, ma partirei prima sul semplice e lascerei le migliorie al futuro.

Meccanizzazione delle porte della sede

Per la porta principale i problemi non sussistono, ma per le due porte della sede bisogna trovare una soluzione per aprire elettronicamente. le soluzioni sono tre:

  • sistema con motori che girano il pomello
  • rilascio del battente (come porta di ingresso)
  • serratura elettrica (che andrebbe a sostituire quella sulla porta)

Bisognerebbe trovare la soluzione migliore per il nostro caso.

Creazione della grafica/ acquisto tessere RFID

ne abbiamo parlato qui: Tessere soci e RFID
In soldoni: grafica già fatta, manca solo da chiedere il preventivo alla azienda fornitrice dei badge RFID.

questo post è una wiki… modifichiamo qua quando si decide qualcosa (o spell checking). si discute nei commenti.

per la meccanizzazione delle porte propongo di fissare un supporto in metallo sulle viti dell’apertura della porta e metterci davanti un semplice servo centrato sul punto di scatto della serratura. sarebbe facile e non rovinerebbe la porta…

2 Mi Piace

Il progetto è ufficialmente partito. Lo trovate sulla wiki e su tasks. I partecipanti (per ora) sono:

Se possibile vorrei unirmi al progetto :slight_smile:

Ogni mano è ben accetta!

Quanto prima bisogna buttare giù le specifiche definitive sulla Wiki, così possiamo iniziare.

Grazie :smiley:
A proposito della Wiki, ho buttato giù un paio di righe di appunti, senza la pretesa che siano considerate specifiche di progetto, giusto per smuovere le acque e spingere un pochino il progetto.
Modificatele pure a vostro piacimento…o cestinatele se sono inutili. :wink:

@xemant Come va il progetto? Ci sono cose che possiamo fare? A me piacerebbe portarlo avanti durante lo hackathon di questo weekend…

Eh…purtroppo come sai ero via, ma quando torno magari vediamo di dare un boost al progetto :wink:

1 Mi Piace

Sorry, pensavo avessi fatto qualcosa quando eri in sede settimana scorsa!

Yeah :slight_smile:

Eh, avrei voluto anch’io andare più avanti…ma siccome per me è quasi tutto nuovo, sto tutt’ora investendo il mio tempo nel documentarmi.
Cmq vedrò quanto prima di smanettare su un “prototipo” reale…

1 Mi Piace

Ciao a tutti!
Avverto che il progetto non è morto, ma “in pausa”. Nel frattempo io ho pensato un paio di modi su come fare il sistema in modo che sia:

  • Sicuro
  • Non clonabile
  • Relativamente robusto (resistente a perdita di rete e di corrente elettrica)
  • Relativamente low cost

Il sistema di apertura

Ogni porta avrà una OrangePi Zero con lettore basato sull’integrato PN532. Ogni Pi avrà una replica di FreeIPA (il nostro sistema di autenticazione centrale) e quindi una copia completa del database di utenti. Ogni Pi controlla il proprio motore che aprirà la porta. Il sistema meccanico di apertura è stato già progettato e realizzato da @wifasoi e @glavermi.

La tessera

La tessera socio è già stata realizzata, e si basa sul sistema MIFARE DESfire EV2. Queste tessere sono equiparate a smart card, e possono utilizzare gli stessi protocolli delle carte contactless.

Come funziona la DESfire?

La carta è divisa in applicazioni, che corrispondono vagamente a cartelle di un file system. Ogni applicazione può contenere file e chiavi. I file possono essere di vari tipi, tra i quali:

  • File standard
  • File standard con backup (mantiene n copie)
  • File strutturato (record) ciclico
  • File strutturato (record) lineare
  • File di valore (supporta operazioni di credito/debito)

Le chiavi specificano permessi per leggere determinati file.


(Fonte: NXP MF3Dx2_MF3DHx2_SDS Preliminary short data sheet Copyright NXP Semiconductors)

Come gestiamo autenticazione e autorizzazione

Le idee di base sono due, una più facile e una più difficile (ma più sicura)

  1. Ogni tessera ha un file con dentro scritto il nome utente e una stringa segreta. Il lettore legge il file in questione e controlla nel database utenti se l’utente è autorizzato ad aprire.
    Questo sistema dovrebbe essere molto semplice da implementare, ma non è molto sicuro, infatti se qualcuno ottiene il file, può autenticarsi come quell’utente.

  2. Ogni tessera genera una coppia di chiavi asimmetriche, e la chiave pubblica viene trasferita nel sistema di autenticazione. Il lettore quindi chiede alla tessera di firmare una stringa di challenge e poi verifica da chi è stata firmata.
    Questo sistema è molto più sicuro in quanto la chiave privata non lascia mai la tessera (è letteralmente impossibile trasferirla mediante il protocollo) ma è un po’ più complesso da fare.

Software

Esistono già librerie FOSS per parlare con le tessere, principalmente libnfc con libfreefare e liblogicalaccess. Le prime due sono documentate un po’ male, ma le ho testate e funzionano con il nostro lettore e la tessera. La seconda è da testare.

Sarebbero quindi da sviluppare due software:

  • Uno che parte da una tessera vuota, e imposta la chiave master (PICC key), crea l’applicazione e il layout necessario
  • Una che legge la tessera e fa l’autenticazione

Questo è quanto, domande e commenti ben accetti!

1 Mi Piace

Salve a tutti!

Il progetto non è morto ma è vivo e vegeto. Questo è uno status update, io e @wifasoi gli abbiamo dato un piccolo boost durante le vacanze di Natale. Ho scritto abbastanza sulla chat Telegram del Mittelab, ma sono ad un punto morto e quindi meglio avere tutto scritto per bene. Domande, commenti, e aiuto sono assolutamente benvenuti :slight_smile:

Abbiamo iniziato a scrivere le librerie per controllare il chip che comunica con la carta (PN532) e per comunicare con la carta (in C++). Rispetto all’idea originale, stiamo implementando il codice per ESP32:

Al momento abbiamo lo sviluppo distribuito tra più branch e repo, dal punto di vista del codice quello più avanti è il mio branch desfire che trovate qui:

Nuova repo:

Status

  • La libreria per controllare il PN532 è completa
  • @wifasoi sta aggiungendo documentazione
  • Si può pilotare il PN532 via High Speed UART, il “driver” per SPI manca, il driver per I2C è in lavorazione da @wifasoi
  • I comandi di base e i protocolli di comunicazione con la tessera sono stati scritti:
    • Autenticazione, crea/rimuovi applicazione, formattazione, info versione, impostazioni app sono implementati e funzionanti
    • Cambiare le chiavi di autenticazione funziona per AES128 e 3K3DES, non funziona per 2K3DES e plain DES.
    • Interfaccia ai file manca completamente

Note sulla tessera e sulla documentazione

La tessera permette di comunicare in maniera “sicura”, nel senso che è possibile autenticarsi con una chiave simmetrica e trasmettere almeno parte dei dati sensibili in maniera crittografata. C’è anche un meccanismo di “scambio chiave” per derivare una chiave casuale per la sessione. La tessera supporta 4 modalità di cifratura:

  • Legacy, con cifratura DES (56 bit) o 2K3DES (112 bit)
  • “Moderno”, cifratura 3K3DES (168 bit) o AES128 (128 bit)

Il problema principale è che il protocollo è proprietario e non c’è documentazione ufficiale. Le “fonti” open source scarseggiano, e per ora ci stiamo basando su:

  1. Questa collezione di esempi come parte del progetto RFDoorLock
  2. Il codice sorgente di RFDoorLock (non proprio di facile lettura)
  3. Questa bella libreria C, Easypay (di un po’ più facile lettura)

E da lì stiamo cercando di ricostruire esattamente come dovrebbe comportarsi la nostra tessera.

Una cosa importante da notare è che i vari progetti non sono necessariamente d’accordo nell’asserire come dovrebbero funzionare questi protocolli di cifratura, vedi post seguenti.

1 Mi Piace

Raccoglierò qui per ora un po’ di informazioni su come funziona la tessera Mifare per chi volesse cimentarsi o aiutare.

Alcuni file per partire:

Modalità di trasmissione

  • I dati possono essere trasmessi in tre modalità
    1. chiaro
    2. autenticati: un MAC (message authentication code) viene accodato ai dati; questo viene derivato dal cifrario attivo e quindi non può essere forgiato e certifica la correttezza dei dati.
    3. cifrati: un CRC (cyclic redundancy check) viene accodato ai dati e poi il tutto viene cifrato; dopo che il messaggio viene decifrato, il CRC viene controllato per verificare che effettivamente sia stato decifrato correttamente e non siano bit spazzatura.
  • I dati in entrata ed uscita possono essere trasmessi con modalità diverse.
  • I dati in entrata ed uscita possono essere solo parzialmente autenticati o parzialmente cifrati: è comune che il codice del comando sia trasmesso in chiaro e poi sia seguito dai dati autenticati o cifrati.

Per ogni comando bisogna sapere esattamente che modalità utilizzare e su che porzione dei dati applicare il giusto protocollo. Questo lo leggiamo da RFDoorLock e Easypay.

Selezione del cifrario

  • Il cifrario da utilizzare dipende dall’applicazione correntemente selezionata. La Mifare può contenere tot applicazioni; alla creazione si specifica che cifrario utilizzare tra DES, 2K3DES, 3K3DES e AES128.
  • La master key della tessera, che fa riferimento all’applicazione root, può essere cambiata in uno degli altri cifrari (ma se qualcosa va storto poi buttiamo la tessera per cui per ora cambiamo solo chiavi in applicazioni create da noi).

Note su DES

DES, 2K3DES e 3K3DES sono la stessa cosa in salse diverse dal punto di vista crittografico; si possono ottenere tutti e tre a partire da 3DES, duplicando e riordinando i blocchi delle chiavi più corte. Di fatto MbedTLS, la libreria che utilizziamo, usa 3DES anche per 2K3DES. La tessera Mifare anche “abusa” di questo fatto: le chiavi DES (8 byte) della Mifare sono rappresentate come chiavi 2K3DES (16 byte) in cui i primi 8 e i secondi 8 byte coincidono.

Potrei sbagliarmi ma credo che anche algoritmicamente, 2K3DES con 8 byte ripetuti sia identico a plain DES perché due chiamate si cancellano l’una con l’altra.

RFDoorLock e Easypay implementano da soli i cifrari (non una buona idea per un progetto nuovo). Anche se la loro implementazione di CBC mette in evidenza che a seconda della modalità, viene fatto XOR con l’IV prima o dopo aver processato i blocchi, questo altro non è che la normale cifratura/decifratura con il relativo cifrario.

Note sulla direzione di cifratura

La prima divergenza tra RFDoorLock e Easypay la abbiamo sul fatto che Easypay decritta i dati in uscita per DES e 2K3DES invece di crittarli, come qualsiasi protocollo di buon senso farebbe. Questo sembrerebbe essere dovuto al fatto che in alcune implementazioni della Mifare, hanno implementato solo l’algoritmo di cifratura (per risparmiare?) per cui i dati vanno trasmessi “de-cifrati” così che possano essere “cifrati” dalla tessera per riottenere il plaintext.

Sappiamo per certo che la nostra tessera è in grado di decifrare DES e 2K3DES perché il comando Authenticate funziona, e dentro al comando Authenticate parte della chiave di sessione viene cifrata da noi, e quindi dev’essere decifrata dalla tessera perché lo scambio chiave vada a buon fine.

Calcolo del CRC

Un unit test rispetto agli esempi forniti conferma che sappiamo calcolare il CRC correttamente (usa un valore di inizializzazione particolare per CRC16, e bisogna negare il risultato rispetto all’implementazione dell’ESP).

Note sul protocollo di trasmissione legacy vs moderno

  • Nel sorgente, legacy: cipher_scheme.cpp, moderno cipher_scheme_impl.hpp; i metodi sono prepare_tx e confirm_rx.
  • Legacy usa CRC16, moderno CRC32
  • Legacy calcola CRC solo sui dati da cifrare, moderno su tutto il payload
  • Legacy calcola MAC solo sui dati da autenticare, moderno su tutto il payload
  • (in teoria) Legacy decritta in uscita invece di crittare
  • Legacy usa crittografia CBC per derivare il MAC, moderno invece deriva due chiavi intermedie e fa delle rotazioni o XOR prima di chiamare CBC per ottenere il MAC.
  • Legacy usa IV=0, moderno usa un IV globale per tutte le operazioni crittografiche
  • (in teoria) Moderno passa dentro al MAC anche i dati trasmessi in chiaro, aggiornando l’IV.

L’IV globale

L’IV globale significa che tutte le operazioni di MAC, cifratura e decifratura devono essere eseguite esattamente nello stesso ordine sullo stesso IV, altrimenti la tessera e l’ESP vanno fuori sync e non possono comunicare. Per fortuna sembrerebbe che l’IV viene resettato soltanto quando viene avviata una nuova sessione (a meno del processo di autenticazione che per fortuna funziona così com’è).

La :poop:

Purtroppo, ogni comando ha le sue regolette su quali dati vadano passati nel MAC, su quali IV vengono resettati, su quale CRC utilizzare. O è fatto apposta, o è fatto male. Ecco qui una bella lista di tutti i parametri da tenere a mente per trasmettere e per ricevere. Scrivere codice per controllare la Mifare è come cercare di trovare ordine in un caos che ordine non ha:

  • cifrario (DES, 2K3DES, 3K3DES, AES128)
  • IV zero/IV globale
  • CRC32 o CRC16
  • Modalità di comunicazione: chiaro, autenticata, cifrata
  • MAC standard o MAC con chiave derivata
  • MAC su dati in chiaro o no
  • Override: ometti il MAC
  • Override: ometti il CRC
  • Override: ometti cifratura (mai visto per ora)
  • Direzione di cifratura: critta o decritta in uscita (sempre usato ‘critta’ per ora)
  • Override: offset dei dati cifrati o autenticati (solo legacy, in teoria)

Ho cercato di “codificare” queste cose in desfire::cipher::config e desfire::tag::comm_cfg.

Senza contare che poi ci sono i comandi che hanno il loro protocollino custom, come… Authenticate e ChangeKey.

Mescolare la :poop:: legacy mode e IV globale

Ad aggiungere divertimento, è possibile mescolare queste modalità. La Mifare prende 3 modalità di autenticazione al comando Authenticate, legacy (DES e 2K3DES), ISO (3K3DES) e AES. Tuttavia è possibile autenticarsi in modalità ISO anche con una chiave DES e 2K3DES.

Quando ci si autentica in modalità ISO con un cifrario legacy, le cose si mescolano. Uno degli effetti è che bisogna utilizzare un IV globale. Al momento la modalità che usiamo è legacy e viene selezionata da desfire::auth_command, tuttavia ho fatto degli esperimenti ed effettivamente funziona, cambiando auth_command e aggiungendo questo snippet in tag::authenticate:

        const auto auth_cmd = auth_command(k.type());
        if (auth_cmd != command_code::authenticate_legacy) {
            pcipher->set_iv_mode(cipher_iv::global);
        }

Perché questo? Perché RFDoorLock usa solo autenticazione ISO per 2K3DES e 3K3DES. Il che sembra implicare che è possibile mescolare i protocolli.

Questo è supportato dal fatto che RFDoorLock usa il protocollo moderno per ChangeKey, con il CRC32 calcolato su tutti i dati invece che il CRC16 calcolato sui dati parziali, come invece fa Easypay.

Le implicazioni di questo e del fatto che RFDoorLock sembra non implementare DES, è che gli esempi non sono utili per testare se ad esempio ChangeKey è corretto per DES e 2K3DES.

Il problema attuale con ChangeKey

ChangeKey con DES e 2K3DES non funziona, la tessera dice che non riconosce il CRC (il che significa che o i dati non sono crittati correttamente, o il messaggio è malformato). Funziona con i cifrari moderni, ma non abbiamo dump di esempi e quindi non possiamo sapere cosa non va, se è la nostra tessera o il codice. Al momento, ho implementato ChangeKey come in Easypay. Per i procolli legacy e per gli esperimenti attuali, in cui la chiave da cambiare è la stessa con cui siamo autenticati, questo significa che viene calcolato un CRC16 sulla chiave soltanto, e poi il pacchetto è formato da:

 0xC4 | KEY_NUM(1b) | ENCRYPT[ KEY_DATA(16b) | CRC16[KEY_DATA](2b) ]

Da confrontare con la versione “moderna” (prendiamo 3K3DES, che imposta la flag 0x40 per marcare il tipo di cifrario)

 0xC4 | KEY_NUM(1b) + 0x40 | ENCRYPT[
     KEY_DATA(24b) | CRC32[
         0xC4 | KEY_NUM(1b) + 0x40 | KEY_DATA(24b)
     ](4b)
 ]

RFDoorLock usa il secondo approccio anche per 2K3DES. Tuttavia nessuna delle due implementazioni sembra funzionare per DES e 2K3DES. Degli unit test verificano che CRC16 sia calcolato correttamente e che la cifratura/decifratura con 2K3DES avvenga correttamente anche con chiavi non banali. Dovrei aver provato tutte le 8 combinazioni di queste varianti

  • usare il “protocollo ChangeKey moderno” invece di quello legacy
  • usare autenticazione ISO e IV globale come in RFDoorLock anche per DES e 2K3DES
  • invertire la direzione del cifrario in uscita (decifrare invece di cifrare, come in Easypay)

E qui è dove mi sono fermato al momento :slight_smile:

Stanotte con @wifasoi siamo riusciti a far partire Easypay sull’ESP32! Quindi siamo riuscti ad ottenere dump di sessione per ChangeKey per DES e 2K3DES, e sono stati convertiti in unit test!

I (2366) DESFIRE: Attempt app select
I (2366) HUHU OUT: 5a 00 00 02
I (2386) HUHU  IN: 00
I (2386) DESFIRE: Attempt autentication
I (2386) HUHU OUT: 0a 00
I (2416) HUHU  IN: af 1c 80 4b 7e 1c 86 d0 d8
I (2416) HUHU OUT: af 21 a7 2a d5 de 36 f1 d8 e4 5d c1 5d be 3f a0 01
I (2446) HUHU  IN: 00 c5 06 71 c6 b2 23 6f 36
I (2446) NEW    DES SESSION KEY: c8 6d b4 4f d3 20 d9 39
I (2446) DESFIRE: Attempt change key
I (2446) HUHU OUT: c4 00 b2 99 f1 06 a0 73 23 44 90 7b 03 41 e6 46 3d 42 41 42 33 a2 8a 12 b1 94
I (2496) HUHU  IN: 00
I (2496) DESFIRE: Attempt autentication 2
I (2496) HUHU OUT: 0a 00
I (2516) HUHU  IN: af 52 49 2d 74 62 bd 77 f7
I (2516) HUHU OUT: af 06 cc d5 17 7f 7e 4e 19 00 09 4f 22 db 44 22 ae
I (2546) HUHU  IN: 00 6f 62 89 c3 d1 f6 77 87
I (2546) NEW 2K3DES SESSION KEY: 4f be aa f4 4b a6 a7 89 da 08 75 12 7c dc e9 b0
I (2546) DESFIRE: Attempt change key 2
I (2546) HUHU OUT: c4 00 92 9d c6 c6 40 57 25 cc 54 cd 0b ad f6 97 b7 3c 7c d3 16 0d 24 1b 80 6b
I (2596) HUHU  IN: 00

I (3546) DESFIRE: Attempt app select
I (3546) HUHU OUT: 5a 00 00 01
I (3566) HUHU  IN: 00
I (3566) DESFIRE: Attempt autentication
I (3566) HUHU OUT: 0a 00
I (3586) HUHU  IN: af b9 92 d3 cc 31 25 f9 e4
I (3586) HUHU OUT: af 21 a7 2a d5 de 36 f1 d8 ea 52 92 fb 49 2a e1 7b
I (3616) HUHU  IN: 00 c5 06 71 c6 b2 23 6f 36
I (3616) NEW    DES SESSION KEY: c8 6d b4 4f 9e 5d 3a b9
I (3616) DESFIRE: Attempt change key
I (3616) HUHU OUT: c4 00 38 b6 ba b4 d0 68 d7 a8 04 77 9e b1 35 93 82 a8 3d ca d9 01 e4 48 ac 27
I (3666) HUHU  IN: 00
I (3666) DESFIRE: Attempt autentication 2
I (3666) HUHU OUT: 0a 00
I (3686) HUHU  IN: af 19 10 ef eb 66 bf 04 cb
I (3686) HUHU OUT: af c3 3d 3b 36 f2 cf 6b 21 b0 af 6a 07 4b 32 6d 4f
I (3716) HUHU  IN: 00 cc ae dc 01 f5 34 08 cd
I (3716) NEW    DES SESSION KEY: 4f be aa f4 eb 4a 8c 90
I (3716) DESFIRE: Attempt change key 2
I (3716) HUHU OUT: c4 00 d5 44 b8 68 97 d5 21 29 fa 2a f5 21 97 92 90 7b e2 d7 06 96 1f 1f 92 ad
I (3766) HUHU  IN: 00,

Questa è un’ottima cosa perché ci permetterà di generare più esempi in modo da poter verificare che il nostro codice funzioni correttamente.

È stato un po’ più complesso del previsto perché cambiare il log level a runtime sembrava causare SEGFAULT! @wifasoi ha appena testato che aumentare il main task size a 8192 risolve il segfault. :raised_hands: :raised_hands:

Ad ogni modo qui il fatto di aver separato il codice del PN532 ci è stato estremamente d’aiuto perché in mezz’oretta sono riuscito a far funzionare Easypay.

#define CONFIG_MAIN_TASK_STACK_SIZE 8192

Trovato dov’era il bug su ChangeKey. Il problema era proprio che la Desfire si aspetta dati de-cifrati in entrata, nel senso che li cifra lei per riottenere il plaintext. Da notare le differenze:

Questo è quello che fa mbedtls in CBC mode:

Questo è quello che fa la Mifare, però attenzione, con direction = MCD_SEND e operation == MCO_DECIPHER:

Il che significa che enc == 0 in questo sottoblocco del DES:

Per cui sostanzialmente, l’algoritmo per cifrare i dati in uscita usato dalla Mifare è cifratura DES, ma con una chiave di DEcifratura. Tradotto in codice, al momento di configurare i contesti di cifratura

// Invece di:
mbedtls_des_setkey_enc(&_enc_context, new_key.data());
// bisogna fare:   ^^^
mbedtls_des_setkey_dec(&_enc_context, new_key.data());
//                 ^^^

La cosa assurda è che non ce ne siamo mai accorti, perché la differenza tra encryption with a decryption key

mbedtls_des_setkey_dec(&_enc_context, new_key.data());
mbedtls_des_crypt_cbc(&_enc_context, MBEDTLS_DES_ENCRYPT, data.size(), iv.data(), data.data(), data.data());

e decryption with a decryption key

mbedtls_des_setkey_dec(&_dec_context, new_key.data());
mbedtls_des_crypt_cbc(&_dec_context, MBEDTLS_DES_DECRYPT, data.size(), iv.data(), data.data(), data.data());

è soltanto dal secondo blocco in poi. Il primo blocco è uguale, perché usa IV costante zero.


EDIT Dopo il bugfix, e un altro bugfix per i cifrari 2K3DES, finalmente:

Ora che abbiamo almeno un ChangeKey vorrei riesumare il discorso sicurezza.

Sicurezza della carta

Sappiamo che la carta non implementa cifratura asimmetrica. Considerata la diffusione capillare delle tessere di questo tipo la cosa mi lascia un po’ triste, perché di base la crittografia simmetrica richiede che i due parties siano a conoscenza di un segreto comune, il che ci porta a dover proteggere tale segreto nel dispositivo di apertura della porta. Un approccio asimmetrico sarebbe stato molto più elegante. Ad ogni modo:

  • al meglio delle nostre conoscenze, la carta non ha comandi che ci permettono di farle eseguire operazioni di cifratura usando le chiavi archiviate internamente (altrimenti potremmo sfruttarlo)
  • ISO 7816 non è implementato completamente, soltanto i metodi che hanno a che vedere con il “filesystem”, e ExternalAuthenticate, InternalAuthenticate, GetChallenge. Tra l’altro, è da vedere se ci sono esempi di questi perché mi sembra siano un po’ diversi dagli altri comandi della Mifare.
  • la carta non implementa operazioni di hashing
  • non si può derivare cifratura asimmetrica da (3)DES o AES128 (almeno cercando non puoi fare una cosa del genere a meno che il block cipher non abbia qualche proprietà particolare).

Quindi direi che cifratura asimmetrica è totalmente esclusa.

Quello che la NXP suggerisce, è diversificare la chiave in base all’UID della tessera, che stanti così le cose è sicuramente appropriato. Hanno anche un documento che suggerisce come diversificare la chiave.

A questo punto dobbiamo chiederci:

Che tipo di scenario vogliamo considerare dal punto di vista della sicurezza?

Da notare, in mancanza delle features nell’elenco di cui sopra, la diversificazione della chiave deve basarsi su dati immutabili e non forgiabili insiti nella tessera. Questi dati ovviamente non li possiamo scrivere noi sulla tessera (e.g. scrivere dati pubblici su un file e buttare via la chiave), perché chiunque sia in grado di leggere la tessera può ricopiarlo su un’altra. Anche se proteggessimo tali dati con una chiave simmetrica, la chiave dovrebbe essere mantenuta al sicuro nel dispositivo.

Quindi almeno idealmente questi dati sono rappresentati dall’UID, dal momento che dovrebbe identificare la tessera univocamente.

Parentesi sulla sicurezza dell’UID

A quel punto la domanda diventa “possiamo forgiare una tessera Mifare con un UID”. Questo thread sul sito della Mifare mi lascia l’amaro in bocca… “di non creare tessere forgiate ci pensiamo noi, usate solo parti originali, quelle clonate hanno pessima performance”; nel 2016 crediamo ancora che la sicurezza della carta sia garantita dalla produzione controllata piuttosto che dalla solidità del cifrario.

Tant’è che, ovviamente, qualcuno ha emulato l’UID delle carte Mifare. Nel paper si parla di clonare l’intera carta, e bisogna sottolineare che, al momento, il nostro modello di carta è ritenuto sicuro. Tuttavia, in via teorica, l’UID resta non sicuro; citando le conclusioni del paper,

With contactless payment, ticketing and access control systems being omnipresent today, it is crucial to realize that only strong cryptography, together with sound protocol designand protection against implementation attacks can ensure long-term security. Bug-fixes forbroken systems based on false assumptions on certain devicecharacteristics, e.g., UID-basedprotection schemes for Mifare Classic, are a fatal design choice, as we demonstrate that exact cloning of cards is feasible at a very low cost.

E non potrei essere più d’accordo. Insomma, siamo costretti a fidarci che NXP sia capace di fare security by obscurity. Data l’impossibilità di trovare il manuale della loro stessa tessera, devo concedergli che almeno quello lo sanno fare :smiley:


Tornando alla domanda originale, dovremo stare attenti che il dispositivo di apertura sia ben protetto, e qualsiasi segreto sia archiviato lì, o scaricato dal backend, o whatever, sia ben protetto.

Yup, purtroppo. Non so perché mi ricordavo (male) che supportava anche RSA.

Per quanto riguarda l’UID, da quanto ne so è possibile simularlo in maniera piuttosto semplice, ma secondo me questo non importa: la cosa che fa fede sarà il file salvato sulla card e non l’UID.

Concordo appieno. Purtroppo da qualche parte dovremo salvarci le chiavi AES (o l’algoritmo per derivarle), e queste dovranno essere custodite con attenzione.

Allora ecco qui tre proposal per come gestire l’autenticazione. Chi ne capisce di IT security e threat model, fatevi avanti perché io non sono un esperto di queste cose. Ci serve il vostro parere.


Possiamo archiviare un file che contiene informazioni firmate con la chiave pubblica del Mittelab nella tessera, e proteggerlo con una chiave diversificata basata sull’UID della tessera.

Dal momento che l’UID è spoofabile, per evitare che la carta possa essere clonata, è importante proteggere la chiave diversificata. Idealmente, la chiave diversificata dovrebbe poter essere derivata soltanto dal dispositivo al momento del contatto con la carta. Potrebbe anche essere utile usare l’UID solo dopo averlo passato in una funzione di hashing, per evitare di poter risalire alla tessera a partire dal socio.

Un po’ di notazione:

  • KDF: key derivation function
  • SIGN: firma usando la chiave privata del Mittelab (o meglio, una chiave derivata)
  • DB: database o archiviato nel backend o nel controller
  • controller: dispositivo localizzato all’interno della sede che decide se aprire la porta. Potrebbe o non potrebbe includere il lettore della carta (a cui potrebbe essere collegato)
  • backend: FreeIPA o qualsiasi altro software faremo girare in infra ext/int per gestire l’apertura porta
  • MID: Member ID, potrebbe essere l’username ad esempio
  • UID: tessera UID
  • SALT_*: un qualche valore scelto a caso in maniera crittograficamente sicura, al momento dell’attivazione
  • STORE(<content>, <key>): archivia un file sulla tessera, con contenuto <content>, criptato con la chiave simmetrica <key>.
  • <entry> -> <value>: mappa unidirezionale da entry a value

Proposta 1

Attivazione carta:

  1. Determina SALT_DB
  2. Deriva una chiave per la tessera,
    K := KDF(HASH(UID), SALT_DB)
  3. Archivia HASH(UID) -> SALT_DB nel controller
  4. Firma e generazione di un file segreto archiviato nella tessera:
    STORE(SIGN(HASH(UID), MID), K)

Verifica accessi:

  1. Il controller calcola HASH(UID) e controlla se ha un’entry HASH(UID) -> SALT_DB.
  2. Se sì, deriva la chiave K = KDF(HASH(UID), SALT_DB)
  3. Legge il file SIGN(HASH(UID), MID)
  4. Verifica che HASH(UID) corrisponda alla tessera, verifica la firma, in caso affermativo logga e apre la porta

Pro:

  • semplice

Con:

  • bisogna aggiornare i dati sul controller ad ogni configurazione di una carta
  • il controller può derivare K da solo
  • bisogna proteggere il database sul controller

Proposta 2

Attivazione carta:

  1. Determina SALT_DB, SALT_CARD
  2. STORE(SALT_CARD) in plaintext (readonly) sulla tessera
  3. Salva sul backend DB
    MID -> HASH(UID), SALT_DB
  4. Deriva la chiave
    K := KDF(HASH(UID), SALT_DB, SALT_CARD)
  5. Firma e salva il file firmato sulla carta:
    STORE(SIGN(HASH(UID), HASH(SALT_CARD), MID), K)

Deploy del controller:

  1. Crea una coppia chiave pubblica KP_c / chiave privata KS_c per ogni controller
  2. Archivia KP_c sul backend

Aggiornamento del DB del controller:

  1. Costruisci la mappa HASH(UID) -> SALT_DB per ogni MID autorizzato
  2. Cripta la mappa HASH(UID) -> SALT_DB con tutte le chiavi pubbliche KP_c.
  3. Consegna il crittogramma ad ogni controller
  4. Il controller decripta con la propria chiave privata KS_c e salva

Verifica degli accessi:

  1. Il controller calcola HASH(UID) e controlla se ha un’entry HASH(UID) -> SALT_DB
  2. Se sì, legge SALT_CARD dalla carta, e deriva K = KDF(HASH(UID), SALT_CARD, SALT_DB)
  3. Usando K, legge SIGN(HASH(UID), HASH(SALT_CARD), MID)
  4. Verifica la firma, verifica che HASH(UID) corrisponda e che HASH(SALT_CARD) corrisponda
  5. Se sì, logga e apre la porta

Pros:

  • Avendo accesso a backend o al controller, non è possibile derivare K, è necessaria anche la tessera
  • La tessera viene deployata separatamente dal controller
  • È possibile deployare più o nuovi controller in maniera autonoma

Cons:

  • Complesso
  • Richiede di ricrittografare il DB ad ogni nuovo controller
  • Richiede di gestire la consegna del DB ai nuovi controller

Proposta 3

Come la 2, ma SALT_CARD viene archiviato criptato:
STORE(SALT_CARD, K0)

K0 è una chiave simmetrica che viene deployata su tutti i controller ed è mantenuta separatamente dal backend.

Pros:

  • L’accesso al backend e alla tessera non permette di clonare la tessera, bisogna anche avere accesso al controller o a K0

Cons:

  • Più complicato, una chiave segreta in più da mantenere

Commenti, suggerimenti?

Un’altra questione importante: @wifasoi c’era una ragione specifica per avere un ESP32 nell’apriporta?

1 Mi Piace

Sinceramente non è necessario che sia per forza ESP32. Può essere benissimo una qualsiasi scheda con linux embedded (e non sono opposto ad usarne una).
L’unica ragione è che la scheda aveva già il circuito per azionare i relè, e porta ethernet… (e che a me piace usare ESP32).
Altri motivi vanno dal prezzo della soluzione (molto contenuta se il progetto ha successo), alla manutenzione minima (non è necessario tenere aggiornato il software perché un sistema non correlato alla nostra applicazione è vulnerabile).

1 Mi Piace

Dato che UID è conosciuto all’attacante quindi HASH(UID) diviene conosciuto a sua volta, quindi questo passaggio non apporta maggior sicurezza all’algoritmo di KDF.

Supponendo che l’attacante riesca fare il dump del DB via l’interfaccia al lettore, L’attacante può ricavarsi facilmente il suo SALT (mentre quello degli altri tag è facilmente ricavabile via bruteforce (solo 7 byte)).
Quindi questo hashing non mitiga questo tipo di attacco.

2 Mi Piace