8000 SASL authentication scheme has benn implemented · LarsG/postgres-async-driver@6f5cf6a · GitHub
[go: up one dir, main page]

Skip to content

Commit 6f5cf6a

Browse files
Marat GainullinMarat Gainullin
authored andcommitted
SASL authentication scheme has benn implemented
1 parent 2172483 commit 6f5cf6a

33 files changed

+975
-118
lines changed

src/main/java/com/github/pgasync/PgConnection.java

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
import static com.github.pgasync.message.backend.RowDescription.ColumnDescription;
1818

19-
import java.nio.charset.Charset;
2019
import java.util.ArrayList;
2120
import java.util.Collections;
2221
import java.util.HashMap;
@@ -43,7 +42,6 @@
4342
import com.github.pgasync.message.frontend.Describe;
4443
import com.github.pgasync.message.Message;
4544
import com.github.pgasync.message.frontend.Parse;
46-
import com.github.pgasync.message.frontend.PasswordMessage;
4745
import com.github.pgasync.message.frontend.Query;
4846
import com.github.pgasync.message.frontend.StartupMessage;
4947

@@ -131,14 +129,12 @@ private String next() {
131129

132130
private final ProtocolStream stream;
133131
private final DataConverter dataConverter;
134-
private final Charset encoding;
135132

136133
private Columns currentColumns;
137134

138-
PgConnection(ProtocolStream stream, DataConverter dataConverter, Charset encoding) {
135+
PgConnection(ProtocolStream stream, DataConverter dataConverter) {
139136
this.stream = stream;
140137
this.dataConverter = dataConverter;
141-
this.encoding = encoding;
142138
}
143139

144140
CompletableFuture<Connection> connect(String username, String password, String database) {
@@ -150,7 +146,7 @@ CompletableFuture<Connection> connect(String username, String password, String d
150146

151147
private CompletableFuture<? extends Message> authenticate(String username, String password, Message message) {
152148
return message instanceof Authentication && !((Authentication) message).isAuthenticationOk()
153-
? stream.authenticate(new PasswordMessage(username, password, ((Authentication) message).getMd5Salt(), encoding))
149+
? stream.authenticate(username, password, ((Authentication) message))
154150
: CompletableFuture.completedFuture(message);
155151
}
156152

src/main/java/com/github/pgasync/PgConnectionPool.java

Lines changed: 1 addition & 1 deletion
< F90C tr class="diff-line-row">
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ public CompletableFuture<Connection> getConnection() {
321321
} else {
322322
if (tryIncreaseSize()) {
323323
obtainStream.get()
324-
.thenApply(stream -> new PooledPgConnection(new PgConnection(stream, dataConverter, encoding))
324+
.thenApply(stream -> new PooledPgConnection(new PgConnection(stream, dataConverter))
325325
.connect(username, password, database))
326326
.thenCompose(Function.identity())
327327
.thenApply(pooledConnection -> {

src/main/java/com/github/pgasync/PgDatabase.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public PgDatabase(ConnectibleBuilder.ConnectibleProperties properties, Supplier<
1717
@Override
1818
public CompletableFuture<Connection> getConnection() {
1919
return obtainStream.get()
20-
.thenApply(stream -> new PgConnection(stream, dataConverter, encoding).connect(username, password, database))
20+
.thenApply(stream -> new PgConnection(stream, dataConverter).connect(username, password, database))
2121
.thenCompose(Function.identity())
2222
.thenApply(connection -> {
2323
if (validationQuery != null && !validationQuery.isBlank()) {

src/main/java/com/github/pgasync/PgProtocolStream.java

Lines changed: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,22 +28,18 @@
2828
import com.github.pgasync.message.backend.ReadyForQuery;
2929
import com.github.pgasync.message.backend.RowDescription;
3030
import com.github.pgasync.message.backend.UnknownMessage;
31-
import com.github.pgasync.message.frontend.Bind;
32-
import com.github.pgasync.message.frontend.Describe;
33-
import com.github.pgasync.message.frontend.Execute;
34-
import com.github.pgasync.message.frontend.FIndicators;
35-
import com.github.pgasync.message.frontend.PasswordMessage;
36-
import com.github.pgasync.message.frontend.Query;
31+
import com.github.pgasync.message.frontend.*;
32+
import com.github.pgasync.sasl.SaslPrep;
3733
import com.pgasync.SqlException;
3834

3935
import java.nio.charset.Charset;
40-
import java.util.HashMap;
41-
import java.util.HashSet;
42-
import java.util.Map;
43-
import java.util.Set;
36+
import java.nio.charset.StandardCharsets;
37+
import java.security.SecureRandom;
38+
import java.util.*;
4439
import java.util.concurrent.CompletableFuture;
4540
import java.util.concurrent.Executor;
4641
import java.util.function.Consumer;
42+
import java.util.function.Function;
4743
import java.util.logging.Level;
4844
import java.util.logging.Logger;
4945

@@ -79,9 +75,57 @@ private CompletableFuture<? super Message> consumeOnResponse() {
7975
return wasOnResponse;
8076
}
8177

78+
private static String saslNonce() {
79+
// Generate nonce
80+
SecureRandom rnd = new SecureRandom();
81+
byte[] nonce = new byte[24];
82+
rnd.nextBytes(nonce);
83+
// Sanitize it
84+
for (int i = 0; i < nonce.length; i++) {
85+
// Ascii space is also substituted with underscore
86+
if (nonce[i] < 33 || nonce[i] >= 127) {
87+
nonce[i] = '_';
88+
}
89+
}
90+
return new String(nonce, StandardCharsets.US_ASCII);
91+
}
92+
8293
@Override
83-
public CompletableFuture<Message> authenticate(PasswordMessage password) {
84-
return send(password);
94+
public CompletableFuture<Message> authenticate(String userName, String password, Authentication authRequired) {
95+
if (authRequired.isSaslScramSha256()) {
96+
String clientNonce = saslNonce();
97+
SASLInitialResponse saslInitialResponse = new SASLInitialResponse(Authentication.SUPPORTED_SASL, null, ""/*SaslPrep.asQueryString(userName) - Postgres requires an empty string here*/, clientNonce);
98+
return send(saslInitialResponse)
99+
.thenApply(message -> {
100+
if (message instanceof Authentication) {
101+
String serverFirstMessage = ((Authentication) message).getSaslContinueData();
102+
if (serverFirstMessage != null) {
103+
return send(SASLResponse.of(password, serverFirstMessage, clientNonce, saslInitialResponse.getGs2Header(), saslInitialResponse.getClientFirstMessageBare()));
104+
} else {
105+
throw new IllegalStateException("Bad SASL authentication sequence message detected on 'server-first-message' step");
106+
}
107+
} else {
108+
throw new IllegalStateException("Bad SASL authentication sequence detected on 'server-first-message' step");
109+
}
110+
})
111+
.thenCompose(Function.identity())
112+
.thenApply(message -> {
113+
if (message instanceof Authentication) {
114+
byte[] serverAdditionalData = ((Authentication) message).getSaslAdditionalData();
115+
if (serverAdditionalData != null) {
116+
return offerRoundTrip(() -> {
117+
});
118+
} else {
119+
throw new IllegalStateException("Bad SASL authentication sequence message detected on 'server-final-message' step");
120+
}
121+
} else {
122+
throw new IllegalStateException("Bad SASL authentication sequence detected on 'server-final-message' step");
123+
}
124+
})
125+
.thenCompose(Function.identity());
126+
} else {
127+
return send(new PasswordMessage(userName, password, authRequired.getMd5Salt(), encoding));
128+
}
85129
}
86130

87131
protected abstract void write(Message... messages);

src/main/java/com/github/pgasync/ProtocolStream.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@
1515
package com.github.pgasync;
1616

1717
import com.github.pgasync.message.Message;
18+
import com.github.pgasync.message.backend.Authentication;
1819
import com.github.pgasync.message.backend.CommandComplete;
1920
import com.github.pgasync.message.backend.DataRow;
2021
import com.github.pgasync.message.backend.RowDescription;
2122
import com.github.pgasync.message.frontend.Bind;
2223
import com.github.pgasync.message.frontend.Describe;
23-
import com.github.pgasync.message.frontend.PasswordMessage;
2424
import com.github.pgasync.message.frontend.Query;
2525
import com.github.pgasync.message.frontend.StartupMessage;
2626

@@ -36,7 +36,7 @@ public interface ProtocolStream {
3636

3737
CompletableFuture<Message> connect(StartupMessage startup);
3838

39-
CompletableFuture<Message> authenticate(PasswordMessage password);
39+
CompletableFuture<Message> authenticate(String userName, String password, Authentication authRequired);
4040

4141
CompletableFuture<Message> send(Message message);
4242

src/main/java/com/github/pgasync/io/Decoder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,6 @@ public interface Decoder<T extends Message> {
3434
/**
3535
* @return Decoded message
3636
*/
37-
T read(ByteBuffer buffer, Charset encoding);
37+
T read(ByteBuffer buffer, int contentLength, Charset encoding);
3838

3939
}

src/main/java/com/github/pgasync/io/backend/AuthenticationDecoder.java

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -66,20 +66,30 @@ public byte getMessageId() {
6666
}
6767

6868
@Override
69-
public Authentication read(ByteBuffer buffer, Charset encoding) {
69+
public Authentication read(ByteBuffer buffer, int contentLength, Charset encoding) {
7070
int type = buffer.getInt();
7171
switch (type) {
7272
case CLEARTEXT_PASSWORD:
7373
return Authentication.CLEAR_TEXT;
7474
case PASSWORD_MD5_CHALLENGE:
75-
byte[] salt = new byte[4];
76-
buffer.get(salt);
77-
return new Authentication(false, false, salt);
75+
byte[] md5Salt = new byte[4];
76+
buffer.get(md5Salt);
77+
return new Authentication(false, false, md5Salt, null, null);
78+
case AUTHENTICATION_SSPI:
79+
throw new UnsupportedOperationException(AUTHENTICATION_IS_NOT_SUPPORTED);
80+
case AUTHENTICATION_GSS:
81+
throw new UnsupportedOperationException("AuthenticationGss" + AUTHENTICATION_IS_NOT_SUPPORTED);
82+
case AUTHENTICATION_SCM_CREDENTIAL:
83+
throw new UnsupportedOperationException("AuthenticationScmCredential" + AUTHENTICATION_IS_NOT_SUPPORTED);
84+
case AUTHENTICATION_KERBEROS_V5:
85+
throw new UnsupportedOperationException("AuthenticationKerberosV5" + AUTHENTICATION_IS_NOT_SUPPORTED);
86+
case OK:
87+
return Authentication.OK;
7888
case SASL:
7989
boolean scramSha256Met = false;
8090
String sasl = IO.getCString(buffer, encoding);
8191
while (!sasl.isEmpty()) {
82-
if ("SCRAM-SHA-256".equals(sasl)) {
92+
if (Authentication.SUPPORTED_SASL.equals(sasl)) {
8393
scramSha256Met = true;
8494
}
8595
sasl = IO.getCString(buffer, encoding);
@@ -89,20 +99,14 @@ public Authentication read(ByteBuffer buffer, Charset encoding) {
8999
} else {
90100
throw new UnsupportedOperationException("The server doesn't support " + Authentication.SUPPORTED_SASL + " SASL mechanism");
91101
}
92-
case AUTHENTICATION_SSPI:
93-
throw new UnsupportedOperationException(AUTHENTICATION_IS_NOT_SUPPORTED);
94-
case AUTHENTICATION_GSS:
95-
throw new U 7904 nsupportedOperationException("AuthenticationGss" + AUTHENTICATION_IS_NOT_SUPPORTED);
96-
case AUTHENTICATION_SCM_CREDENTIAL:
97-
throw new UnsupportedOperationException("AuthenticationScmCredential" + AUTHENTICATION_IS_NOT_SUPPORTED);
98-
case AUTHENTICATION_KERBEROS_V5:
99-
throw new UnsupportedOperationException("AuthenticationKerberosV5" + AUTHENTICATION_IS_NOT_SUPPORTED);
100-
case OK:
101-
return Authentication.OK;
102102
case SASL_CONTINUE:
103-
return Authentication.OK;
103+
byte[] saslContinueData = new byte[contentLength - 4]; // Minus type field size
104+
buffer.get(saslContinueData);
105+
return new Authentication(false, false, null, new String(saslContinueData, encoding), null);
104106
case SASL_FINAL:
105-
return Authentication.OK;
107+
byte[] saslAdditionalData = new byte[contentLength - 4]; // Minus type field size
108+
buffer.get(saslAdditionalData);
109+
return new Authentication(false, false, null, null, saslAdditionalData);
106110
default:
107111
throw new UnsupportedOperationException("Unsupported authentication type: " + type);
108112
}

src/main/java/com/github/pgasync/io/backend/BackendKeyDataDecoder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public byte getMessageId() {
2828
}
2929

3030
@Override
31-
public BackendKeyData read(ByteBuffer buffer, Charset encoding) {
31+
public BackendKeyData read(ByteBuffer buffer, int contentLength, Charset encoding) {
3232
return new BackendKeyData(buffer.getInt(), buffer.getInt());
3333
}
3434
}

src/main/java/com/github/pgasync/io/backend/BindCompleteDecoder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public byte getMessageId() {
4343
}
4444

4545
@Override
46-
public BIndicators read(ByteBuffer buffer, Charset encoding) {
46+
public BIndicators read(ByteBuffer buffer, int contentLength, Charset encoding) {
4747
return BIndicators.BIND_COMPLETE;
4848
}
4949

src/main/java/com/github/pgasync/io/backend/CloseCompleteDecoder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public byte getMessageId() {
4343
}
4444

4545
@Override
46-
public BIndicators read(ByteBuffer buffer, Charset encoding) {
46+
public BIndicators read(ByteBuffer buffer, int contentLength, Charset encoding) {
4747
return BIndicators.CLOSE_COMPLETE;
4848
}
4949

0 commit comments

Comments
 (0)
0