1818package io .vertx .pgclient .impl .util ;
1919
2020import java .nio .charset .StandardCharsets ;
21+ import java .security .cert .Certificate ;
22+ import java .security .cert .CertificateEncodingException ;
23+ import java .security .cert .X509Certificate ;
2124import java .util .ArrayList ;
2225import java .util .List ;
2326
27+ import javax .net .ssl .SSLException ;
28+ import javax .net .ssl .SSLSession ;
29+
2430import 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 ;
2732import com .ongres .scram .common .exception .ScramInvalidServerSignatureException ;
2833import com .ongres .scram .common .exception .ScramParseException ;
2934import com .ongres .scram .common .exception .ScramServerErrorException ;
30- import com .ongres .scram .common .stringprep . StringPreparations ;
35+ import com .ongres .scram .common .util . TlsServerEndpoint ;
3136
3237import io .netty .buffer .ByteBuf ;
38+ import io .netty .channel .ChannelHandlerContext ;
39+ import io .netty .handler .ssl .SslHandler ;
3340import io .vertx .pgclient .impl .codec .ScramClientInitialMessage ;
3441
3542public class ScramAuthentication {
3643
37- private static final String SCRAM_SHA_256 = "SCRAM-SHA-256" ;
38-
3944 private final String username ;
40- private final String password ;
41- private ScramSession scramSession ;
42- private ScramSession .ClientFinalProcessor clientFinalProcessor ;
45+ private final char [] password ;
46+ private ScramClient scramClient ;
4347
44-
45- public ScramAuthentication (String username , String password ) {
48+ public ScramAuthentication (String username , char [] password ) {
4649 this .username = username ;
4750 this .password = password ;
4851 }
@@ -53,39 +56,31 @@ public ScramAuthentication(String username, String password) {
5356 * The message includes the name of the selected mechanism, and
5457 * an optional Initial Client Response, if the selected mechanism uses that.
5558 */
56- public ScramClientInitialMessage createInitialSaslMessage (ByteBuf in ) {
59+ public ScramClientInitialMessage createInitialSaslMessage (ByteBuf in , ChannelHandlerContext ctx ) {
5760 List <String > mechanisms = new ArrayList <>();
5861
5962 while (0 != in .getByte (in .readerIndex ())) {
60- String mechanism = Util .readCStringUTF8 (in );
61- mechanisms .add (mechanism );
63+ String mechanism = Util .readCStringUTF8 (in );
64+ mechanisms .add (mechanism );
6265 }
6366
6467 if (mechanisms .isEmpty ()) {
6568 throw new UnsupportedOperationException ("SASL Authentication : the server returned no mechanism" );
6669 }
6770
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 ());
71+ byte [] channelBindingData = extractChannelBindingData (ctx );
72+ this .scramClient = ScramClient .builder ()
73+ .advertisedMechanisms (mechanisms )
74+ .username (username ) // ignored by the server, use startup message
75+ .password (password )
76+ .stringPreparation (StringPreparation .POSTGRESQL_PREPARATION )
77+ .channelBinding (TlsServerEndpoint .TLS_SERVER_END_POINT , channelBindingData )
78+ .build ();
79+
80+ return new ScramClientInitialMessage (scramClient .clientFirstMessage ().toString (),
81+ scramClient .getScramMechanism ().getName ());
8682 }
8783
88-
8984 /*
9085 * One or more server-challenge and client-response message will follow.
9186 * Each server-challenge is sent in an AuthenticationSASLContinue message,
@@ -95,16 +90,13 @@ public ScramClientInitialMessage createInitialSaslMessage(ByteBuf in) {
9590 public String receiveServerFirstMessage (ByteBuf in ) {
9691 String serverFirstMessage = in .readCharSequence (in .readableBytes (), StandardCharsets .UTF_8 ).toString ();
9792
98- ScramSession .ServerFirstProcessor serverFirstProcessor = null ;
9993 try {
100- serverFirstProcessor = scramSession . receiveServerFirstMessage (serverFirstMessage );
101- } catch (ScramException e ) {
94+ scramClient . serverFirstMessage (serverFirstMessage );
95+ } catch (ScramParseException e ) {
10296 throw new UnsupportedOperationException (e );
10397 }
10498
105- clientFinalProcessor = serverFirstProcessor .clientFinalProcessor (password );
106-
107- return clientFinalProcessor .clientFinalMessage ();
99+ return scramClient .clientFinalMessage ().toString ();
108100 }
109101
110102 /*
@@ -119,9 +111,33 @@ public void checkServerFinalMessage(ByteBuf in) {
119111 String serverFinalMessage = in .readCharSequence (in .readableBytes (), StandardCharsets .UTF_8 ).toString ();
120112
121113 try {
122- clientFinalProcessor . receiveServerFinalMessage (serverFinalMessage );
114+ scramClient . serverFinalMessage (serverFinalMessage );
123115 } catch (ScramParseException | ScramServerErrorException | ScramInvalidServerSignatureException e ) {
124116 throw new UnsupportedOperationException (e );
125117 }
126118 }
119+
120+ private byte [] extractChannelBindingData (ChannelHandlerContext ctx ) {
121+ SslHandler sslHandler = ctx .channel ().pipeline ().get (SslHandler .class );
122+ if (sslHandler != null ) {
123+ SSLSession sslSession = sslHandler .engine ().getSession ();
124+ if (sslSession != null && sslSession .isValid ()) {
125+ try {
126+ // Get the certificate chain from the session
127+ Certificate [] certificates = sslSession .getPeerCertificates ();
128+ if (certificates != null && certificates .length > 0 ) {
129+ Certificate peerCert = certificates [0 ]; // First certificate is the peer's certificate
130+ if (peerCert instanceof X509Certificate ) {
131+ X509Certificate cert = (X509Certificate ) peerCert ;
132+ return TlsServerEndpoint .getChannelBindingData (cert );
133+ }
134+ }
135+ } catch (CertificateEncodingException | SSLException e ) {
136+ // Cannot extract X509Certificate from SSL session
137+ // handle as no channel binding available
138+ }
139+ }
140+ }
141+ return new byte [0 ]; // handle as no channel binding available
142+ }
127143}
0 commit comments