Skip to content

Commit 351bef7

Browse files
committed
Update dependency SCRAM 3.0 and support channel binding
Signed-off-by: Jorge Solórzano <[email protected]>
1 parent acb02de commit 351bef7

File tree

5 files changed

+69
-51
lines changed

5 files changed

+69
-51
lines changed

vertx-pg-client/pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@
6464
<!-- sasl scram authentication -->
6565
<dependency>
6666
<groupId>com.ongres.scram</groupId>
67-
<artifactId>client</artifactId>
68-
<version>2.1</version>
67+
<artifactId>scram-client</artifactId>
68+
<version>3.0</version>
6969
<optional>true</optional>
7070
</dependency>
7171

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

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -233,30 +233,27 @@ $ PGUSER=user \
233233

234234
=== SASL SCRAM-SHA-256 authentication mechanism.
235235

236-
To use the sasl SCRAM-SHA-256 authentication add the following dependency to the _dependencies_ section of your build descriptor:
236+
To use the SASL `SCRAM-SHA-256` authentication add the following dependency to the _dependencies_ section of your build descriptor:
237237

238238
* Maven (in your `pom.xml`):
239239

240240
[source,xml]
241241
----
242242
<dependency>
243243
<groupId>com.ongres.scram</groupId>
244-
<artifactId>client</artifactId>
245-
<version>2.1</version>
244+
<artifactId>scram-client</artifactId>
245+
<version>3.0</version>
246246
</dependency>
247247
----
248248
* Gradle (in your `build.gradle` file):
249249

250250
[source,groovy]
251251
----
252252
dependencies {
253-
compile 'com.ongres.scram:client:2.1'
253+
compile 'com.ongres.scram:scram-client:3.0'
254254
}
255255
----
256256

257-
NOTE: SCRAM-SHA-256-PLUS (added in Postgresql 11) is not supported.
258-
259-
260257
include::queries.adoc[leveloffset=1]
261258

