quoll file p7m come gestirli

p7m firma digitale: come estrarre il contenuto

Pubblicato il Pubblicato in Firma digitale, System administrator

Firma digitale e file con estensione p7m

Vi sarà capitato di dover aprire un file con estensione p7m, ovvero quelli provenienti dalla PEC e firmati digitalmente.
Beh! a me è capitato qualche giorno fa.

Non avendo un software che gestisce direttamente il file ho perso un po’ di tempo ad estrarre sia il contenuto che le firme e verificarne la correttezza.

Quindi per non fare perdere altro tempo anche a voi (ma anche a me quando mi ricapiterà di nuovo) credo che un articolo sia necessario.

Cos’è il formato p7m

Il file p7m è un file di firma digitale nel formato CAdES (CMS Advanced Electronic Signatures) che è un’estensione del Cryptographic Message Syntax (CMS) che è basato a sua volta su PKCS#7.
Quindi il file p7m non è altro che un contenitore che incapsula sia il documento originale (non criptato) che la firma digitale personale e quella della catena della CA che ha rilasciato il certificato.
Andando per ordine nel file p7m troviamo:
– nei primi byte  l’header PKCS#7
– poi viene il nostro documento originale (non criptato)
– quindi l’hash di firma (che valida per legge il contenuto)
– il certificato digitale personale di chi ha firmato
– ed infine il certificato della CA

Estrazione del documento originale

Consideriamo il caso in cui il documento firmato sia un pdf (documento.pdf.p7m).

Il modo più semplice per visualizzare il pdf incapsulato nel documento.pdf.p7m è quello di aprirlo direttamente con un editor pdf come acroread o okular o evince dato che  l’header PKCS#7 dovrebbe essere di pochi byte e quindi, se rinominiamo il file con estensione pdf quasi sicuramente, i lettori standard riusciranno a visualizzare direttamente il file senza dover estrarre il contenuto (magari con qualche messaggio di errore durante l’apertura).
Per far le cose più precise possiamo provare ad editare il file .p7m con “vim” ed eliminare direttamente i primi byte (50 – 100) sino a %PDF-1 (naturalmente lasciando la stringa %PDF-1 come inizio file .pdf).
Dato che ci siamo proviamo ad estrarlo correttamente eliminando anche i byte successivi al fine pdf e cioè la stringa %%EOF.
A questo punto il documento pdf sarà estratto correttamente.

Per estrarlo con un unico comando, senza editarlo possiamo utilizzare il comando

perl -ne 's/^.*%PDF/%PDF/; print if /%PDF/../%%EOF/' documento.pdf.p7m >documento.pdf

oppure utilizziamo il comando openssl con il parametro smime per la verifica della firma (-verify)

openssl smime -verify -noverify -in documento.pdf.p7m -inform DER -out documento.pdf

Estrazione del certificato

Per estrarre il certificato digitale personale di chi ha firmato basta usare openssl con il comando

openssl pkcs7 -inform DER -in documento.pdf.p7m -print_certs -out cert.pem

il certificato è nel file cert.pem e se si vuole visualizzare in modo testuale basta il comando per i certificati x509

openssl x509 -in cert.pem -text -noout

Verifica della firma di tutto il documento

Per verificare la firma bisogna utilizzare il comando

openssl smime -in documento.pdf.p7m -inform DER -verify -out documento.pdf

mentre per verificare la validità del semplice certificato il comando è

openssl verify cert.pem

Naturalmente, provando così com’è, la verifica fallisce (“unable to load certificate“) perché non abbiamo i certificati di fiducia della CA.
Tali Certificate Authority sono state definite per legge italiana e sono registrati sul CNIPA, che dal dicembre 2009  è diventato DigitPA, come portafoglio di certificati XML e li ho trovati sullo stesso sito all’indirizzo https://applicazioni.cnipa.gov.it/TSL/_IT_TSL_signed.xml.
Per far in modo che openssl li gestisca bisogna metterli nel suo formato quindi:

