Uso pratico di Inline::Java, j4sign & Bouncy Castle Crypto APIs
In Italia dal 2011 le applicazioni che implementano meccanismi di firma digitale a valore legale devono supportare
SHA 256 e il recente formato per imbustare documenti con marcatura temporale (
RFC 5544).
Sembra che l'unica libreria Open Source che implementi la RFC 5544 siano le classi Java del progetto
The Legion of the Bouncy Castle a loro volta utilizzate dal progetto
j4sign (An open, multi-platform digital signature solution.).
Per utilizzare delle classi Java dall'ambiente di sviluppo
HTML::MASON è possibile fare uso di
Inline::Java che istanzia una
JVM richiamabile dai processi
Apache dell'applicazione.
Vediamo quindi come utilizzare le classi di Bouncy Castle e di
j4sign e per effettuare la verifica della validità di documenti firmati e marcati digitalmente con le attuali regole italiane, opportunamente modificate per utilizzarle direttamente da Perl in ambiente Apache &
mod_perl sfruttando le potenzialità di Inline::Java.
Deploy classi Java
Nota: Attualmente
(8 Ott 2012) è disponibile la versione 0.2.1 di "verifica-firma", ma la path fornita e stata generata partendo dalla versione 0.2.0
mkdir -p /opt/my_application/src/java
cd /opt/my_application/src/java
wget http://sourceforge.net/projects/j4sign/files/verifica-firma/0.2.0/verifica-firma-0.2.0-src.zip
unzip verifica-firma-0.2.0-src.zip
mv verifica-firma-0.2.0-src verifica-firma-0.2.0-src-GDO
- Applicare la Patch verifica-firma-0.2.0-src.diff alla cartella verifica-firma-0.2.0 :
- Classi modificate:
- it/trento/comune/j4sign/verification/RootsVerifier.java - init dei parametri come parametri passati alla chiamata del metodo getInstance
- it/trento/comune/j4sign/verification/RootsVerifier.java - aggiunti parametri cnipaDir, cnipaCa, cnipaRoots al metodo RootsVerifier ed eliminati i riferimenti alla classe ...Configuration
- it/trento/comune/j4sign/verification/X509CertRL.java - commentata riga "import org.apache.commons.configuration.!ConfigurationException;" - classe non utilizzata
- it/trento/comune/j4sign/verification/CertValidity.java - aggiunto parametro Date ai metodi getExpired e getPassed per indicare la data di verifica della validità del certificato
- it/trento/comune/j4sign/verification/VerifyResult.java - aggiunto parametro Date al metodo getPassed per indicare la data di verifica della validità dei certificati
- it/trento/comune/j4sign/verification/servlet/Verifier.java - aggiunto parametro Date al metodo verify per indicare la data di verifica della validità dei certificati
- Classe aggunta:
- it/trento/comune/j4sign/verification/servlet/VerifyService.java
wget https://www.leader.it/pub/Blog/Uso_pratico_di_InlineJava_j4sign__Bouncy_Castle_Crypto_APIs/verifica-firma-0.2.0-src.diff
cd verifica-firma-0.2.0-src-GDO
# applica le modifiche ...
# N.B. file generato con: diff -rupN verifica-firma-0.2.0-src verifica-firma-0.2.0-src-GDO >verifica-firma-0.2.0-src.diff
patch -p1 < ../verifica-firma-0.2.0-src.diff
- Copia classi java compilate, necessarie, nella cartella /opt/my_application/lib/java
Nota: Nel sito ufficiale di Bouncy Castle la versione jdk16-146 non è più disponibile nel sito Web ma le classi si possono reperire su altri siti in Rete oppure dal server FTP
ftp://ftp.bouncycastle.org/pub/release1.46/ (attenzione che non supporta i collegamenti in "passive mode").
mkdir -p /opt/my_application/lib/java
cd /opt/my_application/lib/java
cp -a /opt/my_application/src/java/verifica-firma-0.2.0-src-GDO/lib/j4sign-core.jar ./
wget --no-passive-ftp ftp://ftp.bouncycastle.org/pub/release1.46/bcmail-jdk16-146.jar
wget --no-passive-ftp ftp://ftp.bouncycastle.org/pub/release1.46/bcpg-jdk16-146.jar
wget --no-passive-ftp ftp://ftp.bouncycastle.org/pub/release1.46/bcprov-jdk16-146.jar
wget --no-passive-ftp ftp://ftp.bouncycastle.org/pub/release1.46/bctest-jdk16-146.jar
wget --no-passive-ftp ftp://ftp.bouncycastle.org/pub/release1.46/bctsp-jdk16-146.jar
- Compilazione delle classi Java
cd /opt/my_application/src/java/verifica-firma-0.2.0-src-GDO
sh compila_java.sh
Verrà generata la classe nel file
/opt/my_application/lib/java/VerifyService.jar
File CA Root
Per la verifica della firma è necessario fornire l'elenco delle CA accreditate al Cnipa (Ora DigitPA)
mkdir -p /opt/my_application/etc/CNIPA
cd /opt/my_application/etc/CNIPA
wget http://www.cnipa.gov.it/site/_files/LISTACER_20120911.zip.p7m
wget http://sourceforge.net/projects/j4sign/files/digitpa-certs/Certificati.zip
Esempio di utilizzo
File di configurazione
- Nel file di configurazione di apache aggiungere:
# Parametri configurazione verify_service_java (vedi /etc/init.d/verify_service_java)
#
# Cartella usata da Inline::Java
PerlSetVar VerifyService_directory "/opt/my_application/lib/java/InlineDir"
PerlSetVar VerifyService_classPath "/opt/my_application/lib/java/VerifyService.jar:/opt/my_application/lib/java/bcprov-jdk16-146.jar:/opt/my_application/lib/java/bcmail-jdk16-146.jar:/opt/my_application/lib/java/bctsp-jdk16-146.jar"
PerlSetVar VerifyService_confDir "/opt/my_application/etc"
# Cartella in "VerifyService_confDir" contenente i certificati
PerlSetVar VerifyService_cnipaDir "CNIPA"
# CA Root
PerlSetVar VerifyService_cnipaCa "DigitPA.cer"
# Elenco delle Root CA accreditate
PerlSetVar VerifyService_cnipaRoots "LISTACER_20101129.zip.p7m"
# L'impronta corrisponde a quella pubblicata nella Gazzetta Ufficiale n. 122 del 27 maggio 2010 (vedi es. http://gazzette.comune.jesi.an.it/2010/122/2.htm )
PerlSetVar VerifyService_fingerprintDigitPA "20EA4FB83593DB5C4407A538C920EBC3C5B1559C"
PerlSetVar VerifyService_validityMPMdate "2011-07-01 00:00:00.000 +0200"
Applicazione di esempio
- Cartelle usate da Inline::Java
mkdir -p /opt/my_application/lib/java/InlineDir
mkdir -p /opt/my_application/htdocs/_Inline
- Codice di upload del file firmato e marcato digitalmente
<FORM ACTION="./firma/put_firmato.html" target="_blank" name="my_file" METHOD="POST" ENCTYPE="multipart/form-data">
<SPAN ID="ArchiviazioneFileFirmato">
Path file: <input type="file" name="file_firmato" lenght=25>
<INPUT TYPE="SUBMIT" VALUE="Archiviazione File Firmato">
</SPAN>
</FORM>
File /firma/put_firmato.html
Nel file vengono definite le quey SQL:
- per estrarre l'elenco dei firmatari del documento da verificare
- per verificare se nel database è presente un documento con la hash corrispondente al file da verificare
- la query per archiviare il file firmato.
<BR>
<BLOCKQUOTE>
<%flags>
inherit => '/firma/put_firmato.comp'
</%flags>
<%perl>
$m->clear_buffer;
$m->call_next;
</%perl>
%# Nome del campo contenente il file scaricato
<%method Field_filename>file_firmato</%method>
%# Firme da cercare nel file firmato
<%method Query_firme>
select distinct cognome, nome, codice_fiscale
from documenti
inner join firmatari_documenti on firmatari_documenti.id_documento =
documenti.id
inner join anagrafiche on firmatari_documenti.id_firmatario =
anagrafiche.id
where documenti.hash_file = ?
</%method>
%# Per verificare se nel DB e' presente la hash calcolata
<%method Query_archivio>
select codice, marca_temporale
from documenti
where hash_file = ?;
</%method>
%# Per aggiornare l'archivio caricando il file firmato
<%method Query_update_archivio>
update documenti
set tsd = ?,
file_firmato = ?,
file_originale = null,
marca_temporale = ?,
stato_rapporto = 'firmato'
where hash_file = ?;
</%method>
Il file put_firmato.html fa riferimento al file put_firmato.comp nel quale sono implementate le procedure di verifica della firma
File /firma/put_firmato.comp
Di seguito si commentano le parti riguardanti l'interazione con le classi Java:
Attivazione del servizio java per la verifica della firma
Viene istanziata una Java Virtual Machine condivisa tra tutti i processi Apache:
use Inline (
Java => 'STUDY',
SHARED_JVM => 1,
START_JVM => 0,
PORT => 7893,
AUTOSTUDY => 1,
# DEBUG => 2,
DIRECTORY => $r->dir_config('VerifyService_directory'),
STUDY => ['it.trento.comune.j4sign.verification.servlet.VerifyService'],
CLASSPATH => $r->dir_config('VerifyService_classPath')
);
- STUDY & AUTOSTUDY - Permettono di inglobare nel codice Perl le chiamate ai metodi delle classi Java elencate
- SHARED_JVM - istanzia una sola JVM condivisa tra tutti i processi Apache
- CLASSPATH - Percorso di ricerca delle classi Java
Inizializzazione della classe nella Java Virtual Machine condivisa, con i parametri di configurazione
I parametri di configurazione vengono letti dal file di configurazione di Apache.
my $VerifyService = new HTML::Mason::Commands::it::trento::comune::j4sign::verification::servlet::VerifyService(
$r->dir_config('VerifyService_confDir'),
$r->dir_config('VerifyService_cnipaDir'),
$r->dir_config('VerifyService_cnipaCa'),
$r->dir_config('VerifyService_cnipaRoots'),
$r->dir_config('VerifyService_fingerprintDigitPA')
);
A seconda dell'estensione del file scaricato viene utilizzato un diverso codice per la lettura dei file.
Nel caso il formato del file sia
m7m vengono estratti file firmato e pmarcatura temporale, contenuti nel file in formato
MIME:
#Parser file MIME
my $parser = new MIME::Parser;
mkdir "$tmpDir/mime_dir" || die "Errore creazione dir $tmpDir/mime_dir: $!\n";
$parser->output_dir("$tmpDir/mime_dir");
. . . . . . . .
my $entity = $parser->parse_open($sfile);
$fileP7M = $entity->parts(0)->{ME_Bodyhandle}->{MB_Path};
my $fileTSR = $entity->parts(1)->{ME_Bodyhandle}->{MB_Path};
if ($entity->parts != 2 || $fileP7M !~ m/\.p7m$/i || $fileTSR !~ m/\.tsr$/i) {
$m->out("<b>Era atteso un file in formato MIME contenente un file .p7m e un file con estensione .tsr ($filename)</b><BR>");
fine();
}
# data di applicazione della marcatura temporale (per la verifica della validità delle firme)
my $token = $VerifyService->verifyTSR($fileTSR); # class org.bouncycastle.tsp.TimeStampToken
# class org.bouncycastle.util.Store restituito chiamando token.getCertificates
my $certificates = $token->getCertificates->getMatches(undef)->toArray;
foreach my $cert (@$certificates){
# class
$m->out('Timestamp emesso da: '.$cert->getIssuer->toString);
}
$timestampDate = $token->getTimeStampInfo->getGenTime;
Se il file è invece nel nuovo formato
tsd si fa ricorso del metodo java
parseTSD:
# il file e' nel formato p7m TSD?
my $res = $VerifyService->parseTSD($sfile);
$fileP7M = "$sfile.p7m";
if(!$VerifyService->validateTSD($fileP7M)){ # nome del file contenuto nella busta, da salvare
$m->out("<b>Il file $sfile non contiene un certificato .p7m marcato temporalmente (TSD).</b><br>\n");
fine();
}
my $tokens = $VerifyService->tokensTSD;
foreach my $token (@$tokens){
my $certificates = $token->getCertificates->getMatches(undef)->toArray;
foreach my $cert (@$certificates){
$m->out('Timestamp emesso da: '.$cert->getIssuer->toString);
}
$timestampDate = $token->getTimeStampInfo->getGenTime;
last;
}
$tsd = 1;
Conversioni e test sulla data
Per entrambi i formati
m7m e
tsd viene effettuata la verifica di validità della marcatura temporale rapportata al limite temporale di validità delle firma applicate:
# conversioni e test sulla data
my $date_string = $timestampDate->toString;
my $date_sec = str2time($date_string);
my($sec,$min,$hour,$mday,$mon,$year) = localtime($date_sec);
my $date_iso = sprintf('%04i-%02i-%02i %02i:%02i:%02i', 1900+$year, $mon+1, $mday, $hour, $min, $sec);
$m->out(" il giorno: $date_iso<BR>\n");
if(!$tsd){
# limite di validità della data per il formato M7M
my $date_limit = $r->dir_config('VerifyService_validityMPMdate');
my $date_limit_sec = str2time($date_limit);
if($date_sec >= $date_limit_sec){
$m->out("<b>La data della marcatura temporale eccede il limite ($date_limit) concordato per il formato M7M</b><br>\n");
next;
}
}
Verifica delle firme nel file firmato
Si verifica inoltre che le firme siano valide alla data della marcatura temporale:
# verifica delle firme nel file firmato
my $Risultati = $VerifyService->verifyP7M($fileP7M, $timestampDate);
# salvataggio del file firmato
$VerifyService->saveContent("$fileP7M.content");
# slurp file
open SLURP, "<$fileP7M.content" || die "$! file $fileP7M.content\n";
my $let_file = do{local $/; <SLURP>};
close SLURP;
# codifica del file nel formato base64
$let_file = encode_base64($let_file);
# Calcola hash sul file letto
my $hash = Digest::SHA1::sha1_hex($let_file, "password");
Individuata l'impronta del file viene verificato che lo stesso sia presente in archivio:
# Verifica se nel DB e' presente la hash calcolata
$sth_archivio->execute($hash);
my $file_sconosciuto = 1;
while(my $archivio = $sth_archivio->fetchrow_hashref){
my $update_nuovo_file = 1;
$file_sconosciuto = 0;
if($archivio->{marca_temporale}){
$m->out("<B>Un file firmato e' gia' presente in archivio con il codice $archivio->{'codice'}</B><BR>\n");
$update_nuovo_file = 0;
}else{
my %lista_firme_verifica;
if($sth_firme){
Verifico che i soggetti firmatari coincidano con i firmatari dichiarati in archivio utilizzando il Codice Fiscale:
$sth_firme->execute($hash, $hash, $hash, $hash);
while(my $row = $sth_firme->fetchrow_hashref) {
$lista_firme_verifica{uc $row->{codice_fiscale}} = $row;
}
}
# lettura e confronto dei risultati
foreach my $result (@{$Risultati->values->toArray}){
my $cert = $result->getCert; # class org.bouncycastle.jce.provider.X509CertificateObject
my $X500str = $cert->getSubjectX500Principal->toString; # class javax.security.auth.x500.X500Principal
my $Subject = SubjectX500stringToHash($X500str);
$m->out("Firmato da: $X500str<BR>\n");
if(!$result->isPassed){
$m->out("<b>Test validita' della firma fallito! ( range date ".$cert->getNotBefore->toString." - ".$cert->getNotAfter->toString.")</b><BR>\n");
$update_nuovo_file = 0;
next;
}
# confronto degli attributi dei soggetti firmatari
my $CF; # Codice Fiscale
if($Subject->{SERIALNUMBER}){
$CF = $Subject->{SERIALNUMBER};
$CF =~ s/^IT://;
}elsif($Subject->{CN}){
my @fields = split /\//, $Subject->{CN}, 4;
$CF = $fields[2];
}
my $firma = $lista_firme_verifica{uc $CF};
if($firma){
# firmatario presente
# verifico che nome e cognome corrispondano
if(uc $Subject->{GIVENNAME} ne uc $firma->{'nome'} ||
uc $Subject->{SURNAME} ne uc $firma->{'cognome'}){
$m->out("<b>Test nominativo non corrispondente: ( $Subject->{GIVENNAME} $Subject->{SURNAME} <> $firma->{'nome'} $firma->{'cognome'} )</b><BR>\n");
$update_nuovo_file = 0;
next;
}
# lo tolgo dalla lista per verificare poi le firme mancanti
delete $lista_firme_verifica{uc $CF};
}else{
# firmatario non previsto: lo ignoro
}
}
# verifica firme mancanti
foreach my $CF (keys %lista_firme_verifica){
$update_nuovo_file = 0;
my $sogg = $lista_firme_verifica{$CF};
$m->out("<B>Manca la firma di $sogg->{nome} $sogg->{cognome} - C.F. $CF</B><BR>\n");
}
}
if($update_nuovo_file){
# aggiorno l'archivio con il file firmato e marcato temporalmente
open FIRMATO, "<$sfile" || die "$! file $sfile\n";
my $firmato = do{local $/; <FIRMATO>};
close FIRMATO;
$sth_update_archivio->execute($tsd, encode_base64($firmato), $timestampDate->toString, $hash);
$dbh->commit();
$file_sconosciuto = 0;
$m->out("<B>Verifica e archiviazione file firmato eseguita con successo</B> ($filename)<BR>");
}
}
Link:
Copyright (©)