Skip to content
Open
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
2 changes: 1 addition & 1 deletion vertx-pg-client/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
<dependency>
<groupId>com.ongres.scram</groupId>
<artifactId>scram-client</artifactId>
<version>3.2</version>
<version>3.4</version>
</dependency>

<!-- Testing purposes -->
Expand Down
35 changes: 34 additions & 1 deletion vertx-pg-client/src/main/asciidoc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ Currently, the client supports the following parameter keys:
* `dbname`
* `sslmode`
* `sslnegotiation`
* `channel_binding`

Additional parameters will be added to the {@link io.vertx.sqlclient.SqlConnectOptions#getProperties properties}.

Expand All @@ -214,6 +215,7 @@ for more details. The following parameters are supported:
* `PGPASSWORD`
* `PGSSLMODE`
* `PGSSLNEGOTIATION`
* `PGCHANNELBINDING`

If you don't specify a data object or a connection URI string to connect, environment variables will take precedence over them.

Expand All @@ -224,7 +226,8 @@ $ PGUSER=user \
PGPASSWORD=secret \
PGDATABASE=the-db \
PGPORT=5432 \
PGSSLMODE=DISABLE
PGSSLMODE=DISABLE \
PGCHANNELBINDING=prefer
----

[source,$lang]
Expand Down Expand Up @@ -767,6 +770,36 @@ Attempting to use `DIRECT` mode with older PostgreSQL versions will result in an

More information can be found in the http://vertx.io/docs/vertx-core/java/#ssl[Vert.x documentation].

=== Channel Binding (SCRAM authentication)

Channel binding is a method for the server to authenticate itself to the client.
It is only supported over SSL connections with PostgreSQL 11 or later servers using the `SCRAM authentication` method.

The client supports three channel binding modes via {@link io.vertx.pgclient.ChannelBinding}:

- `DISABLE`: Prevents the use of channel binding
- `PREFER` (default): Means that the client will choose channel binding if available
- `REQUIRE`: Means that the connection must employ channel binding

[source,$lang]
----
{@link examples.PgClientExamples#channelBinding}
----

You can also configure Channel binding via connection URI:

[source,$lang]
----
{@link examples.PgClientExamples#channelBindingUri}
----

Or using the environment variable:

[source,bash]
----
export PGCHANNELBINDING=require
----

== Using a level 4 proxy

You can configure the client to use an HTTP/1.x CONNECT, SOCKS4a or SOCKS5 level 4 proxy.
Expand Down
27 changes: 27 additions & 0 deletions vertx-pg-client/src/main/java/examples/PgClientExamples.java
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,33 @@ public void sslNegotiationUri() {
PgConnectOptions options = PgConnectOptions.fromUri(connectionUri);
}

public void channelBinding(Vertx vertx) {
PgConnectOptions options = new PgConnectOptions()
.setPort(5432)
.setHost("the-host")
.setDatabase("the-db")
.setUser("user")
.setPassword("secret")
.setSslMode(SslMode.REQUIRE)
.setChannelBinding(ChannelBinding.REQUIRE)
.setSslOptions(new ClientSSLOptions()
.setTrustOptions(new PemTrustOptions().addCertPath("/path/to/server.crt")));

PgConnection.connect(vertx, options)
.onComplete(res -> {
if (res.succeeded()) {
System.out.println("Connected with channel binding enforcement");
} else {
System.out.println("Could not connect: " + res.cause().getMessage());
}
});
}

public void channelBindingUri() {
String connectionUri = "postgresql://localhost/mydb?sslmode=require&channel_binding=require";
PgConnectOptions options = PgConnectOptions.fromUri(connectionUri);
}

public void jsonExample() {

// Create a tuple
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright (c) 2026 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
*
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
*/
package io.vertx.pgclient;

import io.vertx.codegen.annotations.VertxGen;

/**
* The different values for the Channel Binding parameter provide different levels of
* protection. Channel binding is a method for the server to authenticate itself to the client.
* It is only supported over SSL connections with PostgreSQL 11 or later servers using the
* SCRAM authentication method.
*
* @see <a href=
* "https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNECT-CHANNEL-BINDING">
* libpq channel_binding</a>
*/
@VertxGen
public enum ChannelBinding {

/**
* Prevents the use of channel binding
*/
DISABLE("disable"),

/**
* Means that the client will choose channel binding if available.
*/
PREFER("prefer"),

/**
* Means that the connection must employ channel binding.
*/
REQUIRE("require");

public static final ChannelBinding[] VALUES = ChannelBinding.values();

public final String value;

ChannelBinding(String value) {
this.value = value;
}

public static ChannelBinding of(String value) {
for (ChannelBinding channelBinding : VALUES) {
if (channelBinding.value.equalsIgnoreCase(value)) {
return channelBinding;
}
}

throw new IllegalArgumentException("Could not find an appropriate Channel Binding mode for the value [" + value + "].");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ public static PgConnectOptions fromEnv() {
if (getenv("PGSSLNEGOTIATION") != null) {
pgConnectOptions.setSslNegotiation(SslNegotiation.of(getenv("PGSSLNEGOTIATION")));
}
if (getenv("PGCHANNELBINDING") != null) {
pgConnectOptions.setChannelBinding(ChannelBinding.of(getenv("PGCHANNELBINDING")));
}
return pgConnectOptions;
}

Expand All @@ -110,6 +113,7 @@ public static PgConnectOptions fromEnv() {
public static final int DEFAULT_PIPELINING_LIMIT = 256;
public static final SslMode DEFAULT_SSLMODE = SslMode.DISABLE;
public static final SslNegotiation DEFAULT_SSL_NEGOTIATION = SslNegotiation.POSTGRES;
public static final ChannelBinding DEFAULT_CHANNEL_BINDING = ChannelBinding.PREFER;
public static final boolean DEFAULT_USE_LAYER_7_PROXY = false;
public static final Map<String, String> DEFAULT_PROPERTIES;

Expand All @@ -125,6 +129,7 @@ public static PgConnectOptions fromEnv() {
private int pipeliningLimit = DEFAULT_PIPELINING_LIMIT;
private SslMode sslMode = DEFAULT_SSLMODE;
private SslNegotiation sslNegotiation = DEFAULT_SSL_NEGOTIATION;
private ChannelBinding channelBinding = DEFAULT_CHANNEL_BINDING;
private boolean useLayer7Proxy = DEFAULT_USE_LAYER_7_PROXY;

public PgConnectOptions() {
Expand All @@ -143,6 +148,7 @@ public PgConnectOptions(SqlConnectOptions other) {
pipeliningLimit = opts.pipeliningLimit;
sslMode = opts.sslMode;
sslNegotiation = opts.sslNegotiation;
channelBinding = opts.channelBinding;
}
}

Expand All @@ -151,6 +157,7 @@ public PgConnectOptions(PgConnectOptions other) {
pipeliningLimit = other.pipeliningLimit;
sslMode = other.sslMode;
sslNegotiation = other.sslNegotiation;
channelBinding = other.channelBinding;
}

@Override
Expand Down Expand Up @@ -258,6 +265,24 @@ public PgConnectOptions setSslNegotiation(SslNegotiation sslNegotiation) {
return this;
}

/**
* @return the value of current Channel Binding mode
*/
public ChannelBinding getChannelBinding() {
return channelBinding;
}

/**
* Set {@link ChannelBinding} for the client, this option controls the client's use of channel binding.
*
* @param channelBinding the channel binding mode
* @return a reference to this, so the API can be used fluently
*/
public PgConnectOptions setChannelBinding(ChannelBinding channelBinding) {
this.channelBinding = channelBinding;
return this;
}

/**
* @return whether the client interacts with a layer 7 proxy instead of a server
*/
Expand Down Expand Up @@ -342,6 +367,7 @@ public boolean equals(Object o) {
if (pipeliningLimit != that.pipeliningLimit) return false;
if (sslMode != that.sslMode) return false;
if (sslNegotiation != that.sslNegotiation) return false;
if (channelBinding != that.channelBinding) return false;

return true;
}
Expand All @@ -352,6 +378,7 @@ public int hashCode() {
result = 31 * result + pipeliningLimit;
result = 31 * result + sslMode.hashCode();
result = 31 * result + sslNegotiation.hashCode();
result = 31 * result + channelBinding.hashCode();
return result;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
package io.vertx.pgclient.impl;

import io.vertx.core.json.JsonObject;
import io.vertx.pgclient.ChannelBinding;
import io.vertx.pgclient.SslMode;
import io.vertx.pgclient.SslNegotiation;

Expand Down Expand Up @@ -183,6 +184,9 @@ private static void parseParameters(String parametersInfo, JsonObject configurat
case "sslnegotiation":
configuration.put("sslNegotiation", SslNegotiation.of(value));
break;
case "channel_binding":
configuration.put("channelBinding", ChannelBinding.of(value));
break;
default:
properties.put(key, value);
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import io.vertx.core.spi.metrics.ClientMetrics;
import io.vertx.core.internal.ContextInternal;
import io.vertx.core.internal.net.NetSocketInternal;
import io.vertx.pgclient.ChannelBinding;
import io.vertx.pgclient.PgConnectOptions;
import io.vertx.pgclient.PgException;
import io.vertx.pgclient.impl.codec.ExtendedQueryPgCommandMessage;
Expand Down Expand Up @@ -138,6 +139,10 @@ public int getSecretKey() {
return secretKey;
}

public ChannelBinding channelBinding() {
return connectOptions.getChannelBinding();
}

@Override
public DatabaseMetadata databaseMetadata() {
return dbMetaData;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
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;
import io.vertx.pgclient.ChannelBinding;

public class ScramAuthentication {

Expand All @@ -13,10 +13,10 @@ public class ScramAuthentication {
static {
ScramAuthentication instance;
try {
ScramClient.MechanismsBuildStage builder = ScramClient.builder();
logger.debug("Scram authentication is available " + builder);
Class.forName("com.ongres.scram.client.ScramClient");
instance = new ScramAuthentication();
} catch (Throwable notFound) {
logger.debug("SCRAM authentication is NOT available");
instance = null;
}
INSTANCE = instance;
Expand All @@ -25,7 +25,7 @@ public class ScramAuthentication {
private ScramAuthentication() {
}

public ScramSession session(String username, char[] password) {
return new ScramSessionImpl(username, password);
public ScramSession session(String username, char[] password, ChannelBinding channelBinding) {
return new ScramSessionImpl(username, password, channelBinding);
}
}
Loading