P7m codificato

In quel caso il formato è base64 ma non è ASN.1 regolare. Nei casi che ho visto io, c’è uno pezzo di fortuna: il file XML è infatti contiguo e non diviso in chunk, e questo significa che si può estrarlo manualmente anche se openssl non lo digerisce.

Quindi se il file è in base64 (vedi regex più in alto):
controlla se è diviso su più righe
se lo è, unisci le righe
decodifica da base64 e ottieni un file P7M

Decodifica con le librerie openssl (-inform DER)
se va bene, hai lo XML
se fallisce (ed è il caso tuo), controlla se all’interno del file c’è la sequenza <([^>]*)FatturaElettronica…/\1FatturaElettronica>
se non c’è, io non so cos’altro fare e rifiuto il flusso
se c’è, quello è lo XML (non diviso in chunk e quindi non necessitante di ulteriori decodifiche; ma vale la pena controllare se si decodifica. A me si decodifica sempre, ma meglio controllare).

Finché lo XML inizia con un BOM
‘’’ Eh sì, “finché”. Perché ho beccato due XML con BOM raddoppiato.
rimuovi il BOM

Finché lo XML inizia con la testata XML
‘’’ In realtà potrei dire “SE” inizia con la testata perché è impossibile che ce ne sia più d’una,
‘’’ ma il codice è praticamente identico e così mi sento più sicuro.
rimuovi la testata

E a questo punto hai un XML senza testata, pulito e leggibile (oppure segnali errore).

Per ora questo “algoritmo” ha accettato sulle cinquemila fatture senza dare problemi; le fatture che richiedono l’escamotage dell’estrazione via regexp sono circa una ogni venti-trenta, ma ho visto che dipende dal cliente. Ci dev’essere in giro qualche software creativo, là fuori.

Mi chiedo come mai SDI non abbia imposto dei formati standard e come facciano a passare i base64…
Io sono a oltre 10000 fatture ricevute con sole 3 eccezioni che ho dovuto gestire e reimplementare nel codice del parser.
Devo dire che il sistema mi fa sempre più schifo di giorno in giorno, specifiche date a menga e continuamente disattese, procedure di controllo e monitoring che non vanno (è dal 14 che non ricevo le email di monitoraggio dei flussi) file in attesa nelle directory di interscambio per ore (e a volte giorni). Ma è mai possibile che non si riesca mai a fare una cosa in modo, non dico perfetto ma almeno, soddisfacente ?

Una domanda: come si usa openssl da php per “pulire” un file p7m? Qualcuno che ha un esempio?
In rete trovo comandi solo da linea di comando ma a me interessava una versione con le funzioni native del php.
Attualmente sto usando una funzione che rimuove testa e coda e caratteri non utf8 dal file… So che non è sempre valida e ho visto che alcuni file che ho non vengono puliti nel modo giusto…

usa la funzione exec per eseguire openssl.
Non vedo grossi vantaggi ad usare le funzioni native php che comunque lavorano su file (vedi openssl_pkcs7_decrypt)
Se proprio vuoi velocizzare l’operazione meglio lavorare su un filesystem tmpfs.

Il problema è che non credo sia installato openssl sul server che ospita il mio gestionale php. Uso server aruba. E non posso installare programmi esterni…

