diff --git a/vertx-pg-client/pom.xml b/vertx-pg-client/pom.xml
index d873deb1b7..a0c8a3182f 100644
--- a/vertx-pg-client/pom.xml
+++ b/vertx-pg-client/pom.xml
@@ -64,8 +64,8 @@
com.ongres.scram
- client
- 2.1
+ scram-client
+ 3.0
true
diff --git a/vertx-pg-client/src/main/asciidoc/index.adoc b/vertx-pg-client/src/main/asciidoc/index.adoc
index 0312369883..094eebe5ea 100644
--- a/vertx-pg-client/src/main/asciidoc/index.adoc
+++ b/vertx-pg-client/src/main/asciidoc/index.adoc
@@ -233,7 +233,7 @@ $ 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`):
@@ -241,8 +241,8 @@ To use the sasl SCRAM-SHA-256 authentication add the following dependency to the
----
com.ongres.scram
- client
- 2.1
+ scram-client
+ 3.0
----
* Gradle (in your `build.gradle` file):
@@ -250,13 +250,10 @@ To use the sasl SCRAM-SHA-256 authentication add the following dependency to the
[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
diff --git a/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/codec/InitCommandCodec.java b/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/codec/InitCommandCodec.java
index 935d198015..bfc6daefd3 100644
--- a/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/codec/InitCommandCodec.java
+++ b/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/codec/InitCommandCodec.java
@@ -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();
}
diff --git a/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/codec/PgEncoder.java b/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/codec/PgEncoder.java
index 99fa55df74..f11f22b02c 100644
--- a/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/codec/PgEncoder.java
+++ b/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/codec/PgEncoder.java
@@ -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;
diff --git a/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/util/ScramAuthentication.java b/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/util/ScramAuthentication.java
index 74ab203ac9..b1d8d69689 100644
--- a/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/util/ScramAuthentication.java
+++ b/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/util/ScramAuthentication.java
@@ -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;
}
@@ -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 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,
@@ -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();
}
/*
@@ -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
+ }
}