Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

增加对mysql的caching_sha2_password认证插件fullauth流程支持 #4767

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

/**
* 基于mysql socket协议的链接实现
*
*
* @author jianghang 2013-2-18 下午09:22:30
* @version 1.0.1
*/
Expand Down Expand Up @@ -220,55 +220,45 @@ private void negotiate(SocketChannel channel) throws IOException {
packet.fromBytes(body);
authData = packet.authData;
pluginName = packet.authName;
logger.info("auth switch pluginName is {}.", pluginName);
}

boolean isSha2Password = false;
byte[] encryptedPassword = null;
if ("mysql_clear_password".equals(pluginName)) {
encryptedPassword = getPassword().getBytes();
header = authSwitchAfterAuth(encryptedPassword, header);
body = PacketManager.readBytes(channel, header.getPacketBodyLength(), timeout);
} else if ("mysql_native_password".equals(pluginName)) {
try {
encryptedPassword = MySQLPasswordEncrypter.scramble411(getPassword().getBytes(), authData);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("can't encrypt password that will be sent to MySQL server.", e);
}
header = authSwitchAfterAuth(encryptedPassword, header);
body = PacketManager.readBytes(channel, header.getPacketBodyLength(), timeout);
} else if ("caching_sha2_password".equals(pluginName)) {
isSha2Password = true;
byte[] scramble = authData;
try {
encryptedPassword = MySQLPasswordEncrypter.scrambleCachingSha2(getPassword().getBytes(), authData);
encryptedPassword = MySQLPasswordEncrypter.scrambleCachingSha2(getPassword().getBytes(), scramble);
} catch (DigestException e) {
throw new RuntimeException("can't encrypt password that will be sent to MySQL server.", e);
}
}
assert encryptedPassword != null;
AuthSwitchResponsePacket responsePacket = new AuthSwitchResponsePacket();
responsePacket.authData = encryptedPassword;
byte[] auth = responsePacket.toBytes();

h = new HeaderPacket();
h.setPacketBodyLength(auth.length);
h.setPacketSequenceNumber((byte) (header.getPacketSequenceNumber() + 1));
PacketManager.writePkg(channel, h.toBytes(), auth);
logger.info("auth switch response packet is sent out.");

header = null;
header = PacketManager.readHeader(channel, 4);
body = null;
body = PacketManager.readBytes(channel, header.getPacketBodyLength(), timeout);
assert body != null;
if (isSha2Password) {
header = authSwitchAfterAuth(encryptedPassword, header);
body = PacketManager.readBytes(channel, header.getPacketBodyLength(), timeout);
assert body != null;
if (body[0] == 0x01 && body[1] == 0x04) {
// password auth failed
throw new IOException("caching_sha2_password Auth failed");
header = cachingSha2PasswordFullAuth(channel, header, getPassword().getBytes(), scramble);
body = PacketManager.readBytes(channel, header.getPacketBodyLength(), timeout);
} else {
header = PacketManager.readHeader(channel, 4);
body = PacketManager.readBytes(channel, header.getPacketBodyLength(), timeout);
}

header = null;
header = PacketManager.readHeader(channel, 4);
body = null;
} else {
header = authSwitchAfterAuth(encryptedPassword, header);
body = PacketManager.readBytes(channel, header.getPacketBodyLength(), timeout);
}
}

assert body != null;
if (body[0] < 0) {
if (body[0] == -1) {
ErrorPacket err = new ErrorPacket();
Expand All @@ -280,6 +270,62 @@ private void negotiate(SocketChannel channel) throws IOException {
}
}

private HeaderPacket cachingSha2PasswordFullAuth(SocketChannel channel, HeaderPacket header, byte[] pass,
byte[] seed) throws IOException {
AuthSwitchResponsePacket responsePacket = new AuthSwitchResponsePacket();
responsePacket.authData = new byte[] { 2 };
byte[] auth = responsePacket.toBytes();
HeaderPacket h = new HeaderPacket();
h.setPacketBodyLength(auth.length);
h.setPacketSequenceNumber((byte) (header.getPacketSequenceNumber() + 1));
PacketManager.writePkg(channel, h.toBytes(), auth);
logger.info("caching sha2 password fullAuth request public key packet is sent out.");

header = PacketManager.readHeader(channel, 4);
byte[] body = PacketManager.readBytes(channel, header.getPacketBodyLength(), timeout);
AuthSwitchRequestMoreData packet = new AuthSwitchRequestMoreData();
packet.fromBytes(body);
if (packet.status != 0x01) {
throw new IOException("caching_sha2_password get public key failed");
}
logger.info("caching sha2 password fullAuth get server public key succeed.");
byte[] publicKeyBytes = packet.authData;

byte[] encryptedPassword = null;
try {
encryptedPassword = MySQLPasswordEncrypter.scrambleRsa(publicKeyBytes, pass, seed);
} catch (Exception e) {
logger.error("rsa encrypt failed {}", publicKeyBytes);
throw new IOException("caching_sha2_password auth failed", e);
}

// send auth
responsePacket = new AuthSwitchResponsePacket();
responsePacket.authData = encryptedPassword;
auth = responsePacket.toBytes();
h = new HeaderPacket();
h.setPacketBodyLength(auth.length);
h.setPacketSequenceNumber((byte) (header.getPacketSequenceNumber() + 1));
PacketManager.writePkg(channel, h.toBytes(), auth);
logger.info("caching sha2 password fullAuth response auth data packet is sent out.");
return PacketManager.readHeader(channel, 4);
}

private HeaderPacket authSwitchAfterAuth(byte[] encryptedPassword, HeaderPacket header) throws IOException {
assert encryptedPassword != null;
AuthSwitchResponsePacket responsePacket = new AuthSwitchResponsePacket();
responsePacket.authData = encryptedPassword;
byte[] auth = responsePacket.toBytes();

HeaderPacket h = new HeaderPacket();
h.setPacketBodyLength(auth.length);
h.setPacketSequenceNumber((byte) (header.getPacketSequenceNumber() + 1));
PacketManager.writePkg(channel, h.toBytes(), auth);
logger.info("auth switch response packet is sent out.");
header = PacketManager.readHeader(channel, 4);
return header;
}

private void auth323(SocketChannel channel, byte packetSequenceNumber, byte[] seed) throws IOException {
// auth 323
Reply323Packet r323 = new Reply323Packet();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
package com.alibaba.otter.canal.parse.driver.mysql.utils;

import java.security.DigestException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;

public class MySQLPasswordEncrypter {

Expand Down Expand Up @@ -85,6 +94,26 @@ public static String scramble323(String pass, String seed) {
return new String(chars);
}

public static final byte[] scrambleRsa(byte[] publicKeyBytes, byte[] pass,
byte[] seed) throws NoSuchAlgorithmException, InvalidKeySpecException,
NoSuchPaddingException, InvalidKeyException,
IllegalBlockSizeException, BadPaddingException {
byte[] input = new byte[pass.length + 1];
System.arraycopy(pass, 0, input, 0, pass.length);
byte[] encryptedPassword = new byte[input.length];
xorString(input, encryptedPassword, seed, input.length);
String publicKeyPem = new String(publicKeyBytes).replace("\n", "")
.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "");
byte[] certificateData = java.util.Base64.getDecoder().decode(publicKeyPem.getBytes());
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(certificateData);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(keySpec);
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return cipher.doFinal(encryptedPassword);
}

private static long[] hash(String src) {
long nr = 1345345333L;
long add = 7;
Expand Down