Analizziamo il processo di accesso alle smart card, contenitori sicuri di chiavi asimmetriche, ed utilizziamole per applicare ai dati i servizi di integrità, autenticazione e non ripudiabilità rispettando le “politiche Italiane” in campo di Firma Digitale.
Introduzione
Nel corso degli ultimi anni la firma digitale si sta facendo progressivamente strada in campo informatico. Benchè confinata principalmente nelle Pubbliche Amministrazioni Italiane e quindi ristretta ai vari software di Firma digitale proprietari, nulla impedisce di poter implementare i processi di firma e verifica di file utilizzando le chiavi contenute nelle smart card, dispositivi hardware rilasciati dai Certificatori Accreditati, ottenendo come risultato un file firmato nel formato standard. Introdurremo di seguito gli elementi necessari per comprendere il mondo della Firma Digitale, partendo dalla crittografia asimmetrica e passando dagli aspetti legali, per arrivare sino ad S/MIME; vedremo infine il primo esempio di firma e salvataggio di dati.
La Crittografia Asimmetrica per la Firma Digitale
La crittografia asimmetrica, detta anche a chiave pubblica, è forse la più grande rivoluzione dell‘intera storia della crittografia; questa interessa l‘aritmetica modulare, l‘esponenziazione e grandi numeri primi di migliaia di cifre. Ciascuna parte interessata nello scambio di messaggi possiede una coppia di chiavi: una chiave pubblica e una privata. La chiave pubblica, come è intuibile, viene resa disponibile a tutti, pubblicata in internet o inserita nelle e-mail; quella privata è segreta e quindi deve essere conservata in modo sicuro.
Un utente A che vuole mandare un messaggio cifrato ad un utente B non deve fare altro che reperire la chiave pubblica di B, cifrare con questa il messaggio ed inviarlo al destinatario. L‘utente B può così decifrare il messaggio con la sua chiave privata, l‘unica in grado di dare un significato ai dati cifrati.
La firma elettronica si basa sulla crittografia a chiave pubblica; l‘unica differenza è che le chiavi vengono usate in modo inverso al caso sopra descritto: si cifrano i messaggi con la chiave privata e si decifrano con la chiave pubblica.
Un messaggio cifrato (firmato) dall‘utente A con la sua chiave privata può essere decifrato (verificato) da tutti coloro che conoscono la chiave pubblica di A. In realtà , ciò che viene cifrato con la chiave privata è il valore hash del messaggio. Questo valore cifrato viene inviato al destinatario insieme al file originale, rendendo possibile il processo di verifica.
La firma digitale applicata ad un file garantisce ai dati i seguenti servizi:
- Integrità : assicura che un messaggio venga trasmesso senza subire modifiche
da parte di terzi non autorizzati
- Autenticazione: assicura che l‘origine di un messaggio sia correttamente
identificata e che non sia stata falsificata
- Non-ripudiabilità : assicura che ne‘ il mittente ne‘ il destinatario possano
negare di aver trasmesso o ricevuto un messaggio
Le smart card
Il punto debole della crittografia a chiave asimmetrica sta nella memorizzazione sicura della chiave privata. Se un utente malintenzionato venisse a conoscenza di questa potrebbe decifrare tutte le informazioni vanificando così tutti i nostri sforzi.
Le smart card sono una ottima soluzione a questo problema: grazie alle caratteristiche di protezione dei dati intrinseche del microchip ed alla presenza di un coprocessore crittografico che gli consente di eseguire le principali funzioni crittografiche onboard, ovvero senza far uscire la chiave privata dal dispositivo, esse sono un mezzo adeguato per proteggere le informazioni personali da utilizzare nella firma digitale.
Fino a poco tempo fa la programmazione di smart card era ristretta ad un numero limitato di programmatori che sviluppavano applicazioni per hardware mirati, ma negli ultimi anni c‘è stata una crescita grazie alla nascita di gruppi di lavoro portati avanti dalle aziende del settore; questi hanno dato vita a vari standard tra i quali ISO7816, PC/SM, OpenCard, EMV e PKCS#11.
E‘ proprio lo standard PKCS#11 sviluppato da RSA Laboratories che utilizzeremo per accedere al dispositivo, per firmare e decifrare i file; PKCS#11 è un modello di dispositivo astratto che permette di essere indipendenti dall‘hardware crittografico collegato, permettendo così una implementazione di applicazioni portabili.
Gli standard
Questa parte riguarda gli standard che dobbiamo utilizzare per implementare i vari processi di firma e verifica.
- PKCS : in crittografia PKCS si riferisce ad un gruppo di Standard Crittografici a Chiave Pubblica (Public Key Cryptografiphy Standards) divisi e pubblicati dai Laboratori RSA in California. RSA Security ha il compito di sfornare standard basati sull‘algoritmo di cifratura a chiave asimmetrica RSA. Vi sono ben 15 standard, ma noi andremo a guardarne solamente alcuni.
- PKCS#1: definisce le linee guida per l‘implementazione del sistema crittografico a chiave pubblica basato su RSA.
- PKCS#7: definisce principalmente una struttura per incapsulare dati ai quali sono state applicate trasformazioni crittografiche. E‘ divenuto lo standard di S/MIME; i dati contenuti al suo interno possono essere di differenti tipi; quello che andremo ad usare nel caso di file firmati è SignedData.
- PKCS#11: questo standard è stato progettato per definire una interfaccia di programmazione ad alto livello in grado di essere indipendente dal tipo di hardware crittografico utilizzato attraverso la presentazione alle applicazioni di un dispositivo astratto chiamato Cryptographic Token. Le specifiche definiscono una api chiamata Cryptoki che definisce una interfaccia tra l‘applicazione ed il dispositivo; le applicazioni richiedono le funzionalità cryptoki grazie alle librerie (dll) che vengono fornite dal produttore del dispositivo.
- S/MIME: come sappiamo MIME è lo standard internet per il formato delle e-mail che fornisce meccanismi per inviare altri tipi di informazioni oltre ai semplici caratteri ASCII a 7 bit. S/MIME è lo standard internet per i dati crittografati e/o firmati incapsulati in MIME. S/MIME utilizza PKCS#7 per applicare funzioni crittografiche. Per garantire una certa interoperabilità tra mittenti e destinatari sono state definite nuove entità MIME che supportano PKCS#7: quello che a noi interessa è il file con estensione “p7m”, cioè application/pkcs7-mime, lo standard per i file Firmati (SignedData).
- X.509: Il termine certificato X.509 si riferisce generalmente al profilo di certificato PKI e di revoca del certificato (CRL) dell‘IETF (Internet Engineering Task Force, comunità aperta di tecnici ). Una PKI è una infrastruttura che provvede ai servizi di crittografia a chiave pubblica. Nel sistema X.509, una CA rilascia un certificato che accoppia una chiave pubblica ad un Nome. I certificati X.509 sono descritti indipendentemente dalla piattaforma usando la codifica ANS.1 (è una codifica per rappresentare le strutture dati in sistemi eterogenei). X.509 include anche gli standard per le implementazioni di Certificate Revocation List (CRL).
Aspetti Legali
Prima di poter parlare di firma digitale si deve parlare di firma elettronica, ovvero l‘insieme dei dati in forma elettronica, allegati oppure connessi tramite associazione logica ad altri dati elettronici, utilizzati come metodo di autenticazione informatica.
Si parla di firma elettronica avanzata riferendosi alla firma elettronica ottenuta attraverso una procedura informatica che garantisce la connessione univoca al firmatario e la sua univoca autenticazione informatica, creata con mezzi sui quali il firmatario può conservare un controllo esclusivo e collegata ai dati ai quali si riferisce in modo da consentire di rilevare se i dati stessi siano stati successivamente modificati.
Al giorno d‘oggi solo la crittografia a chiavi asimmetriche soddisfa i requisiti per la firma elettronica avanzata; esiste però un problema che non la rende equivalente alla firma autografa; è necessario quindi fare un ulteriore passo avanti.
Anche se nelle varie direttive non compaiono mai, gli addetti ai lavori hanno introdotto due nuove definizioni: firma forte e firma leggera.
La firma forte è la tipologia di firma più importante dal punto di vista legale perché è equivalente alla firma autografa. Affinchè una firma sia equivalente alla firma autografa è necessario che:
- sia basata su un sistema di chiavi asimmetriche
- sia generata con chiavi certificate
- sia riconducibile a un sistema di chiavi provenienti da un certificatore accreditato e soggetto a vigilanza da parte di un organo definito
- sia generata utilizzando un dispositivo sicuro
Un esempio di firma forte è la firma elettronica qualificata , ovvero una firma elettronica avanzata basata su un certificato qualificato e creata mediante un dispositivo sicuro.
Le direttive però conferiscono dignità giuridica anche agli altri tipi di firma (leggera).
Esse non sono definibili tecnologicamente a priori. Possono essere generate senza vincoli sugli strumenti e sulle modalità operative. Un giudice non potrà rifiutare in giudizio queste firme leggere, ma la loro ammissibilità nascerà dalla libera convinzione e non dall‘obbligo di legge previsto per le cosiddette firme forti.
Possiamo adesso introdurre la firma digitale: un particolare tipo di firma elettronica qualificata basata su un sistema di chiavi crittografiche, una pubblica e una privata, correlate tra loro, che consente al titolare tramite la chiave privata ed al destinatario tramite la chiave pubblica, rispettivamente, di rendere manifesta e di verificare la provenienza e l‘integrità di un documento informatico o di un insieme di documenti informatici.
Librerie Utilizzate
Per l‘esempio di firma utilizzeremo l‘implementazione dello standard PKCS#11 (Cryptoki) fornita dall‘università della tecnologia di Graz. IAIK/SIC PKCS#11 (http://jce.iaik.tugraz.at) è una libreria Java che permette l‘accesso a moduli PKCS#11. Un modulo è a sua volta una libreria. Questa non è legata ad uno specifico hardware crittografico (smart card, usb token).
Il modello della libreria è stratificato; questo consiste nel Object Oriented Java Wrapper Api per PKCS#11, il (non Object Oriented) Java Wrapper API for PKCS#11 e il modulo nativo (gli strati in verde).
Il livello più basso è il modulo PKCS#11 per la smart card che il produttore fornisce. Solitamente questo modulo è una dll linkata dinamicamente o staticamente. Come le frecce mostrano, il livello più alto dipende dallo strato sottostante ma non viceversa. Questo significa che si può usare il java
Wrapper per PKCS#11 direttamente ed implementare una applicazione usando un approccio Object Oriented e non. L‘approccio non user-oriented può essere utile per creare piccole applicazioni e quindi non utilizzare tutte le classi del package.
Naturalmente IAIK/SIC fornisce anche il IAIK JCE, ovvero un insieme di api che forniscono funzioni crittografiche come hash, mac, ecc.. Vengono anche supportati gli standard PKCS, ANS.1, X.509 con supporto alle liste di revoca e le soluzioni per costruire PKI.
IAIK non è gratuito per lo sviluppo di applicazioni commerciali.
Processo di Firma
Siamo finalmente giungi alla pratica; per prima cosa è necessario settare i parametri dell‘applicazione, ovvero le corrette librerie, ed inizializzare il modulo che si occupa della comunicazione con i dispositivi crittografici.
Naturalmente dobbiamo rimpiazzare “libreria.dll” con il corretto nome della libreria che viene fornita con la smart card. Andiamo poi a guardare tutti gli slot in cui sono presenti dispositivi, e visto che noi ne abbiamo soltanto uno prendiamo il primo.
//impostazione delle librerie e inizializzazione del moduloModule module = Module.getInstance("libreria.dll");module.initialize (new DefaultInitializeArgs());//elenco token presentiSlot[] slotsWithToken = module.getSlotList(Module.SlotRequirement.TOKEN_PRESENT);Token token = slotsWithToken[0].getToken();
Adesso dobbiamo aprire una sessione nel token che abbiamo selezionato; una sessione di sola lettura non ci permetterà di scrivere nel token, ma ci permetterà di effettuare operazioni crittografiche come firmare file.
Nel nostro caso una sessione di sola lettura è ottima poichè non vogliamo cancellare o modificare i dati della smart card, ovvero chiavi e certificati. Il tipo di sessione SERIAL SESSION è l‘unico tipo supportato dalle librerie.
//apertura della sessioneSession session = token.openSession(Token.SessionType.SERIAL_SESSION,Token.SessionReadWriteBehavior.RO_SESSION,null, null);
L‘operazione seguente è quella di autenticazione se questa è prevista dal dispositivo; si controlla se è necessario l‘inserimento di un PIN o se il dispositivo possiede qualche proprio meccanismo di protezione , ad esempio un PIN-pad presente nel lettore. Il codice PIN è un numero segreto a sei/otto cifre che permette di rendere sicura la smart card anche in caso di smarrimento; non disporre di questo requisito di sicurezza è un problema nel caso in cui un utente malintenzionato, che viene in possesso di un dispositivo altrui, firmi dei dati; questi divengono riconducibili legalmente al proprietario della smart card.
//si prendono informazioni sul dispositivoTokenInfo tokenInfo = token.getTokenInfo();//si controlla se l‘autenticazione è previstaif (tokInfo.isLoginRequired()){// esempio un PIN-pad nel lettoreif (tokInfo.isProtectedAuthenticationPath()){System.out.println("Please enter the user PIN atthe PIN-pad of your reader.");session.login(Session.UserType.USER, null);// the token prompts the PIN by other means; e.g. PIN-pad}else{//richiesta inserimento pinString userPINString = JOptionPane.showInputDialog(null,"Inserisci user-PIN:");if(userPINString == null){return;}//si effettua il loginsession.login(Session.UserType.USER, userPINString.toCharArray());}}
L‘operazione che segue è la ricerca della chiave privata RSA all‘interno della smart card; per fare questo però utilizziamo un nuovo metodo: selectKeyAndCertificate. Questo metodo seleziona all‘interno della smart card tutte le chiavi e certificati; lo si può trovare nelle classi demo che vengono fornite dai programmatori IAIK/SIC.
//chiave da ricercareRSAPrivateKey privateSignatureKeyTemplate = new RSAPrivateKey();//si imposta l‘attributo di firma su veroprivateSignatureKeyTemplate.getSign().setBooleanValue(Boolean.TRUE);//si cerca la chiave ed il certificato, passando sessione e chiaveKeyAndCertificate selectedSignatureKeyAndCertificate =selectKeyAndCertificate(session, privateSignatureKeyTemplate,null,new BufferedReader(new InputStreamReader(System.in)) );if (selectedSignatureKeyAndCertificate == null){session.logout();}//metodo selectKeyAndCertificatepublic static KeyAndCertificate selectKeyAndCertificate(Session session, Key keyTemplate,PrintWriter output, BufferedReader input) {Vector keyList = new Vector(10);//inizializzazionesession.findObjectsInit(keyTemplate);Object[] matchingKeys;//ricerca delle chiavi private RSAwhile ((matchingKeys = session.findObjects(1)).length > 0){keyList.addElement(matchingKeys[0]);}//termina l‘operazione di ricercasession.findObjectsFinal();//strutture nel caso di più chiavi presentiHashtable keyToCertificateTable = new Hashtable(4);Enumeration keyListEnumeration = keyList.elements();while (keyListEnumeration.hasMoreElements()){PrivateKey signatureKey = (PrivateKey) keyListEnumeration.nextElement();byte[] keyID = signatureKey.getId().getByteArrayValue();X509PublicKeyCertificate certificateTemplate = new X509PublicKeyCertificate();//si imposta l‘attributo ID del certificate allo stesso //valore di quello della chiavecertificateTemplate.getId().setByteArrayValue(keyID);//si ricerca il certificato corrispondente alla chiave con keyIDsession.findObjectsInit(certificateTemplate);Object[] correspondingCertificates = session.findObjects(1);if (correspondingCertificates.length > 0) {keyToCertificateTable.put(signatureKey,correspondingCertificates[0]);}session.findObjectsFinal();}Key selectedKey = null;String objectHandleString=null; X509PublicKeyCertificate correspondingCertificate = null;if (keyList.size() == 0) {System.out.println("La chiave non è stata trovata");}else if (keyList.size() == 1) {selectedKey = (Key) keyList.elementAt(0);//si crea un IAIK certificato da un certificato PKCS11correspondingCertificate = (X509PublicKeyCertificate) keyToCertificateTable.get(selectedKey);String correspondingCertificateString = toString(correspondingCertificate);System.out.println("Trovata una chiave privata RSA: ");System.out.println(selectedKey);System.out.println("Il certificato è: ");System.out.println((correspondingCertificateString != null)? correspondingCertificateString: "");} return (selectedKey != null)? new KeyAndCertificate(selectedKey, correspondingCertificate): null ;}
Dopo avere creato la chiave privata ed il certificato per la firma si accede in lettura al file selezionato; vengono creati due array, uno per il contenuto ed uno per il valore hash del file.
L‘operazione di message digest viene effettuata esternamente poichè non tutti i dispositivi crittografici la mettono a disposizione.
InputStream dataInputStream = new FileInputStream("file");//message digest all‘esterno della cartaMessageDigest digestEngine = MessageDigest.getInstance("SHA-1");//si memorizza il contenuto prima dell‘hashing per averlo a disposizione //durante la creazione del PKCS#7ByteArrayOutputStream contentBuffer = new ByteArrayOutputStream();byte[] dataBuffer = new byte[1024];byte[] helpBuffer;int bytesRead;// si passano i dati alla funzione di digestwhile ((bytesRead = dataInputStream.read(dataBuffer)) >= 0){// hash dei datidigestEngine.update(dataBuffer, 0, bytesRead);// buffer dei daticontentBuffer.write(dataBuffer, 0, bytesRead);}byte[] contentHash = digestEngine.digest();contentBuffer.close();
Viene poi creata la struttura SignedData; questa rappresenta l‘implementazione del PKCS#7 content-type SignedData. Questa operazione è necessaria per ottenere un file firmato in formato p7m. Come si può notare, creiamo la struttura passando al costruttore l‘array che contiene il file e Signed-Data.IMPLICIT; questo gli indica che la struttura è implicita, ovvero che la firma, il file originale ed il certificato saranno contenuti all‘interno dello stesso file *.p7m una volta che l‘operazione sarà completa. E‘ per questo motivo che dal file firmato possiamo ottenere l‘originale (opzione presente in tutti i software di verifica).
Si settano poi alcuni attributi per la firma, come contentType, messageDigest e signingTime. Si inizializza con il meccanismo RSA PKCS.
// creiamo il SignedData//implicita = un unico file che contiene file firmato e datiSignedData signedData =new SignedData(contentBuffer.toByteArray(), SignedData.IMPLICIT);//impostiamo il certificatosignedData.setCertificates(new X509Certificate[] { signerCertificate });// creiamo un nuovo SignerInfoSignerInfo signerInfo =new SignerInfo(new IssuerAndSerialNumber(signerCertificate), AlgorithmID.sha1, null);//definiamo gli attributiiaik.asn1.structures.Attribute[] authenticatedAttributes = {new Attribute(ObjectID.contentType,new ASN1Object[] {ObjectID.pkcs7_data}),new Attribute(ObjectID.signingTime,new ASN1Object[] {new ChoiceOfTime().toASN1Object()}),new Attribute(ObjectID.messageDigest,new ASN1Object[] {new OCTET_STRING(contentHash)})};signerInfo.setAuthenticatedAttributes(authenticatedAttributes);// encoding degli attributi, sono i dati che si devono firmarebyte[] toBeSigned =DerCoder.encode(ASN.createSetOf(authenticatedAttributes, true));//message digest usando quello precedentemente creatobyte[] hashToBeSigned = digestEngine.digest(toBeSigned);//in accordo con PKCS11 il digestInfo viene fatto fuori dalla smartcardDigestInfo digestInfoEngine =new DigestInfo(AlgorithmID.sha1, hashToBeSigned);byte[] toBeEncrypted = digestInfoEngine.toByteArray();//inizializzazione per il processo di firmasession.signInit(Mechanism.RSA_PKCS, selectedSignatureKey);//firma dei datibyte[] signatureValue = session.sign(toBeEncrypted);//si mettono i dati appena ottenuti nel signer infosignerInfo.setEncryptedDigest(signatureValue);//aggiungiamo il signerInfo all‘oggetto SignedDatasignedData.addSignerInfo(signerInfo);
L‘ultima operazione è il salvataggio del file firmato sul file system. Il file viene codificato con BASE64. L‘encoding Base64 è la codifica utilizzata da MIME per trasmettere dati non-testuali sopra un canale di comunicazione testuale. Infine si esegue il logout che determina la fine della sessione/comunicazione con il dispositivo.
JFileChooser choose = new JFileChooser();choose.setCurrentDirectory(new File("path"));choose.setSelectedFile(new File("nomeFile"+".p7m"));int result = choose.showSaveDialog();if(result == JFileChooser.APPROVE_OPTION){File signedFile = choose.getSelectedFile();OutputStream signatureOutput =new FileOutputStream(signedFile);//si crea un oggetto ContentInfoStream; rappresenta l‘implementazione//PKCS7 del ContentType, servirà anche in seguito nel processo di //verifica per determinare il tipo di dato PKCS7ContentInfoStream cis = new ContentInfoStream(signedData);BufferedOutputStream bos =new BufferedOutputStream(signatureOutput);//si esegue l‘encoding per MIMEBase64OutputStream b64Out = new Base64OutputStream(bos);//si scrive nel filecis.writeTo(b64Out);b64Out.close();JOptionPane.showMessageDialog(list,"File Firmato con successo!");session.logout();}
Conclusioni
Abbiamo imparato a creare il cuore di una applicazione che prende dei dati ed applica a questi delle funzioni crittografiche, trasformandoli in un formato standard per la Firma Digitale verificabile dai più noti software disponibili nel mercato rilasciati dai Certificatori Accreditati. Nella prossima parte andremo ad approfondire il processo di verifica, dalla credibilità alla revoca dei certificati.
1William Stallings”Crittografia e Sicurezza delle Reti” Mc Graw Hill, 20042Bruce Schneier”Secrets and Lies” Wiley, 20003Jess Garms, Daniel Somerfield”Professional Java Security” Wrox, 20014Maurizio Cinotti “Internet Security” Hoepli, 20025Michele Boreale