HandshakeHandler class

Пакет: com.hypixel.hytale.server.core.io.handlers.login

Файл: com/hypixel/hytale/server/core/io/handlers/login/HandshakeHandler.java

extends: GenericConnectionPacketHandler

Поля (33)

МодификаторыТипИмя
REQUESTING_AUTH_GRANT, AWAITING_AUTH_TOKEN, PROCESSING_AUTH_TOKEN, EXCHANGING_SERVER_TOKEN, AUTHENTICATED
final HytaleLogger LOGGER
break
break
jwtValidator
return
return
sessionServiceClient
String var1
String var1
HytaleServerConfig.TimeoutProfile var11
HytaleServerConfig.TimeoutProfile var2
ChannelConnection var2
ChannelConnection var2
ServerAuthManager var2
UUID var2
UUID var2
String var3
String var3
String var3
String var3
byte[] var3
byte[] var3
String var4
HytaleServerConfig.TimeoutProfile var4
String var4
String var4
X509Certificate var5
ChannelConnection var5
JWTValidator.JWTClaims var6
String var7
String var8
PlayerSkin var9

Методы (33)

МодификаторыВозвратСигнатура
AuthState AuthState()
private void completeAuthenticationvoid completeAuthentication(byte[] var1)
private void exchangeServerAuthGrantvoid exchangeServerAuthGrant(@Nonnull String var1)
private byte[] generatePasswordChallengeIfNeededbyte[] generatePasswordChallengeIfNeeded()
static JWTValidator getJwtValidatorJWTValidator getJwtValidator()
static SessionServiceClient getSessionServiceClientSessionServiceClient getSessionServiceClient()
public void handlevoid handle(@Nonnull ClientDisconnect var1)
public void handlevoid handle(@Nonnull AuthToken var1)
if if(sessionServiceClient == null)
if if(sessionServiceClient == null)
if if(jwtValidator == null)
if if(jwtValidator == null)
if if(this.identityClaims == null)
if if(var2x == null)
if if(this.authState != HandshakeHandler.AuthState.REQUESTING_AUTH_GRANT)
if if(this.identityClaims != null)
if if(this.authState != HandshakeHandler.AuthState.AWAITING_AUTH_TOKEN)
if if(var6 == null)
if if(var9 == null)
if if(var3 == null)
if if(var4 == null)
if if(this.authState != HandshakeHandler.AuthState.EXCHANGING_SERVER_TOKEN)
if if(this.authState == HandshakeHandler.AuthState.EXCHANGING_SERVER_TOKEN)
if if(Constants.SINGLEPLAYER)
if if(var2 == null)
if if(this.referralData != null)
if if(this.referralSource != null)
if if(this.authenticatedSkin != null)
abstract abstract void onAuthenticatedvoid onAuthenticated(byte[] var1)
private void requestAuthGrantvoid requestAuthGrant()
abstract super super(var1, var2, var3)
synchronized synchronized(HandshakeHandler.class)
synchronized synchronized(HandshakeHandler.class)

Исходный код

Показать/скрыть
class="kw">package com.hypixel.hytale.server.core.io.handlers.login;

class="kw">import com.hypixel.hytale.logger.HytaleLogger;
class="kw">import com.hypixel.hytale.protocol.HostAddress;
class="kw">import com.hypixel.hytale.protocol.PlayerSkin;
class="kw">import com.hypixel.hytale.protocol.ToServerPacket;
class="kw">import com.hypixel.hytale.protocol.io.ChannelConnection;
class="kw">import com.hypixel.hytale.protocol.io.ConnectionHandler;
class="kw">import com.hypixel.hytale.protocol.packets.auth.AuthGrant;
class="kw">import com.hypixel.hytale.protocol.packets.auth.AuthToken;
class="kw">import com.hypixel.hytale.protocol.packets.auth.ServerAuthToken;
class="kw">import com.hypixel.hytale.protocol.packets.connection.ClientDisconnect;
class="kw">import com.hypixel.hytale.protocol.packets.connection.ClientType;
class="kw">import com.hypixel.hytale.server.core.Constants;
class="kw">import com.hypixel.hytale.server.core.HytaleServer;
class="kw">import com.hypixel.hytale.server.core.HytaleServerConfig;
class="kw">import com.hypixel.hytale.server.core.Message;
class="kw">import com.hypixel.hytale.server.core.auth.AuthConfig;
class="kw">import com.hypixel.hytale.server.core.auth.JWTValidator;
class="kw">import com.hypixel.hytale.server.core.auth.PlayerAuthentication;
class="kw">import com.hypixel.hytale.server.core.auth.ServerAuthManager;
class="kw">import com.hypixel.hytale.server.core.auth.SessionServiceClient;
class="kw">import com.hypixel.hytale.server.core.cosmetics.CosmeticsModule;
class="kw">import com.hypixel.hytale.server.core.io.ProtocolVersion;
class="kw">import com.hypixel.hytale.server.core.io.handlers.GenericConnectionPacketHandler;
class="kw">import com.hypixel.hytale.server.core.modules.singleplayer.SingleplayerModule;
class="kw">import java.security.SecureRandom;
class="kw">import java.security.cert.X509Certificate;
class="kw">import java.util.UUID;
class="kw">import java.util.logging.Level;
class="kw">import javax.annotation.Nonnull;
class="kw">import javax.annotation.Nullable;

class="kw">public class="kw">abstract class HandshakeHandler class="kw">extends GenericConnectionPacketHandler {
   class="kw">private class="kw">static class="kw">final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass();
   class="kw">private class="kw">static class="kw">volatile SessionServiceClient sessionServiceClient;
   class="kw">private class="kw">static class="kw">volatile JWTValidator jwtValidator;
   class="kw">private class="kw">volatile HandshakeHandler.AuthState authState = HandshakeHandler.AuthState.REQUESTING_AUTH_GRANT;
   class="kw">private class="kw">volatile boolean authTokenPacketReceived = false;
   class="kw">private class="kw">volatile String authenticatedUsername;
   class="kw">private class="kw">volatile PlayerSkin authenticatedSkin;
   class="kw">private class="kw">final ClientType clientType;
   class="kw">private class="kw">final String identityToken;
   class="kw">private class="kw">final byte[] referralData;
   class="kw">private class="kw">final HostAddress referralSource;
   @Nullable
   class="kw">private class="kw">volatile JWTValidator.IdentityTokenClaims identityClaims;

   class="kw">public HandshakeHandler(
      @Nonnull ChannelConnection var1,
      @Nonnull ProtocolVersion var2,
      @Nonnull String var3,
      @Nonnull ClientType var4,
      @Nonnull String var5,
      @Nullable byte[] var6,
      @Nullable HostAddress var7
   ) {
      super(var1, var2, var3);
      this.clientType = var4;
      this.identityToken = var5;
      this.referralData = var6;
      this.referralSource = var7;
   }

   class="kw">private class="kw">static SessionServiceClient getSessionServiceClient() {
      if (sessionServiceClient == null) {
         class="kw">synchronized (HandshakeHandler.class) {
            if (sessionServiceClient == null) {
               sessionServiceClient = new SessionServiceClient("https://sessions.hytale.com");
            }
         }
      }

      class="kw">return sessionServiceClient;
   }

   class="kw">private class="kw">static JWTValidator getJwtValidator() {
      if (jwtValidator == null) {
         class="kw">synchronized (HandshakeHandler.class) {
            if (jwtValidator == null) {
               jwtValidator = new JWTValidator(getSessionServiceClient(), "https://sessions.hytale.com", AuthConfig.getServerAudience());
            }
         }
      }

      class="kw">return jwtValidator;
   }

   @Override
   class="kw">public void accept(@Nonnull ToServerPacket var1) {
      class="kw">switch (var1.getId()) {
         case 1:
            this.handle((ClientDisconnect)var1);
            break;
         case 12:
            this.handle((AuthToken)var1);
            break;
         class="kw">default:
            this.disconnect(Message.translation("client.general.disconnect.protocol.unexpectedPacket").param("packetId", var1.getId()));
      }
   }

   @Override
   class="kw">public void registered0(ConnectionHandler var1) {
      HytaleServerConfig.TimeoutProfile var2 = HytaleServer.get().getConfig().getConnectionTimeouts();
      this.enterStage("auth", var2.getAuth());
      this.identityClaims = getJwtValidator().validateIdentityToken(this.identityToken);
      if (this.identityClaims == null) {
         LOGGER.at(Level.WARNING).log("Identity token validation failed for %s", this.getChannel().formatRemoteAddress());
         this.disconnect(Message.translation("client.general.disconnect.invalidIdentityToken"));
      } else if (this.identityClaims.subject == null || this.identityClaims.subject.isEmpty()) {
         LOGGER.at(Level.WARNING).log("Identity token UUID missing for %s", this.getChannel().formatRemoteAddress());
         this.disconnect(Message.translation("client.general.disconnect.invalidIdentityToken"));
      } else if (this.identityClaims.username != null && !this.identityClaims.username.isEmpty()) {
         String var3 = this.clientType == ClientType.Editor ? "hytale:editor" : "hytale:client";
         if (!this.identityClaims.hasScope(var3)) {
            LOGGER.at(Level.WARNING)
               .log(
                  "Identity token missing required scope for %s from %s (clientType: %s, required: %s, actual: %s)",
                  this.identityClaims.username,
                  this.getChannel().formatRemoteAddress(),
                  this.clientType,
                  var3,
                  this.identityClaims.scope
               );
            this.disconnect(Message.translation("client.general.disconnect.identityTokenMissingScope").param("scope", var3));
         } else {
            LOGGER.at(Level.INFO)
               .log(
                  "Identity token validated for %s (UUID: %s, scope: %s) from %s, requesting auth grant",
                  this.identityClaims.username,
                  this.identityClaims.subject,
                  this.identityClaims.scope,
                  this.getChannel().formatRemoteAddress()
               );
            this.continueStage("auth:grant", var2.getAuthGrant(), () -> this.authState != HandshakeHandler.AuthState.REQUESTING_AUTH_GRANT);
            this.requestAuthGrant();
         }
      } else {
         LOGGER.at(Level.WARNING).log("Identity token username missing for %s", this.getChannel().formatRemoteAddress());
         this.disconnect(Message.translation("client.general.disconnect.invalidIdentityToken"));
      }
   }