262259
== Returning clauses

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,9 @@ public void handleAuthenticationClearTextPassword() {
5757

5858
@Override
5959
void handleAuthenticationSasl(ByteBuf in) {
60-
scramAuthentication = new ScramAuthentication(cmd.username(), cmd.password());
61-
encoder.writeScramClientInitialMessage(scramAuthentication.createInitialSaslMessage(in));
60+
scramAuthentication = new ScramAuthentication(cmd.username(), cmd.password().toCharArray());
61+
encoder.writeScramClientInitialMessage(
62+
scramAuthentication.createInitialSaslMessage(in, encoder.channelHandlerContext()));
6263
encoder.flush();
6364
}
6465

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
import io.vertx.sqlclient.impl.RowDesc;
3131
import io.vertx.sqlclient.impl.command.*;
3232

33-
import java.util.ArrayDeque;
3433
import java.util.Map;
3534

3635
import static io.vertx.pgclient.impl.util.Util.writeCString;

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

Lines changed: 60 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -18,31 +18,35 @@
1818
package io.vertx.pgclient.impl.util;
1919

2020
import java.nio.charset.StandardCharsets;
21+
import java.security.cert.Certificate;
22+
import java.security.cert.CertificateEncodingException;
23+
import java.security.cert.X509Certificate;
2124
import java.util.ArrayList;
2225
import java.util.List;
2326

27+
import javax.net.ssl.SSLEngine;
28+
import javax.net.ssl.SSLException;
29+
2430
import com.ongres.scram.client.ScramClient;
25-
import com.ongres.scram.client.ScramSession;
26-
import com.ongres.scram.common.exception.ScramException;
31+
import com.ongres.scram.common.StringPreparation;
2732
import com.ongres.scram.common.exception.ScramInvalidServerSignatureException;
2833
import com.ongres.scram.common.exception.ScramParseException;
2934
import com.ongres.scram.common.exception.ScramServerErrorException;
30-
import com.ongres.scram.common.stringprep.StringPreparations;
35+
import com.ongres.scram.common.util.TlsServerEndpoint;
3136

3237
import io.netty.buffer.ByteBuf;
38+
import io.netty.channel.Channel;
39+
import io.netty.channel.ChannelHandlerContext;
40+
import io.netty.handler.ssl.SslHandler;
3341
import io.vertx.pgclient.impl.codec.ScramClientInitialMessage;
3442

3543
public class ScramAuthentication {
3644

37-
private static final String SCRAM_SHA_256 = "SCRAM-SHA-256";
38-
3945
private final String username;
40-
private final String password;
41-
private ScramSession scramSession;
42-
private ScramSession.ClientFinalProcessor clientFinalProcessor;
46+
private final char[] password;
47+
private ScramClient scramClient;
4348

44-
45-
public ScramAuthentication(String username, String password) {
49+
public ScramAuthentication(String username, char[] password) {
4650
this.username = username;
4751
this.password = password;
4852
}
@@ -53,39 +57,37 @@ public ScramAuthentication(String username, String password) {
5357
* The message includes the name of the selected mechanism, and
5458
* an optional Initial Client Response, if the selected mechanism uses that.
5559
*/
56-
public ScramClientInitialMessage createInitialSaslMessage(ByteBuf in) {
60+
public ScramClientInitialMessage createInitialSaslMessage(ByteBuf in, ChannelHandlerContext ctx) {
5761
List<String> mechanisms = new ArrayList<>();
5862

5963
while (0 != in.getByte(in.readerIndex())) {
60-
String mechanism = Util.readCStringUTF8(in);
61-
mechanisms.add(mechanism);
64+
String mechanism = Util.readCStringUTF8(in);
65+
mechanisms.add(mechanism);
6266
}
6367

6468
if (mechanisms.isEmpty()) {
6569
throw new UnsupportedOperationException("SASL Authentication : the server returned no mechanism");
6670
}
6771

68-
// SCRAM-SHA-256-PLUS added in postgresql 11 is not supported
69-
if (!mechanisms.contains(SCRAM_SHA_256)) {
70-
throw new UnsupportedOperationException("SASL Authentication : only SCRAM-SHA-256 is currently supported, server wants " + mechanisms);
71-
}
72-
73-
74-
ScramClient scramClient = ScramClient
75-
.channelBinding(ScramClient.ChannelBinding.NO)
76-
.stringPreparation(StringPreparations.NO_PREPARATION)
77-
.selectMechanismBasedOnServerAdvertised(mechanisms.toArray(new String[0]))
78-
.setup();
79-
80-
81-
// this user name will be ignored, the user name that was already sent in the startup message is used instead
82-
// see https://www.postgresql.org/docs/11/sasl-authentication.html#SASL-SCRAM-SHA-256 §53.3.1
83-
scramSession = scramClient.scramSession(this.username);
84-
85-
return new ScramClientInitialMessage(scramSession.clientFirstMessage(), scramClient.getScramMechanism().getName());
72+
byte[] channelBindingData = extractChannelBindingData(ctx);
73+
this.scramClient = ScramClient.builder()
74+
.advertisedMechanisms(mechanisms)
75+
.username(username)
76+
.password(password)
77+
.stringPreparation(StringPreparation.POSTGRESQL_PREPARATION)
78+
.channelBinding(TlsServerEndpoint.TLS_SERVER_END_POINT, channelBindingData)
79+
.build();
80+
81+
// this user name will be ignored, the user name that was already sent in the
82+
// startup message is used instead
83+
// see
84+
// https://www.postgresql.org/docs/11/sasl-authentication.html#SASL-SCRAM-SHA-256
85+
// §53.3.1
86+
87+
return new ScramClientInitialMessage(scramClient.clientFirstMessage().toString(),
88+
scramClient.getScramMechanism().getName());
8689
}
8790

88-
8991
/*
9092
* One or more server-challenge and client-response message will follow.
9193
* Each server-challenge is sent in an AuthenticationSASLContinue message,
@@ -95,16 +97,13 @@ public ScramClientInitialMessage createInitialSaslMessage(ByteBuf in) {
9597
public String receiveServerFirstMessage(ByteBuf in) {
9698
String serverFirstMessage = in.readCharSequence(in.readableBytes(), StandardCharsets.UTF_8).toString();
9799

98-
ScramSession.ServerFirstProcessor serverFirstProcessor = null;
99100
try {
100-
serverFirstProcessor = scramSession.receiveServerFirstMessage(serverFirstMessage);
101-
} catch (ScramException e) {
101+
scramClient.serverFirstMessage(serverFirstMessage);
102+
} catch (ScramParseException e) {
102103
throw new UnsupportedOperationException(e);
103104
}
104105

105-
clientFinalProcessor = serverFirstProcessor.clientFinalProcessor(password);
106-
107-
return clientFinalProcessor.clientFinalMessage();
106+
return scramClient.clientFinalMessage().toString();
108107
}
109108

110109
/*
@@ -119,9 +118,31 @@ public void checkServerFinalMessage(ByteBuf in) {
119118
String serverFinalMessage = in.readCharSequence(in.readableBytes(), StandardCharsets.UTF_8).toString();
120119

121120
try {
122-
clientFinalProcessor.receiveServerFinalMessage(serverFinalMessage);
121+
scramClient.serverFinalMessage(serverFinalMessage);
123122
} catch (ScramParseException | ScramServerErrorException | ScramInvalidServerSignatureException e) {
124123
throw new UnsupportedOperationException(e);
125124
}
126125
}
126+
127+
private byte[] extractChannelBindingData(ChannelHandlerContext ctx) {
128+
Channel channel = ctx.channel();
129+
SslHandler handler = channel.pipeline().get(SslHandler.class);
130+
if (handler != null) {
131+
SSLEngine engine = handler.engine();
132+
try {
133+
// Get the certificate chain from the session
134+
Certificate[] certificates = engine.getSession().getPeerCertificates();
135+
if (certificates != null && certificates.length > 0) {
136+
Certificate peerCert = certificates[0]; // First certificate is the peer's certificate
137+
if (peerCert instanceof X509Certificate) {
138+
X509Certificate cert = (X509Certificate) peerCert;
139+
return TlsServerEndpoint.getChannelBindingData(cert);
140+
}
141+
}
142+
} catch (CertificateEncodingException | SSLException e) {
143+
// IGNORE
144+
}
145+
}
146+
return new byte[0]; // handle as no channel binding available
147+
}
127148
}

0 commit comments

Comments
 (0)