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 + } }