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 dellintera
storia della crittografia; questa interessa laritmetica
modulare, lesponenziazione 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. Lutente B può così
decifrare il messaggio con la sua chiave privata, lunica
in grado di dare un significato ai dati cifrati.
La firma elettronica si basa sulla crittografia a chiave
pubblica; lunica 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) dallutente 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.
Figura
1
Processo di firma
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 lorigine 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 dallhardware 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 sullalgoritmo 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 limplementazione
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
lapplicazione 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) dellIETF (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 linsieme 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 doggi 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 dallobbligo 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 lintegrità
di un documento informatico o di un insieme di documenti
informatici.
Librerie
Utilizzate
Per lesempio di firma utilizzeremo limplementazione
dello standard PKCS#11 (Cryptoki) fornita dalluniversità
della tecnologia di Graz. IAIK/SIC PKCS#11 (http://jce.iaik.tugraz.at)
è una libreria Java che permette laccesso
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).
Figura 2 Modello pkcs#11
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.
Lapproccio 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 dellapplicazione,
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 modulo
Module module = Module.getInstance("libreria.dll");
module.initialize (new DefaultInitializeArgs());
//elenco token presenti
Slot[] 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 è lunico
tipo supportato dalle librerie.
//apertura
della sessione
Session session = token.openSession(Token.SessionType.SERIAL_SESSION,
Token.SessionReadWriteBehavior.RO_SESSION,null,
null);
Loperazione
seguente è quella di autenticazione se questa
è prevista dal dispositivo; si controlla se è
necessario linserimento 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 dispositivo
TokenInfo tokenInfo = token.getTokenInfo();
//si controlla se lautenticazione è prevista
if (tokInfo.isLoginRequired()){
// esempio un PIN-pad nel lettore
if (tokInfo.isProtectedAuthenticationPath()){
System.out.println("Please
enter the user PIN at the 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 pin
String userPINString = JOptionPane.showInputDialog(null,
"Inserisci user-PIN:");
if(userPINString == null){
return;
}
//si effettua il login
session.login(Session.UserType.USER, userPINString.toCharArray());
}
}
Loperazione che segue è la ricerca della
chiave privata RSA allinterno della smart card;
per fare questo però utilizziamo un nuovo metodo:
selectKeyAndCertificate. Questo metodo seleziona allinterno
della smart card tutte le chiavi e certificati; lo si
può trovare nelle classi demo che vengono fornite
dai programmatori IAIK/SIC.
//chiave
da ricercare
RSAPrivateKey privateSignatureKeyTemplate = new RSAPrivateKey();
//si imposta lattributo di firma su vero
privateSignatureKeyTemplate.getSign().setBooleanValue(Boolean.TRUE);
//si cerca la chiave ed il certificato, passando sessione
e chiave
KeyAndCertificate selectedSignatureKeyAndCertificate
=
selectKeyAndCertificate(session, privateSignatureKeyTemplate,
null,new BufferedReader(new InputStreamReader(System.in))
);
if (selectedSignatureKeyAndCertificate == null){
session.logout();
}
//metodo
selectKeyAndCertificate
public static KeyAndCertificate selectKeyAndCertificate(Session
session, Key keyTemplate,PrintWriter output, BufferedReader
input) {
Vector keyList = new Vector(10);
//inizializzazione
session.findObjectsInit(keyTemplate);
Object[] matchingKeys;
//ricerca delle chiavi private RSA
while ((matchingKeys = session.findObjects(1)).length
> 0){
keyList.addElement(matchingKeys[0]);
}
//termina loperazione di ricerca
session.findObjectsFinal();
//strutture nel caso di più chiavi presenti
Hashtable 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 lattributo ID del certificate
allo stesso
//valore di quello della chiave
certificateTemplate.getId().setByteArrayValue(keyID);
//si ricerca il certificato corrispondente
alla chiave con keyID
session.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
PKCS11
correspondingCertificate = (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:
"<no
certificate found>");
}
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.
Loperazione di message digest viene effettuata
esternamente poichè non tutti i dispositivi crittografici
la mettono a disposizione.
InputStream
dataInputStream = new FileInputStream(file);
//message digest allesterno della carta
MessageDigest digestEngine = MessageDigest.getInstance("SHA-1");
//si memorizza il contenuto prima dellhashing
per averlo a disposizione
//durante la creazione del PKCS#7
ByteArrayOutputStream contentBuffer = new ByteArrayOutputStream();
byte[] dataBuffer = new byte[1024];
byte[] helpBuffer;
int bytesRead;
// si passano i dati alla funzione di digest
while ((bytesRead = dataInputStream.read(dataBuffer))
>= 0){
// hash dei dati
digestEngine.update(dataBuffer, 0, bytesRead);
// buffer dei dati
contentBuffer.write(dataBuffer, 0, bytesRead);
}
byte[] contentHash = digestEngine.digest();
contentBuffer.close();
Viene
poi creata la struttura SignedData; questa rappresenta
limplementazione 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 larray
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 allinterno dello stesso file *.p7m una
volta che loperazione sarà completa. E
per questo motivo che dal file firmato possiamo ottenere
loriginale (opzione presente in tutti i software
di verifica).
Figura
3
File p7m
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 dati
SignedData signedData = new SignedData(contentBuffer.toByteArray(),
SignedData.IMPLICIT);
//impostiamo il certificato
signedData.setCertificates(new X509Certificate[] { signerCertificate
});
// creiamo un nuovo SignerInfo
SignerInfo signerInfo = new SignerInfo(new IssuerAndSerialNumber(signerCertificate),
AlgorithmID.sha1,
null);
//definiamo gli attributi
iaik.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
firmare
byte[] toBeSigned = DerCoder.encode(ASN.createSetOf(authenticatedAttributes,
true));
//message digest usando quello precedentemente creato
byte[] hashToBeSigned = digestEngine.digest(toBeSigned);
//in accordo con PKCS11 il digestInfo viene fatto fuori
dalla smartcard
DigestInfo digestInfoEngine = new DigestInfo(AlgorithmID.sha1,
hashToBeSigned);
byte[] toBeEncrypted = digestInfoEngine.toByteArray();
//inizializzazione per il processo di firma
session.signInit(Mechanism.RSA_PKCS, selectedSignatureKey);
//firma dei dati
byte[] signatureValue = session.sign(toBeEncrypted);
//si mettono i dati appena ottenuti nel signer info
signerInfo.setEncryptedDigest(signatureValue);
//aggiungiamo il signerInfo alloggetto SignedData
signedData.addSignerInfo(signerInfo);
Lultima operazione è il salvataggio del
file firmato sul file system. Il file viene codificato
con BASE64. Lencoding 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 limplementazione
//PKCS7 del ContentType, servirà
anche in seguito nel processo di
//verifica per determinare il tipo di dato
PKCS7
ContentInfoStream cis = new ContentInfoStream(signedData);
BufferedOutputStream bos = new BufferedOutputStream(signatureOutput);
//si esegue lencoding per MIME
Base64OutputStream b64Out = new Base64OutputStream(bos);
//si scrive nel file
cis.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.
Bibliografia
[1] William Stallings - Crittografia e Sicurezza delle
Reti, Mc Graw Hill, 2004.
[2] Bruce Schneier, Secrets and Lies. Wiley, 2000.
[3] Jess Garms, Daniel Somerfield, Professional Java
Security. Wrox, 2001.
[4] Maurizio Cinotti, Internet Security, Hoepli, 2002.
[5] Michele Boreale, Note per il corso di Sicurezza
delle Reti, 2005.
Iacopo Pecchi si sta laureando
in Informatica presso luniversità di Firenze.
Ha svolto una tesi sulla Firma Digitale con lutilizzo
di dispositivi crittografici come smart card.
|