1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.shardingsphere.data.pipeline.mysql.ingest.client.netty;
19
20 import io.netty.channel.ChannelHandlerContext;
21 import io.netty.channel.ChannelInboundHandlerAdapter;
22 import io.netty.util.concurrent.Promise;
23 import lombok.RequiredArgsConstructor;
24 import lombok.SneakyThrows;
25 import org.apache.shardingsphere.data.pipeline.core.exception.PipelineInternalException;
26 import org.apache.shardingsphere.data.pipeline.mysql.ingest.client.PasswordEncryption;
27 import org.apache.shardingsphere.data.pipeline.mysql.ingest.client.ServerInfo;
28 import org.apache.shardingsphere.data.pipeline.mysql.ingest.client.ServerVersion;
29 import org.apache.shardingsphere.db.protocol.mysql.constant.MySQLAuthenticationMethod;
30 import org.apache.shardingsphere.db.protocol.mysql.constant.MySQLAuthenticationPlugin;
31 import org.apache.shardingsphere.db.protocol.mysql.constant.MySQLCapabilityFlag;
32 import org.apache.shardingsphere.db.protocol.mysql.packet.generic.MySQLErrPacket;
33 import org.apache.shardingsphere.db.protocol.mysql.packet.generic.MySQLOKPacket;
34 import org.apache.shardingsphere.db.protocol.mysql.packet.handshake.MySQLAuthMoreDataPacket;
35 import org.apache.shardingsphere.db.protocol.mysql.packet.handshake.MySQLAuthSwitchRequestPacket;
36 import org.apache.shardingsphere.db.protocol.mysql.packet.handshake.MySQLAuthSwitchResponsePacket;
37 import org.apache.shardingsphere.db.protocol.mysql.packet.handshake.MySQLHandshakePacket;
38 import org.apache.shardingsphere.db.protocol.mysql.packet.handshake.MySQLHandshakeResponse41Packet;
39
40 import java.security.NoSuchAlgorithmException;
41
42
43
44
45 @RequiredArgsConstructor
46 public final class MySQLNegotiateHandler extends ChannelInboundHandlerAdapter {
47
48 private static final int MAX_PACKET_SIZE = 1 << 24;
49
50 private static final int CHARACTER_SET = 33;
51
52 private static final int REQUEST_PUBLIC_KEY = 2;
53
54 private static final int PERFORM_FULL_AUTHENTICATION = 4;
55
56 private final String username;
57
58 private final String password;
59
60 private final Promise<Object> authResultCallback;
61
62 private ServerInfo serverInfo;
63
64 private byte[] seed;
65
66 private boolean publicKeyRequested;
67
68 @SneakyThrows(NoSuchAlgorithmException.class)
69 @Override
70 public void channelRead(final ChannelHandlerContext ctx, final Object msg) {
71 if (msg instanceof MySQLHandshakePacket) {
72 MySQLHandshakePacket handshake = (MySQLHandshakePacket) msg;
73 MySQLHandshakeResponse41Packet handshakeResponsePacket = new MySQLHandshakeResponse41Packet(MAX_PACKET_SIZE, CHARACTER_SET, username);
74 handshakeResponsePacket.setAuthResponse(generateAuthResponse(handshake.getAuthPluginData().getAuthenticationPluginData()));
75 handshakeResponsePacket.setCapabilityFlags(generateClientCapability());
76 handshakeResponsePacket.setAuthPluginName(MySQLAuthenticationMethod.NATIVE);
77 ctx.channel().writeAndFlush(handshakeResponsePacket);
78 serverInfo = new ServerInfo(new ServerVersion(handshake.getServerVersion()));
79 return;
80 }
81 if (msg instanceof MySQLAuthSwitchRequestPacket) {
82 MySQLAuthSwitchRequestPacket authSwitchRequest = (MySQLAuthSwitchRequestPacket) msg;
83 ctx.channel().writeAndFlush(new MySQLAuthSwitchResponsePacket(getAuthPluginResponse(authSwitchRequest)));
84 seed = authSwitchRequest.getAuthPluginData().getAuthenticationPluginData();
85 return;
86 }
87 if (msg instanceof MySQLAuthMoreDataPacket) {
88 MySQLAuthMoreDataPacket authMoreData = (MySQLAuthMoreDataPacket) msg;
89 handleCachingSha2Auth(ctx, authMoreData);
90 return;
91 }
92 if (msg instanceof MySQLOKPacket) {
93 ctx.channel().pipeline().remove(this);
94 authResultCallback.setSuccess(serverInfo);
95 return;
96 }
97 MySQLErrPacket error = (MySQLErrPacket) msg;
98 ctx.channel().close();
99 throw new PipelineInternalException(error.getErrorMessage());
100 }
101
102 private byte[] getAuthPluginResponse(final MySQLAuthSwitchRequestPacket authSwitchRequest) throws NoSuchAlgorithmException {
103
104 switch (MySQLAuthenticationPlugin.getPluginByName(authSwitchRequest.getAuthPluginName())) {
105 case NATIVE:
106 return PasswordEncryption.encryptWithMySQL41(password.getBytes(), authSwitchRequest.getAuthPluginData().getAuthenticationPluginData());
107 case CACHING_SHA2:
108 return PasswordEncryption.encryptWithSha2(password.getBytes(), authSwitchRequest.getAuthPluginData().getAuthenticationPluginData());
109 default:
110 return password.getBytes();
111 }
112 }
113
114 private void handleCachingSha2Auth(final ChannelHandlerContext ctx, final MySQLAuthMoreDataPacket authMoreData) {
115 if (publicKeyRequested) {
116 ctx.channel().writeAndFlush(new MySQLAuthSwitchResponsePacket(
117 PasswordEncryption.encryptWithRSAPublicKey(password, seed,
118 serverInfo.getServerVersion().greaterThanOrEqualTo(8, 0, 5) ? "RSA/ECB/OAEPWithSHA-1AndMGF1Padding" : "RSA/ECB/PKCS1Padding",
119 new String(authMoreData.getPluginData()))));
120 } else {
121 if (PERFORM_FULL_AUTHENTICATION == authMoreData.getPluginData()[0]) {
122 publicKeyRequested = true;
123 ctx.channel().writeAndFlush(new MySQLAuthSwitchResponsePacket(new byte[]{REQUEST_PUBLIC_KEY}));
124 }
125 }
126 }
127
128 private int generateClientCapability() {
129 return MySQLCapabilityFlag.calculateCapabilityFlags(MySQLCapabilityFlag.CLIENT_LONG_PASSWORD, MySQLCapabilityFlag.CLIENT_LONG_FLAG,
130 MySQLCapabilityFlag.CLIENT_PROTOCOL_41, MySQLCapabilityFlag.CLIENT_INTERACTIVE, MySQLCapabilityFlag.CLIENT_TRANSACTIONS,
131 MySQLCapabilityFlag.CLIENT_SECURE_CONNECTION, MySQLCapabilityFlag.CLIENT_MULTI_STATEMENTS, MySQLCapabilityFlag.CLIENT_PLUGIN_AUTH);
132 }
133
134 @SneakyThrows(NoSuchAlgorithmException.class)
135 private byte[] generateAuthResponse(final byte[] authPluginData) {
136 return null == password || password.isEmpty() ? new byte[0] : PasswordEncryption.encryptWithMySQL41(password.getBytes(), authPluginData);
137 }
138 }