Skip to content

Commit 5fc500e

Browse files
jorsoltsegismont
andauthored
Support channel binding configuration in PgConnectOptions (#1671)
* feat: support channel binding configuration in PgConnectOptions Introduce support for configuring channel binding mechanisms during the SCRAM-SHA-256-PLUS authentication handshake. This allows users to enforce, prefer, or disable channel binding securely. - Add channelBinding property and getters/setters to PgConnectOptions - Update PgConnectionUriParser to parse 'channel_binding' from connection URIs - Store channel binding preference in Netty Channel attributes during initialization - Propagate the configuration to ScramAuthentication to negotiate -PLUS variants - Fail the connection handshake if a mismatch or unsupported negotiation occurs Signed-off-by: Jorge Solorzano <jorsol@gmail.com> * Order imports, remove logging Signed-off-by: Thomas Segismont <tsegismont@gmail.com> --------- Signed-off-by: Jorge Solorzano <jorsol@gmail.com> Signed-off-by: Thomas Segismont <tsegismont@gmail.com> Co-authored-by: Thomas Segismont <tsegismont@gmail.com>
1 parent 5869727 commit 5fc500e

12 files changed

Lines changed: 251 additions & 32 deletions

File tree

vertx-pg-client/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
<dependency>
5252
<groupId>com.ongres.scram</groupId>
5353
<artifactId>scram-client</artifactId>
54-
<version>3.2</version>
54+
<version>3.4</version>
5555
</dependency>
5656

5757
<!-- Testing purposes -->

vertx-pg-client/src/main/asciidoc/index.adoc

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ Currently, the client supports the following parameter keys:
195195
* `dbname`
196196
* `sslmode`
197197
* `sslnegotiation`
198+
* `channel_binding`
198199

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

@@ -214,6 +215,7 @@ for more details. The following parameters are supported:
214215
* `PGPASSWORD`
215216
* `PGSSLMODE`
216217
* `PGSSLNEGOTIATION`
218+
* `PGCHANNELBINDING`
217219

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

@@ -224,7 +226,8 @@ $ PGUSER=user \
224226
PGPASSWORD=secret \
225227
PGDATABASE=the-db \
226228
PGPORT=5432 \
227-
PGSSLMODE=DISABLE
229+
PGSSLMODE=DISABLE \
230+
PGCHANNELBINDING=prefer
228231
----
229232

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

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

773+
=== Channel Binding (SCRAM authentication)
774+
775+
Channel binding is a method for the server to authenticate itself to the client.
776+
It is only supported over SSL connections with PostgreSQL 11 or later servers using the `SCRAM authentication` method.
777+
778+
The client supports three channel binding modes via {@link io.vertx.pgclient.ChannelBinding}:
779+
780+
- `DISABLE`: Prevents the use of channel binding
781+
- `PREFER` (default): Means that the client will choose channel binding if available
782+
- `REQUIRE`: Means that the connection must employ channel binding
783+
784+
[source,$lang]
785+
----
786+
{@link examples.PgClientExamples#channelBinding}
787+
----
788+
789+
You can also configure Channel binding via connection URI:
790+
791+
[source,$lang]
792+
----
793+
{@link examples.PgClientExamples#channelBindingUri}
794+
----
795+
796+
Or using the environment variable:
797+
798+
[source,bash]
799+
----
800+
export PGCHANNELBINDING=require
801+
----
802+
770803
== Using a level 4 proxy
771804

772805
You can configure the client to use an HTTP/1.x CONNECT, SOCKS4a or SOCKS5 level 4 proxy.

vertx-pg-client/src/main/java/examples/PgClientExamples.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,33 @@ public void sslNegotiationUri() {
539539
PgConnectOptions options = PgConnectOptions.fromUri(connectionUri);
540540
}
541541

542+
public void channelBinding(Vertx vertx) {
543+
PgConnectOptions options = new PgConnectOptions()
544+
.setPort(5432)
545+
.setHost("the-host")
546+
.setDatabase("the-db")
547+
.setUser("user")
548+
.setPassword("secret")
549+
.setSslMode(SslMode.REQUIRE)
550+
.setChannelBinding(ChannelBinding.REQUIRE)
551+
.setSslOptions(new ClientSSLOptions()
552+
.setTrustOptions(new PemTrustOptions().addCertPath("/path/to/server.crt")));
553+
554+
PgConnection.connect(vertx, options)
555+
.onComplete(res -> {
556+
if (res.succeeded()) {
557+
System.out.println("Connected with channel binding enforcement");
558+
} else {
559+
System.out.println("Could not connect: " + res.cause().getMessage());
560+
}
561+
});
562+
}
563+
564+
public void channelBindingUri() {
565+
String connectionUri = "postgresql://localhost/mydb?sslmode=require&channel_binding=require";
566+
PgConnectOptions options = PgConnectOptions.fromUri(connectionUri);
567+
}
568+
542569
public void jsonExample() {
543570

544571
// Create a tuple
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright (c) 2026 Contributors to the Eclipse Foundation
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License 2.0 which is available at
6+
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
7+
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
8+
*
9+
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
10+
*/
11+
package io.vertx.pgclient;
12+
13+
import io.vertx.codegen.annotations.VertxGen;
14+
15+
/**
16+
* The different values for the Channel Binding parameter provide different levels of
17+
* protection. Channel binding is a method for the server to authenticate itself to the client.
18+
* It is only supported over SSL connections with PostgreSQL 11 or later servers using the
19+
* SCRAM authentication method.
20+
*
21+
* @see <a href=
22+
* "https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNECT-CHANNEL-BINDING">
23+
* libpq channel_binding</a>
24+
*/
25+
@VertxGen
26+
public enum ChannelBinding {
27+
28+
/**
29+
* Prevents the use of channel binding
30+
*/
31+
DISABLE("disable"),
32+
33+
/**
34+
* Means that the client will choose channel binding if available.
35+
*/
36+
PREFER("prefer"),
37+
38+
/**
39+
* Means that the connection must employ channel binding.
40+
*/
41+
REQUIRE("require");
42+
43+
public static final ChannelBinding[] VALUES = ChannelBinding.values();
44+
45+
public final String value;
46+
47+
ChannelBinding(String value) {
48+
this.value = value;
49+
}
50+
51+
public static ChannelBinding of(String value) {
52+
for (ChannelBinding channelBinding : VALUES) {
53+
if (channelBinding.value.equalsIgnoreCase(value)) {
54+
return channelBinding;
55+
}
56+
}
57+
58+
throw new IllegalArgumentException("Could not find an appropriate Channel Binding mode for the value [" + value + "].");
59+
}
60+
}

vertx-pg-client/src/main/java/io/vertx/pgclient/PgConnectOptions.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ public static PgConnectOptions fromEnv() {
9999
if (getenv("PGSSLNEGOTIATION") != null) {
100100
pgConnectOptions.setSslNegotiation(SslNegotiation.of(getenv("PGSSLNEGOTIATION")));
101101
}
102+
if (getenv("PGCHANNELBINDING") != null) {
103+
pgConnectOptions.setChannelBinding(ChannelBinding.of(getenv("PGCHANNELBINDING")));
104+
}
102105
return pgConnectOptions;
103106
}
104107

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

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

130135
public PgConnectOptions() {
@@ -143,6 +148,7 @@ public PgConnectOptions(SqlConnectOptions other) {
143148
pipeliningLimit = opts.pipeliningLimit;
144149
sslMode = opts.sslMode;
145150
sslNegotiation = opts.sslNegotiation;
151+
channelBinding = opts.channelBinding;
146152
}
147153
}
148154

@@ -151,6 +157,7 @@ public PgConnectOptions(PgConnectOptions other) {
151157
pipeliningLimit = other.pipeliningLimit;
152158
sslMode = other.sslMode;
153159
sslNegotiation = other.sslNegotiation;
160+
channelBinding = other.channelBinding;
154161
}
155162

156163
@Override
@@ -258,6 +265,24 @@ public PgConnectOptions setSslNegotiation(SslNegotiation sslNegotiation) {
258265
return this;
259266
}
260267

268+
/**
269+
* @return the value of current Channel Binding mode
270+
*/
271+
public ChannelBinding getChannelBinding() {
272+
return channelBinding;
273+
}
274+
275+
/**
276+
* Set {@link ChannelBinding} for the client, this option controls the client's use of channel binding.
277+
*
278+
* @param channelBinding the channel binding mode
279+
* @return a reference to this, so the API can be used fluently
280+
*/
281+
public PgConnectOptions setChannelBinding(ChannelBinding channelBinding) {
282+
this.channelBinding = channelBinding;
283+
return this;
284+
}
285+
261286
/**
262287
* @return whether the client interacts with a layer 7 proxy instead of a server
263288
*/
@@ -342,6 +367,7 @@ public boolean equals(Object o) {
342367
if (pipeliningLimit != that.pipeliningLimit) return false;
343368
if (sslMode != that.sslMode) return false;
344369
if (sslNegotiation != that.sslNegotiation) return false;
370+
if (channelBinding != that.channelBinding) return false;
345371

346372
return true;
347373
}
@@ -352,6 +378,7 @@ public int hashCode() {
352378
result = 31 * result + pipeliningLimit;
353379
result = 31 * result + sslMode.hashCode();
354380
result = 31 * result + sslNegotiation.hashCode();
381+
result = 31 * result + channelBinding.hashCode();
355382
return result;
356383
}
357384

vertx-pg-client/src/main/java/io/vertx/pgclient/impl/PgConnectionUriParser.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
package io.vertx.pgclient.impl;
1212

1313
import io.vertx.core.json.JsonObject;
14+
import io.vertx.pgclient.ChannelBinding;
1415
import io.vertx.pgclient.SslMode;
1516
import io.vertx.pgclient.SslNegotiation;
1617

@@ -183,6 +184,9 @@ private static void parseParameters(String parametersInfo, JsonObject configurat
183184
case "sslnegotiation":
184185
configuration.put("sslNegotiation", SslNegotiation.of(value));
185186
break;
187+
case "channel_binding":
188+
configuration.put("channelBinding", ChannelBinding.of(value));
189+
break;
186190
default:
187191
properties.put(key, value);
188192
break;

vertx-pg-client/src/main/java/io/vertx/pgclient/impl/PgSocketConnection.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import io.vertx.core.spi.metrics.ClientMetrics;
2626
import io.vertx.core.internal.ContextInternal;
2727
import io.vertx.core.internal.net.NetSocketInternal;
28+
import io.vertx.pgclient.ChannelBinding;
2829
import io.vertx.pgclient.PgConnectOptions;
2930
import io.vertx.pgclient.PgException;
3031
import io.vertx.pgclient.impl.codec.ExtendedQueryPgCommandMessage;
@@ -138,6 +139,10 @@ public int getSecretKey() {
138139
return secretKey;
139140
}
140141

142+
public ChannelBinding channelBinding() {
143+
return connectOptions.getChannelBinding();
144+
}
145+
141146
@Override
142147
public DatabaseMetadata databaseMetadata() {
143148
return dbMetaData;
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package io.vertx.pgclient.impl.auth.scram;
22

3-
import com.ongres.scram.client.ScramClient;
43
import io.vertx.core.internal.logging.Logger;
54
import io.vertx.core.internal.logging.LoggerFactory;
5+
import io.vertx.pgclient.ChannelBinding;
66

77
public class ScramAuthentication {
88

@@ -13,10 +13,10 @@ public class ScramAuthentication {
1313
static {
1414
ScramAuthentication instance;
1515
try {
16-
ScramClient.MechanismsBuildStage builder = ScramClient.builder();
17-
logger.debug("Scram authentication is available " + builder);
16+
Class.forName("com.ongres.scram.client.ScramClient");
1817
instance = new ScramAuthentication();
1918
} catch (Throwable notFound) {
19+
logger.debug("SCRAM authentication is NOT available");
2020
instance = null;
2121
}
2222
INSTANCE = instance;
@@ -25,7 +25,7 @@ public class ScramAuthentication {
2525
private ScramAuthentication() {
2626
}
2727

28-
public ScramSession session(String username, char[] password) {
29-
return new ScramSessionImpl(username, password);
28+
public ScramSession session(String username, char[] password, ChannelBinding channelBinding) {
29+
return new ScramSessionImpl(username, password, channelBinding);
3030
}
3131
}

0 commit comments

Comments
 (0)