Scelta del certificato client SSL in Java

Il nostro sistema comunica con diversi fornitori di servizi web. Sono tutti invocati da una singola applicazione client Java. Tutti i servizi Web fino ad ora sono stati su SSL, ma nessuno usa i certificati client. Bene, un nuovo partner sta cambiando.

Rendere l’applicazione utilizza un certificato per l’invocazione è facile; l’impostazione javax.net.ssl.keyStore e javax.net.ssl.keyStorePassword lo faranno. Tuttavia, il problema è ora come farlo in modo che utilizzi il certificato solo quando si richiama quel particolare servizio web. Immagino più in generale, vorremmo poter scegliere il certificato client da utilizzare, se esiste.

Una soluzione rapida potrebbe essere impostare le proprietà del sistema, richiamare i metodi e quindi distriggersrli. L’unico problema è che abbiamo a che fare con un’applicazione multi-thread, quindi ora avremmo bisogno di gestire la sincronizzazione o i blocchi o cosa avete.

Ogni client di servizio dovrebbe essere completamente indipendente l’uno dall’altro e sono confezionati singolarmente in JAR separati. Quindi, un’opzione che mi è venuta in mente (anche se non l’abbiamo analizzata correttamente) è di isolare in qualche modo ogni JAR, magari caricandoli ciascuno sotto una VM diversa con parametri diversi. Questa è solo un’idea che non so come implementare (o se è anche ansible, se è per questo).

Questo post suggerisce che è ansible selezionare un singolo certificato da un keystore, ma il modo in cui collegarlo alla richiesta sembra essere un problema completamente diverso.

Stiamo utilizzando Java 1.5, Axis2 e le classi client generate con wsimport o wsdl2java .

I client SSL Java invieranno un certificato solo se richiesto dal server. Un server può inviare un suggerimento opzionale su quali certificati accetterà; questo aiuterà un cliente a scegliere un singolo certificato se ha più di uno.

Normalmente, un nuovo SSLContext viene creato con un certificato client specifico e le istanze Socket vengono create da una factory ottenuta da quel contesto. Sfortunatamente, Axis2 non sembra supportare l’uso di un SSLContext o di un SocketFactory personalizzato. Le sue impostazioni del certificato client sono globali.

La configurazione viene eseguita tramite un SSLContext , che è effettivamente un factory per SSLSocketFactory (o SSLEngine ). Per impostazione predefinita, questo sarà configurato dalle proprietà javax.net.ssl.* . Inoltre, quando un server richiede un certificato, invia un messaggio CertificateRequest TLS / SSL che contiene un elenco di nomi distinti di CA che è disposto ad accettare. Sebbene questo elenco sia strettamente indicativo (cioè i server potrebbero accettare certificati di emittenti non inclusi nell’elenco o rifiutare certificati validi da CA nell’elenco), di solito funziona in questo modo.

Per impostazione predefinita, il selettore di certificati in X509KeyManager configurato all’interno di SSLContext (anche se di solito non devi preoccuparti di questo), sceglierà uno dei certificati emessi da uno nell’elenco (o può essere incatenato a un emittente Là). Tale elenco è il parametro X509KeyManager.chooseClientAlias in X509KeyManager.chooseClientAlias (l’ alias è il nome alias per il certificato che si desidera selezionare, come indicato nel keystore). Se si dispone di più candidati, è ansible utilizzare anche il parametro socket , che consente di ottenere l’indirizzo IP del peer se questo consente di effettuare una scelta.

Se questo ti aiuta, potresti trovare jSSLutils (e il relativo wrapper) per la configurazione del tuo SSLContext (queste sono principalmente classi di supporto per creare SSLContext ). (Si noti che questo esempio serve per scegliere l’alias lato server, ma può essere adattato, il codice sorgente è disponibile .)

