Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions vertx-pg-client/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@
<!-- sasl scram authentication -->
<dependency>
<groupId>com.ongres.scram</groupId>
<artifactId>client</artifactId>
<version>2.1</version>
<artifactId>scram-client</artifactId>
<version>3.0</version>
<optional>true</optional>
</dependency>

Expand Down
11 changes: 4 additions & 7 deletions vertx-pg-client/src/main/asciidoc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -233,30 +233,27 @@ $ PGUSER=user \

=== SASL SCRAM-SHA-256 authentication mechanism.

To use the sasl SCRAM-SHA-256 authentication add the following dependency to the _dependencies_ section of your build descriptor:
To use the SASL `SCRAM-SHA-256` authentication add the following dependency to the _dependencies_ section of your build descriptor:

* Maven (in your `pom.xml`):

[source,xml]
----
<dependency>
<groupId>com.ongres.scram</groupId>
<artifactId>client</artifactId>
<version>2.1</version>
<artifactId>scram-client</artifactId>
<version>3.0</version>
</dependency>
----
* Gradle (in your `build.gradle` file):

[source,groovy]
----
dependencies {
compile 'com.ongres.scram:client:2.1'
compile 'com.ongres.scram:scram-client:3.0'
}
----

NOTE: SCRAM-SHA-256-PLUS (added in Postgresql 11) is not supported.


include::queries.adoc[leveloffset=1]

