From a6a3af778c236bac4a85e347b489bb68940de99c Mon Sep 17 00:00:00 2001 From: dingxiaobo Date: Sat, 12 Oct 2019 18:53:33 +0800 Subject: [PATCH] support "caching_sha2_password" (mysql 8.0 default) --- .../shyiko/mysql/binlog/BinaryLogClient.java | 19 ++- .../binlog/network/ClientCapabilities.java | 1 + .../AuthenticateNativePasswordCommand.java | 2 +- .../command/AuthenticateSHA2Command.java | 142 ++++++++++++++++++ ... AuthenticateSecurityPasswordCommand.java} | 5 +- 5 files changed, 162 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/github/shyiko/mysql/binlog/network/protocol/command/AuthenticateSHA2Command.java rename src/main/java/com/github/shyiko/mysql/binlog/network/protocol/command/{AuthenticateCommand.java => AuthenticateSecurityPasswordCommand.java} (93%) diff --git a/src/main/java/com/github/shyiko/mysql/binlog/BinaryLogClient.java b/src/main/java/com/github/shyiko/mysql/binlog/BinaryLogClient.java index c395aa7b..8d7d4af8 100644 --- a/src/main/java/com/github/shyiko/mysql/binlog/BinaryLogClient.java +++ b/src/main/java/com/github/shyiko/mysql/binlog/BinaryLogClient.java @@ -45,8 +45,9 @@ import com.github.shyiko.mysql.binlog.network.protocol.Packet; import com.github.shyiko.mysql.binlog.network.protocol.PacketChannel; import com.github.shyiko.mysql.binlog.network.protocol.ResultSetRowPacket; -import com.github.shyiko.mysql.binlog.network.protocol.command.AuthenticateCommand; import com.github.shyiko.mysql.binlog.network.protocol.command.AuthenticateNativePasswordCommand; +import com.github.shyiko.mysql.binlog.network.protocol.command.AuthenticateSHA2Command; +import com.github.shyiko.mysql.binlog.network.protocol.command.AuthenticateSecurityPasswordCommand; import com.github.shyiko.mysql.binlog.network.protocol.command.Command; import com.github.shyiko.mysql.binlog.network.protocol.command.DumpBinaryLogCommand; import com.github.shyiko.mysql.binlog.network.protocol.command.DumpBinaryLogGtidCommand; @@ -715,9 +716,11 @@ private void authenticate(GreetingPacket greetingPacket) throws IOException { usingSSLSocket = true; } } - AuthenticateCommand authenticateCommand = new AuthenticateCommand(schema, username, password, - greetingPacket.getScramble()); - authenticateCommand.setCollation(collation); + + Command authenticateCommand = "caching_sha2_password".equals(greetingPacket.getPluginProvidedData()) ? + new AuthenticateSHA2Command(schema, username, password, greetingPacket.getScramble(), collation) : + new AuthenticateSecurityPasswordCommand(schema, username, password, greetingPacket.getScramble(), collation); + channel.write(authenticateCommand, packetNumber); byte[] authenticationResult = channel.read(); if (authenticationResult[0] != (byte) 0x00 /* ok */) { @@ -728,6 +731,14 @@ private void authenticate(GreetingPacket greetingPacket) throws IOException { errorPacket.getSqlState()); } else if (authenticationResult[0] == (byte) 0xFE) { switchAuthentication(authenticationResult, usingSSLSocket); + } else if (authenticationResult[0] == (byte) 0x01) { + if (authenticationResult.length >= 2 && (authenticationResult[1] == 3) || (authenticationResult[1] == 4)) { + // 8.0 auth ok + byte[] authenticationResultSha2 = channel.read(); + logger.log(Level.FINEST, "SHA2 auth result {0}", authenticationResultSha2); + } else { + throw new AuthenticationException("Unexpected authentication result (" + authenticationResult[0] + "&" + authenticationResult[1] + ")"); + } } else { throw new AuthenticationException("Unexpected authentication result (" + authenticationResult[0] + ")"); } diff --git a/src/main/java/com/github/shyiko/mysql/binlog/network/ClientCapabilities.java b/src/main/java/com/github/shyiko/mysql/binlog/network/ClientCapabilities.java index c744d5a9..d81e2e1a 100644 --- a/src/main/java/com/github/shyiko/mysql/binlog/network/ClientCapabilities.java +++ b/src/main/java/com/github/shyiko/mysql/binlog/network/ClientCapabilities.java @@ -42,6 +42,7 @@ public final class ClientCapabilities { public static final int MULTI_RESULTS = 1 << 17; /* enable/disable multi-results */ public static final int PS_MULTI_RESULTS = 1 << 18; /* multi-results in ps-protocol */ public static final int PLUGIN_AUTH = 1 << 19; /* client supports plugin authentication */ + public static final int PLUGIN_AUTH_LENENC_CLIENT_DATA = 1 << 21; public static final int SSL_VERIFY_SERVER_CERT = 1 << 30; public static final int REMEMBER_OPTIONS = 1 << 31; diff --git a/src/main/java/com/github/shyiko/mysql/binlog/network/protocol/command/AuthenticateNativePasswordCommand.java b/src/main/java/com/github/shyiko/mysql/binlog/network/protocol/command/AuthenticateNativePasswordCommand.java index f98eced0..25711af0 100644 --- a/src/main/java/com/github/shyiko/mysql/binlog/network/protocol/command/AuthenticateNativePasswordCommand.java +++ b/src/main/java/com/github/shyiko/mysql/binlog/network/protocol/command/AuthenticateNativePasswordCommand.java @@ -29,6 +29,6 @@ public AuthenticateNativePasswordCommand(String scramble, String password) { } @Override public byte[] toByteArray() throws IOException { - return AuthenticateCommand.passwordCompatibleWithMySQL411(password, scramble); + return AuthenticateSecurityPasswordCommand.passwordCompatibleWithMySQL411(password, scramble); } } diff --git a/src/main/java/com/github/shyiko/mysql/binlog/network/protocol/command/AuthenticateSHA2Command.java b/src/main/java/com/github/shyiko/mysql/binlog/network/protocol/command/AuthenticateSHA2Command.java new file mode 100644 index 00000000..c8afca7e --- /dev/null +++ b/src/main/java/com/github/shyiko/mysql/binlog/network/protocol/command/AuthenticateSHA2Command.java @@ -0,0 +1,142 @@ +/* + * Copyright 2018 dingxiaobo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.shyiko.mysql.binlog.network.protocol.command; + +import com.github.shyiko.mysql.binlog.io.ByteArrayOutputStream; +import com.github.shyiko.mysql.binlog.network.ClientCapabilities; + +import java.io.IOException; +import java.security.DigestException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * @author dingxiaobo + */ +public class AuthenticateSHA2Command implements Command { + + private String schema; + private String username; + private String password; + private String salt; + private int clientCapabilities; + private int collation; + + public AuthenticateSHA2Command(String schema, String username, String password, String salt, int collation) { + this.schema = schema; + this.username = username; + this.password = password; + this.salt = salt; + this.collation = collation; + } + + public void setClientCapabilities(int clientCapabilities) { + this.clientCapabilities = clientCapabilities; + } + + @Override + public byte[] toByteArray() throws IOException { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + int clientCapabilities = this.clientCapabilities; + if (clientCapabilities == 0) { + clientCapabilities |= ClientCapabilities.LONG_FLAG; + clientCapabilities |= ClientCapabilities.PROTOCOL_41; + clientCapabilities |= ClientCapabilities.SECURE_CONNECTION; + clientCapabilities |= ClientCapabilities.PLUGIN_AUTH; + clientCapabilities |= ClientCapabilities.PLUGIN_AUTH_LENENC_CLIENT_DATA; + + if (schema != null) { + clientCapabilities |= ClientCapabilities.CONNECT_WITH_DB; + } + } + buffer.writeInteger(clientCapabilities, 4); + buffer.writeInteger(0, 4); // maximum packet length + buffer.writeInteger(collation, 1); + for (int i = 0; i < 23; i++) { + buffer.write(0); + } + buffer.writeZeroTerminatedString(username); + byte[] passwordSHA1 = encodePassword(); + buffer.writeInteger(passwordSHA1.length, 1); + buffer.write(passwordSHA1); + if (schema != null) { + buffer.writeZeroTerminatedString(schema); + } + buffer.writeZeroTerminatedString("caching_sha2_password"); + + return buffer.toByteArray(); + } + + private byte[] encodePassword() { + if (password == null || "".equals(password)) { + return new byte[0]; + } + // caching_sha2_password + /* + * Server does it in 4 steps (see sql/auth/sha2_password_common.cc Generate_scramble::scramble method): + * + * SHA2(src) => digest_stage1 + * SHA2(digest_stage1) => digest_stage2 + * SHA2(digest_stage2, m_rnd) => scramble_stage1 + * XOR(digest_stage1, scramble_stage1) => scramble + */ + MessageDigest md; + try { + md = MessageDigest.getInstance("SHA-256"); + + int CACHING_SHA2_DIGEST_LENGTH = 32; + byte[] dig1 = new byte[CACHING_SHA2_DIGEST_LENGTH]; + byte[] dig2 = new byte[CACHING_SHA2_DIGEST_LENGTH]; + byte[] scramble1 = new byte[CACHING_SHA2_DIGEST_LENGTH]; + + // SHA2(src) => digest_stage1 + md.update(password.getBytes(), 0, password.getBytes().length); + md.digest(dig1, 0, CACHING_SHA2_DIGEST_LENGTH); + md.reset(); + + // SHA2(digest_stage1) => digest_stage2 + md.update(dig1, 0, dig1.length); + md.digest(dig2, 0, CACHING_SHA2_DIGEST_LENGTH); + md.reset(); + + // SHA2(digest_stage2, m_rnd) => scramble_stage1 + md.update(dig2, 0, dig1.length); + md.update(salt.getBytes(), 0, salt.getBytes().length); + md.digest(scramble1, 0, CACHING_SHA2_DIGEST_LENGTH); + + // XOR(digest_stage1, scramble_stage1) => scramble + byte[] mysqlScrambleBuff = new byte[CACHING_SHA2_DIGEST_LENGTH]; + xor(dig1, mysqlScrambleBuff, scramble1, CACHING_SHA2_DIGEST_LENGTH); + + return mysqlScrambleBuff; + } catch (NoSuchAlgorithmException ex) { + throw new RuntimeException(ex); + } catch (DigestException e) { + throw new RuntimeException(e); + } + } + + private void xor(byte[] from, byte[] to, byte[] scramble, int length) { + int pos = 0; + int scrambleLength = scramble.length; + + while (pos < length) { + to[pos] = (byte) (from[pos] ^ scramble[pos % scrambleLength]); + pos++; + } + } + +} diff --git a/src/main/java/com/github/shyiko/mysql/binlog/network/protocol/command/AuthenticateCommand.java b/src/main/java/com/github/shyiko/mysql/binlog/network/protocol/command/AuthenticateSecurityPasswordCommand.java similarity index 93% rename from src/main/java/com/github/shyiko/mysql/binlog/network/protocol/command/AuthenticateCommand.java rename to src/main/java/com/github/shyiko/mysql/binlog/network/protocol/command/AuthenticateSecurityPasswordCommand.java index a045fe24..d99d6317 100644 --- a/src/main/java/com/github/shyiko/mysql/binlog/network/protocol/command/AuthenticateCommand.java +++ b/src/main/java/com/github/shyiko/mysql/binlog/network/protocol/command/AuthenticateSecurityPasswordCommand.java @@ -25,7 +25,7 @@ /** * @author Stanley Shyiko */ -public class AuthenticateCommand implements Command { +public class AuthenticateSecurityPasswordCommand implements Command { private String schema; private String username; @@ -34,11 +34,12 @@ public class AuthenticateCommand implements Command { private int clientCapabilities; private int collation; - public AuthenticateCommand(String schema, String username, String password, String salt) { + public AuthenticateSecurityPasswordCommand(String schema, String username, String password, String salt, int collation) { this.schema = schema; this.username = username; this.password = password; this.salt = salt; + this.collation = collation; } public void setClientCapabilities(int clientCapabilities) {