Una volta fatto ciò, dovresti cercare la documentazione relativa alla proprietà di sistema axis.socketSecureFactory in Axis (e SecureSocketFactory ). Se si guarda il codice sorgente di Axis, non dovrebbe essere troppo difficile creare un org.apache.axis.components.net.SunJSSESocketFactory inizializzato dal SSLContext di propria scelta (vedere questa domanda ).

Ho appena realizzato che stavi parlando di Axis2, in cui sembra che la SecureSocketFactory sia scomparsa. Potresti riuscire a trovare una soluzione alternativa utilizzando l’ SSLContext predefinito, ma ciò influirà sull’intera applicazione (che non è eccezionale). Se si utilizza X509KeyManagerWrapper di jSSLutils, è ansible utilizzare X509KeyManager predefinito e considerare solo alcuni host come eccezione. (Questa non è una situazione ideale, non sono sicuro di come utilizzare un SSLContext / SSLSocketFactory personalizzato SSLContext 2).

In alternativa, secondo questo documento dell’Asse 2 , sembra che Axis 2 utilizzi Apache HTTP Client 3.x:

Se si desidera eseguire l’autenticazione del client SSL (SSL a 2 vie), è ansible utilizzare la funzionalità Protocol.registerProtocol di HttpClient. È ansible sovrascrivere il protocollo “https” o utilizzare un protocollo diverso per le comunicazioni di autenticazione del client SSL se non si vuole interferire con i normali https. Trova maggiori informazioni su http://jakarta.apache.org/commons/httpclient/sslguide.html

In questo caso, SslContextedSecureProtocolSocketFactory dovrebbe aiutarti a configurare un SSLContext .

Ho inizializzato le istanze EasySSLProtocolSocketFactory e Protocol per diversi endpoint e registro il protocollo con una chiave univoca come questa:

 /** * This method does the following: * 1. Creates a new and unique protocol for each SSL URL that is secured by client certificate * 2. Bind keyStore related information to this protocol * 3. Registers it with HTTP Protocol object * 4. Stores the local reference for this custom protocol for use during furture collect calls * * @throws Exception */ public void registerProtocolCertificate() throws Exception { EasySSLProtocolSocketFactory easySSLPSFactory = new EasySSLProtocolSocketFactory(); easySSLPSFactory.setKeyMaterial(createKeyMaterial()); myProtocolPrefix = (HTTPS_PROTOCOL + uniqueCounter.incrementAndGet()); Protocol httpsProtocol = new Protocol(myProtocolPrefix,(ProtocolSocketFactory) easySSLPSFactory, port); Protocol.registerProtocol(myProtocolPrefix, httpsProtocol); log.trace("Protocol [ "+myProtocolPrefix+" ] registered for the first time"); } /** * Load keystore for CLIENT-CERT protected endpoints */ private KeyMaterial createKeyMaterial() throws GeneralSecurityException, Exception { KeyMaterial km = null; char[] password = keyStorePassphrase.toCharArray(); File f = new File(keyStoreLocation); if (f.exists()) { try { km = new KeyMaterial(keyStoreLocation, password); log.trace("Keystore location is: " + keyStoreLocation + ""); } catch (GeneralSecurityException gse) { if (logErrors){ log.error("Exception occured while loading keystore from the following location: "+keyStoreLocation, gse); throw gse; } } } else { log.error("Unable to load Keystore from the following location: " + keyStoreLocation ); throw new CollectorInitException("Unable to load Keystore from the following location: " + keyStoreLocation); } return km; } 

Quando devo richiamare il servizio web, lo faccio (che sostanzialmente sostituisce “https” nell’URL con https1, o https2 o qualcos’altro a seconda del protocollo che hai inizializzato per quel particolare endpoint):

 httpClient.getHostConfiguration().setHost(host, port,Protocol.getProtocol(myProtocolPrefix)); initializeHttpMethod(this.url.toString().replace(HTTPS_PROTOCOL, myProtocolPrefix)); 

Esso funziona magicamente!