wget -O - https://applicazioni.cnipa.gov.it/TSL/_IT_TSL_signed.xml | perl -ne 'if (/<X509Certificate>/) {
s/^\s+//; s/\s+$//;
s/<\/*X509Certificate>//g;
print "-----BEGIN CERTIFICATE-----\n";
while (length($_)>64) {
print substr($_,0,64)."\n";
$_=substr($_,64);
}
print $_."\n";
print "-----END CERTIFICATE-----\n";
}' >CA.pem

Per gli aggiornamenti allo script consultare https://github.com/eniocarboni/getTrustCAP7m

In tal modo abbiamo tutti i certificati presenti in un unico file CA.pem, purtroppo anche quelli che forse sono scaduti.
Anche se scaduti potremmo aver bisogno di verificare un file vecchio e quindi ci possono servire anche gli scaduti.

Adesso possiamo verificare il certificato con esito positivo con:

openssl verify -CAfile CA.pem cert.pem

ed il file p7m con il comando:

openssl smime -in documento.pdf.p7m -inform DER -verify -CAfile CA.pem -out documento.pdf

Naturalmente quest’ultimo comando oltreché verificare la validità della firma estrae anche il documento originale.

Collegamenti esterni:

CAdES vedere

CMS vedere

PKCS#7 vedere

Continua a leggere la seconda parte dell’articolo

34 pensieri su “p7m firma digitale: come estrarre il contenuto

    1. Ciao Antonio,
      direi che per creare un p7m dato il file pdf “documento.pdf” basta utilizzare il comando “smime”

      openssl smime -sign -outform DER -binary -md sha256 -in documento.pdf -out documento.pdf.p7m -signer your_priv_certs.crt.pem -inkey your_priv_certs.key.pem -noverify -nodetach
      

      Per l’ultima versione smime, spero anche compatibile con il CNIPA, credo che bisogna utilizzare “cms” al posto di “smime”:

      openssl cms -sign -outform DER -binary -md sha256 -in documento.pdf -out documento.pdf.p7m -signer your_priv_certs.crt.pem -inkey your_priv_certs.key.pem -noverify -nodetach
      

      Perché sia valido a livello legale dovresti controllare il file con qualche tool presente online e fare tutti i controlli possibili.

  1. Io invece ho una cartella zeppa di file in formato .p7m ed ho notato che la ricerca di una stringa di testo di windows non funziona; c’è qualcuno che può suggerire una soluzione?

    1. Ciao Andrea,
      non so bene come funzioni in windows ma non credo che riesca ad indicizzare direttamente i documenti presenti in file .p7m.
      Sotto Linux basta utilizzare un semplice script come questo:

      #! /bin/bash
      stringa_da_trovare=$1;
      for file in *.p7m; do
      trovato=$(openssl smime -verify -noverify -in "$file" -inform DER  2>/dev/null | pdftotext - - | grep  "$stringa_da_trovare")
      if [ $? == 0 ]; then
      IFS="\n"
      t=$(echo $trovato | sed 's/^/\t/g')
      echo -e "trovato in $file:\n$t"
      IFS=''
      fi
      done

      Supponiamo di salvarlo nel file find_in_p7m.sh nella stessa cartella dove sono i .p7m e supponendo sempre che i .p7m contengano pdf (per gli xml o altro bastano poche modifiche).
      Lanciamo il comando per rendere eseguibile lo script:

      chmod +x find_in_p7m.sh

      e poi supponiamo di dover cercare la stringa “fattura” basta lanciare il comando:

      ./find_in_p7m.sh "fattura"

      che restituisce sia i file .p7m trovati che il contenuto trovato.
      Credo che con powershell dovresti riuscire a far la stessa cosa sotto windows magari scaricandoti pdftotext da http://www.foolabs.com/xpdf/download.html e naturalmente avendo a disposizione openssl (http://gnuwin32.sourceforge.net/packages/openssl.htm)

  2. Buona sera, se volessi estrarre il contenuto pdf da un p7m in php come potrei fare? sul mio host il servizio exec di php non è abilitato e devo usare solo funzioni di php
    Grazie

    1. Ciao Fabio,
      purtroppo credo che in php la libreria OpenSSL (http://php.net/manual/en/ref.openssl.php) non permetta di usare “smime” per estrarre documenti nel formato CAdES (ma forse mi sbaglio e qualcuno mi correggerà).
      Potresti, comunque, provare ad estrarlo direttamente dal file .p7m come l’ho descritto nell’articolo ovvero aprendo il file, saltando tutti i caratteri sino a (ma escluso) %PDF-1 e poi predi tutto sino a %%EOF ed il resto lo tralasci.
      Non dovrebbe esser un operazione difficile (direi poche righe di codice).

      1. Grazie della risposta. Ho provato anche quello ma, soprattutto per i pdf/a, l’estrazione della stringa così come descritto non produce un pdf valido

        1. Ciao Fabio,
          è molto strano che per i pdf/a non funzioni dato che il pdf/a non è altro che un sottoinsieme del pdf dove vengono eliminate tutte le funzioni che non permettono l’archiviazione a lungo termine (ad esempio la crittografia con password del testo).
          Se hai un .pdf.p7m non personale che è possibile visionare prova a postare qui il link per poterlo verificare.

  3. Io ho provato a cancellare le righe che stanno prima di %PDF-1 e tutto ciò che sta dopo %%EOF ma il PDF che ottengo è vuoto. Ho provato anche a lanciare la riga di comando:

    perl -ne 's/^.*%PDF/%PDF/; print if /%PDF/../%%EOF/' documento.pdf.p7m >documento.pdf

    E ottengo comunque un PDF con una pagina sola e completamente bianca

    1. Ciao Roberto,
      Forse nel tuo pdf c’è un’altra stringa %%EOF, magari come commento. In tal caso prova con:

      perl -ne ‘s/^.*%PDF/%PDF/; print if /%PDF/../^%%EOF/’ documento.pdf.p7m >documento.pdf

      dove in tal caso considero che %%EOF deve stare ad inizio riga.
      Inoltre se il file pdf ha delle “modifiche incrementali” (incremental update) o revisioni queste vengono messe in coda aggiungendo i vari pezzi e terminando con un ulteriore %%EOF (ad esempio se il pdf firmato viene modificato e poi rifirmato nelle modifiche).
      In tal caso devi lasciare tutti i %%EOF esistenti sino all’ultimo ed il comando “perl” non funzionerà mentre “openssl” dovrebbe andar sempre bene.
      Fammi sapere se sei in uno di questi casi e se sei riuscito a risolvere.

      1. Ciao Enio,
        grazie innanzitutto della risposta. No anche con quella stringa non riesco, ottengo lo stesso PDF vuoto.

        Alla fine ho usato l’altro tuo suggerimento (openssl) ma ora volevo chiederti una cosa in più se la sai. Sarebbe possibile tramite Java far la stessa cosa usando (immagino) la libreria openssl? Ne sai qualcosa? In questa maniera potrei fare io un .jar al volo che funzionerebbe anche su Windows.

        Grazie e ciao.

  4. Caio Enio,
    bellissimo articolo! Mi hai aiutato a risolvere diverse grane (e a non usare Windows…)
    In questi giorni ho scoperto che mentre alla pubblica amministrazione piacciono molto i file .P7M, spesso all’estero usano firmare i documenti tenendo separati file e firma (che ho scoperto si possono ottenere senza l’opzione -nodetach, quindi generando una “clear signature”).
    Sai se esiste un modo, usando OpenSSL, per fare una sorta di “merging” tra il file e la sua firma (che come standard ha estensione .P7B) ottenendo un file .P7M?
    Complimenti ancora! Ciao.

  5. Ciao Enio,
    di recentissimo mi sono imbattuto nei problemi di firma digitale… e quindi in questo “fantastico” meccanismo messo in piedi dalla nostra PA.
    Sai dov’è possibile trovare la specifica del file P7M? Spero l’abbiano fatta open…
    Poi mi frulla un’altra idea, fatto 30 facciamo 31: chiaro il meccanismo con cui leggo e produco il file P7M con openSSL… ma come recupero la mia chiave privata imprigionata nella smart card che mi hanno dato? Sai se openSSL possa interfacciarsi ai lettori, oppure se c’è qualche tool che possa interaggire?

    Grazie mille e ottimo lavoro!
    Albe

    1. Da quello che ho capito io: usando il driver apposito per la tua smartcard si può estrarre il certificato (io ci sono riuscito) però poi non è possibile usare openssl per generare una CADES a meno di non usare una versione modificata con una patch che, al momento, non è stata accettata nel programma originale. I dettagli li trovi qui: http://www.blia.it/firmadigitale/index.php

  6. Io vorrei conoscere una applicazione che mi apre in serie, ovvero mi estrae immediatamente con un solo right-click da piu file selezionati, il file non firmato.
    Oltre al DIKE che ne apre uno solo ed uno alla volta, ognuno in una finestra separata, non esiste altro ?

    Grazie

  7. è possibile generalizzare gli scripts di estrazione in modo tale che agiscano in batch su più files?

    perl -ne ‘s/^.*%PDF/%PDF/; print if /%PDF/../%%EOF/’ documento.pdf.p7m >documento.pdf
    openssl smime -verify -noverify -in documento.pdf.p7m -inform DER -out documento.pdf

    grazie per l’ottimo articolo

    1. Grazie per i complimenti.
      Per elaborare in batch diversi file .pdf.p7m basta metterli in una directory e lanciare da lì la seguente riga di codice bash:

      for file in *.pdf.p7m; do pdfname=$(basename "$file" .p7m); openssl smime -verify -noverify -in "$file" -inform DER -out "$pdfname"; done 
      1. grazie per la cortese risposta
        anche io sono riuscito a mettere insieme qualcosa del genere (sintassi leggermente differente e meno compatta)

        ############
        for file in *.p7m
        do
        filename=”${file##*/}”
        filename_new=”${filename%.*}”
        openssl smime -verify -noverify -in “$filename” -inform DER -out “$filename_new”
        done

        ##########

  8. è possibile verificare la firma di un documento firmato digitalmente in formato PAdES (pdf) tramite uno script bash?

    grazie e complimenti ancora

  9. È possibile generare un file p7m a partire da un xml con PHP che sia validato dal sito https://postecert.poste.it/verificatore/ ?
    ho provato con:
    openssl_pkcs7_sign(
    __DIR__.’/prova.xml’,
    __DIR__.’/prova.p7m’,
    ‘file://’.__DIR__.’/XXX.pem’,
    [‘file://’.__DIR__.’/XXX.pem’,’123456′],
    array(“To” => “oscar@example.com”, “From: HQ “, “Subject” => “Eyes only”));

    pur creando il file p7m, il file non viene mai validato dal sistema delle poste.
    La PA vuole le fatture tramite sito, con un xml firmato in formato p7m.

  10. Ciao, ho approffitato della tua documentazione per farmi uno script che poi ho mutuato da Linux a Win, mentre l’apertura dei *.pdf.p7m o *.doc.p7m mi ha sempre funzionato bene, openssl da’ invece un errore sugli otd.p7m

    Un saluto

    1. Grazie Maurizio del file che mi hai girato.
      Dopo un attento controllo ho visto che il file è in formato pem invece che der quindi la cosa da fare in questa situazione è convertire il file in der.
      Per farlo basta il semplice comando openssl:

      openssl pkcs7 -outform der -in file-originale-pem.odt.p7m -out file-originale-der.odt.p7m

      A questo punto se tutto va bene usiamo il solito comando:

      openssl smime -verify -noverify -in file-originale-der.odt.p7m -out file-originale.odt
  11. Ciao enio, grazie per l’articolo.
    Io ho dei file xml in formato codificati p7m in cui l’xml è stato modificato con l’aggiunta “ciclica” di 4 caratteri di controllo.
    Mi sembra che siano sempre gli ultimi quattro presenti prima dell’inizio dell’xml
    ES: (i 4 caratteri sono ‚è)
    0€ *†H†÷
     €0€10
    `†He 0€ *†H†÷
     €$€‚è

    IT
    01234567890

    00001
    SDI10
    AAAAAA

    060601
    prova@mail.it

    IT
    01234567890

    Societa’ alpha S.r.l.
    ‚è

    Ne sai qualcosa?

    Grazie

    1. Ciao Andrea,
      all’interno dei .p7m ci può esser qualunque documento, il contenuto in genere dovrebbe esser conosciuto sia dal mittente che dal ricevente.
      Prova a chiedere spiegazioni al mittente del tuo messaggio dato che dovrebbe conoscere esattamente cosa ti manda per email.
      Grazie per i complimenti.

  12. Ciao Enio, grazie per l’ottima guida.

    Ciò che sto tentando di fare è estrarre l’allegato contenuto in un file “NomeFile.xml.p7m”.

    Il file xml è ovviamente una fattura elettronica, e c’è il tag “” con all’interno una stringa che a me pareva fosse codificata in base64.

    Quindi ciò che ho fatto è stato:
    · usare openssl per “estrarre” il contenuto “pulito” del file come da tuo esempio, esportandolo in “file.txt”
    · estrarre la sola parte contenuta tra i tag ” ….. ”
    · decodificare la stringa da base64 e provare a scrivere un file con estensione PDF

    Purtroppo mi restituisce un file PDF non valido.

    Ciò che non riesco a capire è in che algoritmo è codificato l’allegato …. mi sfugge qualcosa?

    Ti ringrazio per l’aiuto che vorrai darmi e ti auguro buon lavoro!

    Zak

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *