EncryptedAuthCredentialStore class

Пакет: com.hypixel.hytale.server.core.auth

Файл: com/hypixel/hytale/server/core/auth/EncryptedAuthCredentialStore.java

implements: IAuthCredentialStore

Поля (60)

МодификаторыТипИмя
final HytaleLogger LOGGER
String accessToken
break
Instant expiresAt
UUID profileUuid
String refreshToken
return
return
return
PBEKeySpec var1
ArrayList var1
String var1
var1
String var1
Path var1
Path var1
byte[] var1
EncryptedAuthCredentialStore.StoredCredentials var1
String var12
EncryptedAuthCredentialStore.ResolvedPassphrases var2
SecretKeyFactory var2
String var2
var2
String var2
String var2
String var2
byte[] var2
var2
BsonDocument var2
byte[] var2
ByteBuffer var2
SecretKey var3
String var3
var3
boolean var3
var3
byte[] var3
Cipher var3
byte[] var3
SecretKey var4
String var4
var4
byte[] var4
byte[] var4
byte[] var4
String var5
ByteBuffer var5
Cipher var5
String var6
var6
UUID var7
BsonDocument var7
String var8
var8
var8
var8
var8
var8
EncryptedAuthCredentialStore.StoredCredentials var8
ArrayList var9

Методы (28)

МодификаторыВозвратСигнатура
abstract throw new IllegalStateExceptionthrow new IllegalStateException("Failed to read encryption key file specified by HYTALE_AUTH_KEY_FILE: " + var5, var10)
abstract throw new IllegalStateExceptionthrow new IllegalStateException("Encryption key file specified by HYTALE_AUTH_KEY_FILE is empty: " + var5)
private record ResolvedPassphrasesrecord ResolvedPassphrases(@Nullable String writePassphrase, @Nonnull List<String> readPassphrases)
private StoredCredentialsprivate StoredCredentials()
for for(String var3 : var0)
for for(SecretKey var5 : this.decryptionKeys)
if if(this.encryptionKey == null)
if if(var4 != null)
if if(var7 != null)
if if(var1 != null)
if if(var1 != null)
if if(var2 != null)
if if(var4 != null)
if if(var3 != null)
if if(var2 != null)
if if(var2 == null)
if if(var7 == null)
if if(var8 != null)
if if(var3)
if if(this.encryptionKey == null)
if if(var4 == null)
if if(this.encryptionKey == null)
if if(var0.length < 12)
private void loadvoid load()
static void restrictFilePermissionsvoid restrictFilePermissions(@Nonnull Path var0)
abstract restrictFilePermissions restrictFilePermissions(var1)
abstract restrictFilePermissions restrictFilePermissions(this.path)
private void savevoid save()

Исходный код

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

class="kw">import com.hypixel.hytale.codec.Codec;
class="kw">import com.hypixel.hytale.codec.KeyedCodec;
class="kw">import com.hypixel.hytale.codec.builder.BuilderCodec;
class="kw">import com.hypixel.hytale.common.util.HardwareUtil;
class="kw">import com.hypixel.hytale.logger.HytaleLogger;
class="kw">import com.hypixel.hytale.server.core.util.BsonUtil;
class="kw">import java.io.IOException;
class="kw">import java.nio.ByteBuffer;
class="kw">import java.nio.charset.StandardCharsets;
class="kw">import java.nio.file.Files;
class="kw">import java.nio.file.Path;
class="kw">import java.nio.file.attribute.PosixFilePermission;
class="kw">import java.security.SecureRandom;
class="kw">import java.time.Instant;
class="kw">import java.util.ArrayList;
class="kw">import java.util.List;
class="kw">import java.util.Set;
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">import javax.crypto.Cipher;
class="kw">import javax.crypto.SecretKey;
class="kw">import javax.crypto.SecretKeyFactory;
class="kw">import javax.crypto.spec.GCMParameterSpec;
class="kw">import javax.crypto.spec.PBEKeySpec;
class="kw">import javax.crypto.spec.SecretKeySpec;
class="kw">import org.bson.BsonDocument;