   class="kw">private void requestAuthGrant() {
      String var1 = ServerAuthManager.getInstance().getSessionToken();
      if (var1 != null && !var1.isEmpty()) {
         ChannelConnection var2 = this.getChannel();
         getSessionServiceClient().requestAuthorizationGrantAsync(this.identityToken, AuthConfig.getServerAudience(), var1).thenAccept(var2x -> {
            if (var2.isActive()) {
               if (var2x == null) {
                  var2.execute(() -> this.disconnect(Message.translation("client.general.disconnect.authGrantFailed")));
               } else {
                  String var3 = ServerAuthManager.getInstance().getIdentityToken();
                  if (var3 != null && !var3.isEmpty()) {
                     String var4 = var3;
                     var2.execute(() -> {
                        if (var2.isActive()) {
                           if (this.authState != HandshakeHandler.AuthState.REQUESTING_AUTH_GRANT) {
                              LOGGER.at(Level.WARNING).log("State changed during auth grant request, current state: %s", this.authState);
                           } else {
                              this.clearTimeout();
                              LOGGER.at(Level.INFO).log("Sending AuthGrant to %s (with server identity: %s)", var2.formatRemoteAddress(), !var4.isEmpty());
                              this.write(new AuthGrant(var2x, var4));
                              this.authState = HandshakeHandler.AuthState.AWAITING_AUTH_TOKEN;
                              HytaleServerConfig.TimeoutProfile var4 = HytaleServer.get().getConfig().getConnectionTimeouts();
                              this.continueStage("auth:token", var4.getAuthToken(), () -> this.authState != HandshakeHandler.AuthState.AWAITING_AUTH_TOKEN);
                           }
                        }
                     });
                  } else {
                     LOGGER.at(Level.SEVERE).log("Server identity token not available - cannot complete mutual authentication");
                     var2.execute(() -> this.disconnect(Message.translation("client.general.disconnect.serverAuthUnavailable")));
                  }
               }
            }
         }).exceptionally(var2x -> {
            ((HytaleLogger.Api)LOGGER.at(Level.WARNING).withCause(var2x)).log("Error requesting auth grant");
            var2.execute(() -> this.disconnect(Message.translation("client.general.disconnect.authError")));
            class="kw">return null;
         });
      } else {
         LOGGER.at(Level.SEVERE).log("Server session token not available - cannot request auth grant");
         this.disconnect(Message.translation("client.general.disconnect.serverAuthUnavailable"));
      }
   }

   class="kw">public void handle(@Nonnull ClientDisconnect var1) {
      this.disconnectReason.setClientDisconnectType(var1.type);
      if (this.identityClaims != null) {
         LOGGER.at(Level.INFO)
            .log(
               "%s (%s) at %s left with reason: %s - %s",
               this.identityClaims.subject,
               this.identityClaims.username,
               this.getChannel().formatRemoteAddress(),
               var1.type.name(),
               var1.reason.name()
            );
      }

      this.getChannel().closeApplicationConnection();
   }

