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
Biografia autore
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.