class="kw">public class EncryptedAuthCredentialStore class="kw">implements IAuthCredentialStore {
   class="kw">private class="kw">static class="kw">final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass();
   class="kw">private class="kw">static class="kw">final String ENV_AUTH_KEY = "HYTALE_AUTH_KEY";
   class="kw">private class="kw">static class="kw">final String ENV_AUTH_KEY_FILE = "HYTALE_AUTH_KEY_FILE";
   class="kw">private class="kw">static class="kw">final String ALGORITHM = "AES/GCM/NoPadding";
   class="kw">private class="kw">static class="kw">final int GCM_IV_LENGTH = 12;
   class="kw">private class="kw">static class="kw">final int GCM_TAG_LENGTH = 128;
   class="kw">private class="kw">static class="kw">final int KEY_LENGTH = 256;
   class="kw">private class="kw">static class="kw">final int PBKDF2_ITERATIONS = 100000;
   class="kw">private class="kw">static class="kw">final byte[] SALT = "HytaleAuthCredentialStore".getBytes(StandardCharsets.UTF_8);
   class="kw">private class="kw">static class="kw">final Set<PosixFilePermission> OWNER_ONLY_PERMISSIONS = Set.of(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE);
   class="kw">private class="kw">static class="kw">final BuilderCodec<EncryptedAuthCredentialStore.StoredCredentials> CREDENTIALS_CODEC = BuilderCodec.builder(
         EncryptedAuthCredentialStore.StoredCredentials.class, EncryptedAuthCredentialStore.StoredCredentials::new
      )
      .append(new KeyedCodec<>("AccessToken", Codec.STRING), (var0, var1) -> var0.accessToken = var1, var0 -> var0.accessToken)
      .add()
      .append(new KeyedCodec<>("RefreshToken", Codec.STRING), (var0, var1) -> var0.refreshToken = var1, var0 -> var0.refreshToken)
      .add()
      .append(new KeyedCodec<>("ExpiresAt", Codec.INSTANT), (var0, var1) -> var0.expiresAt = var1, var0 -> var0.expiresAt)
      .add()
      .append(new KeyedCodec<>("ProfileUuid", Codec.UUID_STRING), (var0, var1) -> var0.profileUuid = var1, var0 -> var0.profileUuid)
      .add()
      .build();
   class="kw">private class="kw">final Path path;
   @Nullable
   class="kw">private class="kw">final SecretKey encryptionKey;
   class="kw">private class="kw">final List<SecretKey> decryptionKeys;
   class="kw">private IAuthCredentialStore.OAuthTokens tokens = new IAuthCredentialStore.OAuthTokens(null, null, null);
   @Nullable
   class="kw">private UUID profile;

   class="kw">public EncryptedAuthCredentialStore(@Nonnull Path var1) {
      this.path = var1;
      EncryptedAuthCredentialStore.ResolvedPassphrases var2 = resolvePassphrases(var1);
      this.encryptionKey = var2.writePassphrase != null ? deriveKey(var2.writePassphrase) : null;
      this.decryptionKeys = deriveKeys(var2.readPassphrases);
      if (this.encryptionKey == null) {
         LOGGER.at(Level.WARNING).log("Cannot derive encryption key - encrypted storage will not persist credentials");
      } else {
         this.load();
      }
   }

   @Nullable
   class="kw">private class="kw">static SecretKey deriveKey(@Nonnull String var0) {
      PBEKeySpec var1 = new PBEKeySpec(var0.toCharArray(), SALT, 100000, 256);

      try {
         SecretKeyFactory var2 = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
         SecretKey var3 = var2.generateSecret(var1);
         class="kw">return new SecretKeySpec(var3.getEncoded(), "AES");
      } catch (Exception var8) {
         ((HytaleLogger.Api)LOGGER.at(Level.WARNING).withCause(var8)).log("Failed to derive encryption key");
         class="kw">return null;
      } class="kw">finally {
         var1.clearPassword();
      }
   }

   @Nonnull
   class="kw">private class="kw">static List<SecretKey> deriveKeys(@Nonnull List<String> var0) {
      ArrayList var1 = new ArrayList<>();

      for (String var3 : var0) {
         SecretKey var4 = deriveKey(var3);
         if (var4 != null) {
            var1.add(var4);
         }
      }

      class="kw">return var1;
   }

   @Nonnull
   class="kw">private class="kw">static EncryptedAuthCredentialStore.ResolvedPassphrases resolvePassphrases(@Nonnull Path var0) {
      String var1 = null;
      String var2 = null;
      String var3 = null;
      String var4 = null;
      String var5 = System.getenv("HYTALE_AUTH_KEY_FILE");
      if (var5 != null && !var5.isBlank()) {
         String var6;
         try {
            var6 = Files.readString(Path.of(var5), StandardCharsets.UTF_8).trim();
         } catch (IOException var10) {
            throw new IllegalStateException("Failed to read encryption key file specified by HYTALE_AUTH_KEY_FILE: " + var5, var10);
         }

         if (var6.isEmpty()) {
            throw new IllegalStateException("Encryption key file specified by HYTALE_AUTH_KEY_FILE is empty: " + var5);
         }

         LOGGER.at(Level.INFO).log("Using encryption passphrase from %s", "HYTALE_AUTH_KEY_FILE");
         var1 = var6;
      }

