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 .SSLEngine ;
28+ import javax .net .ssl .SSLException ;
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 .Channel ;
39+ import io .netty .channel .ChannelHandlerContext ;
40+ import io .netty .handler .ssl .SslHandler ;
3341import io .vertx .pgclient .impl .codec .ScramClientInitialMessage ;
3442
3543public 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