postgresql-jdbc.git / summary / log / commit / refs
commit befea18d153dda7814daef4e036d3f5daf8de1e5
Author: Álvaro Hernández Tortosa <aht@8kdata.com>
Commit: Dave Cramer <davecramer@gmail.com>
Date: Tue Nov 21 11:42:28 2017 +0000
Add SCRAM-SHA-256 support (#842)
* Add SCRAM-SHA-256 support
PostgreSQL 10 comes with SCRAM-SHA-256 support. This commit introduces
support for it.
Work is based on an external dependency, the SCRAM client library:
https://github.com/ongres/scram/
which is now imported as a dependency.
TODO:
- SCRAM client library will be improved, on its own.
Particularly, StringPrep support needs to be added. Final
version of pgjdbc will depend on v1.0
- Testing
- Probably macros to avoid this Java8-only code propagating to
Java < 8 versions of the driver
* Prepare Java8-only code to be excluded for jre<=8
Actually implemented via macros by JDBC < 4.2.
* On HEAD all testing connections will use SCRAM
* Better error message for drivers w/o SCRAM support
* added configuration for shaded jar
remove any extraneous classes picked up in the shade plugin
.travis.yml | 3 +-
pgjdbc/pom.xml | 60 +++++++-
.../postgresql/core/v3/ConnectionFactoryImpl.java | 35 +++++
.../postgresql/jre8/sasl/ScramAuthenticator.java | 166 +++++++++++++++++++++
4 files changed, 262 insertions(+), 2 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index 0b11bc35..5de16385 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -8,7 +8,8 @@ before_script:
- test "x$XA" == 'x' || ./.travis/travis_configure_xa.sh
- test "x$REPLICATION" == 'x' || ./.travis/travis_configure_replication.sh
- ./.travis/travis_start_postgres.sh
- - psql -U postgres -c "create user test with password 'test';"
+ - test "x$PG_VERSION" != 'xHEAD' || psql -U postgres -c "set password_encryption='scram-sha-256'; create user test with password 'test';"
+ - test "x$PG_VERSION" = 'xHEAD' || psql -U postgres -c "create user test with password 'test';"
- test "x$REPLICATION" == 'x' || psql -U postgres -c "alter user test with replication;"
- psql -c 'create database test owner test;' -U postgres
- echo "MAVEN_OPTS='-Xmx1g -Dgpg.skip=true'" > ~/.mavenrc
diff --git a/pgjdbc/pom.xml b/pgjdbc/pom.xml
index ac72cef0..9f74a373 100644
--- a/pgjdbc/pom.xml
+++ b/pgjdbc/pom.xml
@@ -36,6 +36,14 @@
<checkstyle.version>7.8.2</checkstyle.version>
</properties>
+ <dependencies>
+ <dependency>
+ <groupId>com.ongres.scram</groupId>
+ <artifactId>client</artifactId>
+ <version>1.0.0-beta.2</version>
+ </dependency>
+ </dependencies>
+
<profiles>
<profile>
<id>translate</id>
@@ -249,6 +257,56 @@
<outputEncoding>UTF-8</outputEncoding>
</configuration>
</plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-shade-plugin</artifactId>
+ <version>3.1.0</version>
+ <configuration>
+ <minimizeJar>true</minimizeJar>
+ <filters>
+ <filter>
+ <artifact>com.ongres.scram:client</artifact>
+ <includes>
+ <include>*.*</include>
+ </includes>
+ </filter>
+ <filter>
+ <artifact>com.github.dblock.waffle:waffle-jna</artifact>
+ <excludes>
+ <exclude>**</exclude>
+ </excludes>
+ </filter>
+ <filter>
+ <artifact>org.slf4j:jcl-over-slf4j</artifact>
+ <excludes>
+ <exclude>**</exclude>
+ </excludes>
+ </filter>
+ <filter>
+ <artifact>*:*</artifact>
+ <excludes>
+ <exclude>com/sun/jna/**</exclude>
+ </excludes>
+ </filter>
+ </filters>
+ </configuration>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>shade</goal>
+ </goals>
+ <configuration>
+ <relocations>
+ <relocation>
+ <pattern>com.ongres</pattern>
+ <shadedPattern>org.postgresql.shaded.com.ongres</shadedPattern>
+ </relocation>
+ </relocations>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
</plugins>
<pluginManagement>
@@ -266,7 +324,7 @@
</dependencies>
<configuration>
<configLocation>src/main/checkstyle/checks.xml</configLocation>
- <suppressionsLocation>src/main/checkstyle/suppressions.xml</suppressionsLocation>
+ <suppressionsLocation>src/main/checkstyle/suppressions.xml</suppressionsLocation>
<violationSeverity>error</violationSeverity>
<failOnViolation>true</failOnViolation>
<failsOnError>true</failsOnError>
diff --git a/pgjdbc/src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java b/pgjdbc/src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java
index 3ccfc913..92da28d6 100644
--- a/pgjdbc/src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java
+++ b/pgjdbc/src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java
@@ -39,6 +39,7 @@ import java.util.Properties;
import java.util.TimeZone;
import java.util.logging.Level;
import java.util.logging.Logger;
+
import javax.net.SocketFactory;
/**
@@ -59,6 +60,9 @@ public class ConnectionFactoryImpl extends ConnectionFactory {
private static final int AUTH_REQ_GSS = 7;
private static final int AUTH_REQ_GSS_CONTINUE = 8;
private static final int AUTH_REQ_SSPI = 9;
+ private static final int AUTH_REQ_SASL = 10;
+ private static final int AUTH_REQ_SASL_CONTINUE = 11;
+ private static final int AUTH_REQ_SASL_FINAL = 12;
/**
* Marker exception; thrown when we want to fall back to using V2.
@@ -413,6 +417,11 @@ public class ConnectionFactoryImpl extends ConnectionFactory {
/* SSPI negotiation state, if used */
ISSPIClient sspiClient = null;
+ //#if mvn.project.property.postgresql.jdbc.spec >= "JDBC4.2"
+ /* SCRAM authentication state, if used */
+ org.postgresql.jre8.sasl.ScramAuthenticator scramAuthenticator = null;
+ //#endif
+
try {
authloop: while (true) {
int beresp = pgStream.receiveChar();
@@ -604,6 +613,32 @@ public class ConnectionFactoryImpl extends ConnectionFactory {
sspiClient.continueSSPI(l_msgLen - 8);
break;
+ case AUTH_REQ_SASL:
+ LOGGER.log(Level.FINEST, " <=BE AuthenticationSASL");
+
+ //#if mvn.project.property.postgresql.jdbc.spec >= "JDBC4.2"
+ scramAuthenticator = new org.postgresql.jre8.sasl.ScramAuthenticator(user, password, pgStream);
+ scramAuthenticator.processServerMechanismsAndInit();
+ scramAuthenticator.sendScramClientFirstMessage();
+ //#else
+ if (true) {
+ throw new PSQLException(GT.tr(
+ "SCRAM authentication is not supported by this driver. You need JDK >= 8 and pgjdbc >= 42.2.0 (not \".jre\" vesions)",
+ areq), PSQLState.CONNECTION_REJECTED);
+ }
+ //#endif
+ break;
+
+ //#if mvn.project.property.postgresql.jdbc.spec >= "JDBC4.2"
+ case AUTH_REQ_SASL_CONTINUE:
+ scramAuthenticator.processServerFirstMessage(l_msgLen - 4 - 4);
+ break;
+
+ case AUTH_REQ_SASL_FINAL:
+ scramAuthenticator.verifyServerSignature(l_msgLen - 4 - 4);
+ break;
+ //#endif
+
case AUTH_REQ_OK:
/* Cleanup after successful authentication */
LOGGER.log(Level.FINEST, " <=BE AuthenticationOk");
diff --git a/pgjdbc/src/main/java/org/postgresql/jre8/sasl/ScramAuthenticator.java b/pgjdbc/src/main/java/org/postgresql/jre8/sasl/ScramAuthenticator.java
new file mode 100644
index 00000000..87daa05a
--- /dev/null
+++ b/pgjdbc/src/main/java/org/postgresql/jre8/sasl/ScramAuthenticator.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (c) 2017, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+
+package org.postgresql.jre8.sasl;
+
+import org.postgresql.core.PGStream;
+import org.postgresql.util.GT;
+import org.postgresql.util.PSQLException;
+import org.postgresql.util.PSQLState;
+
+import com.ongres.scram.client.ScramClient;
+import com.ongres.scram.client.ScramSession;
+import com.ongres.scram.common.exception.ScramException;
+import com.ongres.scram.common.exception.ScramInvalidServerSignatureException;
+import com.ongres.scram.common.exception.ScramParseException;
+import com.ongres.scram.common.exception.ScramServerErrorException;
+import com.ongres.scram.common.stringprep.StringPreparations;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class ScramAuthenticator {
+ private static final Logger LOGGER = Logger.getLogger(ScramAuthenticator.class.getName());
+
+ private final String user;
+ private final String password;
+ private final PGStream pgStream;
+ private ScramClient scramClient;
+ private ScramSession scramSession;
+ private ScramSession.ServerFirstProcessor serverFirstProcessor;
+ private ScramSession.ClientFinalProcessor clientFinalProcessor;
+
+ @FunctionalInterface
+ private interface BodySender {
+ void sendBody(PGStream pgStream) throws IOException;
+ }
+
+ private void sendAuthenticationMessage(int bodyLength, BodySender bodySender)
+ throws IOException {
+ pgStream.sendChar('p');
+ pgStream.sendInteger4(Integer.BYTES + bodyLength);
+ bodySender.sendBody(pgStream);
+ pgStream.flush();
+ }
+
+ public ScramAuthenticator(String user, String password, PGStream pgStream) {
+ this.user = user;
+ this.password = password;
+ this.pgStream = pgStream;
+ }
+
+ public void processServerMechanismsAndInit() throws IOException, PSQLException {
+ List<String> mechanisms = new ArrayList<>();
+ do {
+ mechanisms.add(pgStream.receiveString());
+ } while (pgStream.peekChar() != 0);
+ int c = pgStream.receiveChar();
+ assert c == 0;
+ if (mechanisms.size() < 1) {
+ throw new PSQLException(
+ GT.tr("No SCRAM mechanism(s) advertised by the server"),
+ PSQLState.CONNECTION_REJECTED
+ );
+ }
+
+ try {
+ scramClient = ScramClient
+ .channelBinding(ScramClient.ChannelBinding.NO)
+ .stringPreparation(StringPreparations.NO_PREPARATION)
+ .selectMechanismBasedOnServerAdvertised(mechanisms.toArray(new String[]{}))
+ .setup();
+ } catch (IllegalArgumentException e) {
+ throw new PSQLException(
+ GT.tr("Invalid or unsupported by client SCRAM mechanisms", e),
+ PSQLState.CONNECTION_REJECTED
+ );
+ }
+ LOGGER.log(Level.FINEST, " Using SCRAM mechanism {0}", scramClient.getScramMechanism().getName());
+
+ scramSession =
+ scramClient.scramSession("*"); // Real username is ignored by server, uses startup one
+ }
+
+ public void sendScramClientFirstMessage() throws IOException {
+ String clientFirstMessage = scramSession.clientFirstMessage();
+ LOGGER.log(Level.FINEST, " FE=> SASLInitialResponse( {0} )", clientFirstMessage);
+
+ String scramMechanismName = scramClient.getScramMechanism().getName();
+ byte[] scramMechanismNameBytes = scramMechanismName.getBytes(StandardCharsets.UTF_8);
+ byte[] clientFirstMessageBytes = clientFirstMessage.getBytes(StandardCharsets.UTF_8);
+ sendAuthenticationMessage(
+ (scramMechanismNameBytes.length + 1) + 4 + clientFirstMessageBytes.length,
+ s -> {
+ s.send(scramMechanismNameBytes);
+ s.sendChar(0); // List terminated in '\0'
+ s.sendInteger4(clientFirstMessageBytes.length);
+ s.send(clientFirstMessageBytes);
+ }
+ );
+ }
+
+ public void processServerFirstMessage(int length) throws IOException, PSQLException {
+ String serverFirstMessage = pgStream.receiveString(length);
+ LOGGER.log(Level.FINEST, " <=BE AuthenticationSASLContinue( {0} )", serverFirstMessage);
+
+ try {
+ serverFirstProcessor = scramSession.receiveServerFirstMessage(serverFirstMessage);
+ } catch (ScramException e) {
+ throw new PSQLException(
+ GT.tr("Invalid server-first-message: {0}", serverFirstMessage),
+ PSQLState.CONNECTION_REJECTED,
+ e
+ );
+ }
+ LOGGER.log(Level.FINEST,
+ " <=BE AuthenticationSASLContinue(salt={0}, iterations={1})",
+ new Object[] { serverFirstProcessor.getSalt(), serverFirstProcessor.getIteration() }
+ );
+
+ clientFinalProcessor = serverFirstProcessor.clientFinalProcessor(password);
+
+ String clientFinalMessage = clientFinalProcessor.clientFinalMessage();
+ LOGGER.log(Level.FINEST, " FE=> SASLResponse( {0} )", clientFinalMessage);
+
+ byte[] clientFinalMessageBytes = clientFinalMessage.getBytes(StandardCharsets.UTF_8);
+ sendAuthenticationMessage(
+ clientFinalMessageBytes.length,
+ s -> s.send(clientFinalMessageBytes)
+ );
+ }
+
+ public void verifyServerSignature(int length) throws IOException, PSQLException {
+ String serverFinalMessage = pgStream.receiveString(length);
+ LOGGER.log(Level.FINEST, " <=BE AuthenticationSASLFinal( {0} )", serverFinalMessage);
+
+ try {
+ clientFinalProcessor.receiveServerFinalMessage(serverFinalMessage);
+ } catch (ScramParseException e) {
+ throw new PSQLException(
+ GT.tr("Invalid server-final-message: {0}", serverFinalMessage),
+ PSQLState.CONNECTION_REJECTED,
+ e
+ );
+ } catch (ScramServerErrorException e) {
+ throw new PSQLException(
+ GT.tr("SCRAM authentication failed, server returned error: {0}",
+ e.getError().getErrorMessage()),
+ PSQLState.CONNECTION_REJECTED,
+ e
+ );
+ } catch (ScramInvalidServerSignatureException e) {
+ throw new PSQLException(
+ GT.tr("Invalid server SCRAM signature"),
+ PSQLState.CONNECTION_REJECTED,
+ e
+ );
+ }
+ }
+}
[parent: 279fb435b392]