      String var12 = System.getenv("HYTALE_AUTH_KEY");
      if (var12 != null && !var12.isBlank()) {
         LOGGER.at(Level.INFO).log("Using encryption passphrase from %s", "HYTALE_AUTH_KEY");
         var2 = var12;
      }

      UUID var7 = HardwareUtil.getUUID();
      if (var7 != null) {
         var3 = var7.toString();
      }

      var4 = loadGeneratedKeyFile(var0);
      String var8;
      if (var1 != null) {
         var8 = var1;
      } else if (var2 != null) {
         var8 = var2;
      } else if (var3 != null) {
         var8 = var3;
      } else if (var4 != null) {
         var8 = var4;
      } else {
         var8 = generateKeyFile(var0);
      }

      ArrayList var9 = new ArrayList<>(4);
      if (var1 != null) {
         var9.add(var1);
      }

      if (var2 != null) {
         var9.add(var2);
      }

      if (var4 != null) {
         var9.add(var4);
      }

      if (var3 != null) {
         var9.add(var3);
      }

      class="kw">return new EncryptedAuthCredentialStore.ResolvedPassphrases(var8, var9);
   }

   @Nonnull
   class="kw">public class="kw">static Path keyFilePath(@Nonnull Path var0) {
      String var1 = var0.getFileName().toString();
      String var2 = var1.contains(".") ? var1.substring(0, var1.lastIndexOf(46)) + ".key" : var1 + ".key";
      class="kw">return var0.resolveSibling(var2);
   }

   @Nullable
   class="kw">private class="kw">static String loadGeneratedKeyFile(@Nonnull Path var0) {
      Path var1 = keyFilePath(var0);
      if (!Files.exists(var1)) {
         class="kw">return null;
      }

      try {
         String var2 = Files.readString(var1, StandardCharsets.UTF_8).trim();
         if (!var2.isEmpty()) {
            LOGGER.at(Level.INFO).log("Using auto-generated encryption key from %s", var1);
            class="kw">return var2;
         }
      } catch (IOException var3) {
         ((HytaleLogger.Api)LOGGER.at(Level.WARNING).withCause(var3)).log("Failed to read generated key file %s", var1);
      }

      class="kw">return null;
   }

   @Nullable
   class="kw">private class="kw">static String generateKeyFile(@Nonnull Path var0) {
      Path var1 = keyFilePath(var0);

      try {
         String var2 = UUID.randomUUID().toString();
         Files.writeString(var1, var2, StandardCharsets.UTF_8);
         restrictFilePermissions(var1);
         LOGGER.at(Level.INFO).log("Generated new encryption key file at %s", var1);
         class="kw">return var2;
      } catch (IOException var3) {
         ((HytaleLogger.Api)LOGGER.at(Level.WARNING).withCause(var3)).log("Failed to generate key file at %s", var1);
         class="kw">return null;
      }
   }

   class="kw">private class="kw">static void restrictFilePermissions(@Nonnull Path var0) {
      try {
         Files.setPosixFilePermissions(var0, OWNER_ONLY_PERMISSIONS);
      } catch (UnsupportedOperationException var2) {
      } catch (IOException var3) {
         ((HytaleLogger.Api)LOGGER.at(Level.WARNING).withCause(var3)).log("Failed to set file permissions on %s", var0);
      }
   }

   class="kw">private void load() {
      if (this.encryptionKey != null && Files.exists(this.path)) {
         try {
            byte[] var1 = Files.readAllBytes(this.path);
            byte[] var2 = null;
            boolean var3 = false;

            for (SecretKey var5 : this.decryptionKeys) {
               var2 = decrypt(var1, var5);
               if (var2 != null) {
                  if (!var5.equals(this.encryptionKey)) {
                     LOGGER.at(Level.INFO).log("Decrypted credentials using fallback key - will re-encrypt with current key");
                     var3 = true;
                  }
                  break;
               }
            }

            if (var2 == null) {
               LOGGER.at(Level.WARNING).log("Failed to decrypt credentials from %s - file may be corrupted or from different key source", this.path);
               class="kw">return;
            }

            BsonDocument var7 = BsonUtil.readFromBytes(var2);
            if (var7 == null) {
               LOGGER.at(Level.WARNING).log("Failed to parse credentials from %s", this.path);
               class="kw">return;
            }

            EncryptedAuthCredentialStore.StoredCredentials var8 = CREDENTIALS_CODEC.decode(var7);
            if (var8 != null) {
               this.tokens = new IAuthCredentialStore.OAuthTokens(var8.accessToken, var8.refreshToken, var8.expiresAt);
               this.profile = var8.profileUuid;
            }

            LOGGER.at(Level.INFO).log("Loaded encrypted credentials from %s", this.path);
            if (var3) {
               this.save();
               LOGGER.at(Level.INFO).log("Migrated credentials to current encryption key");
            }
         } catch (Exception var6) {
            ((HytaleLogger.Api)LOGGER.at(Level.WARNING).withCause(var6)).log("Failed to load encrypted credentials from %s", this.path);
         }
      }
   }

