sabato 30 dicembre 2006

Web service over SSL, in Java

Le soluzioni di sicurezza in ambito web service abbondano.
Fortunatamente? Certamente è un bene disporre di un'ampia scelta di soluzioni, per altro quasi tutte a carattere innovativo, che consentono di autenticare le parti, cifrare i messaggi, firmarli digitalmente etc...
D'altro canto, non sempre la sicurezza legata al payload è quello che serve, senza considerare poi che molte delle tecnologie di protezione delle buste XML non sono poi così consolidate da garantire una completa protezione dell'informazione.
Tutto questo per dire che, dovendo scegliere come rendere sicura la comunicazione tra un servizio che opera come client ed un web service, ho preferito ricorrere a ciò che già abbiamo a disposizione da tempo, dato che, in definitiva, ho "solo" bisogno di autenticare tra loro i servizi e rendere sicuro il canale di comunicazione.
Non fraintendete: non sono per "accontentarmi" del canale, nè credo che la sicurezza del canale risolva immediatamente tutti i problemi legati alla protezione della comunicazione e dei dati, tuttavia, credetemi sulla parola, il contesto è tale da non rendere necessario alcuna altra forma di protezione diversa (di tipo applicativo, si intende!), e certamente la cifratura dei singoli messaggi non avrebbe apportato miglioramenti di alcun genere. Anzi...
Così, posto il problema, ecco la soluzione.
JSSE, la libreria SUN di Java che opera come security provider per l'implementazione del protocollo SSL/TLS , fa da sola gran parte del lavoro necessario.
Lato server, la configurazione necessaria è di tipo prevalentemente sistemistico: si tratta di attivare l'HTTPS e l'autenticazione dei client. Potete vedere un esempio qui. Potete aggiungere, estraendo come "properties" della connessione SSL i dati del certificato, dei controlli applicativi sul contenuto (per esempio, se configurate il server per accettare tutti i certificati emessi da una particolare CA, potete poi verificare che il CN dell'utente corrisponda ad uno precedentemente autorizzato...), fidandovi del fatto che il web server abbia svolto tutti i controlli crittografici a monte per instaurare la sessione autenticata e cifrata.
Lato client il problema è più complesso: il web service client è normalmente costruito a partire da framework semi-automatici che limitano lo spazio di azione, risolvendo gran parte del lavoro, tuttavia con il limite di dover letteralmente "impazzire" per ogni minima personalizzazione. D'altro canto, è quanto questa tecnologia promette, tra le altre cose, agli sviluppatori!
Tra le vittime di questo livello di astrazione, vi è il canale di comunicazione, così come tutto quanto c'è al di sotto della logica applicativa...
Torniamo all'oggetto: consentire ad un web service client di comunicare con un web service mediante canale SSL con mutua autenticazione.
Se vi affidate a Java, i vari framework che sono coinvolti in maniera pressochè invisibile allo sviluppatore includono anche JSSE, e quindi la possibilità di utilizzare e configurare dinamicamente il "substrato" SSL.
Per questo, è necessario indicare all'applicazione dove potrà trovare la propria chiave privata, il proprio certificato, e come verificare l'identità del server.
A tale scopo sono necessari un keystore, contenente il certificato e la chiave privata del client, ed un truststore, contenente, in alternativa:
  • il certificato del server con il quale comunicare, che consentirà quindi un riconoscimento univoco e mirato al quel particolare servizio/sistema
  • il certificato dell'Autorità di Certificazione che ha emesso il certificato del server, che permetterà quindi di ritenere affidabile sia il suddetto server, sia qualunque altro servizio che si è rivolto alla medesima autorità.
Per creare il keystore, è possibile rivolgersi a molteplici servizi di certificazione. Tipicamente, dall'esito della procedura di certificazione si otterranno due file in formato PEM (di cui quello della chiave cifrato) o un file in formato PKCS#12. In ogni caso, tramite openssl, è possibile ricondurci nella condizione di disporre di un file PKCS#12 cifrato con password (di seguito indicato come pkcs12.p12, con password "password").
Relativamente al truststore, il modo più semplice di crearlo è affidarsi all'utilità keytool, avendo a disposizione il certificato dell'Autorità di Certificazione (il file cacert.cer):

keytool -import -v -keystore truststore.jks -storepass 12345678 -file cacert.cer

Naturalmente, è opportuno inserire una password diversa da "12345678" e non dimenticare di confermare, quando richiesto, che si ritiene tale certificato appartenente ad una CA "trusted".
Adesso, mani al codice!
Nella procedura dalla quale si richiamano le classi di creazione del web service client, prima di creare un'istanza dello stesso, inserire le seguenti chiamate:
//indicare il nome del file del keystore
System.setProperty("javax.net.ssl.keyStore","/home/client/pkcs12.p12");

//indicare la password di accesso al keystore
System.setProperty("javax.net.ssl.keyStorePassword","password");

//specificare che il keystore è un file PKCS#12
System.setProperty("javax.net.ssl.keyStoreType","PKCS12");

//indicare il file del truststore
System.setProperty("javax.net.ssl.trustStore","/home/bechelli/truststore.jks");

//specificare la password di accesso al truststore
System.setProperty("javax.net.ssl.trustStorePassword","12345678");

//specificare che il truststore è nel formato Java KeyStore
System.setProperty("javax.net.ssl.trustStoreType","JKS");

Il gioco è fatto: il web service client, riscontrato che il protocollo di accesso è HTTPS, verificherà l'autenticità del server mediante il proprio truststore, mentre all'atto della richiesta di autenticazione del server risponderà con il proprio certificato.
Per ulteriori controlli o personalizzazioni, fare riferimento alla documentazione di JSSE.

Eventi a cui partecipo