== Returning clauses
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,9 @@ public void handleAuthenticationClearTextPassword() {

@Override
void handleAuthenticationSasl(ByteBuf in) {
scramAuthentication = new ScramAuthentication(cmd.username(), cmd.password());
encoder.writeScramClientInitialMessage(scramAuthentication.createInitialSaslMessage(in));
scramAuthentication = new ScramAuthentication(cmd.username(), cmd.password().toCharArray());
encoder.writeScramClientInitialMessage(
scramAuthentication.createInitialSaslMessage(in, encoder.channelHandlerContext()));
encoder.flush();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
import io.vertx.sqlclient.impl.RowDesc;
import io.vertx.sqlclient.impl.command.*;

import java.util.ArrayDeque;
import java.util.Map;

import static io.vertx.pgclient.impl.util.Util.writeCString;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,31 +18,34 @@
package io.vertx.pgclient.impl.util;

import java.nio.charset.StandardCharsets;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;

import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;

import com.ongres.scram.client.ScramClient;
import com.ongres.scram.client.ScramSession;
import com.ongres.scram.common.exception.ScramException;
import com.ongres.scram.common.StringPreparation;
import com.ongres.scram.common.exception.ScramInvalidServerSignatureException;
import com.ongres.scram.common.exception.ScramParseException;
import com.ongres.scram.common.exception.ScramServerErrorException;
import com.ongres.scram.common.stringprep.StringPreparations;
import com.ongres.scram.common.util.TlsServerEndpoint;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.ssl.SslHandler;
import io.vertx.pgclient.impl.codec.ScramClientInitialMessage;

public class ScramAuthentication {

private static final String SCRAM_SHA_256 = "SCRAM-SHA-256";

private final String username;
private final String password;
private ScramSession scramSession;
private ScramSession.ClientFinalProcessor clientFinalProcessor;
private final char[] password;
private ScramClient scramClient;


public ScramAuthentication(String username, String password) {
public ScramAuthentication(String username, char[] password) {
this.username = username;
this.password = password;
}
Expand All @@ -53,39 +56,31 @@ public ScramAuthentication(String username, String password) {
* The message includes the name of the selected mechanism, and
* an optional Initial Client Response, if the selected mechanism uses that.
*/
public ScramClientInitialMessage createInitialSaslMessage(ByteBuf in) {
public ScramClientInitialMessage createInitialSaslMessage(ByteBuf in, ChannelHandlerContext ctx) {
List<String> mechanisms = new ArrayList<>();

while (0 != in.getByte(in.readerIndex())) {
String mechanism = Util.readCStringUTF8(in);
mechanisms.add(mechanism);
String mechanism = Util.readCStringUTF8(in);
mechanisms.add(mechanism);
}

if (mechanisms.isEmpty()) {
throw new UnsupportedOperationException("SASL Authentication : the server returned no mechanism");
}

// SCRAM-SHA-256-PLUS added in postgresql 11 is not supported
if (!mechanisms.contains(SCRAM_SHA_256)) {
throw new UnsupportedOperationException("SASL Authentication : only SCRAM-SHA-256 is currently supported, server wants " + mechanisms);
}


ScramClient scramClient = ScramClient
.channelBinding(ScramClient.ChannelBinding.NO)
.stringPreparation(StringPreparations.NO_PREPARATION)
.selectMechanismBasedOnServerAdvertised(mechanisms.toArray(new String[0]))
.setup();


// this user name will be ignored, the user name that was already sent in the startup message is used instead
// see https://www.postgresql.org/docs/11/sasl-authentication.html#SASL-SCRAM-SHA-256 §53.3.1
scramSession = scramClient.scramSession(this.username);

return new ScramClientInitialMessage(scramSession.clientFirstMessage(), scramClient.getScramMechanism().getName());
byte[] channelBindingData = extractChannelBindingData(ctx);
this.scramClient = ScramClient.builder()
.advertisedMechanisms(mechanisms)
.username(username) // ignored by the server, use startup message
.password(password)
.stringPreparation(StringPreparation.POSTGRESQL_PREPARATION)
.channelBinding(TlsServerEndpoint.TLS_SERVER_END_POINT, channelBindingData)
.build();

return new ScramClientInitialMessage(scramClient.clientFirstMessage().toString(),
scramClient.getScramMechanism().getName());
}


/*
* One or more server-challenge and client-response message will follow.
* Each server-challenge is sent in an AuthenticationSASLContinue message,
Expand All @@ -95,16 +90,13 @@ public ScramClientInitialMessage createInitialSaslMessage(ByteBuf in) {
public String receiveServerFirstMessage(ByteBuf in) {
String serverFirstMessage = in.readCharSequence(in.readableBytes(), StandardCharsets.UTF_8).toString();

ScramSession.ServerFirstProcessor serverFirstProcessor = null;
try {
serverFirstProcessor = scramSession.receiveServerFirstMessage(serverFirstMessage);
} catch (ScramException e) {
scramClient.serverFirstMessage(serverFirstMessage);
} catch (ScramParseException e) {
throw new UnsupportedOperationException(e);
}

clientFinalProcessor = serverFirstProcessor.clientFinalProcessor(password);

return clientFinalProcessor.clientFinalMessage();
return scramClient.clientFinalMessage().toString();
}

/*
Expand All @@ -119,9 +111,33 @@ public void checkServerFinalMessage(ByteBuf in) {
String serverFinalMessage = in.readCharSequence(in.readableBytes(), StandardCharsets.UTF_8).toString();

try {
clientFinalProcessor.receiveServerFinalMessage(serverFinalMessage);
scramClient.serverFinalMessage(serverFinalMessage);
} catch (ScramParseException | ScramServerErrorException | ScramInvalidServerSignatureException e) {
throw new UnsupportedOperationException(e);
}
}

private byte[] extractChannelBindingData(ChannelHandlerContext ctx) {
SslHandler sslHandler = ctx.channel().pipeline().get(SslHandler.class);
if (sslHandler != null) {
SSLSession sslSession = sslHandler.engine().getSession();
if (sslSession != null && sslSession.isValid()) {
try {
// Get the certificate chain from the session
Certificate[] certificates = sslSession.getPeerCertificates();
if (certificates != null && certificates.length > 0) {
Certificate peerCert = certificates[0]; // First certificate is the peer's certificate
if (peerCert instanceof X509Certificate) {
X509Certificate cert = (X509Certificate) peerCert;
return TlsServerEndpoint.getChannelBindingData(cert);
}
}
} catch (CertificateEncodingException | SSLException e) {
// Cannot extract X509Certificate from SSL session
// handle as no channel binding available
}
}
}
return new byte[0]; // handle as no channel binding available
}
}