diff --git a/src/main/java/org/mariadb/jdbc/client/impl/StandardClient.java b/src/main/java/org/mariadb/jdbc/client/impl/StandardClient.java index 4b54b85a6..16f54d767 100644 --- a/src/main/java/org/mariadb/jdbc/client/impl/StandardClient.java +++ b/src/main/java/org/mariadb/jdbc/client/impl/StandardClient.java @@ -55,10 +55,7 @@ import org.mariadb.jdbc.message.ClientMessage; import org.mariadb.jdbc.message.client.*; import org.mariadb.jdbc.message.server.*; -import org.mariadb.jdbc.plugin.AuthenticationPlugin; -import org.mariadb.jdbc.plugin.Credential; -import org.mariadb.jdbc.plugin.CredentialPlugin; -import org.mariadb.jdbc.plugin.TlsSocketPlugin; +import org.mariadb.jdbc.plugin.*; import org.mariadb.jdbc.plugin.authentication.AuthenticationPluginLoader; import org.mariadb.jdbc.plugin.authentication.addon.ClearPasswordPlugin; import org.mariadb.jdbc.plugin.authentication.standard.NativePasswordPlugin; @@ -220,8 +217,8 @@ public StandardClient( .encode(writer, context); authPlugin = "mysql_clear_password".equals(authenticationPluginType) - ? new ClearPasswordPlugin() - : new NativePasswordPlugin(); + ? new ClearPasswordPlugin(credential.getPassword()) + : new NativePasswordPlugin(credential.getPassword(), handshake.getSeed()); writer.flush(); authenticationHandler(credential, hostAddress); @@ -284,19 +281,21 @@ public void authenticationHandler(Credential credential, HostAddress hostAddress // https://mariadb.com/kb/en/library/connection/#authentication-switch-request // ************************************************************************************* AuthSwitchPacket authSwitchPacket = AuthSwitchPacket.decode(buf); - authPlugin = AuthenticationPluginLoader.get(authSwitchPacket.getPlugin(), conf); - if (authPlugin.requireSsl() && !context.hasClientCapability(SSL)) { + AuthenticationPluginFactory authPluginFactory = + AuthenticationPluginLoader.get(authSwitchPacket.getPlugin(), conf); + if (authPluginFactory.requireSsl() && !context.hasClientCapability(SSL)) { throw context .getExceptionFactory() .create( "Cannot use authentication plugin " - + authPlugin.type() + + authPluginFactory.type() + " if SSL is not enabled.", "08000"); } + authPlugin = + authPluginFactory.initialize( + credential.getPassword(), authSwitchPacket.getSeed(), conf, hostAddress); - authPlugin.initialize( - credential.getPassword(), authSwitchPacket.getSeed(), conf, hostAddress); buf = authPlugin.process(writer, reader, context); break; @@ -338,7 +337,7 @@ public void authenticationHandler(Credential credential, HostAddress hostAddress throw context .getExceptionFactory() .create( - "Self signed certificates. Either set sslMode=trust, set a password or" + "Self signed certificates. Either set sslMode=trust, use password with a MitM-Proof authentication plugin or" + " provide server certificate to client", "08000"); } diff --git a/src/main/java/org/mariadb/jdbc/plugin/AuthenticationPlugin.java b/src/main/java/org/mariadb/jdbc/plugin/AuthenticationPlugin.java index 12f0cf018..d0eed76ea 100644 --- a/src/main/java/org/mariadb/jdbc/plugin/AuthenticationPlugin.java +++ b/src/main/java/org/mariadb/jdbc/plugin/AuthenticationPlugin.java @@ -5,8 +5,6 @@ import java.io.IOException; import java.sql.SQLException; -import org.mariadb.jdbc.Configuration; -import org.mariadb.jdbc.HostAddress; import org.mariadb.jdbc.client.Context; import org.mariadb.jdbc.client.ReadableByteBuf; import org.mariadb.jdbc.client.socket.Reader; @@ -15,24 +13,6 @@ /** Authentication plugin descriptor */ public interface AuthenticationPlugin { - /** - * Authentication plugin type. - * - * @return authentication plugin type. ex: mysql_native_password - */ - String type(); - - /** - * Plugin initialization. - * - * @param authenticationData authentication data (password/token) - * @param seed server provided seed - * @param conf Connection options - * @param hostAddress host address - */ - void initialize( - String authenticationData, byte[] seed, Configuration conf, HostAddress hostAddress); - /** * Process plugin authentication. * @@ -64,13 +44,4 @@ default boolean isMitMProof() { default byte[] hash(Credential credential) { return null; } - - /** - * Authentication plugin required SSL to be used - * - * @return true if SSL is required - */ - default boolean requireSsl() { - return false; - } } diff --git a/src/main/java/org/mariadb/jdbc/plugin/AuthenticationPluginFactory.java b/src/main/java/org/mariadb/jdbc/plugin/AuthenticationPluginFactory.java new file mode 100644 index 000000000..163cbaf0b --- /dev/null +++ b/src/main/java/org/mariadb/jdbc/plugin/AuthenticationPluginFactory.java @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (c) 2012-2014 Monty Program Ab +// Copyright (c) 2015-2024 MariaDB Corporation Ab +package org.mariadb.jdbc.plugin; + +import org.mariadb.jdbc.Configuration; +import org.mariadb.jdbc.HostAddress; + +/** Authentication plugin descriptor */ +public interface AuthenticationPluginFactory { + + /** + * Authentication plugin type. + * + * @return authentication plugin type. ex: mysql_native_password + */ + String type(); + + /** + * Plugin initialization. + * + * @param authenticationData authentication data (password/token) + * @param seed server provided seed + * @param conf Connection options + * @param hostAddress host address + */ + AuthenticationPlugin initialize( + String authenticationData, byte[] seed, Configuration conf, HostAddress hostAddress); + + /** + * Authentication plugin required SSL to be used + * + * @return true if SSL is required + */ + default boolean requireSsl() { + return false; + } +} diff --git a/src/main/java/org/mariadb/jdbc/plugin/authentication/AuthenticationPluginLoader.java b/src/main/java/org/mariadb/jdbc/plugin/authentication/AuthenticationPluginLoader.java index 9c3124981..566fa5c47 100644 --- a/src/main/java/org/mariadb/jdbc/plugin/authentication/AuthenticationPluginLoader.java +++ b/src/main/java/org/mariadb/jdbc/plugin/authentication/AuthenticationPluginLoader.java @@ -8,7 +8,7 @@ import java.util.ServiceLoader; import org.mariadb.jdbc.Configuration; import org.mariadb.jdbc.Driver; -import org.mariadb.jdbc.plugin.AuthenticationPlugin; +import org.mariadb.jdbc.plugin.AuthenticationPluginFactory; /** permit loading authentication plugins */ public final class AuthenticationPluginLoader { @@ -22,14 +22,15 @@ public final class AuthenticationPluginLoader { * @return Authentication plugin corresponding to type * @throws SQLException if no authentication plugin in classpath have indicated type */ - public static AuthenticationPlugin get(String type, Configuration conf) throws SQLException { + public static AuthenticationPluginFactory get(String type, Configuration conf) + throws SQLException { - ServiceLoader loader = - ServiceLoader.load(AuthenticationPlugin.class, Driver.class.getClassLoader()); + ServiceLoader loader = + ServiceLoader.load(AuthenticationPluginFactory.class, Driver.class.getClassLoader()); String[] authList = (conf.restrictedAuth() != null) ? conf.restrictedAuth().split(",") : null; - for (AuthenticationPlugin implClass : loader) { + for (AuthenticationPluginFactory implClass : loader) { if (type.equals(implClass.type())) { if (authList == null || Arrays.stream(authList).anyMatch(type::contains)) { return implClass; diff --git a/src/main/java/org/mariadb/jdbc/plugin/authentication/addon/ClearPasswordPlugin.java b/src/main/java/org/mariadb/jdbc/plugin/authentication/addon/ClearPasswordPlugin.java index 3f5833299..adb985e8c 100644 --- a/src/main/java/org/mariadb/jdbc/plugin/authentication/addon/ClearPasswordPlugin.java +++ b/src/main/java/org/mariadb/jdbc/plugin/authentication/addon/ClearPasswordPlugin.java @@ -5,8 +5,6 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; -import org.mariadb.jdbc.Configuration; -import org.mariadb.jdbc.HostAddress; import org.mariadb.jdbc.client.Context; import org.mariadb.jdbc.client.ReadableByteBuf; import org.mariadb.jdbc.client.socket.Reader; @@ -15,31 +13,15 @@ /** Clear password plugin. */ public class ClearPasswordPlugin implements AuthenticationPlugin { - /** plugin name */ - public static final String TYPE = "mysql_clear_password"; private String authenticationData; - @Override - public String type() { - return TYPE; - } - - @Override - public boolean requireSsl() { - return true; - } - /** * Initialization. * * @param authenticationData authentication data (password/token) - * @param seed server provided seed - * @param conf Connection string options - * @param hostAddress host information */ - public void initialize( - String authenticationData, byte[] seed, Configuration conf, HostAddress hostAddress) { + public ClearPasswordPlugin(String authenticationData) { this.authenticationData = authenticationData; } diff --git a/src/main/java/org/mariadb/jdbc/plugin/authentication/addon/ClearPasswordPluginFactory.java b/src/main/java/org/mariadb/jdbc/plugin/authentication/addon/ClearPasswordPluginFactory.java new file mode 100644 index 000000000..565968714 --- /dev/null +++ b/src/main/java/org/mariadb/jdbc/plugin/authentication/addon/ClearPasswordPluginFactory.java @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (c) 2012-2014 Monty Program Ab +// Copyright (c) 2015-2024 MariaDB Corporation Ab +package org.mariadb.jdbc.plugin.authentication.addon; + +import org.mariadb.jdbc.Configuration; +import org.mariadb.jdbc.HostAddress; +import org.mariadb.jdbc.plugin.AuthenticationPlugin; +import org.mariadb.jdbc.plugin.AuthenticationPluginFactory; + +/** Clear password plugin. */ +public class ClearPasswordPluginFactory implements AuthenticationPluginFactory { + + @Override + public String type() { + return "mysql_clear_password"; + } + + @Override + public boolean requireSsl() { + return true; + } + + /** + * Initialization. + * + * @param authenticationData authentication data (password/token) + * @param seed server provided seed + * @param conf Connection string options + * @param hostAddress host information + */ + public AuthenticationPlugin initialize( + String authenticationData, byte[] seed, Configuration conf, HostAddress hostAddress) { + return new ClearPasswordPlugin(authenticationData); + } +} diff --git a/src/main/java/org/mariadb/jdbc/plugin/authentication/addon/SendGssApiAuthPacket.java b/src/main/java/org/mariadb/jdbc/plugin/authentication/addon/SendGssApiAuthPacket.java index 04d89af4d..8e736d95d 100644 --- a/src/main/java/org/mariadb/jdbc/plugin/authentication/addon/SendGssApiAuthPacket.java +++ b/src/main/java/org/mariadb/jdbc/plugin/authentication/addon/SendGssApiAuthPacket.java @@ -6,7 +6,6 @@ import java.io.IOException; import java.sql.SQLException; import org.mariadb.jdbc.Configuration; -import org.mariadb.jdbc.HostAddress; import org.mariadb.jdbc.client.Context; import org.mariadb.jdbc.client.ReadableByteBuf; import org.mariadb.jdbc.client.impl.StandardReadableByteBuf; @@ -35,21 +34,13 @@ public class SendGssApiAuthPacket implements AuthenticationPlugin { private byte[] seed; private String optionServicePrincipalName; - @Override - public String type() { - return "auth_gssapi_client"; - } - /** * Initialization. * - * @param authenticationData authentication data (password/token) * @param seed server provided seed * @param conf Connection string options - * @param hostAddress host information */ - public void initialize( - String authenticationData, byte[] seed, Configuration conf, HostAddress hostAddress) { + public SendGssApiAuthPacket(byte[] seed, Configuration conf) { this.seed = seed; this.optionServicePrincipalName = conf.servicePrincipalName(); } diff --git a/src/main/java/org/mariadb/jdbc/plugin/authentication/addon/SendGssApiAuthPacketFactory.java b/src/main/java/org/mariadb/jdbc/plugin/authentication/addon/SendGssApiAuthPacketFactory.java new file mode 100644 index 000000000..6839efe63 --- /dev/null +++ b/src/main/java/org/mariadb/jdbc/plugin/authentication/addon/SendGssApiAuthPacketFactory.java @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (c) 2012-2014 Monty Program Ab +// Copyright (c) 2015-2024 MariaDB Corporation Ab +package org.mariadb.jdbc.plugin.authentication.addon; + +import org.mariadb.jdbc.Configuration; +import org.mariadb.jdbc.HostAddress; +import org.mariadb.jdbc.plugin.AuthenticationPlugin; +import org.mariadb.jdbc.plugin.AuthenticationPluginFactory; + +/** GSSAPI plugin */ +public class SendGssApiAuthPacketFactory implements AuthenticationPluginFactory { + + @Override + public String type() { + return "auth_gssapi_client"; + } + + /** + * Initialization. + * + * @param authenticationData authentication data (password/token) + * @param seed server provided seed + * @param conf Connection string options + * @param hostAddress host information + */ + public AuthenticationPlugin initialize( + String authenticationData, byte[] seed, Configuration conf, HostAddress hostAddress) { + return new SendGssApiAuthPacket(seed, conf); + } +} diff --git a/src/main/java/org/mariadb/jdbc/plugin/authentication/standard/CachingSha2PasswordPlugin.java b/src/main/java/org/mariadb/jdbc/plugin/authentication/standard/CachingSha2PasswordPlugin.java index 1a8878c29..be3ec5528 100644 --- a/src/main/java/org/mariadb/jdbc/plugin/authentication/standard/CachingSha2PasswordPlugin.java +++ b/src/main/java/org/mariadb/jdbc/plugin/authentication/standard/CachingSha2PasswordPlugin.java @@ -38,6 +38,14 @@ public class CachingSha2PasswordPlugin implements AuthenticationPlugin { private Configuration conf; private HostAddress hostAddress; + public CachingSha2PasswordPlugin( + String authenticationData, byte[] seed, Configuration conf, HostAddress hostAddress) { + this.authenticationData = authenticationData; + this.seed = seed; + this.conf = conf; + this.hostAddress = hostAddress; + } + /** * Send an SHA-2 encrypted password. encryption XOR(SHA256(password), SHA256(seed, * SHA256(SHA256(password)))) @@ -150,11 +158,6 @@ public static byte[] encrypt(PublicKey publicKey, String password, byte[] seed) } } - @Override - public String type() { - return TYPE; - } - /** * Initialized data. * diff --git a/src/main/java/org/mariadb/jdbc/plugin/authentication/standard/CachingSha2PasswordPluginFactory.java b/src/main/java/org/mariadb/jdbc/plugin/authentication/standard/CachingSha2PasswordPluginFactory.java new file mode 100644 index 000000000..c11d0f851 --- /dev/null +++ b/src/main/java/org/mariadb/jdbc/plugin/authentication/standard/CachingSha2PasswordPluginFactory.java @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (c) 2012-2014 Monty Program Ab +// Copyright (c) 2015-2024 MariaDB Corporation Ab +package org.mariadb.jdbc.plugin.authentication.standard; + +import org.mariadb.jdbc.Configuration; +import org.mariadb.jdbc.HostAddress; +import org.mariadb.jdbc.plugin.AuthenticationPlugin; +import org.mariadb.jdbc.plugin.AuthenticationPluginFactory; + +/** Mysql caching sha2 password plugin */ +public class CachingSha2PasswordPluginFactory implements AuthenticationPluginFactory { + + @Override + public String type() { + return "caching_sha2_password"; + } + + public AuthenticationPlugin initialize( + String authenticationData, byte[] seed, Configuration conf, HostAddress hostAddress) { + return new CachingSha2PasswordPlugin(authenticationData, seed, conf, hostAddress); + } +} diff --git a/src/main/java/org/mariadb/jdbc/plugin/authentication/standard/Ed25519PasswordPlugin.java b/src/main/java/org/mariadb/jdbc/plugin/authentication/standard/Ed25519PasswordPlugin.java index efe3a9207..3f655d7f2 100644 --- a/src/main/java/org/mariadb/jdbc/plugin/authentication/standard/Ed25519PasswordPlugin.java +++ b/src/main/java/org/mariadb/jdbc/plugin/authentication/standard/Ed25519PasswordPlugin.java @@ -84,20 +84,7 @@ private static byte[] ed25519SignWithPassword(final String password, final byte[ } } - @Override - public String type() { - return "client_ed25519"; - } - - /** - * Initialization. - * - * @param authenticationData authentication data (password/token) - * @param seed server provided seed - * @param conf Connection string options - * @param hostAddress host information - */ - public void initialize( + public Ed25519PasswordPlugin( String authenticationData, byte[] seed, Configuration conf, HostAddress hostAddress) { this.seed = seed; this.authenticationData = authenticationData; @@ -146,7 +133,7 @@ public byte[] hash(Credential credential) { final byte[] sm = new byte[64 + mlen]; byte[] az = hash.digest(bytePwd); - az[0] &= 248; + az[0] &= (byte) 248; az[31] &= 63; az[31] |= 64; diff --git a/src/main/java/org/mariadb/jdbc/plugin/authentication/standard/Ed25519PasswordPluginFactory.java b/src/main/java/org/mariadb/jdbc/plugin/authentication/standard/Ed25519PasswordPluginFactory.java new file mode 100644 index 000000000..2933aae84 --- /dev/null +++ b/src/main/java/org/mariadb/jdbc/plugin/authentication/standard/Ed25519PasswordPluginFactory.java @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (c) 2012-2014 Monty Program Ab +// Copyright (c) 2015-2024 MariaDB Corporation Ab +package org.mariadb.jdbc.plugin.authentication.standard; + +import org.mariadb.jdbc.Configuration; +import org.mariadb.jdbc.HostAddress; +import org.mariadb.jdbc.plugin.AuthenticationPlugin; +import org.mariadb.jdbc.plugin.AuthenticationPluginFactory; + +/** ED25519 password plugin */ +public class Ed25519PasswordPluginFactory implements AuthenticationPluginFactory { + + @Override + public String type() { + return "client_ed25519"; + } + + public AuthenticationPlugin initialize( + String authenticationData, byte[] seed, Configuration conf, HostAddress hostAddress) { + return new Ed25519PasswordPlugin(authenticationData, seed, conf, hostAddress); + } +} diff --git a/src/main/java/org/mariadb/jdbc/plugin/authentication/standard/NativePasswordPlugin.java b/src/main/java/org/mariadb/jdbc/plugin/authentication/standard/NativePasswordPlugin.java index c690808f8..fe027518a 100644 --- a/src/main/java/org/mariadb/jdbc/plugin/authentication/standard/NativePasswordPlugin.java +++ b/src/main/java/org/mariadb/jdbc/plugin/authentication/standard/NativePasswordPlugin.java @@ -7,8 +7,6 @@ import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import org.mariadb.jdbc.Configuration; -import org.mariadb.jdbc.HostAddress; import org.mariadb.jdbc.client.Context; import org.mariadb.jdbc.client.ReadableByteBuf; import org.mariadb.jdbc.client.socket.Reader; @@ -20,9 +18,6 @@ /** Native password implementation */ public class NativePasswordPlugin implements AuthenticationPlugin { - /** plugin name */ - public static final String TYPE = "mysql_native_password"; - private String authenticationData; private byte[] seed; @@ -73,21 +68,13 @@ public static byte[] encryptPassword(final CharSequence password, final byte[] s } } - @Override - public String type() { - return TYPE; - } - /** * Initialized data. * * @param authenticationData authentication data (password/token) * @param seed server provided seed - * @param conf Connection string options - * @param hostAddress host information */ - public void initialize( - String authenticationData, byte[] seed, Configuration conf, HostAddress hostAddress) { + public NativePasswordPlugin(String authenticationData, byte[] seed) { this.seed = seed; this.authenticationData = authenticationData; } diff --git a/src/main/java/org/mariadb/jdbc/plugin/authentication/standard/NativePasswordPluginFactory.java b/src/main/java/org/mariadb/jdbc/plugin/authentication/standard/NativePasswordPluginFactory.java new file mode 100644 index 000000000..9c66d0d6f --- /dev/null +++ b/src/main/java/org/mariadb/jdbc/plugin/authentication/standard/NativePasswordPluginFactory.java @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (c) 2012-2014 Monty Program Ab +// Copyright (c) 2015-2024 MariaDB Corporation Ab +package org.mariadb.jdbc.plugin.authentication.standard; + +import org.mariadb.jdbc.Configuration; +import org.mariadb.jdbc.HostAddress; +import org.mariadb.jdbc.plugin.AuthenticationPlugin; +import org.mariadb.jdbc.plugin.AuthenticationPluginFactory; + +/** Native password implementation */ +public class NativePasswordPluginFactory implements AuthenticationPluginFactory { + + @Override + public String type() { + return "mysql_native_password"; + } + + public AuthenticationPlugin initialize( + String authenticationData, byte[] seed, Configuration conf, HostAddress hostAddress) { + return new NativePasswordPlugin(authenticationData, seed); + } +} diff --git a/src/main/java/org/mariadb/jdbc/plugin/authentication/standard/ParsecPasswordPlugin.java b/src/main/java/org/mariadb/jdbc/plugin/authentication/standard/ParsecPasswordPlugin.java index 12ebc76e3..4b1eaa824 100644 --- a/src/main/java/org/mariadb/jdbc/plugin/authentication/standard/ParsecPasswordPlugin.java +++ b/src/main/java/org/mariadb/jdbc/plugin/authentication/standard/ParsecPasswordPlugin.java @@ -5,46 +5,41 @@ import java.io.IOException; import java.security.*; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.*; import java.sql.SQLException; +import java.util.Arrays; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; -import org.mariadb.jdbc.Configuration; -import org.mariadb.jdbc.HostAddress; import org.mariadb.jdbc.client.Context; import org.mariadb.jdbc.client.ReadableByteBuf; import org.mariadb.jdbc.client.socket.Reader; import org.mariadb.jdbc.client.socket.Writer; import org.mariadb.jdbc.plugin.AuthenticationPlugin; +import org.mariadb.jdbc.plugin.Credential; /** Parsec password plugin */ public class ParsecPasswordPlugin implements AuthenticationPlugin { private static byte[] pkcs8Ed25519header = - new byte[] { - 0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x04, 0x22, 0x04, - 0x20 - }; + new byte[] { + 0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x04, 0x22, 0x04, + 0x20 + }; private String authenticationData; private byte[] seed; + private byte[] salt; + private int iterations = 100; - @Override - public String type() { - return "parsec"; - } + private byte[] hash; /** * Initialization. * * @param authenticationData authentication data (password/token) * @param seed server provided seed - * @param conf Connection string options - * @param hostAddress host information */ - public void initialize( - String authenticationData, byte[] seed, Configuration conf, HostAddress hostAddress) { + public ParsecPasswordPlugin(String authenticationData, byte[] seed) { this.seed = seed; this.authenticationData = authenticationData; } @@ -60,7 +55,7 @@ public void initialize( * @throws IOException if socket error */ public ReadableByteBuf process(Writer out, Reader in, Context context) - throws SQLException, IOException { + throws SQLException, IOException { // request ext-salt out.writeEmptyPacket(); @@ -68,7 +63,6 @@ public ReadableByteBuf process(Writer out, Reader in, Context context) ReadableByteBuf buf = in.readReusablePacket(); byte firstByte = 0; - int iterations = 100; if (buf.readableBytes() > 2) { firstByte = buf.readByte(); @@ -80,10 +74,10 @@ public ReadableByteBuf process(Writer out, Reader in, Context context) throw new SQLException("Wrong parsec authentication format", "S1009"); } - byte[] salt = new byte[buf.readableBytes()]; + salt = new byte[buf.readableBytes()]; buf.readBytes(salt); char[] password = - this.authenticationData == null ? new char[0] : this.authenticationData.toCharArray(); + this.authenticationData == null ? new char[0] : this.authenticationData.toCharArray(); KeyFactory ed25519KeyFactory; Signature ed25519Signature; @@ -99,9 +93,9 @@ public ReadableByteBuf process(Writer out, Reader in, Context context) ed25519Signature = Signature.getInstance("Ed25519", "BC"); } catch (NoSuchAlgorithmException | NoSuchProviderException ee) { throw new SQLException( - "Parsec authentication not available. Either use Java 15+ or add BouncyCastle" - + " dependency", - e); + "Parsec authentication not available. Either use Java 15+ or add BouncyCastle" + + " dependency", + e); } } @@ -113,9 +107,24 @@ public ReadableByteBuf process(Writer out, Reader in, Context context) // create a PKCS8 ED25519 private key with raw secret PKCS8EncodedKeySpec keySpec = - new PKCS8EncodedKeySpec(combineArray(pkcs8Ed25519header, derivedKey)); + new PKCS8EncodedKeySpec(combineArray(pkcs8Ed25519header, derivedKey)); PrivateKey privateKey = ed25519KeyFactory.generatePrivate(keySpec); + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("Ed25519"); + keyPairGenerator.initialize( + new NamedParameterSpec("Ed25519"), new StaticSecureRandom(derivedKey)); + byte[] spki = + keyPairGenerator + .generateKeyPair() + .getPublic() + .getEncoded(); // public key in SPKI format; the last 32 bytes are the raw public key + byte[] rawPublicKey = + Arrays.copyOfRange(spki, spki.length - 32, spki.length); // 32 bytes raw public key + + hash = + combineArray( + combineArray(new byte[] {(byte) 'P', (byte) iterations}, salt), rawPublicKey); + // generate client nonce byte[] clientScramble = new byte[32]; SecureRandom.getInstanceStrong().nextBytes(clientScramble); @@ -134,14 +143,41 @@ public ReadableByteBuf process(Writer out, Reader in, Context context) return in.readReusablePacket(); } catch (NoSuchAlgorithmException - | InvalidKeySpecException - | InvalidKeyException - | SignatureException e) { + | InvalidKeySpecException + | InvalidKeyException + | InvalidAlgorithmParameterException + | SignatureException e) { // not expected throw new SQLException("Error during parsec authentication", e); } } + private class StaticSecureRandom extends SecureRandom { + private byte[] privateKey; + + public StaticSecureRandom(byte[] privateKey) { + this.privateKey = privateKey; + } + + public void nextBytes(byte[] bytes) { + System.arraycopy(privateKey, 0, bytes, 0, privateKey.length); + } + } + + public boolean isMitMProof() { + return true; + } + + /** + * Return Hash + * + * @param credential Credential + * @return hash + */ + public byte[] hash(Credential credential) { + return hash; + } + private byte[] combineArray(byte[] arr1, byte[] arr2) { byte[] combined = new byte[arr1.length + arr2.length]; System.arraycopy(arr1, 0, combined, 0, arr1.length); diff --git a/src/main/java/org/mariadb/jdbc/plugin/authentication/standard/ParsecPasswordPluginFactory.java b/src/main/java/org/mariadb/jdbc/plugin/authentication/standard/ParsecPasswordPluginFactory.java new file mode 100644 index 000000000..7c668e09f --- /dev/null +++ b/src/main/java/org/mariadb/jdbc/plugin/authentication/standard/ParsecPasswordPluginFactory.java @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (c) 2012-2014 Monty Program Ab +// Copyright (c) 2015-2024 MariaDB Corporation Ab +package org.mariadb.jdbc.plugin.authentication.standard; + +import org.mariadb.jdbc.Configuration; +import org.mariadb.jdbc.HostAddress; +import org.mariadb.jdbc.plugin.AuthenticationPlugin; +import org.mariadb.jdbc.plugin.AuthenticationPluginFactory; + +/** Parsec password plugin */ +public class ParsecPasswordPluginFactory implements AuthenticationPluginFactory { + + @Override + public String type() { + return "parsec"; + } + + /** + * Initialization. + * + * @param authenticationData authentication data (password/token) + * @param seed server provided seed + * @param conf Connection string options + * @param hostAddress host information + */ + public AuthenticationPlugin initialize( + String authenticationData, byte[] seed, Configuration conf, HostAddress hostAddress) { + return new ParsecPasswordPlugin(authenticationData, seed); + } +} diff --git a/src/main/java/org/mariadb/jdbc/plugin/authentication/standard/SendPamAuthPacket.java b/src/main/java/org/mariadb/jdbc/plugin/authentication/standard/SendPamAuthPacket.java index bab26c9bb..17959e63e 100644 --- a/src/main/java/org/mariadb/jdbc/plugin/authentication/standard/SendPamAuthPacket.java +++ b/src/main/java/org/mariadb/jdbc/plugin/authentication/standard/SendPamAuthPacket.java @@ -7,7 +7,6 @@ import java.nio.charset.StandardCharsets; import java.sql.SQLException; import org.mariadb.jdbc.Configuration; -import org.mariadb.jdbc.HostAddress; import org.mariadb.jdbc.client.Context; import org.mariadb.jdbc.client.ReadableByteBuf; import org.mariadb.jdbc.client.socket.Reader; @@ -24,11 +23,6 @@ public class SendPamAuthPacket implements AuthenticationPlugin { private Configuration conf; private int counter = 0; - @Override - public String type() { - return "dialog"; - } - /** * Initialization. * @@ -37,8 +31,7 @@ public String type() { * @param conf Connection string options * @param hostAddress host information */ - public void initialize( - String authenticationData, byte[] seed, Configuration conf, HostAddress hostAddress) { + public SendPamAuthPacket(String authenticationData, Configuration conf) { this.authenticationData = authenticationData; this.conf = conf; } diff --git a/src/main/java/org/mariadb/jdbc/plugin/authentication/standard/SendPamAuthPacketFactory.java b/src/main/java/org/mariadb/jdbc/plugin/authentication/standard/SendPamAuthPacketFactory.java new file mode 100644 index 000000000..777db81bf --- /dev/null +++ b/src/main/java/org/mariadb/jdbc/plugin/authentication/standard/SendPamAuthPacketFactory.java @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (c) 2012-2014 Monty Program Ab +// Copyright (c) 2015-2024 MariaDB Corporation Ab +package org.mariadb.jdbc.plugin.authentication.standard; + +import org.mariadb.jdbc.Configuration; +import org.mariadb.jdbc.HostAddress; +import org.mariadb.jdbc.plugin.AuthenticationPlugin; +import org.mariadb.jdbc.plugin.AuthenticationPluginFactory; + +/** + * PAM (dialog) authentication plugin. This is a multi-step exchange password. If more than one + * step, passwordX (password2, password3, ...) options must be set. + */ +public class SendPamAuthPacketFactory implements AuthenticationPluginFactory { + + @Override + public String type() { + return "dialog"; + } + + /** + * Initialization. + * + * @param authenticationData authentication data (password/token) + * @param seed server provided seed + * @param conf Connection string options + * @param hostAddress host information + */ + public AuthenticationPlugin initialize( + String authenticationData, byte[] seed, Configuration conf, HostAddress hostAddress) { + return new SendPamAuthPacket(authenticationData, conf); + } +} diff --git a/src/main/java/org/mariadb/jdbc/plugin/authentication/standard/ed25519/math/GroupElement.java b/src/main/java/org/mariadb/jdbc/plugin/authentication/standard/ed25519/math/GroupElement.java index 1df002360..a38dac4b4 100644 --- a/src/main/java/org/mariadb/jdbc/plugin/authentication/standard/ed25519/math/GroupElement.java +++ b/src/main/java/org/mariadb/jdbc/plugin/authentication/standard/ed25519/math/GroupElement.java @@ -333,12 +333,12 @@ static byte[] toRadix16(final byte[] a) { /* e[63] is between 0 and 7 */ int carry = 0; for (i = 0; i < 63; i++) { - e[i] += carry; + e[i] += (byte) carry; carry = e[i] + 8; carry >>= 4; - e[i] -= carry << 4; + e[i] -= (byte)(carry << 4); } - e[63] += carry; + e[63] += (byte) carry; /* each e[i] is between -8 and 7 */ return e; } @@ -370,10 +370,10 @@ static byte[] slide(final byte[] a) { // Accumulate bits if possible if (r[i + b] != 0) { if (r[i] + (r[i + b] << b) <= 15) { - r[i] += r[i + b] << b; + r[i] += (byte) (r[i + b] << b); r[i + b] = 0; } else if (r[i] - (r[i + b] << b) >= -15) { - r[i] -= r[i + b] << b; + r[i] -= (byte) (r[i + b] << b); for (int k = i + b; k < 256; ++k) { if (r[k] == 0) { r[k] = 1; diff --git a/src/main/java/org/mariadb/jdbc/plugin/codec/FloatArrayCodec.java b/src/main/java/org/mariadb/jdbc/plugin/codec/FloatArrayCodec.java index c1f3a9740..5788f2b8d 100644 --- a/src/main/java/org/mariadb/jdbc/plugin/codec/FloatArrayCodec.java +++ b/src/main/java/org/mariadb/jdbc/plugin/codec/FloatArrayCodec.java @@ -22,7 +22,7 @@ public class FloatArrayCodec implements Codec { /** default instance */ public static final FloatArrayCodec INSTANCE = new FloatArrayCodec(); - private static Class floatArrayClass = Array.newInstance(float.class, 0).getClass(); + private static Class floatArrayClass = Array.newInstance(float.class, 0).getClass(); private static final EnumSet COMPATIBLE_TYPES = EnumSet.of( diff --git a/src/main/java/org/mariadb/jdbc/plugin/codec/FloatObjectArrayCodec.java b/src/main/java/org/mariadb/jdbc/plugin/codec/FloatObjectArrayCodec.java index bad603aaa..355cc2418 100644 --- a/src/main/java/org/mariadb/jdbc/plugin/codec/FloatObjectArrayCodec.java +++ b/src/main/java/org/mariadb/jdbc/plugin/codec/FloatObjectArrayCodec.java @@ -23,7 +23,7 @@ public class FloatObjectArrayCodec implements Codec { /** default instance */ public static final FloatObjectArrayCodec INSTANCE = new FloatObjectArrayCodec(); - private static Class floatArrayClass = Array.newInstance(Float.class, 0).getClass(); + private static Class floatArrayClass = Array.newInstance(Float.class, 0).getClass(); private static final EnumSet COMPATIBLE_TYPES = EnumSet.of( DataType.BLOB, diff --git a/src/main/java/org/mariadb/jdbc/plugin/tls/main/DefaultTlsSocketPlugin.java b/src/main/java/org/mariadb/jdbc/plugin/tls/main/DefaultTlsSocketPlugin.java index 0b9924142..23dfdc577 100644 --- a/src/main/java/org/mariadb/jdbc/plugin/tls/main/DefaultTlsSocketPlugin.java +++ b/src/main/java/org/mariadb/jdbc/plugin/tls/main/DefaultTlsSocketPlugin.java @@ -118,7 +118,7 @@ public TrustManager[] getTrustManager( if (conf.trustStore() != null) { InputStream inStream; try { - inStream = new URL(conf.trustStore()).openStream(); + inStream = loadFromUrl(conf.trustStore()); } catch (IOException ioexception) { try { inStream = new FileInputStream(conf.trustStore()); diff --git a/src/main/java/org/mariadb/jdbc/util/ThreadUtils.java b/src/main/java/org/mariadb/jdbc/util/ThreadUtils.java index 18f86ebdb..9201b227f 100644 --- a/src/main/java/org/mariadb/jdbc/util/ThreadUtils.java +++ b/src/main/java/org/mariadb/jdbc/util/ThreadUtils.java @@ -7,12 +7,14 @@ import javax.security.auth.Subject; public class ThreadUtils { + @SuppressWarnings("deprecation") public static long getId(Thread thread) { // must be return thread.threadId() for java 19+, // but since we support java 8, cannot be removed for now return thread.getId(); } + @SuppressWarnings("deprecation") public static void callAs( final Subject subject, final Callable> action) throws Exception { diff --git a/src/main/java9/module-info.java b/src/main/java9/module-info.java index 81464b600..5f51b8977 100644 --- a/src/main/java9/module-info.java +++ b/src/main/java9/module-info.java @@ -35,18 +35,19 @@ uses java.sql.Driver; uses org.mariadb.jdbc.plugin.CredentialPlugin; uses org.mariadb.jdbc.plugin.Codec; - uses org.mariadb.jdbc.plugin.AuthenticationPlugin; + uses org.mariadb.jdbc.plugin.AuthenticationPluginFactory; uses org.mariadb.jdbc.plugin.TlsSocketPlugin; provides java.sql.Driver with org.mariadb.jdbc.Driver; - provides org.mariadb.jdbc.plugin.AuthenticationPlugin with - org.mariadb.jdbc.plugin.authentication.addon.ClearPasswordPlugin, - org.mariadb.jdbc.plugin.authentication.addon.SendGssApiAuthPacket, - org.mariadb.jdbc.plugin.authentication.standard.Ed25519PasswordPlugin, - org.mariadb.jdbc.plugin.authentication.standard.NativePasswordPlugin, - org.mariadb.jdbc.plugin.authentication.standard.SendPamAuthPacket, - org.mariadb.jdbc.plugin.authentication.standard.CachingSha2PasswordPlugin; + provides org.mariadb.jdbc.plugin.AuthenticationPluginFactory with + org.mariadb.jdbc.plugin.authentication.addon.ClearPasswordPluginFactory, + org.mariadb.jdbc.plugin.authentication.addon.SendGssApiAuthPacketFactory, + org.mariadb.jdbc.plugin.authentication.standard.Ed25519PasswordPluginFactory, + org.mariadb.jdbc.plugin.authentication.standard.NativePasswordPluginFactory, + org.mariadb.jdbc.plugin.authentication.standard.SendPamAuthPacketFactory, + org.mariadb.jdbc.plugin.authentication.standard.CachingSha2PasswordPluginFactory, + org.mariadb.jdbc.plugin.authentication.standard.ParsecPasswordPluginFactory; provides org.mariadb.jdbc.plugin.Codec with BigDecimalCodec, BigIntegerCodec, @@ -59,7 +60,9 @@ DateCodec, DoubleCodec, DurationCodec, + FloatArrayCodec, FloatCodec, + FloatObjectArrayCodec, GeometryCollectionCodec, IntCodec, LineStringCodec, diff --git a/src/main/resources/META-INF/services/org.mariadb.jdbc.plugin.AuthenticationPlugin b/src/main/resources/META-INF/services/org.mariadb.jdbc.plugin.AuthenticationPluginFactory similarity index 85% rename from src/main/resources/META-INF/services/org.mariadb.jdbc.plugin.AuthenticationPlugin rename to src/main/resources/META-INF/services/org.mariadb.jdbc.plugin.AuthenticationPluginFactory index ed428a88b..66fa6415c 100644 --- a/src/main/resources/META-INF/services/org.mariadb.jdbc.plugin.AuthenticationPlugin +++ b/src/main/resources/META-INF/services/org.mariadb.jdbc.plugin.AuthenticationPluginFactory @@ -1,7 +1,7 @@ -org.mariadb.jdbc.plugin.authentication.addon.ClearPasswordPlugin -org.mariadb.jdbc.plugin.authentication.addon.SendGssApiAuthPacket -org.mariadb.jdbc.plugin.authentication.standard.Ed25519PasswordPlugin -org.mariadb.jdbc.plugin.authentication.standard.NativePasswordPlugin -org.mariadb.jdbc.plugin.authentication.standard.SendPamAuthPacket -org.mariadb.jdbc.plugin.authentication.standard.CachingSha2PasswordPlugin -org.mariadb.jdbc.plugin.authentication.standard.ParsecPasswordPlugin \ No newline at end of file +org.mariadb.jdbc.plugin.authentication.addon.ClearPasswordPluginFactory +org.mariadb.jdbc.plugin.authentication.addon.SendGssApiAuthPacketFactory +org.mariadb.jdbc.plugin.authentication.standard.Ed25519PasswordPluginFactory +org.mariadb.jdbc.plugin.authentication.standard.NativePasswordPluginFactory +org.mariadb.jdbc.plugin.authentication.standard.SendPamAuthPacketFactory +org.mariadb.jdbc.plugin.authentication.standard.CachingSha2PasswordPluginFactory +org.mariadb.jdbc.plugin.authentication.standard.ParsecPasswordPluginFactory \ No newline at end of file diff --git a/src/test/java/org/mariadb/jdbc/integration/SslTest.java b/src/test/java/org/mariadb/jdbc/integration/SslTest.java index 2b7c87774..1e6e6171d 100644 --- a/src/test/java/org/mariadb/jdbc/integration/SslTest.java +++ b/src/test/java/org/mariadb/jdbc/integration/SslTest.java @@ -239,6 +239,42 @@ public void mandatoryEphemeralSsled25519() throws SQLException { stmt.execute("drop user if exists verificationEd25519AuthPlugin@'%'"); } + @Test + public void mandatoryEphemeralSslParsec() throws SQLException { + Assumptions.assumeTrue(!"maxscale".equals(System.getenv("srv"))); + Assumptions.assumeTrue(isMariaDBServer() && minVersion(11, 6, 1)); + + Statement stmt = sharedConn.createStatement(); + try { + stmt.execute("INSTALL SONAME 'auth_parsec'"); + } catch (SQLException sqle) { + Assumptions.assumeTrue(false, "server doesn't have auth_parsec plugin, cancelling test"); + } + try { + stmt.execute("drop user if exists verificationParsecAuthPlugin@'%'"); + } catch (SQLException e) { + // eat + } + stmt.execute( + "CREATE USER IF NOT EXISTS verificationParsecAuthPlugin@'%' IDENTIFIED " + + "VIA parsec USING PASSWORD('MySup8%rPassw@ord') REQUIRE SSL"); + stmt.execute( + "GRANT SELECT ON " + sharedConn.getCatalog() + ".* TO verificationParsecAuthPlugin@'%' "); + try (Connection con = + createCon( + "user=verificationParsecAuthPlugin&password=MySup8%rPassw@ord&sslMode=verify-ca", + sslPort)) { + assertNotNull(getSslVersion(con)); + } + try (Connection con = + createCon( + "user=verificationParsecAuthPlugin&password=MySup8%rPassw@ord&sslMode=trust", + sslPort)) { + assertNotNull(getSslVersion(con)); + } + stmt.execute("drop user if exists verificationParsecAuthPlugin@'%'"); + } + @Test void ensureUnixSocketSsl() throws SQLException { Assumptions.assumeTrue( diff --git a/src/test/java/org/mariadb/jdbc/unit/plugin/AuthenticationPluginLoaderTest.java b/src/test/java/org/mariadb/jdbc/unit/plugin/AuthenticationPluginLoaderTest.java index fd5f6485d..2b0448c7b 100644 --- a/src/test/java/org/mariadb/jdbc/unit/plugin/AuthenticationPluginLoaderTest.java +++ b/src/test/java/org/mariadb/jdbc/unit/plugin/AuthenticationPluginLoaderTest.java @@ -9,18 +9,18 @@ import org.junit.jupiter.api.Test; import org.mariadb.jdbc.Configuration; import org.mariadb.jdbc.integration.Common; -import org.mariadb.jdbc.plugin.AuthenticationPlugin; +import org.mariadb.jdbc.plugin.AuthenticationPluginFactory; import org.mariadb.jdbc.plugin.authentication.AuthenticationPluginLoader; -import org.mariadb.jdbc.plugin.authentication.standard.NativePasswordPlugin; +import org.mariadb.jdbc.plugin.authentication.standard.NativePasswordPluginFactory; public class AuthenticationPluginLoaderTest extends Common { @Test public void authenticationPluginLoaderTest() throws SQLException { Configuration conf = Configuration.parse("jdbc:mariadb://localhost/"); - AuthenticationPlugin authenticationPlugin = + AuthenticationPluginFactory authenticationPluginFactory = AuthenticationPluginLoader.get("mysql_native_password", conf); - assertTrue(authenticationPlugin instanceof NativePasswordPlugin); + assertTrue(authenticationPluginFactory instanceof NativePasswordPluginFactory); Common.assertThrowsContains( SQLException.class, () -> AuthenticationPluginLoader.get("UNKNOWN", conf), diff --git a/src/test/java/org/mariadb/jdbc/unit/util/VersionFactoryTest.java b/src/test/java/org/mariadb/jdbc/unit/util/VersionFactoryTest.java index 68b512cb7..80255a56b 100644 --- a/src/test/java/org/mariadb/jdbc/unit/util/VersionFactoryTest.java +++ b/src/test/java/org/mariadb/jdbc/unit/util/VersionFactoryTest.java @@ -1,20 +1,20 @@ -// SPDX-License-Identifier: LGPL-2.1-or-later -// Copyright (c) 2012-2014 Monty Program Ab -// Copyright (c) 2015-2024 MariaDB Corporation Ab -package org.mariadb.jdbc.unit.util; - -import static org.junit.jupiter.api.Assertions.assertNotNull; - -import org.junit.jupiter.api.Test; -import org.mariadb.jdbc.util.Version; -import org.mariadb.jdbc.util.VersionFactory; - -class VersionFactoryTest { - - @Test - public void testGetInstance() { - Version actual = VersionFactory.getInstance(); - - assertNotNull(actual); - } -} +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (c) 2012-2014 Monty Program Ab +// Copyright (c) 2015-2024 MariaDB Corporation Ab +package org.mariadb.jdbc.unit.util; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.Test; +import org.mariadb.jdbc.util.Version; +import org.mariadb.jdbc.util.VersionFactory; + +class VersionFactoryTest { + + @Test + public void testGetInstance() { + Version actual = VersionFactory.getInstance(); + + assertNotNull(actual); + } +}