MokaByte 103 - Gennaio 2006
 
MokaByte 103 - Gennaio 2006 Prima pagina Cerca Home Page

 

 

 

Firma Digitale con dispositivo crittografico sicuro
II parte – Il processo di verifica

Dopo avere analizzato le modalità di accesso alle smart card e aver firmato dei dati andiamo a studiare le parti che compongono il processo di Verifica di un file con firma digitale in formato “S/MIME PKCS#7 SignedData” (*.p7m).

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:

  1. 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).
  2. 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)
  3. decifrare con la chiave pubblica del mittente il messaggio firmato (verifica della firma del messaggio) e controllare che questo coincida con quello inviato.
  4. 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 dell’ipotetico 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 all’utente
    // 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 dell’ente (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
//all’interno 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 dell’Issuer 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 l’autorità 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 all’interno 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 all’interno della busta PKCS#7 corrisponde alla relativa pubblica ottenuta dal certificato all’interno 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 dell’issuer e authID, ovvero //“l’identificativo 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 dell’Issuer da verificare
private X509Certificate certificateTarget;
//nome dell’issuer
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 all’interno 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 l’università di Firenze. Ha svolto una tesi sviluppando un prototipo di applicazione per la Firma Digitale con l’utilizzo dispositivi crittografici come smart card.