Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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