Ciao a tutti. Io ho incontrato lo stesso problema (headers intermedi nel p7m) nella stesura di mie procudure per Excel e
Libreoffice.
Ho trovato una possibile soluzione notando che i caratteri sono sempre a coppia, uno non ascii128 e il secondo anche ascii standard.
In Excel è bastata una regex di sostituzione del tipo “[^\x00-\X7F].” (trova due caratteri di cui il primo NON ascii 0-128, il secondo un carattere qualsiasi. Sembra funzionare per varie fatture.
Attendo confronti.
PS pubblico i file su www.marcogualmini.it/http://www.marcogualmini.it/strumenti_fattura_elettronica.htm
tra qualche gg per fare alcuni test
Ciao
Marco

Oggi faccio il pignolo.
Quello che hai scoperto non è necessariamente vero. Dipende da quanto sono lunghi i “blocchi” in cui è spezzato il file XML. Piuttosto che fare a caso, forse è meglio leggere le specifiche (ASN.1 con codifica BER), oppure usare un tool già pronto (tipo openssl).
Comunque, se il file XML è spezzato in blocchi, ogni blocco avrà davanti un header di due o più byte fatto così:

  1. Il primo byte sarà 0x04 (tipo OCTET STRING)
  2. Se la lunghezza del blocco è <=127, il secondo byte contiene la lunghezza del blocco. Se la lunghezza del blocco è >=128, il secondo byte ha il bit più significativo settato e gli altri sette indicano quanti byte successivi vengono usati per specificare la lunghezza.
  3. -n. I byte dal terzo in poi contengono la lunghezza (se >= 128).

Quindi: se un blocco ha lunghezza minore o uguale a 127, il header sarà lungo 2 byte. Se la lunghezza è compresa tra 128 e 255, il header sarà lungo 3 byte. Se la lunghezza è compresa tra 256 e 65535, il header sarà lungo 4 byte. Se la lunghezza è compresa tra 64K e 16M, il header sarà lungo 5 byte, ecc.

Nei file che ho analizzato (ma non è che ho controllato tutte le fatture ricevute) i blocchi avevano lunghezza 1000, quindi con header di 4 byte. In questo caso il tuo algoritmo funziona, ma se la lunghezza fosse per esempio di 8K, il terzo byte del header sarebbe 0x20, ovvero uno spazio, e non lo rileveresti.

1 Mi Piace

io inizialmente avevo risolto eliminando i caratteri non stampabili ma dopo 20 fatture ne è arrivata una dove a metà tag aveva aggiunto una S maiuscola.

l’unica soluzione è stata salvare l’xml in un file temporaneo, farlo interpretare da OpenSSL e ricavare il file XML pulito

Ciao Romolo, scusa non ho fatto a meno di notare che nomini “email di monitoraggio flussi”. Cosa sono?

Io ho un canale aperto SDICoop, e da intermediario volevo sapere se c’è un modo di verificare in che stato sono le fatture inviate all’SDI (un po’ come avveniva nella modalità test sul portale fatturaPA).

Grazie

Sono mail che SDI dovrebbe e dico dovrebbe inviare agli intermediari SDIFTP ma che al momento non invia più, riportanti le statistiche di ricezione dei flussi inviati e lo stato di elaborazione…
E’ dal 14 che non ne ricevo una nonostante tutti i possibili ticket aperti…

Ok, quindi essendo io accreditato come SDICoop non è previsto che riceva alcunchè.
Grazie mille

Grazie per l’eccellente sintesi di questo aspetto del protocollo. A questo punto ci metto poco a scrivere una routine per la pulizia. Che pubblico poi qui.
Ciao
Marco

Per la pulizia di una singola fattura conviene usare strumenti appositi tipo arubasign o dike e poi utilizzare xml estratto in un visualizzatore. Detti strumenti inoltre sono gli unici che consentono la verifica della firma digitale che se inutile nel caso di terzo soggetto o intermediario, potrebbe invece essere utile per verificare l’autenticità di fatture firmate direttamente dal prestatore.
Per flussi massivi invece credo che utilizzare openssl (disponibile anche su windows) sia sempre la soluzione migliore.

L’altro problema è identificare l’inizio e la fine del file xml all’interno del file p7m,
perché non tutti i file delle fatture elettroniche iniziano con <?xml. La dichiarazione xml è opzionale. Molti iniziano direttamente con l’elemento <FatturaElettronica>. Solo che può anche avere un prefisso per il namespace (<pippo:FatturaElettronica>).
A me questo approccio euristico non piace per niente, considerando che il formato è standard e documentato. La cosa migliore è utilizzare una libreria già pronta, oppure richiamare un programma esterno (tipo openssl).
Volendo usare una soluzione “fai-da-te”, andrebbe implementato il parsing dei file .p7m seguendo lo standard. Io ci ho messo un paio d’ore, basandomi su questa guida introduttiva al formato ASN.1:
http://luca.ntop.org/Teaching/Appunti/asn1.html

1 Mi Piace

Se vuoi puoi usare openssl_pkcs7_verify()
Devi passare come flags PKCS7_NOVERIFY per evitare failure in decodifica per mancanza della CA e PKCS7_BINARY se il file è in formato binary (eventualmente dopo averlo decodificato con base64_decode).
per scriverti un esempio:

openssl_pkcs7_verify ( "IT01234567890_00001.xml.p7m" , PKCS7_NOVERIFY|PKCS7_BINARY , "IT01234567890_00001.cert" , "" , "" , "IT01234567890_00001.xml");

puoi poi usare openssl_x509_read and openssl_x509_parse per analizzare il certificato se ti interessa.

Grazie della dritta. Farò una prova con il codice… Quello che avevo trovato mi dava sempre - 1

Il codice che ti ho dato sulle nuove versioni di PHP non funziona più almeno non con i setting di default.

per farlo funzionare (ed ottenere il file) ho dovuto fare delle porcate inenarrabili:

  1. ho dovuto ottenere una lista di CA…

  2. ho modificato la chiamata in questo modo

    openssl_pkcs7_verify ("IT01234567890_00001.xml.p7m-b6m" , 0 , null ,array("CA.pem"),"CA.pem","IT01234567890_00001.xml");

dove CA.pem è la lista delle possibili CA vedi questo sito per come ottenerne una

usa al posto di

https://applicazioni.cnipa.gov.it/TSL/_IT_TSL_signed.xml

https://eidas.agid.gov.it/TL/TSL-IT.xml

b6m è la versione SMIME del file p7m (leggi i post di openssl_pkcs7_verify per come creare quel file

per me è un UCAS…

ho visto tuttavia che esiste una libreria commerciale [Chilkat] che dovrebbe poter decodificare i file… https://www.example-code.com/phpext/crypt_extract_from_p7m.asp
Tuttavia:

  1. non l’ho mai provata e non garantisco
  2. non so se la puoi installare sui tuoi server

Ecco perchè a me piace avere la mia infrastruttura :slight_smile:

Grazie oggi provo a fare qualche test.
Si la libreria chilkat l’avevo trovata ma non mi è possibile installarla… oltre ad non essere free sfortunatamente.
Ora devo solo capire come convertire quello script .sh per recuperare i CA e convertirlo in funzione PHP e poi faccio qualche prova e vi tengo aggiornati. Uso ancora PHP 5.6 quindi spero di cavarmela con il primo codice postato.

anche nel caso di primo codice postato comunque il file deve essere in formato smime

Non so dove sbaglio ma non riesco a convertire il p7m come dici tu.
Mi restituisce solo un valore INT.

ho usato la funzione trovata:

function der2smime($file)
{
$to=<<<TXT
MIME-Version: 1.0
Content-Disposition: attachment; filename=“smime.p7m”
Content-Type: application/x-pkcs7-mime; smime-type=signed-data; name=“smime.p7m”
Content-Transfer-Encoding: base64
\n
TXT;
$from=file_get_contents($file);
$to.=chunk_split(base64_encode($from));
return file_put_contents($file,$to);
}

$file = “IT03461720173_00B60.xml.p7m”;
$smimefile = der2smime($file);
var_dump($smimefile); // restituisce solamente un INT