StoredCredentials class
Пакет: com.hypixel.hytale.server.core.auth
Файл: com/hypixel/hytale/server/core/auth/EncryptedAuthCredentialStore.java
Поля (4)
| Модификаторы | Тип | Имя |
|---|---|---|
|
String |
accessToken |
|
Instant |
expiresAt |
|
UUID |
profileUuid |
|
String |
refreshToken |
Исходный код
Показать/скрыть
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() {
}
}
}