in src/main/protocol-impl/java/com/mysql/cj/protocol/a/NativeAuthenticationProvider.java [363:533]
private void proceedHandshakeWithPluggableAuthentication(final NativePacketPayload challenge) {
ServerSession serverSession = this.protocol.getServerSession();
if (this.authenticationPlugins == null) {
loadAuthenticationPlugins();
}
boolean forChangeUser = true;
if (challenge != null) {
this.serverDefaultAuthenticationPluginName = challenge.readString(StringSelfDataType.STRING_TERM, "ASCII");
forChangeUser = false;
}
serverSession.getCharsetSettings().configurePreHandshake(forChangeUser);
/*
* Select the initial plugin:
* Choose the client-side default authentication plugin, if explicitely specified, otherwise choose the server-side default authentication plugin.
*/
String pluginName;
if (this.clientDefaultAuthenticationPluginExplicitelySet) {
pluginName = this.clientDefaultAuthenticationPluginName;
} else {
pluginName = this.serverDefaultAuthenticationPluginName != null ? this.serverDefaultAuthenticationPluginName
: this.clientDefaultAuthenticationPluginName;
}
AuthenticationPlugin<NativePacketPayload> plugin = getAuthenticationPlugin(pluginName);
if (plugin == null) {
/* Use default if there is no plugin for pluginName. */
pluginName = this.clientDefaultAuthenticationPluginName;
plugin = getAuthenticationPlugin(pluginName);
}
boolean skipPassword = false;
if (pluginName.equals(Sha256PasswordPlugin.PLUGIN_NAME) && !pluginName.equals(this.clientDefaultAuthenticationPluginName)
&& !this.protocol.getSocketConnection().isSSLEstablished()
&& this.propertySet.getStringProperty(PropertyKey.serverRSAPublicKeyFile).getValue() == null
&& !this.propertySet.getBooleanProperty(PropertyKey.allowPublicKeyRetrieval).getValue()) {
/*
* Fall back to client default if plugin is 'sha256_password' but required conditions for this to work aren't met.
* If client default is other than 'sha256_password', this will result in an immediate authentication switch request, allowing for other plugins to
* authenticate successfully. If client default is 'sha256_password' then the authentication will fail as expected. In both cases user's password
* won't be sent to avoid subjecting it to lesser security levels.
*/
plugin = getAuthenticationPlugin(this.clientDefaultAuthenticationPluginName);
skipPassword = true;
}
checkConfidentiality(plugin);
// Servers not affected by Bug#70865 expect the Change User Request containing a correct answer to seed sent by the server during the initial handshake,
// thus we reuse it here. Servers affected by Bug#70865 will just ignore it and send the Auth Switch.
NativePacketPayload fromServer = new NativePacketPayload(StringUtils.getBytes(this.seed));
String sourceOfAuthData = this.serverDefaultAuthenticationPluginName;
NativePacketPayload lastSent = null;
NativePacketPayload lastReceived = challenge;
ArrayList<NativePacketPayload> toServer = new ArrayList<>();
boolean firstPacket = true;
// MFA authentication factor
int mfaNthFactor = 1;
/* Max iterations number */
int counter = 100;
while (0 < counter--) {
/*
* call plugin
*/
plugin.setAuthenticationParameters(this.username, skipPassword ? null : getNthFactorPassword(mfaNthFactor));
plugin.setSourceOfAuthData(sourceOfAuthData);
plugin.nextAuthenticationStep(fromServer, toServer);
/*
* send response to server
*/
if (firstPacket) {
NativePacketPayload authData = toServer.isEmpty() ? new NativePacketPayload(0) : toServer.get(0);
if (forChangeUser) {
// write COM_CHANGE_USER Packet
lastSent = createChangeUserPacket(serverSession, plugin.getProtocolPluginName(), authData);
this.protocol.send(lastSent, lastSent.getPosition());
} else {
// write HandshakeResponse packet
lastSent = createHandshakeResponsePacket(serverSession, plugin.getProtocolPluginName(), authData);
this.protocol.send(lastSent, lastSent.getPosition());
}
firstPacket = false;
} else if (!toServer.isEmpty()) {
// write AuthSwitchResponse packet or raw packet(s)
toServer.forEach(b -> this.protocol.send(b, b.getPayloadLength()));
}
/*
* read packet from server
*/
lastReceived = this.protocol.checkErrorMessage();
if (lastReceived.isOKPacket()) {
// read OK packet
OkPacket ok = OkPacket.parse(lastReceived, serverSession);
serverSession.setStatusFlags(ok.getStatusFlags(), true);
serverSession.getServerSessionStateController().setSessionStateChanges(ok.getSessionStateChanges());
// authentication complete
plugin.destroy();
break;
} else if (lastReceived.isAuthMethodSwitchRequestPacket()) {
// read AuthSwitchRequest Packet
skipPassword = false;
pluginName = lastReceived.readString(StringSelfDataType.STRING_TERM, "ASCII");
if (plugin.getProtocolPluginName().equals(pluginName)) {
plugin.reset(); // just reset the current one
} else {
// get new plugin
plugin.destroy();
plugin = getAuthenticationPlugin(pluginName);
if (plugin == null) {
throw ExceptionFactory.createException(WrongArgumentException.class,
Messages.getString("AuthenticationProvider.BadAuthenticationPlugin", new Object[] { pluginName }), getExceptionInterceptor());
}
}
checkConfidentiality(plugin);
fromServer = new NativePacketPayload(lastReceived.readBytes(StringSelfDataType.STRING_EOF));
} else if (lastReceived.isAuthNextFactorPacket()) {
// authentication not done yet, there's another MFA iteration
mfaNthFactor++;
skipPassword = false;
pluginName = lastReceived.readString(StringSelfDataType.STRING_TERM, "ASCII");
if (plugin.getProtocolPluginName().equals(pluginName)) {
plugin.reset(); // just reset the current one
} else {
// get new plugin
plugin.destroy();
plugin = getAuthenticationPlugin(pluginName);
if (plugin == null) {
throw ExceptionFactory.createException(WrongArgumentException.class,
Messages.getString("AuthenticationProvider.BadAuthenticationPlugin", new Object[] { pluginName }), getExceptionInterceptor());
}
}
checkConfidentiality(plugin);
fromServer = new NativePacketPayload(lastReceived.readBytes(StringSelfDataType.STRING_EOF));
} else {
// read raw (from AuthMoreData) packet
if (!this.protocol.versionMeetsMinimum(5, 5, 16)) {
lastReceived.setPosition(lastReceived.getPosition() - 1);
}
fromServer = new NativePacketPayload(lastReceived.readBytes(StringSelfDataType.STRING_EOF));
}
sourceOfAuthData = pluginName;
}
if (counter == 0) {
throw ExceptionFactory.createException(WrongArgumentException.class,
Messages.getString("CommunicationsException.TooManyAuthenticationPluginNegotiations"), getExceptionInterceptor());
}
this.protocol.afterHandshake();
if (!this.useConnectWithDb) {
this.protocol.changeDatabase(this.database);
}
}