From 81584654c190b303d67f63b760e5b259de616789 Mon Sep 17 00:00:00 2001
From: BenCodez <17074231+BenCodez@users.noreply.github.com>
Date: Sat, 26 Sep 2026 16:41:10 -0600
Subject: [PATCH] Add explicit PostgreSQL TLS verification mode
---
SimpleAPI/pom.xml | 2 +
.../simpleapi/core/sql/MysqlConfigView.java | 6 +-
.../sql/mysql/ConnectionManager.java | 45 +++++++++-
.../bencodez/simpleapi/sql/mysql/MySQL.java | 1 +
.../sql/mysql/PostgreSqlTlsMode.java | 33 ++++++++
.../sql/mysql/config/MysqlConfig.java | 10 +++
.../sql/mysql/config/MysqlConfigBungee.java | 2 +
.../sql/mysql/config/MysqlConfigSpigot.java | 2 +
.../sql/mysql/config/MysqlConfigVelocity.java | 3 +
.../sql/mysql/PostgreSqlTlsConfigTest.java | 53 ++++++++++++
.../sql/mysql/PostgreSqlTlsModeTest.java | 82 +++++++++++++++++++
docs/postgresql-tls.md | 25 ++++++
12 files changed, 259 insertions(+), 5 deletions(-)
create mode 100644 SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/PostgreSqlTlsMode.java
create mode 100644 SimpleAPI/src/test/java/com/bencodez/simpleapi/sql/mysql/PostgreSqlTlsConfigTest.java
create mode 100644 SimpleAPI/src/test/java/com/bencodez/simpleapi/sql/mysql/PostgreSqlTlsModeTest.java
create mode 100644 docs/postgresql-tls.md
diff --git a/SimpleAPI/pom.xml b/SimpleAPI/pom.xml
index 84123654..da0f62aa 100644
--- a/SimpleAPI/pom.xml
+++ b/SimpleAPI/pom.xml
@@ -89,6 +89,7 @@
com/bencodez/simpleapi/sql/mysql/AbstractSqlTable*.class
com/bencodez/simpleapi/sql/mysql/ConnectionManager*.class
com/bencodez/simpleapi/sql/mysql/DbType.class
+ com/bencodez/simpleapi/sql/mysql/PostgreSqlTlsMode.class
com/bencodez/simpleapi/sql/mysql/MySQL*.class
com/bencodez/simpleapi/sql/mysql/config/MysqlConfig.class
com/bencodez/simpleapi/sql/mysql/config/MysqlConfigView.class
@@ -117,6 +118,7 @@
com/bencodez/simpleapi/sql/mysql/AbstractSqlTable.java
com/bencodez/simpleapi/sql/mysql/ConnectionManager.java
com/bencodez/simpleapi/sql/mysql/DbType.java
+ com/bencodez/simpleapi/sql/mysql/PostgreSqlTlsMode.java
com/bencodez/simpleapi/sql/mysql/MySQL.java
com/bencodez/simpleapi/sql/mysql/config/MysqlConfig.java
com/bencodez/simpleapi/sql/mysql/config/MysqlConfigView.java
diff --git a/SimpleAPI/src/main/java/com/bencodez/simpleapi/core/sql/MysqlConfigView.java b/SimpleAPI/src/main/java/com/bencodez/simpleapi/core/sql/MysqlConfigView.java
index c807ea36..57f1b2ef 100644
--- a/SimpleAPI/src/main/java/com/bencodez/simpleapi/core/sql/MysqlConfigView.java
+++ b/SimpleAPI/src/main/java/com/bencodez/simpleapi/core/sql/MysqlConfigView.java
@@ -1,10 +1,11 @@
package com.bencodez.simpleapi.core.sql;
-import com.bencodez.simpleapi.sql.mysql.config.MysqlConfig;
-
import java.util.Objects;
+
import com.bencodez.simpleapi.file.config.ConfigView;
import com.bencodez.simpleapi.sql.mysql.DbType;
+import com.bencodez.simpleapi.sql.mysql.PostgreSqlTlsMode;
+import com.bencodez.simpleapi.sql.mysql.config.MysqlConfig;
/** Platform-neutral snapshot of the existing MySQL section keys and defaults. */
public class MysqlConfigView extends MysqlConfig {
@@ -31,6 +32,7 @@ public MysqlConfigView(ConfigView section) {
: section.getBoolean("UseMariaDB", false) ? DbType.MARIADB : DbType.MYSQL);
setDriver(section.getString("Driver", ""));
setUseSSL(section.getBoolean("UseSSL", false));
+ setPostgreSqlTlsMode(PostgreSqlTlsMode.fromString(section.getString("PostgreSqlTlsMode", "LEGACY")));
setPublicKeyRetrieval(section.getBoolean("PublicKeyRetrieval", false));
setUseMariaDB(section.getBoolean("UseMariaDB", false));
setLine(section.getString("Line", ""));
diff --git a/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/ConnectionManager.java b/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/ConnectionManager.java
index 92fd4afe..d1639056 100644
--- a/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/ConnectionManager.java
+++ b/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/ConnectionManager.java
@@ -1,7 +1,11 @@
package com.bencodez.simpleapi.sql.mysql;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.sql.SQLException;
+import java.util.Locale;
+import java.util.Objects;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
@@ -81,6 +85,13 @@ public class ConnectionManager {
@Setter
private boolean useSSL = false;
+ @Getter
+ private PostgreSqlTlsMode postgreSqlTlsMode = PostgreSqlTlsMode.LEGACY;
+
+ public void setPostgreSqlTlsMode(PostgreSqlTlsMode mode) {
+ postgreSqlTlsMode = Objects.requireNonNull(mode, "mode");
+ }
+
/**
* Legacy flag still supported; only used if dbType isn't explicitly set.
*/
@@ -207,7 +218,7 @@ private String resolveMariaDbWithFallback() throws ClassNotFoundException {
}
}
- private String buildJdbcUrl(String driverClassName) {
+ String buildJdbcUrl(String driverClassName) {
String extra = (str == null ? "" : str);
// Postgres
@@ -216,10 +227,17 @@ private String buildJdbcUrl(String driverClassName) {
// Defaults:
// - reWriteBatchedInserts improves batch perf
- // - sslmode if useSSL
+ // - absent explicit mode preserves the legacy UseSSL behavior
String defaults = "reWriteBatchedInserts=true";
- if (useSSL) {
+ if (postgreSqlTlsMode == PostgreSqlTlsMode.LEGACY && useSSL) {
defaults += "&sslmode=require";
+ } else if (postgreSqlTlsMode != PostgreSqlTlsMode.LEGACY) {
+ rejectConflictingPostgreSqlTlsOptions(extra);
+ defaults += "&sslmode=" + postgreSqlTlsMode.getJdbcValue();
+ if (postgreSqlTlsMode == PostgreSqlTlsMode.REQUIRE
+ || postgreSqlTlsMode == PostgreSqlTlsMode.VERIFY_FULL) {
+ defaults += "&gssEncMode=disable";
+ }
}
if (extra.isEmpty()) {
@@ -240,6 +258,9 @@ private String buildJdbcUrl(String driverClassName) {
}
// MySQL / MariaDB
+ if (postgreSqlTlsMode != PostgreSqlTlsMode.LEGACY) {
+ throw new IllegalArgumentException("PostgreSqlTlsMode applies only to PostgreSQL connections");
+ }
boolean maria = (dbType == DbType.MARIADB) || "org.mariadb.jdbc.Driver".equals(driverClassName);
String base = maria ? String.format("jdbc:mariadb://%s:%s/%s", host, port, database)
: String.format("jdbc:mysql://%s:%s/%s", host, port, database);
@@ -250,6 +271,24 @@ private String buildJdbcUrl(String driverClassName) {
+ extra;
}
+ private void rejectConflictingPostgreSqlTlsOptions(String extra) {
+ String options = extra.startsWith("?") || extra.startsWith("&") ? extra.substring(1) : extra;
+ for (String parameter : options.split("&", -1)) {
+ String rawKey = parameter.split("=", 2)[0];
+ String key;
+ try {
+ key = URLDecoder.decode(rawKey, StandardCharsets.UTF_8).trim().toLowerCase(Locale.ROOT);
+ } catch (IllegalArgumentException ex) {
+ throw new IllegalArgumentException("Invalid encoded parameter name in PostgreSQL Line", ex);
+ }
+ if (key.equals("sslmode") || key.equals("ssl") || key.equals("sslfactory")
+ || key.equals("sslhostnameverifier")
+ || (postgreSqlTlsMode != PostgreSqlTlsMode.DISABLE && key.equals("gssencmode"))) {
+ throw new IllegalArgumentException("PostgreSqlTlsMode conflicts with a TLS parameter in Line");
+ }
+ }
+ }
+
// --- Pool Configuration ---
public boolean open() {
diff --git a/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/MySQL.java b/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/MySQL.java
index 526756df..57753d45 100644
--- a/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/MySQL.java
+++ b/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/MySQL.java
@@ -54,6 +54,7 @@ public boolean connect(MysqlConfig config) {
} else {
connectionManager.setDbType(config.isUseMariaDB() ? DbType.MARIADB : DbType.MYSQL);
}
+ connectionManager.setPostgreSqlTlsMode(config.getPostgreSqlTlsMode());
String driver = config.getDriver();
if (driver != null && !driver.isEmpty()) {
diff --git a/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/PostgreSqlTlsMode.java b/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/PostgreSqlTlsMode.java
new file mode 100644
index 00000000..fa403e5c
--- /dev/null
+++ b/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/PostgreSqlTlsMode.java
@@ -0,0 +1,33 @@
+package com.bencodez.simpleapi.sql.mysql;
+
+import java.util.Locale;
+
+/** PostgreSQL JDBC TLS policy. LEGACY preserves the existing UseSSL setting. */
+public enum PostgreSqlTlsMode {
+ LEGACY(null),
+ DISABLE("disable"),
+ REQUIRE("require"),
+ VERIFY_FULL("verify-full");
+
+ private final String jdbcValue;
+
+ PostgreSqlTlsMode(String jdbcValue) {
+ this.jdbcValue = jdbcValue;
+ }
+
+ public String getJdbcValue() {
+ return jdbcValue;
+ }
+
+ public static PostgreSqlTlsMode fromString(String raw) {
+ if (raw == null || raw.trim().isEmpty()) {
+ return LEGACY;
+ }
+ String normalized = raw.trim().replace('-', '_').toUpperCase(Locale.ROOT);
+ try {
+ return valueOf(normalized);
+ } catch (IllegalArgumentException ex) {
+ throw new IllegalArgumentException("Invalid PostgreSqlTlsMode; expected LEGACY, DISABLE, REQUIRE, or VERIFY_FULL", ex);
+ }
+ }
+}
diff --git a/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/config/MysqlConfig.java b/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/config/MysqlConfig.java
index 02951415..b5e6098a 100644
--- a/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/config/MysqlConfig.java
+++ b/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/config/MysqlConfig.java
@@ -1,6 +1,9 @@
package com.bencodez.simpleapi.sql.mysql.config;
+import java.util.Objects;
+
import com.bencodez.simpleapi.sql.mysql.DbType;
+import com.bencodez.simpleapi.sql.mysql.PostgreSqlTlsMode;
import lombok.Getter;
import lombok.Setter;
@@ -82,6 +85,13 @@ public class MysqlConfig {
@Setter
private boolean useSSL;
+ @Getter
+ private PostgreSqlTlsMode postgreSqlTlsMode = PostgreSqlTlsMode.LEGACY;
+
+ public void setPostgreSqlTlsMode(PostgreSqlTlsMode mode) {
+ postgreSqlTlsMode = Objects.requireNonNull(mode, "mode");
+ }
+
@Getter
@Setter
private boolean publicKeyRetrieval;
diff --git a/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/config/MysqlConfigBungee.java b/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/config/MysqlConfigBungee.java
index e42c87c8..3b03b69d 100644
--- a/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/config/MysqlConfigBungee.java
+++ b/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/config/MysqlConfigBungee.java
@@ -1,6 +1,7 @@
package com.bencodez.simpleapi.sql.mysql.config;
import com.bencodez.simpleapi.sql.mysql.DbType;
+import com.bencodez.simpleapi.sql.mysql.PostgreSqlTlsMode;
import net.md_5.bungee.config.Configuration;
@@ -50,6 +51,7 @@ public MysqlConfigBungee(Configuration section) {
// --- Driver / Behavior Options ---
setUseSSL(section.getBoolean("UseSSL", false));
+ setPostgreSqlTlsMode(PostgreSqlTlsMode.fromString(section.getString("PostgreSqlTlsMode", "LEGACY")));
setPublicKeyRetrieval(section.getBoolean("PublicKeyRetrieval", false));
setUseMariaDB(section.getBoolean("UseMariaDB", false));
diff --git a/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/config/MysqlConfigSpigot.java b/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/config/MysqlConfigSpigot.java
index 54976805..d6241a4f 100644
--- a/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/config/MysqlConfigSpigot.java
+++ b/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/config/MysqlConfigSpigot.java
@@ -3,6 +3,7 @@
import org.bukkit.configuration.ConfigurationSection;
import com.bencodez.simpleapi.sql.mysql.DbType;
+import com.bencodez.simpleapi.sql.mysql.PostgreSqlTlsMode;
public class MysqlConfigSpigot extends MysqlConfig {
@@ -49,6 +50,7 @@ public MysqlConfigSpigot(ConfigurationSection section) {
// --- Driver / Behavior Options ---
setUseSSL(section.getBoolean("UseSSL", false));
+ setPostgreSqlTlsMode(PostgreSqlTlsMode.fromString(section.getString("PostgreSqlTlsMode", "LEGACY")));
setPublicKeyRetrieval(section.getBoolean("PublicKeyRetrieval", false));
setUseMariaDB(section.getBoolean("UseMariaDB", false));
diff --git a/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/config/MysqlConfigVelocity.java b/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/config/MysqlConfigVelocity.java
index e06ac3c1..f293ed48 100644
--- a/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/config/MysqlConfigVelocity.java
+++ b/SimpleAPI/src/main/java/com/bencodez/simpleapi/sql/mysql/config/MysqlConfigVelocity.java
@@ -4,6 +4,7 @@
import com.bencodez.simpleapi.file.velocity.VelocityYMLFile;
import com.bencodez.simpleapi.sql.mysql.DbType;
+import com.bencodez.simpleapi.sql.mysql.PostgreSqlTlsMode;
public class MysqlConfigVelocity extends MysqlConfig {
@@ -59,6 +60,8 @@ private void load(String prePath, VelocityYMLFile config) {
// --- Driver / Behavior Options ---
setUseSSL(config.getBoolean(node(config, prePath, "UseSSL"), false));
+ setPostgreSqlTlsMode(PostgreSqlTlsMode.fromString(
+ config.getString(node(config, prePath, "PostgreSqlTlsMode"), "LEGACY")));
setPublicKeyRetrieval(config.getBoolean(node(config, prePath, "PublicKeyRetrieval"), false));
setUseMariaDB(config.getBoolean(node(config, prePath, "UseMariaDB"), false));
diff --git a/SimpleAPI/src/test/java/com/bencodez/simpleapi/sql/mysql/PostgreSqlTlsConfigTest.java b/SimpleAPI/src/test/java/com/bencodez/simpleapi/sql/mysql/PostgreSqlTlsConfigTest.java
new file mode 100644
index 00000000..925566f7
--- /dev/null
+++ b/SimpleAPI/src/test/java/com/bencodez/simpleapi/sql/mysql/PostgreSqlTlsConfigTest.java
@@ -0,0 +1,53 @@
+package com.bencodez.simpleapi.sql.mysql;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.nio.file.Path;
+
+import org.bukkit.configuration.MemoryConfiguration;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import org.spongepowered.configurate.BasicConfigurationNode;
+
+import com.bencodez.simpleapi.file.config.configurate.ConfigurateConfigView;
+import com.bencodez.simpleapi.file.velocity.VelocityYMLFile;
+import com.bencodez.simpleapi.sql.mysql.config.MysqlConfigBungee;
+import com.bencodez.simpleapi.sql.mysql.config.MysqlConfigSpigot;
+import com.bencodez.simpleapi.sql.mysql.config.MysqlConfigVelocity;
+import com.bencodez.simpleapi.sql.mysql.config.MysqlConfigView;
+
+import net.md_5.bungee.config.Configuration;
+
+class PostgreSqlTlsConfigTest {
+ @TempDir Path tempDir;
+
+ @Test void allAdaptersReadExplicitModeAndPreserveLegacyDefault() {
+ MemoryConfiguration spigot = new MemoryConfiguration();
+ Configuration bungee = new Configuration();
+ var shared = BasicConfigurationNode.root();
+ VelocityYMLFile velocity = new VelocityYMLFile(tempDir.resolve("mysql.yml").toFile());
+ assertEquals(PostgreSqlTlsMode.LEGACY, new MysqlConfigSpigot(spigot).getPostgreSqlTlsMode());
+ assertEquals(PostgreSqlTlsMode.LEGACY, new MysqlConfigBungee(bungee).getPostgreSqlTlsMode());
+ assertEquals(PostgreSqlTlsMode.LEGACY,
+ new MysqlConfigView(new ConfigurateConfigView(shared)).getPostgreSqlTlsMode());
+ assertEquals(PostgreSqlTlsMode.LEGACY, new MysqlConfigVelocity(velocity).getPostgreSqlTlsMode());
+
+ spigot.set("PostgreSqlTlsMode", "verify-full");
+ bungee.set("PostgreSqlTlsMode", "verify-full");
+ shared.node("PostgreSqlTlsMode").raw("verify-full");
+ velocity.getNode("MySQL", "PostgreSqlTlsMode").raw("verify-full");
+ assertEquals(PostgreSqlTlsMode.VERIFY_FULL, new MysqlConfigSpigot(spigot).getPostgreSqlTlsMode());
+ assertEquals(PostgreSqlTlsMode.VERIFY_FULL, new MysqlConfigBungee(bungee).getPostgreSqlTlsMode());
+ assertEquals(PostgreSqlTlsMode.VERIFY_FULL,
+ new MysqlConfigView(new ConfigurateConfigView(shared)).getPostgreSqlTlsMode());
+ assertEquals(PostgreSqlTlsMode.VERIFY_FULL,
+ new MysqlConfigVelocity("MySQL", velocity).getPostgreSqlTlsMode());
+ }
+
+ @Test void invalidModeFailsAtConfigLoad() {
+ MemoryConfiguration spigot = new MemoryConfiguration();
+ spigot.set("PostgreSqlTlsMode", "anything");
+ assertThrows(IllegalArgumentException.class, () -> new MysqlConfigSpigot(spigot));
+ }
+}
diff --git a/SimpleAPI/src/test/java/com/bencodez/simpleapi/sql/mysql/PostgreSqlTlsModeTest.java b/SimpleAPI/src/test/java/com/bencodez/simpleapi/sql/mysql/PostgreSqlTlsModeTest.java
new file mode 100644
index 00000000..ae7c119e
--- /dev/null
+++ b/SimpleAPI/src/test/java/com/bencodez/simpleapi/sql/mysql/PostgreSqlTlsModeTest.java
@@ -0,0 +1,82 @@
+package com.bencodez.simpleapi.sql.mysql;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+class PostgreSqlTlsModeTest {
+ private ConnectionManager postgres() {
+ ConnectionManager manager = new ConnectionManager("db.example.test", "5432", "user", "password", "votes");
+ manager.setDbType(DbType.POSTGRESQL);
+ return manager;
+ }
+
+ @Test void legacyModePreservesBothUseSslSettingsAndRawLine() {
+ ConnectionManager manager = postgres();
+ assertEquals("jdbc:postgresql://db.example.test:5432/votes?reWriteBatchedInserts=true",
+ manager.buildJdbcUrl("org.postgresql.Driver"));
+ manager.setUseSSL(true);
+ assertEquals("jdbc:postgresql://db.example.test:5432/votes?reWriteBatchedInserts=true&sslmode=require",
+ manager.buildJdbcUrl("org.postgresql.Driver"));
+ manager.setStr("?sslmode=disable");
+ assertEquals("jdbc:postgresql://db.example.test:5432/votes?sslmode=disable&reWriteBatchedInserts=true&sslmode=require",
+ manager.buildJdbcUrl("org.postgresql.Driver"));
+ }
+
+ @Test void explicitModeControlsPostgresTlsRegardlessOfUseSsl() {
+ ConnectionManager manager = postgres();
+ manager.setPostgreSqlTlsMode(PostgreSqlTlsMode.VERIFY_FULL);
+ manager.setStr("?sslrootcert=/etc/db/ca.pem&applicationName=votes");
+ String verified = "jdbc:postgresql://db.example.test:5432/votes?sslrootcert=/etc/db/ca.pem"
+ + "&applicationName=votes&reWriteBatchedInserts=true&sslmode=verify-full&gssEncMode=disable";
+ assertEquals(verified, manager.buildJdbcUrl("org.postgresql.Driver"));
+ manager.setUseSSL(true);
+ assertEquals(verified, manager.buildJdbcUrl("org.postgresql.Driver"));
+ manager.setPostgreSqlTlsMode(PostgreSqlTlsMode.REQUIRE);
+ manager.setStr("&applicationName=votes");
+ assertEquals("jdbc:postgresql://db.example.test:5432/votes?reWriteBatchedInserts=true&sslmode=require&gssEncMode=disable&applicationName=votes",
+ manager.buildJdbcUrl("org.postgresql.Driver"));
+ manager.setPostgreSqlTlsMode(PostgreSqlTlsMode.DISABLE);
+ manager.setStr("");
+ assertEquals("jdbc:postgresql://db.example.test:5432/votes?reWriteBatchedInserts=true&sslmode=disable",
+ manager.buildJdbcUrl("org.postgresql.Driver"));
+ }
+
+ @Test void explicitModeRejectsConflictingRawLineOptions() {
+ ConnectionManager manager = postgres();
+ manager.setPostgreSqlTlsMode(PostgreSqlTlsMode.VERIFY_FULL);
+ for (String line : new String[] {"?sslmode=disable", "&SSLMODE=require", "ssl%6dode=disable",
+ "?applicationName=votes&ssl=false", "&sslfactory=com.example.TrustAll",
+ "sslhostnameverifier=com.example.TrustAll", "gssEncMode=prefer"}) {
+ manager.setStr(line);
+ assertThrows(IllegalArgumentException.class, () -> manager.buildJdbcUrl("org.postgresql.Driver"), line);
+ }
+ manager.setStr("?ssl%zz=disable");
+ assertThrows(IllegalArgumentException.class, () -> manager.buildJdbcUrl("org.postgresql.Driver"));
+ }
+
+ @Test void mysqlAndMariaDbKeepLegacyUrlsAndRejectPostgresModes() {
+ ConnectionManager manager = postgres();
+ manager.setDbType(DbType.MYSQL);
+ String mysqlUrl = manager.buildJdbcUrl("com.mysql.cj.jdbc.Driver");
+ assertEquals("jdbc:mysql://db.example.test:5432/votes?useSSL=false&allowMultiQueries=true"
+ + "&rewriteBatchedStatements=true&useDynamicCharsetInfo=false&allowPublicKeyRetrieval=false"
+ + "&tcpKeepAlive=true&connectTimeout=10000&socketTimeout=30000&serverTimezone=UTC", mysqlUrl);
+ manager.setPostgreSqlTlsMode(PostgreSqlTlsMode.VERIFY_FULL);
+ assertThrows(IllegalArgumentException.class, () -> manager.buildJdbcUrl("com.mysql.cj.jdbc.Driver"));
+ manager.setDbType(DbType.MARIADB);
+ assertThrows(IllegalArgumentException.class, () -> manager.buildJdbcUrl("org.mariadb.jdbc.Driver"));
+ manager.setPostgreSqlTlsMode(PostgreSqlTlsMode.LEGACY);
+ assertEquals(mysqlUrl.replace("jdbc:mysql:", "jdbc:mariadb:"),
+ manager.buildJdbcUrl("org.mariadb.jdbc.Driver"));
+ }
+
+ @Test void parsesModeNamesAndRejectsUnknownValues() {
+ assertEquals(PostgreSqlTlsMode.LEGACY, PostgreSqlTlsMode.fromString(null));
+ assertEquals(PostgreSqlTlsMode.LEGACY, PostgreSqlTlsMode.fromString(" "));
+ assertEquals(PostgreSqlTlsMode.VERIFY_FULL, PostgreSqlTlsMode.fromString(" verify-full "));
+ assertEquals(PostgreSqlTlsMode.VERIFY_FULL, PostgreSqlTlsMode.fromString("verify_full"));
+ assertThrows(IllegalArgumentException.class, () -> PostgreSqlTlsMode.fromString("verify-ca"));
+ }
+}
diff --git a/docs/postgresql-tls.md b/docs/postgresql-tls.md
new file mode 100644
index 00000000..d83e4e60
--- /dev/null
+++ b/docs/postgresql-tls.md
@@ -0,0 +1,25 @@
+# PostgreSQL TLS configuration
+
+Set `DbType: POSTGRESQL` and choose `PostgreSqlTlsMode` in the SQL configuration section. The same key works with the Bukkit, Bungee, Velocity, and platform-neutral configuration adapters.
+
+| `PostgreSqlTlsMode` | PostgreSQL JDBC setting | Behavior |
+| --- | --- | --- |
+| omitted or `LEGACY` | Existing `UseSSL` behavior | `UseSSL: true` sets `sslmode=require`, which encrypts without checking server identity. `UseSSL: false` leaves `sslmode` unset, so the driver's default applies. |
+| `VERIFY_FULL` | `sslmode=verify-full`, `gssEncMode=disable` | Encrypts and verifies the server certificate and hostname. |
+| `REQUIRE` | `sslmode=require`, `gssEncMode=disable` | Explicit TLS encryption-only compatibility mode; no server identity verification. |
+| `DISABLE` | `sslmode=disable` | Explicitly disables PostgreSQL TLS. |
+
+To migrate an existing PostgreSQL connection to identity verification:
+
+```yaml
+DbType: POSTGRESQL
+Host: db.example.com
+PostgreSqlTlsMode: VERIFY_FULL
+Line: sslrootcert=/path/to/ca.pem
+```
+
+Use a hostname covered by the server certificate and provide a trusted CA certificate as needed. `VERIFY_FULL` fails the connection if verification fails. An explicit mode takes precedence over `UseSSL`. Existing configurations retain their current behavior until a mode is selected. See the [pgJDBC connection parameters](https://jdbc.postgresql.org/documentation/use/#connection-parameters) for certificate and hostname requirements.
+
+When a mode other than `LEGACY` is selected, `Line` cannot set `sslmode`, `ssl`, `sslfactory`, or `sslhostnameverifier`, including differently cased or encoded parameter names. `REQUIRE` and `VERIFY_FULL` also reject `gssEncMode` in `Line` and disable GSS encryption negotiation, so the selected TLS policy is used. Other options such as `sslrootcert` remain available. `LEGACY` keeps existing `Line` handling for compatibility.
+
+`PostgreSqlTlsMode` applies only to PostgreSQL. MySQL and MariaDB connections continue to use their existing `UseSSL` behavior; setting a non-legacy PostgreSQL mode for those drivers is a configuration error. Their `UseSSL` flag alone does not promise hostname verification.