diff --git a/vertx-pg-client/pom.xml b/vertx-pg-client/pom.xml index 44c659b627..18f6828544 100644 --- a/vertx-pg-client/pom.xml +++ b/vertx-pg-client/pom.xml @@ -161,6 +161,21 @@ + + missing-scram-test + + integration-test + + integration-test + + + io/vertx/pgclient/it/MissingScramTest.java + + + com.ongres.scram:scram-client + + + diff --git a/vertx-pg-client/src/main/asciidoc/index.adoc b/vertx-pg-client/src/main/asciidoc/index.adoc index 0dddca21b4..2ade01339a 100644 --- a/vertx-pg-client/src/main/asciidoc/index.adoc +++ b/vertx-pg-client/src/main/asciidoc/index.adoc @@ -254,6 +254,8 @@ dependencies { } ---- +When the database requires a scram authentication and the scram client jar is not on the class/module path, the connection will be closed by the client. + include::queries.adoc[leveloffset=1] == Returning clauses diff --git a/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/auth/scram/ScramAuthentication.java b/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/auth/scram/ScramAuthentication.java new file mode 100644 index 0000000000..658977c4f5 --- /dev/null +++ b/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/auth/scram/ScramAuthentication.java @@ -0,0 +1,31 @@ +package io.vertx.pgclient.impl.auth.scram; + +import com.ongres.scram.client.ScramClient; +import io.vertx.core.internal.logging.Logger; +import io.vertx.core.internal.logging.LoggerFactory; + +public class ScramAuthentication { + + private static final Logger logger = LoggerFactory.getLogger(ScramAuthentication.class); + + public static ScramAuthentication INSTANCE; + + static { + ScramAuthentication instance; + try { + ScramClient.MechanismsBuildStage builder = ScramClient.builder(); + logger.debug("Scram authentication is available " + builder); + instance = new ScramAuthentication(); + } catch (Throwable notFound) { + instance = null; + } + INSTANCE = instance; + } + + private ScramAuthentication() { + } + + public ScramSession session(String username, char[] password) { + return new ScramSessionImpl(username, password); + } +} diff --git a/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/auth/scram/ScramSession.java b/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/auth/scram/ScramSession.java new file mode 100644 index 0000000000..1cc05c5ec2 --- /dev/null +++ b/vertx-pg-client/src/main/java/io/vertx/pgclient/impl/auth/scram/ScramSession.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2017 Julien Viet + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.vertx.pgclient.impl.auth.scram; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.vertx.pgclient.impl.codec.ScramClientInitialMessage; + +public interface ScramSession { + + /* + * The client selects one of the supported mechanisms from the list, + * and sends a SASLInitialResponse message to the server. + * The message includes the name of the selected mechanism, and + * an optional Initial Client Response, if the selected mechanism uses that. + */ + ScramClientInitialMessage createInitialSaslMessage(ByteBuf in, ChannelHandlerContext ctx); + + /* + * One or more server-challenge and client-response message will follow. + * Each server-challenge is sent in an AuthenticationSASLContinue message, + * followed by a response from client in an SASLResponse message. + * The particulars of the messages are mechanism specific. + */ + String receiveServerFirstMessage(ByteBuf in); + + /* + * Finally, when the authentication exchange is completed successfully, + * the server sends an AuthenticationSASLFinal message, followed immediately by an AuthenticationOk message. + * The AuthenticationSASLFinal contains additional server-to-client data, + * whose content is particular to the selected authentication mechanism. + * If the authentication mechanism doesn't use additional data that's sent at completion, + * the AuthenticationSASLFinal message is not sent + */ + void checkServerFinalMessage(ByteBuf in); + +} 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/auth/scram/ScramSessionImpl.java similarity index 96% rename from vertx-pg-client/src/main/java/io/vertx/pgclient/impl/util/ScramAuthentication.java rename to vertx-pg-client/src/main/java/io/vertx/pgclient/impl/auth/scram/ScramSessionImpl.java index b1d8d69689..66fb7e797f 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/auth/scram/ScramSessionImpl.java @@ -15,17 +15,7 @@ * */ -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; +package io.vertx.pgclient.impl.auth.scram; import com.ongres.scram.client.ScramClient; import com.ongres.scram.common.StringPreparation; @@ -33,19 +23,28 @@ import com.ongres.scram.common.exception.ScramParseException; import com.ongres.scram.common.exception.ScramServerErrorException; 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; +import io.vertx.pgclient.impl.util.Util; + +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLSession; +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; -public class ScramAuthentication { +public class ScramSessionImpl implements ScramSession { private final String username; private final char[] password; private ScramClient scramClient; - public ScramAuthentication(String username, char[] password) { + public ScramSessionImpl(String username, char[] password) { this.username = username; this.password = password; } 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 2fa0332cb7..a92505d1cb 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 @@ -20,9 +20,11 @@ import java.nio.charset.StandardCharsets; import io.netty.buffer.ByteBuf; +import io.vertx.core.VertxException; import io.vertx.pgclient.impl.PgDatabaseMetadata; import io.vertx.pgclient.impl.PgSocketConnection; -import io.vertx.pgclient.impl.util.ScramAuthentication; +import io.vertx.pgclient.impl.auth.scram.ScramAuthentication; +import io.vertx.pgclient.impl.auth.scram.ScramSession; import io.vertx.sqlclient.internal.Connection; import io.vertx.sqlclient.internal.command.CommandResponse; import io.vertx.sqlclient.internal.command.InitCommand; @@ -31,7 +33,7 @@ class InitCommandCodec extends PgCommandCodec { private PgEncoder encoder; private String encoding; - private ScramAuthentication scramAuthentication; + private ScramSession scramSession; InitCommandCodec(InitCommand cmd) { super(cmd); @@ -57,21 +59,26 @@ public void handleAuthenticationClearTextPassword() { @Override void handleAuthenticationSasl(ByteBuf in) { - scramAuthentication = new ScramAuthentication(cmd.username(), cmd.password().toCharArray()); + ScramAuthentication scramAuth = ScramAuthentication.INSTANCE; + if (scramAuth == null) { + // This will close the connection + throw new VertxException("Scram authentication not supported, missing com.ongres.scram:scram-client on the class/module path"); + } + scramSession = scramAuth.session(cmd.username(), cmd.password().toCharArray()); encoder.writeScramClientInitialMessage( - scramAuthentication.createInitialSaslMessage(in, encoder.channelHandlerContext())); + scramSession.createInitialSaslMessage(in, encoder.channelHandlerContext())); encoder.flush(); } @Override void handleAuthenticationSaslContinue(ByteBuf in) { - encoder.writeScramClientFinalMessage(new ScramClientFinalMessage(scramAuthentication.receiveServerFirstMessage(in))); + encoder.writeScramClientFinalMessage(new ScramClientFinalMessage(scramSession.receiveServerFirstMessage(in))); encoder.flush(); } @Override void handleAuthenticationSaslFinal(ByteBuf in) { - scramAuthentication.checkServerFinalMessage(in); + scramSession.checkServerFinalMessage(in); } @Override diff --git a/vertx-pg-client/src/test/java/io/vertx/pgclient/it/MissingScramTest.java b/vertx-pg-client/src/test/java/io/vertx/pgclient/it/MissingScramTest.java new file mode 100644 index 0000000000..f938a07163 --- /dev/null +++ b/vertx-pg-client/src/test/java/io/vertx/pgclient/it/MissingScramTest.java @@ -0,0 +1,57 @@ +package io.vertx.pgclient.it; + +import io.vertx.core.Vertx; +import io.vertx.ext.unit.Async; +import io.vertx.ext.unit.TestContext; +import io.vertx.ext.unit.junit.VertxUnitRunner; +import io.vertx.pgclient.PgConnectOptions; +import io.vertx.pgclient.PgConnection; +import io.vertx.pgclient.junit.ContainerPgRule; +import org.junit.After; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assume.assumeTrue; + +@RunWith(VertxUnitRunner.class) +public class MissingScramTest { + + @ClassRule + public static ContainerPgRule rule = new ContainerPgRule(); + + private Vertx vertx; + + private PgConnectOptions options; + + @Before + public void setup() throws Exception { + vertx = Vertx.vertx(); + options = rule.options(); + } + + private PgConnectOptions options() { + return new PgConnectOptions(options); + } + + @Test + public void testSaslConnectionFails(TestContext ctx) throws InterruptedException { + assumeTrue(ContainerPgRule.isAtLeastPg10()); + Async async = ctx.async(); + PgConnectOptions options = new PgConnectOptions(options()); + options.setUser("saslscram"); + options.setPassword("saslscrampwd"); + + PgConnection.connect(vertx, options).onComplete( + ctx.asyncAssertFailure(ar -> { + async.complete(); + }) + ); + } + + @After + public void teardown(TestContext ctx) { + vertx.close().onComplete(ctx.asyncAssertSuccess()); + } +}