Un'introduzione a Java SASL

Java Top

Ho appena annunciato il nuovo corso Learn Spring , incentrato sui fondamenti di Spring 5 e Spring Boot 2:

>> SCOPRI IL CORSO

1. Panoramica

In questo tutorial, esamineremo le basi di Simple Authentication and Security Layer (SASL). Capiremo come Java supporta l'adozione di SASL per proteggere la comunicazione.

Nel processo, utilizzeremo una semplice comunicazione client e server, proteggendola con SASL.

2. Cos'è SASL ?

SASL è un framework per l'autenticazione e la sicurezza dei dati nei protocolli Internet . Ha lo scopo di separare i protocolli Internet da specifici meccanismi di autenticazione. Comprenderemo meglio parti di questa definizione man mano che procediamo.

La necessità di sicurezza nella comunicazione è implicita. Proviamo a capirlo nel contesto della comunicazione tra client e server . In genere client e server si scambiano dati sulla rete. È fondamentale che entrambe le parti possano fidarsi l'una dell'altra e inviare dati in modo sicuro.

2.1. Dove si inserisce SASL ?

In un'applicazione, possiamo utilizzare SMTP per inviare e-mail e utilizzare LDAP per accedere ai servizi di directory. Ma ciascuno di questi protocolli può supportare un altro meccanismo di autenticazione, come Digest-MD5 o Kerberos.

E se ci fosse un modo per i protocolli di scambiare i meccanismi di autenticazione in modo più dichiarativo? Questo è esattamente il punto in cui SASL entra in gioco. I protocolli che supportano SASL possono supportare invariabilmente qualsiasi meccanismo SASL.

Pertanto, le applicazioni possono negoziare un meccanismo appropriato e adottarlo per l'autenticazione e la comunicazione sicura.

2.2. Come funziona SASL ?

Ora che abbiamo visto dove si inserisce SASL nello schema generale di sicurezza, capiamo come funziona.

SASL è un framework challenge-response . Qui, il server invia una sfida al client e il client invia una risposta basata sulla sfida. La sfida e la risposta sono array di byte di lunghezza arbitraria e, quindi, possono trasportare qualsiasi dato specifico del meccanismo.

Questo scambio può continuare per più iterazioni e alla fine termina quando il server non invia ulteriori sfide.

Inoltre, il client e il server possono negoziare una post-autenticazione del livello di sicurezza. Tutte le comunicazioni successive possono quindi sfruttare questo livello di sicurezza. Tuttavia, tieni presente che alcuni dei meccanismi possono supportare solo l'autenticazione.

È importante capire qui che SASL fornisce solo un framework per lo scambio di dati di challenge e response . Non menziona nulla sui dati stessi o su come vengono scambiati. Questi dettagli vengono lasciati alle applicazioni che adottano per utilizzare SASL.

3. Supporto SASL in Java

Esistono API in Java che supportano lo sviluppo di applicazioni lato client e lato server con SASL. L'API non dipende dai meccanismi effettivi stessi. Le applicazioni che utilizzano Java SASL API possono selezionare un meccanismo basato sulle funzionalità di sicurezza richieste.

3.1. API Java SASL

Le interfacce chiave da notare, come parte del pacchetto "javax.security.sasl", sono SaslServer e SaslClient .

SaslServer rappresenta il meccanismo lato server di SASL.

Vediamo come possiamo istanziare un SaslServer :

SaslServer ss = Sasl.createSaslServer( mechanism, protocol, serverName, props, callbackHandler);

Stiamo usando la classe factory Sasl per istanziare SaslServer. Il metodo createSaslServer accetta diversi parametri:

  • meccanismo : il nome registrato IANA di un meccanismo supportato da SASL
  • protocollo : il nome del protocollo per il quale viene eseguita l'autenticazione
  • serverName - il nome host completo del server
  • props - un insieme di proprietà utilizzate per configurare lo scambio di autenticazione
  • callbackHandler - un gestore di callback che deve essere utilizzato dal meccanismo selezionato per ottenere ulteriori informazioni

Di quanto sopra, solo i primi due sono obbligatori e il resto annullabile.

SaslClient rappresenta il meccanismo lato client di SASL. Vediamo come possiamo istanziare un SaslClient :

SaslClient sc = Sasl.createSaslClient( mechanisms, authorizationId, protocol, serverName, props, callbackHandler);

Anche in questo caso, stiamo usando la classe factory Sasl per istanziare il nostro SaslClient . L'elenco dei parametri che createSaslClient accetta è praticamente lo stesso di prima.