   class="kw">public void handle(@Nonnull AuthToken var1) {
      ChannelConnection var2 = this.getChannel();
      if (this.authState != HandshakeHandler.AuthState.AWAITING_AUTH_TOKEN) {
         LOGGER.at(Level.WARNING).log("Received unexpected AuthToken packet in state %s from %s", this.authState, var2.formatRemoteAddress());
         this.disconnect(Message.translation("client.general.disconnect.protocol.unexpectedAuthToken"));
      } else if (this.authTokenPacketReceived) {
         LOGGER.at(Level.WARNING).log("Received duplicate AuthToken packet from %s", var2.formatRemoteAddress());
         this.disconnect(Message.translation("client.general.disconnect.protocol.duplicateAuthToken"));
      } else {
         this.authTokenPacketReceived = true;
         this.authState = HandshakeHandler.AuthState.PROCESSING_AUTH_TOKEN;
         this.clearTimeout();
         String var3 = var1.accessToken;
         if (var3 != null && !var3.isEmpty()) {
            String var4 = var1.serverAuthorizationGrant;
            X509Certificate var5 = var2.getClientCertificate();
            LOGGER.at(Level.INFO)
               .log(
                  "Received AuthToken from %s, validating JWT (mTLS cert present: %s, server auth grant: %s)",
                  var2.formatRemoteAddress(),
                  var5 != null,
                  var4 != null && !var4.isEmpty()
               );
            JWTValidator.JWTClaims var6 = getJwtValidator().validateToken(var3, var5);
            if (var6 == null) {
               LOGGER.at(Level.WARNING).log("JWT validation failed for %s", var2.formatRemoteAddress());
               this.disconnect(Message.translation("client.general.disconnect.invalidAccessToken"));
            } else if (this.identityClaims == null) {
               LOGGER.at(Level.WARNING).log("Identity token is null prior to validating authentication token for %s", this.getChannel().formatRemoteAddress());
               this.disconnect(Message.translation("client.general.disconnect.invalidIdentityToken"));
            } else {
               String var7 = var6.subject;
               String var8 = var6.username;
               if (var7 == null || !var7.equals(this.identityClaims.subject)) {
                  LOGGER.at(Level.WARNING)
                     .log("JWT UUID mismatch for %s (expected: %s, got: %s)", this.getChannel().formatRemoteAddress(), this.identityClaims.subject, var7);
                  this.disconnect(Message.translation("client.general.disconnect.tokenUuidMismatch"));
               } else if (var8 == null || var8.isEmpty()) {
                  LOGGER.at(Level.WARNING).log("JWT missing username for %s", var2.formatRemoteAddress());
                  this.disconnect(Message.translation("client.general.disconnect.tokenMissingUsername"));
               } else if (!var8.equals(this.identityClaims.username)) {
                  LOGGER.at(Level.WARNING)
                     .log("JWT username mismatch for %s (expected: %s, got: %s)", this.getChannel().formatRemoteAddress(), this.identityClaims.username, var8);
                  this.disconnect(Message.translation("client.general.disconnect.tokenUsernameMismatch"));
               } else {
                  this.authenticatedUsername = var8;
                  if (this.identityClaims.skin != null && !this.identityClaims.skin.isEmpty()) {
                     try {
                        PlayerSkin var9 = CosmeticsModule.get().parseSkinFromJson(this.identityClaims.skin);
                        if (var9 == null) {
                           this.disconnect(Message.translation("client.general.disconnect.invalidSkin").param("details", "failed to parse skin data"));
                           class="kw">return;
                        }

                        CosmeticsModule.get().validateSkin(var9);
                        this.authenticatedSkin = var9;
                     } catch (CosmeticsModule.InvalidSkinException var10) {
                        this.disconnect(Message.translation("client.general.disconnect.invalidSkin").param("details", var10.getMessage()));
                        class="kw">return;
                     }
                  }

                  if (var4 != null && !var4.isEmpty()) {
                     this.authState = HandshakeHandler.AuthState.EXCHANGING_SERVER_TOKEN;
                     HytaleServerConfig.TimeoutProfile var11 = HytaleServer.get().getConfig().getConnectionTimeouts();
                     this.continueStage(
                        "auth:server-exchange", var11.getAuthServerExchange(), () -> this.authState != HandshakeHandler.AuthState.EXCHANGING_SERVER_TOKEN
                     );
                     this.exchangeServerAuthGrant(var4);
                  } else {
                     LOGGER.at(Level.WARNING).log("Client did not provide server auth grant for mutual authentication");
                     this.disconnect(Message.translation("client.general.disconnect.mutualAuthRequired"));
                  }
               }
            }
         } else {
            LOGGER.at(Level.WARNING).log("Received AuthToken packet with empty access token from %s", var2.formatRemoteAddress());
            this.disconnect(Message.translation("client.general.disconnect.invalidAccessToken"));
         }
      }
   }