   class="kw">private void save() {
      if (this.encryptionKey == null) {
         LOGGER.at(Level.WARNING).log("Cannot save credentials - no encryption key available");
      } else {
         try {
            EncryptedAuthCredentialStore.StoredCredentials var1 = new EncryptedAuthCredentialStore.StoredCredentials();
            var1.accessToken = this.tokens.accessToken();
            var1.refreshToken = this.tokens.refreshToken();
            var1.expiresAt = this.tokens.accessTokenExpiresAt();
            var1.profileUuid = this.profile;
            BsonDocument var2 = (BsonDocument)CREDENTIALS_CODEC.encode(var1);
            byte[] var3 = BsonUtil.writeToBytes(var2);
            byte[] var4 = this.encrypt(var3);
            if (var4 == null) {
               LOGGER.at(Level.SEVERE).log("Failed to encrypt credentials");
               class="kw">return;
            }

            Files.write(this.path, var4);
            restrictFilePermissions(this.path);
         } catch (IOException var5) {
            ((HytaleLogger.Api)LOGGER.at(Level.SEVERE).withCause(var5)).log("Failed to save encrypted credentials to %s", this.path);
         }
      }
   }

   @Nullable
   class="kw">private byte[] encrypt(@Nonnull byte[] var1) {
      if (this.encryptionKey == null) {
         class="kw">return null;
      }

      try {
         byte[] var2 = new byte[12];
         new SecureRandom().nextBytes(var2);
         Cipher var3 = Cipher.getInstance("AES/GCM/NoPadding");
         var3.init(1, this.encryptionKey, new GCMParameterSpec(128, var2));
         byte[] var4 = var3.doFinal(var1);
         ByteBuffer var5 = ByteBuffer.allocate(var2.length + var4.length);
         var5.put(var2);
         var5.put(var4);
         class="kw">return var5.array();
      } catch (Exception var6) {
         ((HytaleLogger.Api)LOGGER.at(Level.SEVERE).withCause(var6)).log("Encryption failed");
         class="kw">return null;
      }
   }

   @Nullable
   class="kw">private class="kw">static byte[] decrypt(@Nonnull byte[] var0, @Nonnull SecretKey var1) {
      if (var0.length < 12) {
         class="kw">return null;
      }

      try {
         ByteBuffer var2 = ByteBuffer.wrap(var0);
         byte[] var3 = new byte[12];
         var2.get(var3);
         byte[] var4 = new byte[var2.remaining()];
         var2.get(var4);
         Cipher var5 = Cipher.getInstance("AES/GCM/NoPadding");
         var5.init(2, var1, new GCMParameterSpec(128, var3));
         class="kw">return var5.doFinal(var4);
      } catch (Exception var6) {
         class="kw">return null;
      }
   }

   @Override
   class="kw">public void setTokens(@Nonnull IAuthCredentialStore.OAuthTokens var1) {
      this.tokens = var1;
      this.save();
   }

   @Nonnull
   @Override
   class="kw">public IAuthCredentialStore.OAuthTokens getTokens() {
      class="kw">return this.tokens;
   }

   @Override
   class="kw">public void setProfile(@Nullable UUID var1) {
      this.profile = var1;
      this.save();
   }

   @Nullable
   @Override
   class="kw">public UUID getProfile() {
      class="kw">return this.profile;
   }

   @Override
   class="kw">public void clear() {
      this.tokens = new IAuthCredentialStore.OAuthTokens(null, null, null);
      this.profile = null;

      try {
         Files.deleteIfExists(this.path);
      } catch (IOException var2) {
         ((HytaleLogger.Api)LOGGER.at(Level.WARNING).withCause(var2)).log("Failed to delete encrypted credentials file %s", this.path);
      }
   }

   class="kw">private record ResolvedPassphrases(@Nullable String writePassphrase, @Nonnull List<String> readPassphrases) {
      class="kw">private ResolvedPassphrases {
      }
   }

   class="kw">private class="kw">static class StoredCredentials {
      @Nullable
      String accessToken;
      @Nullable
      String refreshToken;
      @Nullable
      Instant expiresAt;
      @Nullable
      UUID profileUuid;

      class="kw">private StoredCredentials() {
      }
   }
}