Tuttavia, ci sono alcune sottili differenze:

  • meccanismi - qui, questo è un elenco di meccanismi da cui provare
  • ID autorizzazione : si tratta di un'identificazione dipendente dal protocollo da utilizzare per l'autorizzazione

Gli altri parametri sono simili nel significato e nella loro opzionalità.

3.2. Provider di sicurezza Java SASL

Sotto l'API SASL Java ci sono i meccanismi effettivi che forniscono le funzionalità di sicurezza. L' implementazione di questi meccanismi è fornita dai fornitori di sicurezza registrati con Java Cryptography Architecture (JCA).

Possono esserci più fornitori di sicurezza registrati con JCA. Ciascuno di questi può supportare uno o più dei meccanismi SASL .

Java viene fornito con SunSASL come provider di sicurezza, che viene registrato come provider JCA per impostazione predefinita. Tuttavia, questo può essere rimosso o riordinato con qualsiasi altro provider disponibile.

Moreover, it is always possible to provide a custom security provider. This will require us to implement the interfaces SaslClient and SaslServer. In doing so, we may implement our custom security mechanism as well!

4. SASL Through an Example

Now that we've seen how to create a SaslServer and a SaslClient, it's time to understand how to use them. We'll be developing client and server components. These will exchange challenge and response iteratively to achieve authentication. We'll make use of the DIGEST-MD5 mechanism in our simple example here.

4.1. Client and Server CallbackHandler

As we saw earlier, we need to provide implementations of CallbackHandler to SaslServer and SaslClient. Now, CallbackHandler is a simple interface that defines a single method — handle. This method accepts an array of Callback.

Here, Callback presents a way for the security mechanism to collect authentication data from the calling application. For instance, a security mechanism may require a username and password. There are quite a few Callback implementations like NameCallback and PasswordCallback available for use.

Let's see how we can define a CallbackHandler for the server, to begin with:

public class ServerCallbackHandler implements CallbackHandler { @Override public void handle(Callback[] cbs) throws IOException, UnsupportedCallbackException { for (Callback cb : cbs) { if (cb instanceof AuthorizeCallback) { AuthorizeCallback ac = (AuthorizeCallback) cb; //Perform application-specific authorization action ac.setAuthorized(true); } else if (cb instanceof NameCallback) { NameCallback nc = (NameCallback) cb; //Collect username in application-specific manner nc.setName("username"); } else if (cb instanceof PasswordCallback) { PasswordCallback pc = (PasswordCallback) cb; //Collect password in application-specific manner pc.setPassword("password".toCharArray()); } else if (cb instanceof RealmCallback) { RealmCallback rc = (RealmCallback) cb; //Collect realm data in application-specific manner rc.setText("myServer"); } } } }

Now, let's see our client-side of the Callbackhandler:

public class ClientCallbackHandler implements CallbackHandler { @Override public void handle(Callback[] cbs) throws IOException, UnsupportedCallbackException { for (Callback cb : cbs) { if (cb instanceof NameCallback) { NameCallback nc = (NameCallback) cb; //Collect username in application-specific manner nc.setName("username"); } else if (cb instanceof PasswordCallback) { PasswordCallback pc = (PasswordCallback) cb; //Collect password in application-specific manner pc.setPassword("password".toCharArray()); } else if (cb instanceof RealmCallback) { RealmCallback rc = (RealmCallback) cb; //Collect realm data in application-specific manner rc.setText("myServer"); } } } }

To clarify, we're looping through the Callback array and handling only specific ones. The ones that we have to handle is specific to the mechanism in use, which is DIGEST-MD5 here.

4.2. SASL Authentication

So, we've written our client and server CallbackHandler. We've also instantiated SaslClient and SaslServer for DIGEST-MD5 mechanism.

Now is the time to see them in action:

@Test public void givenHandlers_whenStarted_thenAutenticationWorks() throws SaslException { byte[] challenge; byte[] response; challenge = saslServer.evaluateResponse(new byte[0]); response = saslClient.evaluateChallenge(challenge); challenge = saslServer.evaluateResponse(response); response = saslClient.evaluateChallenge(challenge); assertTrue(saslServer.isComplete()); assertTrue(saslClient.isComplete()); }

Let's try to understand what is happening here:

  • First, our client gets the default challenge from the server
  • The client then evaluates the challenge and prepares a response
  • This challenge-response exchange continues for one more cycle
  • In the process, the client and server make use of callback handlers to collect any additional data as needed by the mechanism
  • This concludes our authentication here, but in reality, it can iterate over multiple cycles

A typical exchange of challenge and response byte arrays happens over the network. But, here for simplicity, we've assumed local communication.

4.3. SASL Secure Communication

As we discussed earlier, SASL is a framework capable of supporting secure communication beyond just authentication. However, this is only possible if the underlying mechanism supports it.

Firstly, let's first check if we have been able to negotiate a secure communication:

String qop = (String) saslClient.getNegotiatedProperty(Sasl.QOP); assertEquals("auth-conf", qop);

Here, QOP stands for the quality of protection. This is something that the client and server negotiate during authentication. A value of “auth-int” indicates authentication and integrity. While, a value of “auth-conf” indicates authentication, integrity, and confidentiality.

Once we have a security layer, we can leverage that to secure our communication.

Let's see how we can secure outgoing communication in the client:

byte[] outgoing = "Baeldung".getBytes(); byte[] secureOutgoing = saslClient.wrap(outgoing, 0, outgoing.length); // Send secureOutgoing to the server over the network

And, similarly, the server can process incoming communication:

// Receive secureIncoming from the client over the network byte[] incoming = saslServer.unwrap(secureIncoming, 0, netIn.length); assertEquals("Baeldung", new String(incoming, StandardCharsets.UTF_8));

5. SASL in the Real World

So, we now have a fair understanding of what SASL is and how to use it in Java. But, typically, that's not what we'll end up using SASL for, at least in our daily routine.

As we saw earlier, SASL is primarily meant for protocols like LDAP and SMTP. Although, more and more applications and coming on board with SASL — for instance, Kafka. So, how do we use SASL to authenticate with such services?

Let's suppose we've configured Kafka Broker for SASL with PLAIN as the mechanism of choice. PLAIN simply means that it authenticates using a combination of username and password in plain text.

Let's now see how can we configure a Java client to use SASL/PLAIN to authenticate against the Kafka Broker.

We begin by providing a simple JAAS configuration, “kafka_jaas.conf”:

KafkaClient { org.apache.kafka.common.security.plain.PlainLoginModule required username="username" password="password"; };

We make use of this JAAS configuration while starting the JVM:

-Djava.security.auth.login.config=kafka_jaas.conf

Finally, we have to add a few properties to pass to our producer and consumer instances:

security.protocol=SASL_SSL sasl.mechanism=PLAIN

That's all there is to it. This is just a small part of Kafka client configurations, though. Apart from PLAIN, Kafka also supports GSSAPI/Kerberos for authentication.

6. SASL in Comparision

Although SASL is quite effective in providing a mechanism-neutral way of authenticating and securing client and server communication. However, SASL is not the only solution available in this regard.

Java itself provides other mechanisms to achieve this objective. We'll briefly discuss them and understand how they fare against SASL:

  • Java Secure Socket Extension (JSSE): JSSE is a set of packages in Java that implements Secure Sockets Layer (SSL) for Java. It provides data encryption, client and server authentication, and message integrity. Unlike SASL, JSSE relies on a Public Key Infrastructure (PKI) to work. Hence, SASL works out to be more flexible and lightweight than JSSE.
  • Java GSS API (JGSS): JGGS is the Java language binding for Generic Security Service Application Programming Interface (GSS-API). GSS-API is an IETF standard for applications to access security services. In Java, under GSS-API, Kerberos is the only mechanism supported. Kerberos again requires a Kerberised infrastructure to work. Compared to SASL, here yet, choices are limited and heavyweight.

Nel complesso, SASL è un framework molto leggero e offre un'ampia varietà di funzionalità di sicurezza tramite meccanismi collegabili. Le applicazioni che adottano SASL hanno molte scelte nell'implementazione del giusto set di funzionalità di sicurezza, a seconda delle necessità.

7. Conclusione

Per riassumere, in questo tutorial, abbiamo compreso le basi del framework SASL, che fornisce autenticazione e comunicazione sicura. Abbiamo anche discusso delle API disponibili in Java per l'implementazione del lato client e server di SASL.

Abbiamo visto come utilizzare un meccanismo di sicurezza tramite un provider JCA. Infine, abbiamo anche parlato dell'utilizzo di SASL nel lavorare con diversi protocolli e applicazioni.

Come sempre, il codice può essere trovato su GitHub.

Fondo Java

Ho appena annunciato il nuovo corso Learn Spring , incentrato sui fondamenti di Spring 5 e Spring Boot 2:

>> SCOPRI IL CORSO