   class="kw">private void exchangeServerAuthGrant(@Nonnull String var1) {
      ServerAuthManager var2 = ServerAuthManager.getInstance();
      String var3 = var2.getServerCertificateFingerprint();
      if (var3 == null) {
         LOGGER.at(Level.SEVERE).log("Server certificate fingerprint not available for mutual auth");
         this.disconnect(Message.translation("client.general.disconnect.serverAuthUnavailable"));
      } else {
         String var4 = var2.getSessionToken();
         LOGGER.at(Level.FINE).log("Server session token available: %s, identity token available: %s", var4 != null, var2.getIdentityToken() != null);
         if (var4 == null) {
            LOGGER.at(Level.SEVERE).log("Server session token not available for auth grant exchange");
            LOGGER.at(Level.FINE)
               .log("Auth mode: %s, has session token: %s, has identity token: %s", var2.getAuthStatus(), var2.hasSessionToken(), var2.hasIdentityToken());
            this.disconnect(Message.translation("client.general.disconnect.serverAuthUnavailable"));
         } else {
            LOGGER.at(Level.FINE).log("Using session token (first 20 chars): %s...", var4.length() > 20 ? var4.substring(0, 20) : var4);
            ChannelConnection var5 = this.getChannel();
            getSessionServiceClient().exchangeAuthGrantForTokenAsync(var1, var3, var4).thenAccept(var2x -> {
               if (var5.isActive()) {
                  var5.execute(() -> {
                     if (var5.isActive()) {
                        if (this.authState != HandshakeHandler.AuthState.EXCHANGING_SERVER_TOKEN) {
                           LOGGER.at(Level.WARNING).log("State changed during server token exchange, current state: %s", this.authState);
                        } else if (var2x == null) {
                           LOGGER.at(Level.SEVERE).log("Failed to exchange server auth grant for access token");
                           this.disconnect(Message.translation("client.general.disconnect.serverAuthFailed"));
                        } else {
                           byte[] var3 = this.generatePasswordChallengeIfNeeded();
                           LOGGER.at(Level.INFO).log("Sending ServerAuthToken to %s (with password challenge: %s)", var5.formatRemoteAddress(), var3 != null);
                           this.write(new ServerAuthToken(var2x, var3));
                           this.completeAuthentication(var3);
                        }
                     }
                  });
               }
            }).exceptionally(var2x -> {
               ((HytaleLogger.Api)LOGGER.at(Level.WARNING).withCause(var2x)).log("Error exchanging server auth grant");
               var5.execute(() -> {
                  if (this.authState == HandshakeHandler.AuthState.EXCHANGING_SERVER_TOKEN) {
                     this.disconnect(Message.translation("client.general.disconnect.serverAuthFailed"));
                  }
               });
               class="kw">return null;
            });
         }
      }
   }

   class="kw">private byte[] generatePasswordChallengeIfNeeded() {
      String var1 = HytaleServer.get().getConfig().getPassword();
      if (var1 != null && !var1.isEmpty()) {
         if (Constants.SINGLEPLAYER) {
            UUID var2 = SingleplayerModule.getUuid();
            if (var2 != null && var2.equals(this.identityClaims.getSubjectAsUUID())) {
               class="kw">return null;
            }
         }

         byte[] var3 = new byte[32];
         new SecureRandom().nextBytes(var3);
         class="kw">return var3;
      } else {
         class="kw">return null;
      }
   }

   class="kw">private void completeAuthentication(byte[] var1) {
      UUID var2 = this.identityClaims.getSubjectAsUUID();
      if (var2 == null) {
         LOGGER.at(Level.SEVERE).log("Identity token subject is not a valid UUID: %s", this.identityClaims.subject);
         this.disconnect(Message.translation("client.general.disconnect.invalidIdentityToken"));
      } else {
         this.auth = new PlayerAuthentication(var2, this.authenticatedUsername);
         if (this.referralData != null) {
            this.auth.setReferralData(this.referralData);
         }

         if (this.referralSource != null) {
            this.auth.setReferralSource(this.referralSource);
         }

         if (this.authenticatedSkin != null) {
            this.auth.setSkin(this.authenticatedSkin);
         }

         this.authState = HandshakeHandler.AuthState.AUTHENTICATED;
         this.clearTimeout();
         LOGGER.at(Level.INFO)
            .log(
               "Mutual authentication complete for %s (%s) from %s",
               this.authenticatedUsername,
               this.identityClaims.subject,
               this.getChannel().formatRemoteAddress()
            );
         this.onAuthenticated(var1);
      }
   }

   class="kw">protected class="kw">abstract void onAuthenticated(byte[] var1);

   class="kw">private enum AuthState {
      REQUESTING_AUTH_GRANT,
      AWAITING_AUTH_TOKEN,
      PROCESSING_AUTH_TOKEN,
      EXCHANGING_SERVER_TOKEN,
      AUTHENTICATED;

      AuthState() {
      }
   }
}