Invio Fattura in Java, uso certificati

Salve,

Sto cercando di integrare il mio servizio con l’SDI ma non riesco a superare i test. Ho creato il bundle di certificati (usando il certificato client) e sono riuscito a inviare fatture usando postman. In Java però continuo a ricevere

“PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target”

Sto usando jaxws-rt come libreria client e wsdl2java per generare le classi. Ho provato con

-Djavax.net.ssl.trustStore=keystore.jks -Djavax.net.ssl.trustStorePassword=test

ma niente. Qualcuno ha avuto successo in Java?

Attenzione a non confondere trust store e key store.
Il trust store serve per la validazione del certificato del server che chiami. In generale, il certificato esibito dal server dev’essere validabile, nel senso di essere emesso da una CA nota al “sistema”. Per sistema si intende la JRE, che però, a seconda dell’ambiente di installazione, potrebbe avere un elenco di certificati propri o (come più spesso accade in Linux, ad esempio), usare quelli del sistema operativo. Ora non ricordo se SdI in test usi certificati self-signed, certificati emessi da una CA “di test”, o emessi da una CA che fa capo ad una root CA ben nota oppure no. Nell’ultimo caso (quello più facile), l’errore che ricevi potrebbe significare che stai usando una versione di Java non aggiornata o, altrimenti, che l’elenco dei certificati installati nel tuo sistema non è sufficientemente aggiornato. Ad esempio in Ubuntu dovresti aggiornare i pacchetti ca-certificates e ca-certificates-java (forse uno tira dentro l’altro, non ricordo). Se, invece, il certificato del server SdI usato in test non è emesso da CA nota, devi recuperarti il certificato della CA radice utilizzata (di solito SdI li fornisce in questo caso - se il certificato è self-signed, è il certificato stesso) e crearti un truststore JKS dove dentro ci metti solo quel certificato e poi lo specifichi da riga di comando come stai facendo. Se ancora non funziona, prova ad aggiungerci l’eventuale certificato CA intermedio, se c’è.
Alternativamente alla specifica mediante riga di comando come stai facendo, potresti aggiungere il certificato CA in questione all’elenco dei certificati di sistema (vedi ad esempio qui per Ubuntu), ma dipende dall’ambiente che stai usando, perché chiaramente vai a rendere come attendibile per l’intero sistema un certificato “di test”.

Tuttavia, nella comunicazione con SdI entra in gioco anche il certificato client che ti serve per comunicare con loro. In questo caso il certificato client che ti forniscono, assieme alla tua chiave privata (che hai generato quando hai inviato loro la CSR con la richiesta di emissione del certificato) vanno messi in un key store (qui di solito si usa il formato p12, ma può essere anche un jks) che va specificato a livello di socket factory che usi per creare le connessioni con cui ti connetti ai loro web service. Qui a seconda della tecnologia che usi le tecniche potrebbero essere diverse.

Una proof-of-concept per creare una SSLSocketFactory (dove clientCertificateArchiveInputStream è un InputStream che punta ad un file .p12 con chiave privata e certificato client):

public static SSLSocketFactory createClientCertificateSslSocketFactory(
			final InputStream clientCertificateArchiveInputStream,
			final char[] clientCertificateArchivePassword,
			final Provider provider) throws IOException, CertificateException,
			UnrecoverableKeyException {
		final KeyManagerFactory keyManagerFactory;
		try {
			keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
			final KeyStore keyStore;
			try {
				keyStore = KeyStore.getInstance("PKCS12");
			} catch (final KeyStoreException e) {
				// no PKCS12 provider - should never happen
				throw new RuntimeException(e);
			}
			keyStore.load(clientCertificateArchiveInputStream,
					clientCertificateArchivePassword);
			try {
				keyManagerFactory.init(keyStore,
						clientCertificateArchivePassword);
			} catch (final KeyStoreException e) {
				// we do not expect this to happen
				throw new RuntimeException(e);
			}
			final SSLContext context = provider != null
					? SSLContext.getInstance("TLS", provider)
					: SSLContext.getInstance("TLS");
			try {
				context.init(keyManagerFactory.getKeyManagers(), null,
						new SecureRandom());
			} catch (final KeyManagementException e) {
				// another unexpected failures
				throw new RuntimeException(e);
			}
			return context.getSocketFactory();
		} catch (final NoSuchAlgorithmException e) {
			// missing or problematic SunX509 provider - should never happen
			throw new RuntimeException(e);
		}
	}

dopodiché, se ad esempio usi Spring Web Service dovrai impostare un opportuno WebServiceMessageSender, che nel metodo init() crea una SSLSocketFactory come sopra, e nel metodo prepareConnection(HttpURLConnection) la imposta sulla Connection:

protected void prepareConnection(final HttpURLConnection connection)
			throws IOException {
		// if the client certificate is present, make the connection use it
		if (sslSocketFactory != null
				&& connection instanceof HttpsURLConnection)
			((HttpsURLConnection) connection)
					.setSSLSocketFactory(sslSocketFactory);
		super.prepareConnection(connection);
		// set the timeouts
		connection.setConnectTimeout(connectTimeout);
		connection.setReadTimeout(readTimeout);
	}

Spero di averti dato qualche input per risolvere il tuo problema.

1 Mi Piace

Per quanto riguarda i certificati esposti dai server SdI, quello di produzione (servizi.fatturapa.it) usa un certificato SSL “normale”, firmato da una CA nota (attualmente Actalis), che dovrebbe essere già inclusa nelle CA “di sistema”.
Il server di test (testservizi.fatturapa.it) però usa un certificato firmato da una CA dell’Agenzia delle Entrate, il cui certificato trovi nel file CAEntrate_prod.der (lo stesso usato per firmare i tuoi certificati server e client).

Da notare che oltre ai certificati che entrano in gioco quando ti colleghi a SdI (quelli server loro e quello client tuo), ci sono anche i certificati usati quando SdI si collega al tuo server. In questo caso tu devi esporre il tuo certificato server (firmato da loro) e loro si collegano usando un certificato client, diverso tra ambiente di test e produzione. L’ambiente di test usa il certificato SistemaInterscambioFatturaPATest.cer firmato da CAEntratetest.cer, mentre l’ambiente di produzione usa SistemaInterscambioFatturaPA.cer firmato da CAEntrate_prod.der (uguale a caentrate_new.cer).

Grazie a entrambi. Sono riuscito ad inviare le fatture. Ora devo risolvere la ricezione delle notifiche.
Se ho capito bene, il keystore per la configurazione del certificato sul mio server di test deve contenere in questo ordine i file:

SDI-xxx-server.cer
SistemaInterscambioFatturaPATest.cer
CAEntratetest.cer

o sbaglio?

Per quanto riguarda la ricezione dipende molto da che tecnologia usi.
Se, ad esempio, usi Apache come proxy (con mod_proxy o mod_jk) davanti ad un Tomcat, la configurazione del certificato del tuo server va fatta su Apache nel classico modo, mettendo a disposizione di Apache la tua chiave privata, il certificato del tuo server rilasciatoti da SdI e l’eventuale certificato intermedio. Al più potrebbe essere necessario in test aggiungere ai certificati di sistema quello radice che ha emesso il certificato client che ti viene presentato da SdI.
Se, invece, esponi Tomcat direttamente, allora chiave privata e certificato del tuo server vanno configurati su Tomcat con un keystore, mentre il certificato radice che ha emesso quello client di SdI va messo in un truststore, anch’esso dato in pasto a Tomcat con l’opzione di configurazione dedicata (oppure, anche in questo caso, potrebbe essere possibile semplicemente aggiungere quest’ultimo certificato a quelli “di sistema”, evitando di dover utilizzare un truststore).

Ora a memoria non so dirti quei file che mi elenchi cosa siano, anche perché nel tempo SdI cambia i nomi. A naso direi che CAEntratetest.cer è la root CA dei certificati emessi da SdI, SDI-xxx-server.cer è il certificato server di SdI, SistemaInterscambioFatturaPATest.cer immagino possa essere il certificato client con cui si presenta SdI sul tuo endpoint.

Un’ultima precisazione: il certificato client che SdI presenta sul tuo endpoint, come indicato nelle istruzioni fornite da SdI, potrebbe servirti come no. Ergo: se vuoi fare un controllo molto stringente sul certificato che SdI ti presenta, allora ti serve, per confrontarlo con quanto ti arriva nella chiamata in ingresso. Se, altrimenti, ti è sufficiente la verifica standard che normalmente si fa (ossia: assicurati che il certificato che ti viene presentato sia emesso da una CA attendibile, al più configurando come unica CA attendibile quella della CA dell’Agenzia delle Entrate), allora in realtà del certificato client presentato da SdI non te ne fai nulla.
Da notare che se però vuoi implementare un controllo stringente sul certificato che ti viene presentato, la cosa non è così banale. A suo tempo trovai difficoltà, ad esempio, a configurare la cosa su Apache (vedi qui), per cui alla fine ho optato per fare il confronto programmaticamente su Tomcat, ma nel caso in oggetto (Tomcat non esposto direttamente ma dietro un proxy Apache) occorre configurare Apache in modo che faccia il forward dei certificati a Tomcat (es.: SSLOptions +StdEnvVars +ExportCertData).

Purtroppo non riesco ancora a ricevere le notifiche per un errore SSLHandshakeException: General SSLEngine problem.

