private void proceedHandshakeWithPluggableAuthentication()

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);
        }
    }