International Kernel Patch spialaspia@inventati.org v0.01, 08/05/2002 Abstract La crittografia nel kernel di linux Table of Contents 1 Panoramica ed utilizzo 2 Tentando di esemplificare 3 Scrivere moduli che utilizzino le crypto api 1 Panoramica ed utilizzo Uno dei maggiori ostacoli all'utilizzo, alla diffusione della crittografia sono i paesi che ne proibiscono l'uso nelle sue forme più inviolabili, più sicure. Per aggirare il problema il supporto per la crittografia non viene incluso nel kernel di linux, ma gestito attraverso un'apposita patch, detta appunto International kernel Patch, un tempo scaricabile presso www.kerneli.org, ed ora spostata dopo alcune peripezie su http://crypto-api.sourceforge.net. Il sito ospita anche la mailing list del progetto. kernel.org mantiene comunque la patch nella sezione crypto, mirrorata anche su autistici, ftp://ftp.autistici.org La posizione ufficiale nell'albero di directory dei siti ftp contenenti il kernel è /pub/linux/kernel/crypto/v2.x. Attualmente esiste sia per i kernel della versione 2.2., che per i 2.4. Per i kernel 2.0 i riferimenti rimangono immutati rispetto a quanto già pubblicato su kriptonite. L'installazione della patch è descritta con dovizia di particolari nel Encryption-HOWTO, prelevabile presso http://encryptionhowto.sourceforge.org Esiste anche un howto, un poco più vecchio, totalmente dedicato alla cifratura di file system attraverso il loopback device ( il metodo adottato dal progetto kerneli e discusso qui di seguito ), disponibile anche in italiano: l' Encrypted loopback filesystem howto, presso http://www.linuxdoc.org/HOWTO/Loopback-Encrypted-Filesystem-HOWTO.html Il primo è sicuramente da preferirsi, in quanto più aggiornato e più completo. L'installazione della patch avviene attraverso l'apposito comando patch, dopo aver copiato il file scaricato nella directory con i sorgenti del kernel ed essersi posizionati in essa: gzip -d patch-int-a.b.c.d.gz ( decomprimo il file ) patch -p1 < patch-int-a.b.c.d ( installo la patch ) oppure tutto in una volta sola zcat patch-int-a.b.c.d.gz | patch -p1 Per i kernel della versione 2.4, ed in particolare per tutti quelli successivi alla versione 2.4.3, sara' necessario applicare anche una patch al loop device, per correggere un errore delle versioni precedenti. Quindi si dovra' scaricare anche la patch loop-hvr-a.b.c.d.patch ed installarla in maniera del tutto analoga alla precedente. Per maggiori informazioni su come ricompilare il proprio kernel o applicare ad esso una patch è possibile consulatare il kernel-howto, presso http://www.linuxdoc.org/HOWTO/Kernel-HOWTO.html Se non si verificano errori in questa fase, si può passare alla compilazione. L'installazione corretta della patch comporta che nel menu di configurazione del kernel ( make menuconfig o make xconfig, o make config, se l'autismo vi pervade ), appaia la voce Crypto options. Selezionando questo menu apparirà la dicitura Crypto support, marcata la quale, sarà possibile abilitare i vari algoritmi di crittografia e di message digest supportati e scegliere quali si desideri utilizzare ( Crypto options, Digest Algorithms ). Alla voce Block devices sarà quindi necessario abilitare il supporto per il loop device ( Loopback device support ) Il resto è opzionale e potete decidere consultando la documentazione del kernel. Al termine della compilazione il nuovo kernel sarà predisposto per fornire una serie di funzionalità crittografiche, che verranno utilizzate per la cifratura del file system. Come esemplificato in seguito, i programmi necessari per manipolare il file system ed il device loopback sono essenzialmente tre: mount, umount ed losetup. Perchè quest'ultimi siano in grado di utilizzare le potenzialità introdotte nel kernel è necessario apportare alcune modifiche anche a queste utilities. È possibile scaricare dai siti sopra indicati anche questa patch util-linux-x.xx.int.patch. dove x.xx rappresenta la versione del pacchetto util-linux da prelevare. L'applicazione è un poco delicata, per questo rimando agli howto sopra citati. Terminata quest'ultima fase è finalmente possibile utilizzare una combinazione dei comandi suddetti per creare file system cifrati. 2 Tentando di esemplificare Il loopback device serve per associare un file qualsiasi ad un device a blocchi ed a trattarlo come tale. Esemplificando: un hard disk è un device a blocchi, su di esso posso costruire un file system ed effettuarne il mount, quindi trattando un file qualunque come un device a blocchi, posso compiere le medesime operazioni. Più in teoria. Linux supporta tre tipi di devices: a caratteri, a blocchi e di rete. Sui primi si legge e si scrive senza bufferizzazzione, sui secondi è possibile effettuare queste operazioni solo per multipli delle dimensioni di un blocco ( normalmente 512 0 1024 byte alla volta ). A quest'ultimi si accede attraverso un buffer ed è consentito l'accesso diretto ad ogni blocco, indipendente dalla sua posizione nel device. Normalmente si fa riferimento ad essi attraverso un file system, anche se nulla vieta di accedere direttamente al file speciale corrispondente. ( es: /dev/hd* ). Ai devices di rete si fa riferimento attraverso l' interfaccia a socket BSD. Per capire meglio come tutto questo funzioni in pratica, iniziamo a giocare un poco con comandi, file e file system: si generi un file di 500k pieno di zeri ratmuske:~# dd if=/dev/zero of=black_hole bs=1k count=500 entrati 500+0 record usciti 500+0 record si crei il file system all'interno del file ratmuske:~# mke2fs black_hole mke2fs 1.18, 11-Nov-1999 for EXT2 FS 0.5b, 95/08/09 black_hole is not a block special device. Proceed anyway? (y,n) y Filesystem label= OS type: Linux Block size=1024 (log=0) Fragment size=1024 (log=0) 64 inodes, 500 blocks 25 blocks (5.00%) reserved for the super user First data block=1 1 block group 8192 blocks per group, 8192 fragments per group 64 inodes per group Writing inode tables: done Writing superblocks and filesystem accounting information: done si effettui il mount specificando l'opzione -o loop, che indica al programma di utilizzare il loopback device, per trattare il file come un device a blocchi ratmuske:~# mount -o loop black_hole /mnt infatti se ora si controllano quali file system sono mountati ratmuske:/mnt# mount /dev/hdc1 on / type ext2 (rw,errors=remount-ro,errors=remount-ro) proc on /proc type proc (rw) devpts on /dev/pts type devpts (rw,gid=5,mode=620) /dev/hdc6 on /src type ext2 (rw) /dev/loop0 on /src/doc type ext2 (rw) /ginox/black_hole on /mnt type ext2 (rw,loop=/dev/loop1) l'ultima riga indica che il file black_hole è stato mountato in /mnt attraverso il /dev/loop1, cioè questo device è l'astrazione grazie alla quale il kernel riesce ad interpretare black_hole come un device a blocchi. Attraverso l'utilities losetup è possibile compiere una serie di operazioni su i loop devices, ed in particolare associare un file o un device a blocchi ad uno di essi, effettuare l'operazione inversa e controllarne lo stato. Se effettuo una query su /dev/loop0 ratmuske:/usr/src/linux# losetup /dev/loop0 ottengo /dev/loop0: [1606]:12 (/src/panopticum) offset 0, serpent encryption Al fondo della riga sono contenute le informazioni più importanti, che ci consentono, di passare da un utilizzo semplice dei loopback devices ad uno più avanzato e più interessante: serpent encryption indica il tipo di cifratura applicata. L'opzione -e del comando losetup mi consente di specificare quale algoritmo applicare per la cifratura dei dati. Una veloce scorsa alla man page chiarisce quali siano i possibili valori per l'opzione, in sostanza gli algoritmi che abbiamo scelto durante la fase di configurazione del kernel. Le operazioni da effettuare sono del tutto simili a quelle compiute per la creazione del file black_hole, con la sola differenza che ora utilizzeremo il comando losetup per configurare opportunamente il loop device in modo che applichi un algoritmo di cifratura ai dati contenuti all'interno del file system. Ripercorriamo quindi i vari passi necessari alla creazione di un file system cifrato con questo metodo: * Si crei un file grande quanto necessario: ratmuske:/usr/src/# dd if=/dev/zero of=black_hole bs=1M count=10 * Si configuri il device di loopback in maniera appropriata ratmuske:/usr/src/# losetup -e serpent /dev/loop0 black_hole * Si crei su di esso un file system ratmuske:/usr/src/# mke2fs /dev/loop0 * Si proceda con il mount ratmuske:/usr/src/# mount -t ext2 /dev/loop0 /mnt/crypt * Terminato l'utilizzo: ratmuske:/usr/src/# umount /mnt/crypt * e si liberi il /dev/loop0 effettuando l'operazione di deattach ratmuske:/usr/src/# losetup -d /dev/loop0 Sulla mia macchina utilizzo un semplice script per automatizzare queste procedure, lo riporto qui di seguito per offrire qualche spunto. ******* bhole.sh #!/bin/bash PATH_TO_SCRIPT=/usr/bin CYPHER_TYPE=serpent LOOP_DEVICE1=/dev/loop0 FILE_CRYPT1=/black_hole FILE_MOUNT1=/doc case $@ in start) losetup -e $CYPHER_TYPE $LOOP_DEVICE1 $FILE_CRYPT1 mount -t ext2 $LOOP_DEVICE1 $FILE_MOUNT1 ;; stop) umount $LOOP_DEVICE1 losetup -d $LOOP_DEVICE1 cd / ;; #clean serve in caso di spegnimento brutale del computer, analizza il #file sytem prima di effettuare l'operazione di mount, ma dopo la #decifratura clean) if mount | grep $LOOP_DEVICE1 then PATH_TO_SCRIPT/src stop fi losetup -e $CYPHER_TYPE $LOOP_DEVICE1 \ $FILE_CRYPT1 e2fsck $LOOP_DEVICE1 losetup -d $LOOP_DEVICE1 cd / ;; *) echo "usage: $0 (start|stop|clean)" ;; esac 3 Scrivere moduli che utilizzino le crypto api Questa sezione è frutto di prove, tentativi e letture del codice delle crypto api. Quanto riportato sono poco più che appunti e speriamo siano utili per gli sviluppatori che intendono usare queste api e per i curiosi che vogliono capirne meglio il funzionamento. I commenti si riferiscono alla patch per la versione 2.4.17. Si presuppongono una certa familiarita' con il linguaggio C, con la scrittura di moduli per il kernel e qualche cognizione di crittografia. La patch aggiunge una directory crypto nella radice dei sorgenti del kernel con il codice, ed una directory omonima in Documentation. In essa vi è un file cryptoapi.txt con un esempio per l'utilizzo delle api. A partire da questo codice si cerchera' di sviscerare la struttura delle funzioni messe a disposizione, e si offrira' un semplice esempio di modulo in grado di utilizzarle. struct cipher_context* setup_cipher (const char *ciphername, const u8 *key, u32 keylen) { int error = 0; struct cipher_context *cx = NULL; struct cipher_implementation* ci = NULL; /*riempio una struct_implementation con i dati relativi ad un certo algoritmo*/ ci = find_cipher_by_name (ciphername, 1 /* == atomic - i.e. cipher must not sleep */); if (!ci) /* cipher not found */ return NULL; ci->lock (); cx = ci->realloc_context (NULL, ci, /* max expected */ keylen); if (!cx) /* some error */ { ci->unlock (); return NULL; } /* Se il tentativo di inizializzare l'algortimo con una certa chiave fallisce, ripulisco la struttura, wipe_context(), e libero la memoria occupata, free_context. */ if (ci->set_key (cx, key, keylen) < 0) { /* error while setting key */ ci->wipe_context(cx); ci->free_context(cx); ci->unlock (); return NULL; } return cx; /* everything ok so far */ } Lo scopo di setup_cipher è di inizializzare una struttura di tipo cipher_context, questa struttura come è possibile verificare controllando nell'header linux/crypto.h contiene principalmente una struttura di tipo cipher_implementation, che è il cuore delle api. Quest'ultima possiede infatti le informazioni e le funzioni necessarie ad utilizzare un certo algoritmo: la lunghezza del blocco di dati trattati, la lunghezza della chiave, del vettore di inizializzazione, ed i puntatori ad una serie di funzioni atte ad attivare ed utilizzare un particolare algoritmo: * int (*encrypt), int (*decrypt): per cifrare e decifrare. * int (*set_key): per inizializzare gli algoritmi con una certa chiave. * struct cipher_context * (*realloc_context): rialloca o alloca in caso non esistesse ancora un struttura cipher_context a partire da una certa cipher_implementation. * void (*wipe_context): ripulisce una certa struct cipher_context. * void (*free_context): libera la memoria occupata da una certa struct cipher_context. In generale utilizzare le api significa scegliere un certo algoritmo e ricavare attraverso la funzione find_cipher_by_name una struct cipher_implementation da applicare in un cipher_context ben preciso. Un cipher_context puo' essere immaginato come una sessione composta di dati da cifrare-decifrare, un algoritmo, una chiave, ecc... L'applicazione di un cipher_context viene chiarita nella funzione d'esempio test_cipher. Le operazioni compiute in essa sono piuttosto semplici da analizzare; dato un certo cipher_context, usiamo la procedura encrypt per cifrare una certa porzione di dati e decrypt per decifrarla. Nel primo caso l'output dovra' essere abbastanza capiente per ospitare size bytes di dati più il padding per renderlo un multiplo delle dimensioni del blocco utilizzato dall'algoritmo. int test_cipher (struct cipher_context *cx, u8 *data, u32 datalen) { int error = 0; u_int32_t iv[4] = { 0, 0, 0, 0 }; /* dummy IV value */ if ((error = cx->ci->encrypt (cx, data, data, datalen, &iv)) < 0) return error; /* *data should contain ciphertext now */ if ((error = cx->ci->decrypt( cx, data, data, datalen, &iv)) < 0) return error; /* plaintext again */ return 0; } Il modulo proposto si avvale di queste due funzioni un poco modificate e semplicemente cifra la frase "Sono nella nostra testa" e la decifra, stampa il risultato cifrato ed in chiaro utilizzando la printk, dunque sara' possibile controllare i messaggi dati dal modulo con il comando dmesg. E' possibile scaricare da http://crypto-api.sourceforge.net}{qui}il sorgente del modulo con il relativo Makefile. Sara' forse necessario modificare alcune righe di codice per vederlo funzionare correttamente, in partcolare il ciphername per la funzione setup_cipher dipendera' da quali algoritmi avrete inserito nel kernel. Per passare il valore corretto si potranno controllare gli algoritmi a disposizione ed il nome con il quale vengono identificati attraverso /proc/crypto/cipher. Qui di seguito è riportato il sorgente #include #include #include struct cipher_context* setup_cipher (const char *ciphername, const u8 *key, u32 keylen) { struct cipher_context *cx = NULL; struct cipher_implementation* ci = NULL; ci = find_cipher_by_name (ciphername, 1 /* == atomic - i.e. cipher must not sleep */); if (!ci) /* cipher not found */ { printk("ciphername \ n"); return NULL; } ci->lock (); cx = ci->realloc_context (NULL, ci, /* max expected */ keylen); if (!cx) /* some error */ { printk("realloc \ n"); ci->unlock (); return NULL; } if (ci->set_key (cx, key, keylen) < 0) { /* error while setting key */ printk("key \ n"); ci->wipe_context(cx); ci->free_context(cx); ci->unlock (); return NULL; } return cx; /* everything ok so far */ } int test_cipher (struct cipher_context *cx, u8 *data, u32 datalen) { u8 c_data[30]; int error = 0; u_int32_t iv[4] = { 0, 0, 0, 0 }; /* dummy IV value */ c_data[25]=' \ 0'; if ((error = cx->ci->encrypt (cx, data, c_data, datalen, iv)) < 0) return error; printk("c_data: \ n"); printk("%s \ n",c_data); /* *data should contain ciphertext now */ if ((error = cx->ci->decrypt(cx, c_data, data, datalen, iv)) < 0) return error; printk("data: %s \ n",data); /* plaintext again */ return 0; } int init_module() { struct cipher_context* c_context; u8 *data = "Sono nella nostra testa \ n"; u8 key[16] = {'s','s','s','s','s','s','s','s','s','s','s','s','s','s','s','s'}; printk("init: www.SpiaLaSpia.org - for privacy self-defence \ n"); if ( ( c_context = setup_cipher("idea-cbc",key,16) ) != NULL ) test_cipher(c_context,data,25); else { printk("Non funziona \ n"); return 1; } return 0; } void cleanup_module() { printk("cleanup: www.SpiaLaSpia.org - for privacy self-defence \ n"); }