Questa è la descrizione dei file che ho ricevuto dallo SDI

CERTIFICATI DI TEST

  • testservizi.fatturapa.it.cer: Certificato SERVER esposto dai servizi di test del Sistema di Interscambio

  • SistemaInterscambioFatturaPATest.cer: Parte pubblica del certificato CLIENT utilizzato dal Sistema di Interscambio per invocare i servizi di test da voi esposti

CERTIFICATI DI PRODUZIONE

  • servizi.fatturapa.it.cer: Certificato SERVER esposto dai servizi del Sistema di Interscambio

  • SistemaInterscambioFatturaPA.cer: Parte pubblica del certificato CLIENT utilizzato dal Sistema di Interscambio per invocare i servizi da voi esposti

CERTIFICATI DI CA

  • caentrate.cer: certificato di CA per ambiente di produzione
  • CAEntratetest.cer: certificato di CA per validare il certificato SdI di test
  • CAActalisOV.cer: certificato di Ca per servizi.fatturapa.it.cer per Ambiente di produzione
  • RootActalis.pem: certificato di CA per servizi.fatturapa.it.cer per ambiente di produzione

Ho creato un keystore contente la private key usata per la .CSR server e il certificato scaricato dal portale SDI-xxx-server.cer convertito in formato pem. Ho poi incluso i certificati caentrate.cer e CAEntratetest.cer nel java cacerts. Non capisco cosa manca

Ma quindi hai esposto Tomcat direttamente e non usi un reverse proxy?
Io sulla parte Java non so aiutarti, ma posso dirti un paio di cose generali sui certificati (usando questa volta i nomi dei file che hai tu).

Quando SdI si collega al tuo server, si aspetta di vedere il certificato SDI-xxx-server.cer (firmato da caentrate.cer). Da notare che il loro client non supporta SNI, quindi bisogna avere un indirizzo IP dedicato (almeno una volta era così, non so se è cambiato qualcosa nel frattempo).

Il client a sua volta si autentica con un certificato:

  • SistemaInterscambioFatturaPATest.cer firmato da CAEntratetest.cer per il web service di test
  • SistemaInterscambioFatturaPA.cer firmato da caentrate.cer per il web service di produzione

Come dice Mauro, non è strettamente necessario verificare che il certificato client sia esattamente quello, ma io non mi fiderei della sola verifica della CA, questo perché tutti i certificati (server e client) rilasciati ai canali accreditati sono firmati dalla stessa CA, quindi chiunque di questi potrebbe impersonare il SdI collegandosi al tuo web service.
La soluzione che abbiamo adottato noi è quella di accettare solo certificati firmati da una delle due CA dell’AdE (test e produzione) e verificare che il CN corrisponda a “Sistema Interscambio Fattura PA” (che è uguale sia per l’ambiente di test che produzione, anche se la CA cambia). In questo modo non dobbiamo preoccuparci della scadenza del loro certificato client (a patto che mantengano lo stesso CN quando lo aggiornano).

Purtroppo non so dirti come si faccia a fare questa configurazione in Apache o in Tomcat. Noi usiamo HAProxy.

Confermo quanto scritto da Vladan (per la cronaca, anche noi facciamo un controllo forte sul certificato client, persino più forte del controllo della sola CN).

In linea di massima mi sembra corretto. Chiaramente non dici (ma immagino sia così) che questo keystore l’hai passato a Tomcat. Qui trovi i dettagli, ad esempio, per Tomcat 8.5 (ma di fatto non ci hai detto cosa usi).

In più, devi abilitare la client authentication, che richiederà al client SdI di inviarti il suo certificato client, da validare. Qui trove molte informazioni. Puoi ad esempio provare ad impostare un truststore, se l’aggiunta dei certificati root CA a quelli di sistema non ha funzionato.

Se ancora non funziona bisogna provare ad abilitare un log avanzato di SSL: anche qui, dipende ovviamente da cosa usi (Tomcat, Apache, …?) per vedere esattamente quel “General SSLEngine problem” in cosa consiste.

Qui piacerebbe anche a me avere informazioni più aggiornate. Di fatto noi stiamo usando il medesimo IP per esporre i servizi di test e produzione sia per il canale delle fatture in uscita che per quello delle fatture in ingresso, su virtualhost Apache distinti. In teoria, ognuno di essi usa un certificato diverso e ad oggi non abbiamo avuto problemi, però è pur vero che trattasi sempre di certificati emessi da SdI, per cui non posso escludere che le cose “funzionino” solo perché fanno controlli blandi (ergo: certificati emessi da loro, senza stare a guardare se sto effettivamente esponendo quello giusto per il canale delle fatture in uscita e quello giusto per il canale delle fatture in entrata). Un giorno potrebbe servirci sapere con maggior precisione se SNI lo supportano correttamente o no… Del resto mi aspetto che nel 2022 funzioni…

