Introduzione
In questa seconda parte mostreremo come possiamo essere
sicuri che un file con firma digitale apposta su esso
possa garantire ai dati contenuti al suo interno i servizi
di integrità, autenticazione e non ripudiabilità.
Prenderemo il file firmato ed effettueremo la verifica
della firma e la verifica di credibilità.
La
crittografia asimmetrica nel processo di verifica
Prima di entrare nei dettagli ripassiamo i processi
di firma e verifica di un messaggio.
Per messaggio in chiaro indichiamo il messaggio originale
al quale non è stata applicata nessuna trasformazione.
Un utente che vuole firmare ed inviare un messaggio
invia, oltre al messaggio stesso, una versione cifrata
con la propria chiave privata. Il destinatario del messaggio,
che presupponiamo sia a conoscenza della chiave pubblica
del mittente, decifra il messaggio con questa e verifica
che il messaggio in chiaro corrisponda a quello appena
decifrato; se è così il destinatario può
provare che il messaggio non è stato modificato
(integrità) e che il mittente sia realmente quello
dichiarato, ovvero quello in possesso della chiave privata
con la quale il messaggio è stato firmato. In
realtà non viene inviata una nuova copia del
messaggio cifrata con la chiave privata, ma viene allegato
al messaggio in chiaro il Message Digest o Impronta
Digitale di questo (cifrato con la chiave privata).
Il message digest è una funzione hash che applicata
ad un messaggio produce una sequenza di lunghezza fissa
legata univocamente con il messaggio; non è praticamente
possibile creare due message digest identici da due
messaggi diversi.
Il ricevente, dopo aver decifrato con la chiave pubblica
il message digest, calcola un nuovo message digest sul
messaggio in chiaro e se questo coincide con quello
inviatogli la verifica ha successo.
Nasce adesso un ulteriore problema, ovvero di come il
ricevente possa essere sicuro che la chiave pubblica
in suo possesso sia realmente quella del mittente. Questo
problema è risolto dai certificati, ovvero i
documenti che attestano il legame tra un soggetto e
la sua chiave pubblica.
I
Certificati Digitali e le liste di Revoca
La verifica di una firma digitale apposta su di un documento
non ci rende abbastanza sicuri della provenienza del
messaggio; se la verifica ha successo un utente ha la
certezza che il messaggio è autentico ed integro,
ovvero che il messaggio proviene veramente dalla chiave
privata che ha firmato il messaggio, ma non ha la certezza
che una parte malintenzionata non abbia utilizzato una
falsa coppia di chiavi per impersonare qualcun'altro.
Per esempio potrebbe succedere che un malintenzionato
utilizzi un indirizzo e-mail rubato come proprio identificativo
associando ad esso una coppia di chiavi falsa.
Nascono per questo motivo dei documenti che associano
l'identità di una parte ad una coppia di chiavi
e delle strutture che emettano e siano in grado di verificare
questi documenti. Stiamo parlando dei Certificati elettronici
e dei Certificatori (Certification Authority).
Per certificatore si intende una autorità di
fiducia che genera certificati e attesta le identità
e le chiavi pubbliche di coloro per cui questi sono
stati emessi. In pratica un certificato elettronico
consiste nelle informazioni personali del proprietario
(nome, cognome, ecc..) e la sua chiave pubblica, il
tutto cifrato con la chiave privata dell'ente certificatore.
Il certificato contiene anche la data di scadenza (periodo
di validità), un numero seriale ed il nome dell'ente
Certificatore che lo ha rilasciato.
Ecco quali sono i passi che si devono effettuare con
successo per poter verificare la firma apposta su di
un messaggio:
- reperire
da una fonte sicura la chiave pubblica del certificatore
e del firmatario del messaggio (la chiave pubblica
del firmatario può essere ottenuta dal certificato).
- decifrare
con la chiave pubblica del certificatore il certificato
(verificando che esso appartenga ad una Autorità
di Certificazione inclusa nell'elenco dei Certificatori,
che non sia scaduto e non sia sospeso o revocato)
- decifrare
con la chiave pubblica del mittente il messaggio firmato
(verifica della firma del messaggio) e controllare
che questo coincida con quello inviato.
- verificare
la corrispondenza tra i dati (identità-chiavi)
del certificato e i dati del mittente del messaggio
Un
modo sicuro per autenticare una firma è quella
di includere al messaggio più certificati: colui
che riceve il messaggio, essendo in possesso delle chiavi
pubbliche della CA (ottenute da una fonte sicura) decifra
prima i certificati ed in seguito il messaggio.
Una firma può essere autenticata da una terza
parte, la quale può a sua volta essere ulteriormente
autenticata. Questo meccanismo gerarchico termina nella
parte più alta della catena con una Certification
Authority. A volte questo complesso meccanismo non viene
usato ed è sostituito da una catena a due livelli:
certificatori e utenti (è il caso dell'Italia).
In Italia non esiste un organismo gerarchico per la
sottoscrizione dei Certificati digitali. Non esiste
una Certification Authority che assegna certificati
ad altre sub-CA; la struttura si limita a due livelli:
- Autorità
di Certificazione
- Possessori
dei certificati
La
legislazione del nostro paese demanda la gestione delle
certificazioni alla Centro Nazionale per l'Informatica
nella Pubblica Amministrazione. L'attuale presidente
del CNIPA, Dott. Livio Zoffoli, ha rilasciato
un elenco di Autorità Accreditate per distribuire
certificati firmato con la propria chiave privata.
La chiave pubblica del Dott. Zoffoli è stata
opportunamente pubblicata nella gazzetta ufficiale.
Vediamo adesso un esempio: il nostro obbiettivo è
quello di verificare la firma ed il certificato di Paolo
Rossi rilasciato da ``CertificatoreEsempio''.
CertificatoreEsempio ha a sua volta un proprio certificato;
questo certificato è self-signed, ovvero dichiara
che il proprio certificato è valido (sicuro).
Questa sarebbe una contraddizione se il certificato
di "CertificatoreEsempio" non fosse presente
nella lista delle Autorità Accreditate rilasciata
da CNIPA (non ci possiamo fidare a priori di CertificatoreEsempio).
Ecco i passi che si devono effettuare per verificare
la firma ed il certificato di Paolo Rossi:
- Verificare
che la firma di Paolo Rossi sia corretta (decifrare
la firma con la chiave pubblica di Paolo Rossi e verificare
che il messaggio corrisponda con quello inviato).
- Scaricare
la lista di certificati rilasciata da CNIPA.
- Verificare
la correttezza della firma della lista (lista firmata
dal Dott. Zoffoli).
- Controllare
che l'impronta digitale della chiave pubblica di Livio
Zoffoli corrisponda a quella pubblicata nella gazzetta
ufficiale.
- Individuare
all'interno della lista di Autorità scaricata
dal CNIPA quella che ha rilasciato il certificato
a Paolo Rossi, prenderne la chiave pubblica ed usarla
per verificare il certificato.
- Scaricare
la lista CRL del certificatore che ha rilasciato il
certificato di Paolo Rossi.
- Verificare
le informazioni di Revoca del Certificato di Paolo
Rossi.
Le
CRL (Certificate Revocation List) sono delle liste di
certificati che hanno perso la loro validità
prima del periodo di scadenza poiché ad esempio
il proprietario di questi comunica che la sua chiave
è stata compromessa. Le CRL sono distribuite
nel formato standard X.509 e vengono aggiornate periodicamente.
I certificati all'interno sono identificati tramite
il Serial Number e rimossi quando raggiungono la data
di scadenza (i certificati che raggiungono la data di
scadenza non vengono inseriti nelle CRL).
La legge in campo di Firma Digitale impone ai Certificatori
di mettere a disposizione una lista di revoca CRL per
poter controllare le varie informazioni sullo stato
dei Certificati che gli stessi hanno rilasciato.
Proviamo ad aprire un certificato di un Ente Certificatore
ed andiamo a guardare i vari campi; noteremo che vi
sono dei campi (sono delle estensioni) nei quali sono
indicati i vari punti di distribuzione delle Liste dei
Certificati Revocati. In ogni certificato il certificatore
dichiara l'indirizzo internet dove, nel caso di revoca
o sospensione del certificato, inserirà l'informazione.
I vari software per la firma Digitale non fanno altro
che, una volta verificato il certificato, collegarsi
all'URL dichiarato e controllare le informazioni sulla
revoca. Questi sono gli obblighi che i certificatori
hanno. Vi sono varie modalità di accesso a queste
liste; si possono recuperare tramite un url o tramite
ldap.
Verifica
della firma
La prima operazione consiste nel verificare la validità
della firma del file S/MIME con estensione p7m. In questa
fase si decifra con la chiave pubblica presente nel
certificato il file che era stato precedentemente cifrato
con la chiave privata dellipotetico mittente.
Viene aperto un inputStream al file firmato che ci permette
di leggere il contenuto. L'inputStream è di tipo
ANS1InputStream; questa è una classe filtro che
offre una codifica Standard per strutture dati e ci
permette di leggere differenti formati (come DER codificato
ANS1, Base64 codificato ANS1 ecc..).
L'oggetto ANS1InputStream diviene il parametro del costruttore
dell'oggetto ContentInfoStream:
questa classe rappresenta l'implementazione del tipo
PKCS\#7 ContentType. Viene infatti utilizzata per determinare
che tipo di trasformazioni crittografiche sono state
applicate ai dati (data, signedData, envelopedData,
signedAndEnvelopedData, digestedData, encryptedData).
Con un costrutto if controlliamo se il file
è di tipo SignedData, ovvero se è un file
firmato.
InputStream
encodedStream = new FileInputStream(f);
ASN1InputStream asn1 = new ASN1InputStream(encodedStream);
ContentInfoStream cis = new ContentInfoStream(asn1);
SignedDataStream signedData;
// si guard ail tipo di contenuto
if (cis.getContentType().equals(ObjectID.cms_signedData))
{
//si prende il contanuto
signedData = (SignedDataStream)cis.getContent();
}
else {
//errore, ci aspettiamo un SignedData
}
L'operazione
successiva è quella di memorizzare il contenuto
del file originale (non firmato) in un array di byte.
Questo servirà poi per poter salvare il file
originale sul file system.
InputStream
contentStream = signedData.getInputStream();
byte[] read = new byte[10240];
int byteRead;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
while((byteRead = contentStream.read(read)) > 0){
baos.write(read,0,byteRead);
}
Siamo
giunti alla vera e propria verifica: questa è
una operazione molto semplice poiché viene fatta
automaticamente dai metodi messi a disposizione da IAIK.
Dopo avere recuperato le informazioni sul firmatario
si effettua la verifica; se questa ha successo si visualizza
il certificato; questo viene mostrato in una nuova finestra
che mette a disposizione le operazioni di salvataggio
del file originale, di verifica di Credibilità
del Certificatore e di Informazioni sulla Revoca. Se
la verifica non ha successo si lancia una eccezione.
SignerInfo[]
signerInfos = signedData.getSignerInfos();
for (int i=0; i < signerInfos.length; i++) {
try {
// si verifica la firma
X509Certificate signerCertificate
= signedData.verify(i);
CertificateFactory factory =
CertificateFactory.getInstance("X.509");
//si mostrano le informazioni
sulla correttezza della
//firma, visualizzando il certificato
in un frame
CertFrame dialog = new CertFrame(null,"Firma
corretta",signerCertificate,
signerCertificate.getIssuerDN().toString(),
signerCertificate.getFingerprint(),f,byteArrayFileTemp);
// questo che segue è
un frame che permette allutente
// di scegliere se salvare il
documento originale
// sul file system o se effettuare
la verifica
// di credibilità e di
revoca (il codice per la creazione
// non è stato inserito)
frame.setSize(600,470);
frame.setResizable(false);
frame.setVisible(true);
}
catch (SignatureException ex) {
// se la firma non è
ok si lancia una eccezione
AppletFirmata.manageError("Signature
ERROR from signer with certificate: "+
signedData.getCertificate(signerInfos[i].getIssuerAndSerialNumber()));
ex.printStackTrace();
}
}
Verifica di Credibilità
Andiamo ora a guardare come viene effettuata la seconda
parte della verifica, ovvero la verifica della credibilità
del certificato che viene rilasciato all'utente da parte
di un organismo Certificatore.
Quello che dobbiamo controllare è che chi rilascia
il certificato sia realmente il Certificatore Accreditato
e non qualcuno che si spaccia per esso, ad esempio mettendo
un nome falso identico a quello dellente (questo
è simile al bug di Arsen Lupin di un famoso software
per la firma Digitale).
Come sappiamo un certificato non è altro che
delle informazioni cifrate con la chiave privata di
una CA; per la verifica di credibilità si deve
reperire la chiave pubblica di questa CA e con questa
effettuare la verifica.
La prima cosa da fare è il download di una lista
di Enti Certificatori che il Centro Nazionale per l'informatica
nella Pubblica Amministrazione (CNIPA) rende disponibile
on-line.
Questo elenco viene firmato dal Dott. Livio Zoffoli,
presidente del CNIPA, ed aggiornato periodicamente.
Ecco il codice che scarica l'elenco delle CA; per fare
questo si usa il metodo getNameListCertFromURL. Questo
metodo estrae dalla pagina html il nome della lista
firmata che varia a seconda della data di aggiornamento.
Successivamente si verifica la firma apposta dal Dott.
Zoffoli controllando che il fingerprint del certificato
di Zoffoli corrisponda con quello rilasciato nella Gazzetta
Ufficiale (del quale siamo a conoscenza). Infine si
decomprime la lista.
URL
listaSource = new URL(
"http://www.cnipa.gov.it/site/_files/lista%20dei%20certificati.html");
//metodo per prendere il nome della lista dalla pagina
html
String nomeLista = getNameListCertFromURL(listaSource);
File listaCertSigned = new File(nomeLista);
System.out.println("Lista certificati: "+nomeLista);
System.out.println("Download :"+listaCertSigned.getAbsolutePath());
URL urlLista = new
URL("http://www.cnipa.gov.it/site/_files/"+nomeLista);
InputStream in = urlLista.openStream();
FileOutputStream fos = new FileOutputStream(listaCertSigned);
int a;
while((a=in.read())!=-1){
fos.write(a);
}
fos.close();
in.close();
//verifica
della firma apposta sulla lista
//allinterno viene fatto anche il controllo sul
fingerprint del Dott. Zoffoli:
//si controlla che il fingerprint della lista corrisponda
a quello che abbiamo
//ottenuto da una fonte sicura (gazzetta ufficiale)
byte[] fileList = verificaFilePKCS7(listaCertSigned);
if(fileList == null){
System.out.println("Certificate Not
Valid!");
}
nomeLista=nomeLista.substring(0,nomeLista.length()-4);
File fileZip = new File(nomeLista);
System.out.println("fileZip: "+fileZip.toString());
FileOutputStream fosZip = new FileOutputStream(fileZip);
fosZip.write(fileList);
fosZip.close();
//3:decomprimo la lista
System.out.println(ZipUtils.ZipToXml(nomeLista,
nomeLista.substring(0,nomeLista.length()-4)));
nomeLista=nomeLista.substring(0,nomeLista.length()-4);
System.out.println("Nome file lista cert :"+nomeLista+"\\");
Il
metodo che effettua la verifica è verificaFilePKCS7(listaCertSigned);
questo chiama checkZoffoliFingerPrint(X509Certificate
cert) per controllare il fingerprint del Dott. Zoffoli:
private
boolean checkZoffoliFingerPrint(X509Certificate cert){
String zoffoli = "F7:58:2B:22:38:91:32:58:A5:F3:4F:FF:A0:6A:5A:26:89:97:73:2B";
String certString = cert.toString();
System.out.println("Certificato: "+certString);
if(certString.contains(zoffoli)){
return true;
}else{
return false;
}
}
Dopo
avere decompresso la lista il metodo di verifica accede
alla cartella dei certificati e li importa in un array.
In seguito si sceglierà la cartella avente il
nome dellIssuer per andare a cercare il certificato
da usare per la verifica. Se la cartella viene trovata
si prosegue alla ricerca di un certificato che abbia
lo stesso identificativo chiave autorità
dell'ISSUER (chi ha rilasciato il certificato). Non
riportiamo il metodo di accesso alle cartelle che contengono
i certificati, ma facciamo vedere i dettagli del metodo
che controlla se lautorità che ha rilasciato
il certificato è realmente quella dichiarata
nella lista di Enti Certificatori. Dopo avere identificato
il certificato della CA interessata, ne prendiamo la
chiave pubblica e la utilizziamo per verificare il certificato
del presunto Issuer che è contenuto allinterno
della busta PKCS#7. Se la verifica ha successo significa
che la chiave privata utilizzata per firmare il certificato
(per firmare info e chiave pubblica del firmatario)
contenuto allinterno della busta PKCS#7 corrisponde
alla relativa pubblica ottenuta dal certificato allinterno
della lista e quindi che il certificatore che ha rilasciato
il certificato è Accreditato.
Se il metodo verify non lancia una eccezione
la verifica di credibilità termina con successo.
//si
passa al costruttore la stringa dellissuer e authID,
ovvero //lidentificativo chiave autorità
del certificato che vogliamo verificare
//il metodo importX509CertFromDirectory crea un array
di certificati accedendo //alle directory
CertList lista = new CertList(nomeLista+ZipUtils.FileSep,certificateIssuerString,authID);
lista.importX509CertFromDirectory();
//certificate dellIssuer da verificare
private X509Certificate certificateTarget;
//nome dellissuer
private String certificateIssuerString;
//identificativo chiave autorità
private V3Extension authID;
[
.]
X509Certificate certIssuerFromList = lista.findCertificate();
boolean certVerified;
try{
certificateTarget.verify(certIssuerFromList.getPublicKey());
certVerified = true;
}
catch(Exception ex){
certVerified = false;
}
if(certIssuerFromList != null && certVerified
== true){
System.out.println(Verifica Credibilità
terminata con successo!);
}
Il
metodo findCertificate controlla allinterno della
lista alla ricerca di un certificato che abbia lo stesso
subjectDN e la stessa estensione identificativo
chiave autorità del certificato che dobbiamo
verificare. Se lo trova viene ritornato.
private
iaik.x509.X509Certificate[] certList = new X509Certificate[1000];
[...]
public
X509Certificate findCertificate() {
int i=0;
while(certList[i]!=null && i <
certList.length){
if(certList[i].getSubjectDN().toString().equals(certificateAuthority)
&&
certList[i].getExtension(new
ObjectID("2.5.29.35")).toString().equals(ext.toString())
){
System.out.println("Certificato
"+certList[i].getSubjectDN().toString()+"
trovato!");
return certList[i];
}
i++;
}
return null;
}
Conclusioni
Abbiamo analizzato i processi di verifica della firma
di un file nel formato p7m, ovvero una busta PKCS#7,
e di verifica di credibilità di un certificato
rilasciato da un Ente Certificatore. Nel prossimo articolo
andremo ad analizzare la verifica delle informazioni
di revoca, entrando nei dettagli delle liste di revoca
dei certificati (CRL) e del protocollo OCSP.
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.
[6] RFC3280, RSA Laboratories, 2002
Iacopo Pecchi si sta laureando
in Informatica presso luniversità di Firenze.
Ha svolto una tesi sviluppando un prototipo di applicazione
per la Firma Digitale con lutilizzo dispositivi
crittografici come smart card.
|