classpublicPriority 3
EncryptedAuthCredentialStore
com.hypixel.hytale.server.core.auth.EncryptedAuthCredentialStore
implements IAuthCredentialStore
5
Methods
5
Public Methods
3
Fields
1
Constructors
Constants
StringALGORITHM= "AES/GCM/NoPadding"
BuilderCodec<EncryptedAuthCredentialStore.StoredCredentials>CREDENTIALS_CODEC= BuilderCodec.builder(
EncryptedAuthCredentialStore.StoredCredentials.class, EncryptedAut...
intGCM_IV_LENGTH= 12
intGCM_TAG_LENGTH= 128
intKEY_LENGTH= 256
HytaleLoggerLOGGER= HytaleLogger.forEnclosingClass()
intPBKDF2_ITERATIONS= 100000
byte[]SALT= "HytaleAuthCredentialStore".getBytes(StandardCharsets.UTF_8)
Constructors
public
EncryptedAuthCredentialStore(Path path)Methods
Public Methods (5)
public
void clear()@Override
public
UUID getProfile()@Nullable@Override
public
IAuthCredentialStore.OAuthTokens getTokens()@Nonnull@Override
public
void setProfile(UUID uuid)@Override
public
void setTokens(IAuthCredentialStore.OAuthTokens tokens)@Override
Fields
Private/Package Fields (3)
private
SecretKey encryptionKeyprivate
Path pathprivate
UUID profileInheritance
Parent
Current
Interface
Child
Use mouse wheel to zoom, drag to pan. Click nodes to navigate.
Related Classes
Source Code
package com.hypixel.hytale.server.core.auth;
import com.hypixel.hytale.codec.Codec;
import com.hypixel.hytale.codec.KeyedCodec;
import com.hypixel.hytale.codec.builder.BuilderCodec;
import com.hypixel.hytale.common.util.HardwareUtil;
import com.hypixel.hytale.logger.HytaleLogger;
import com.hypixel.hytale.server.core.util.BsonUtil;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.SecureRandom;
import java.time.Instant;
import java.util.UUID;
import java.util.logging.Level;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import org.bson.BsonDocument;
public class EncryptedAuthCredentialStore implements IAuthCredentialStore {
private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass();
private static final String ALGORITHM = "AES/GCM/NoPadding";
private static final int GCM_IV_LENGTH = 12;
private static final int GCM_TAG_LENGTH = 128;
private static final int KEY_LENGTH = 256;
private static final int PBKDF2_ITERATIONS = 100000;
private static final byte[] SALT = "HytaleAuthCredentialStore".getBytes(StandardCharsets.UTF_8);
private static final BuilderCodec<EncryptedAuthCredentialStore.StoredCredentials> CREDENTIALS_CODEC = BuilderCodec.builder(
EncryptedAuthCredentialStore.StoredCredentials.class, EncryptedAuthCredentialStore.StoredCredentials::new
)
.append(new KeyedCodec<>("AccessToken", Codec.STRING), (o, v) -> o.accessToken = v, o -> o.accessToken)
.add()
.append(new KeyedCodec<>("RefreshToken", Codec.STRING), (o, v) -> o.refreshToken = v, o -> o.refreshToken)
.add()
.append(new KeyedCodec<>("ExpiresAt", Codec.INSTANT), (o, v) -> o.expiresAt = v, o -> o.expiresAt)
.add()
.append(new KeyedCodec<>("ProfileUuid", Codec.UUID_STRING), (o, v) -> o.profileUuid = v, o -> o.profileUuid)
.add()
.build();
private final Path path;
@Nullable
private final SecretKey encryptionKey;
private IAuthCredentialStore.OAuthTokens tokens = new IAuthCredentialStore.OAuthTokens(null, null, null);
@Nullable
private UUID profile;
public EncryptedAuthCredentialStore(@Nonnull Path path) {
this.path = path;
this.encryptionKey = deriveKey();
if (this.encryptionKey == null) {
LOGGER.at(Level.WARNING).log("Cannot derive encryption key - encrypted storage will not persist credentials");
} else {
this.load();
}
}
@Nullable
private static SecretKey deriveKey() {
UUID hardwareId = HardwareUtil.getUUID();
if (hardwareId == null) {
return null;
} else {
try {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
PBEKeySpec spec = new PBEKeySpec(hardwareId.toString().toCharArray(), SALT, 100000, 256);
SecretKey tmp = factory.generateSecret(spec);
return new SecretKeySpec(tmp.getEncoded(), "AES");
} catch (Exception var4) {
((HytaleLogger.Api)LOGGER.at(Level.WARNING).withCause(var4)).log("Failed to derive encryption key");
return null;
}
}
}
private void load() {
if (this.encryptionKey != null && Files.exists(this.path)) {
try {
byte[] encrypted = Files.readAllBytes(this.path);
byte[] decrypted = this.decrypt(encrypted);
if (decrypted == null) {
LOGGER.at(Level.WARNING).log("Failed to decrypt credentials from %s - file may be corrupted or from different hardware", this.path);
return;
}
BsonDocument doc = BsonUtil.readFromBytes(decrypted);
if (doc == null) {
LOGGER.at(Level.WARNING).log("Failed to parse credentials from %s", this.path);
return;
}
EncryptedAuthCredentialStore.StoredCredentials stored = CREDENTIALS_CODEC.decode(doc);
if (stored != null) {
this.tokens = new IAuthCredentialStore.OAuthTokens(stored.accessToken, stored.refreshToken, stored.expiresAt);
this.profile = stored.profileUuid;
}
LOGGER.at(Level.INFO).log("Loaded encrypted credentials from %s", this.path);
} catch (Exception var5) {
((HytaleLogger.Api)LOGGER.at(Level.WARNING).withCause(var5)).log("Failed to load encrypted credentials from %s", this.path);
}
}
}
private void save() {
if (this.encryptionKey == null) {
LOGGER.at(Level.WARNING).log("Cannot save credentials - no encryption key available");
} else {
try {
EncryptedAuthCredentialStore.StoredCredentials stored = new EncryptedAuthCredentialStore.StoredCredentials();
stored.accessToken = this.tokens.accessToken();
stored.refreshToken = this.tokens.refreshToken();
stored.expiresAt = this.tokens.accessTokenExpiresAt();
stored.profileUuid = this.profile;
BsonDocument doc = (BsonDocument)CREDENTIALS_CODEC.encode(stored);
byte[] plaintext = BsonUtil.writeToBytes(doc);
byte[] encrypted = this.encrypt(plaintext);
if (encrypted == null) {
LOGGER.at(Level.SEVERE).log("Failed to encrypt credentials");
return;
}
Files.write(this.path, encrypted);
} catch (IOException var5) {
((HytaleLogger.Api)LOGGER.at(Level.SEVERE).withCause(var5)).log("Failed to save encrypted credentials to %s", this.path);
}
}
}
@Nullable
private byte[] encrypt(@Nonnull byte[] plaintext) {
if (this.encryptionKey == null) {
return null;
} else {
try {
byte[] iv = new byte[12];
new SecureRandom().nextBytes(iv);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(1, this.encryptionKey, new GCMParameterSpec(128, iv));
byte[] ciphertext = cipher.doFinal(plaintext);
ByteBuffer result = ByteBuffer.allocate(iv.length + ciphertext.length);
result.put(iv);
result.put(ciphertext);
return result.array();
} catch (Exception var6) {
((HytaleLogger.Api)LOGGER.at(Level.SEVERE).withCause(var6)).log("Encryption failed");
return null;
}
}
}
@Nullable
private byte[] decrypt(@Nonnull byte[] encrypted) {
if (this.encryptionKey != null && encrypted.length >= 12) {
try {
ByteBuffer buffer = ByteBuffer.wrap(encrypted);
byte[] iv = new byte[12];
buffer.get(iv);
byte[] ciphertext = new byte[buffer.remaining()];
buffer.get(ciphertext);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(2, this.encryptionKey, new GCMParameterSpec(128, iv));
return cipher.doFinal(ciphertext);
} catch (Exception var6) {
((HytaleLogger.Api)LOGGER.at(Level.WARNING).withCause(var6)).log("Decryption failed");
return null;
}
} else {
return null;
}
}
@Override
public void setTokens(@Nonnull IAuthCredentialStore.OAuthTokens tokens) {
this.tokens = tokens;
this.save();
}
@Nonnull
@Override
public IAuthCredentialStore.OAuthTokens getTokens() {
return this.tokens;
}
@Override
public void setProfile(@Nullable UUID uuid) {
this.profile = uuid;
this.save();
}
@Nullable
@Override
public UUID getProfile() {
return this.profile;
}
@Override
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);
}
}
private static class StoredCredentials {
@Nullable
String accessToken;
@Nullable
String refreshToken;
@Nullable
Instant expiresAt;
@Nullable
UUID profileUuid;
private StoredCredentials() {
}
}
}