Veramente c’è un solo certificato server da usare per tutti e 4 gli endpoint (Trasmissione+Ricezione test e Trasmissione+Ricezione produzione), quindi non c’è nessun problema ad usare lo stesso indirizzo IP. Se non fosse così non sarebbe nemmeno possibile usare un unico hostname, come facciamo noi (è solo il path che distingue i vari endpoint).
Quello che non si può fare (o quantomeno non si poteva fare) è usare lo stesso indirizzo IP per un sito web “normale”, perché bisogna per forza metterci il certificato fornito dall’AdE, la cui CA non è tra quelle riconosciute dai browser (o dai S.O.).
Anche se mi pare che con apache si può usare il trucco di mettere come default (per i client che non supportano SNI) il certificato dell’AdE e poi i certificati che si vogliono per gli altri siti.

Mi avete perso sullo SNI, ahimé nella mia carriera ho sempre evitato di lavorare su queste cose.

Sto usando Micronaut, quindi dovrebbe essere un server netty se non sbaglio. Immagino non lo abbiate mai usato. Cmq questo è il file di configurazione:

micronaut:
  application:
    name: testapp
  server:
    cors:
      enabled: true
    ssl:
      client-authentication: want
      key-store:
        path: classpath:keystore_server.jks
        password: test
        type: JKS
      trust-store:
        path: classpath:truststore.jks
        password: test
        type: JKS
  http:
    client:
      read-timeout: 180s

  security:
    x509:
      enabled: true

  ssl:
    enabled: true
    keyStore:
      path: classpath:keystore_server.jks
      password: test
      type: JKS
    port: 443

il server è su un mio dominio in dynamic dns che punta direttamente sulla mia macchina.
Per essere totalmente onesto non capisco perché micronaut ha due configurazioni ssl. Nel dubbio ho usato lo stesso keystore

Noi abbiamo due hostname distinti e due certificati distinti con scadenze diverse perché abbiamo fatto le due procedure di accreditamento distinte, in tempi diversi. Però usiamo un unico indirizzo IP.

@sh4d0000 purtroppo non so aiutarti per Micronaut. Quello che non vedo è l’attivazione della richiesta del certificato client in ricezione: lo fa quel security.x509.enabled = true?
Forse bisognerebbe capire effettivamente cosa sono quelle due configurazioni SSL e cosa controllano. Ad esempio nella seconda non vedo impostato il trust store.

client-authentication: want attiva il controllo del certificato client se presente. Ho le linee di log ma sono abbastanza criptiche per me, non ci trovo niente che possa suggerire il problema. Riusciresti a capirle se le allego?

Un’altra domanda, la mutua autenticazione in SSL è indipendente dal certificato SSL del dominio https? o si usano gli stessi certificati?

Nel caso di SdI, si usano certificati diversi. Da quello che scrivi, SdI in test usa testservizi.fatturapa.it.cer come certificato server e SistemaInterscambioFatturaPATest.cer come certificato client, mentre per te avrai l’altro set di certificati (tipo SDI-xxx-server.cer e, immagino, SDI-xxx-client.cer) rispettivamente come tuo certificato server e client.

Altri (es. pagoPA, da quanto ne so) usano lo stesso certificato sia per server che per client, ma non SdI.

Per quanto riguarda i log… boh, prova a postarli, non garantisco ma ci proviamo. client-authentication: want in effetti m’era sfuggito. Hai capito che differenza c’è tra i due blocchi SSL?

ok uno dei due blocchi in micronaut sta per essere deprecato.

Credo di essere riuscito a superare l’ssl handshake. Ho creato un truststore nuovo includendo anche SistemaInterscambioFatturaPATest.cer e i certificati di Actalis, forse questo ha sbloccato la situazione perché adesso ho un nuovo errore:

org.apache.axis2.AxisFault: HTTP ( 403 ) Forbidden address : https://26.2.218.245:80

Non so da dove arrivi questo indirizzo. Molto strano.

Ignora l’indirizzo. Per qualche motivo oscuro SdI mette questi indirizzi strani (del dipartimento della difesa americano) nei messaggi di errore.
Stando a quello che c’è scritto qui:

il messaggio vuol dire che il SdI ha ricevuto l’errore 403 dal tuo server, quindi probabilmente un problema con la verifica del certificato client del SdI.

Dal log sembra che un certificato valido nel truststore viene trovato. Poi purtroppo non mi pare ci sia qualche altra informazione rilevante.

Ok l’autenticazione mTls ha funzionato, il 403 erano ulteriori restrizioni del mio server. Grazie mille per l’aiuto!