diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/auth/core/MySQLCoreProvider.java b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/core/MySQLCoreProvider.java index c87f769c..11a72699 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/auth/core/MySQLCoreProvider.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/core/MySQLCoreProvider.java @@ -1,5 +1,7 @@ package pro.gravit.launchserver.auth.core; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.JwtException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import pro.gravit.launcher.ClientPermissions; @@ -13,6 +15,7 @@ import pro.gravit.launchserver.auth.core.interfaces.provider.AuthSupportHardware; import pro.gravit.launchserver.auth.core.interfaces.user.UserSupportHardware; import pro.gravit.launchserver.auth.password.PasswordVerifier; +import pro.gravit.launchserver.helper.LegacySessionHelper; import pro.gravit.launchserver.manangers.AuthManager; import pro.gravit.launchserver.socket.response.auth.AuthResponse; import pro.gravit.utils.helper.IOHelper; @@ -21,6 +24,8 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.sql.*; +import java.time.Duration; +import java.time.LocalDateTime; import java.util.Base64; import java.util.LinkedList; import java.util.List; @@ -30,6 +35,7 @@ public class MySQLCoreProvider extends AuthCoreProvider implements AuthSupportHa private transient final Logger logger = LogManager.getLogger(); public MySQLSourceConfig mySQLHolder; + public int expireSeconds = 3600; public String uuidColumn; public String usernameColumn; public String accessTokenColumn; @@ -63,6 +69,8 @@ public class MySQLCoreProvider extends AuthCoreProvider implements AuthSupportHa private transient String updateAuthSQL; private transient String updateServerIDSQL; + private transient LaunchServer server; + @Override public User getUserByUsername(String username) { try { @@ -95,12 +103,38 @@ public User getUserByLogin(String login) { @Override public UserSession getUserSessionByOAuthAccessToken(String accessToken) throws OAuthAccessTokenExpired { - return null; + try { + var info = LegacySessionHelper.getJwtInfoFromAccessToken(accessToken, server.keyAgreementManager.ecdsaPublicKey); + var user = (MySQLUser) getUserByUUID(info.uuid()); + if(user == null) { + return null; + } + return new MySQLUserSession(user); + } catch (ExpiredJwtException e) { + throw new OAuthAccessTokenExpired(); + } catch (JwtException e) { + return null; + } } @Override public AuthManager.AuthReport refreshAccessToken(String refreshToken, AuthResponse.AuthContext context) { - return null; + String[] parts = refreshToken.split("\\."); + if(parts.length != 2) { + return null; + } + String username = parts[0]; + String token = parts[1]; + var user = (MySQLUser) getUserByUsername(username); + if(user == null || user.password == null) { + return null; + } + var realToken = LegacySessionHelper.makeRefreshTokenFromPassword(username, user.password, server.keyAgreementManager.legacySalt); + if(!token.equals(realToken)) { + return null; + } + var accessToken = LegacySessionHelper.makeAccessJwtTokenFromString(user, LocalDateTime.now().plusSeconds(expireSeconds), server.keyAgreementManager.ecdsaPrivateKey); + return new AuthManager.AuthReport(null, accessToken, refreshToken, expireSeconds * 1000L, new MySQLUserSession(user)); } @Override @@ -119,17 +153,20 @@ public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext c } } MySQLUserSession session = new MySQLUserSession(mySQLUser); + var accessToken = LegacySessionHelper.makeAccessJwtTokenFromString(mySQLUser, LocalDateTime.now().plusSeconds(expireSeconds), server.keyAgreementManager.ecdsaPrivateKey); + var refreshToken = LegacySessionHelper.makeRefreshTokenFromPassword(mySQLUser.username, mySQLUser.password, server.keyAgreementManager.legacySalt); if (minecraftAccess) { String minecraftAccessToken = SecurityHelper.randomStringToken(); updateAuth(mySQLUser, minecraftAccessToken); - return AuthManager.AuthReport.ofMinecraftAccessToken(minecraftAccessToken, session); + return AuthManager.AuthReport.ofOAuthWithMinecraft(minecraftAccessToken, accessToken, refreshToken, expireSeconds * 1000L, session); } else { - return AuthManager.AuthReport.ofMinecraftAccessToken(null, session); + return AuthManager.AuthReport.ofOAuth(accessToken, refreshToken, expireSeconds * 1000L, session); } } @Override public void init(LaunchServer server) { + this.server = server; if (mySQLHolder == null) logger.error("mySQLHolder cannot be null"); if (uuidColumn == null) logger.error("uuidColumn cannot be null"); if (usernameColumn == null) logger.error("usernameColumn cannot be null"); diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/auth/core/PostgresSQLCoreProvider.java b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/core/PostgresSQLCoreProvider.java index 747f089e..e1f99ac9 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/auth/core/PostgresSQLCoreProvider.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/core/PostgresSQLCoreProvider.java @@ -1,5 +1,7 @@ package pro.gravit.launchserver.auth.core; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.JwtException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import pro.gravit.launcher.ClientPermissions; @@ -10,18 +12,21 @@ import pro.gravit.launchserver.auth.MySQLSourceConfig; import pro.gravit.launchserver.auth.PostgreSQLSourceConfig; import pro.gravit.launchserver.auth.password.PasswordVerifier; +import pro.gravit.launchserver.helper.LegacySessionHelper; import pro.gravit.launchserver.manangers.AuthManager; import pro.gravit.launchserver.socket.response.auth.AuthResponse; import pro.gravit.utils.helper.SecurityHelper; import java.io.IOException; import java.sql.*; +import java.time.LocalDateTime; import java.util.UUID; public class PostgresSQLCoreProvider extends AuthCoreProvider { private transient final Logger logger = LogManager.getLogger(); public PostgreSQLSourceConfig postgresSQLHolder; + public int expireSeconds = 3600; public String uuidColumn; public String usernameColumn; public String accessTokenColumn; @@ -42,6 +47,8 @@ public class PostgresSQLCoreProvider extends AuthCoreProvider { private transient String updateAuthSQL; private transient String updateServerIDSQL; + private transient LaunchServer server; + @Override public User getUserByUsername(String username) { try { @@ -74,12 +81,38 @@ public User getUserByLogin(String login) { @Override public UserSession getUserSessionByOAuthAccessToken(String accessToken) throws OAuthAccessTokenExpired { - return null; + try { + var info = LegacySessionHelper.getJwtInfoFromAccessToken(accessToken, server.keyAgreementManager.ecdsaPublicKey); + var user = (MySQLCoreProvider.MySQLUser) getUserByUUID(info.uuid()); + if(user == null) { + return null; + } + return new MySQLCoreProvider.MySQLUserSession(user); + } catch (ExpiredJwtException e) { + throw new OAuthAccessTokenExpired(); + } catch (JwtException e) { + return null; + } } @Override public AuthManager.AuthReport refreshAccessToken(String refreshToken, AuthResponse.AuthContext context) { - return null; + String[] parts = refreshToken.split("\\."); + if(parts.length != 2) { + return null; + } + String username = parts[0]; + String token = parts[1]; + var user = (MySQLCoreProvider.MySQLUser) getUserByUsername(username); + if(user == null || user.password == null) { + return null; + } + var realToken = LegacySessionHelper.makeRefreshTokenFromPassword(username, user.password, server.keyAgreementManager.legacySalt); + if(!token.equals(realToken)) { + return null; + } + var accessToken = LegacySessionHelper.makeAccessJwtTokenFromString(user, LocalDateTime.now().plusSeconds(expireSeconds), server.keyAgreementManager.ecdsaPrivateKey); + return new AuthManager.AuthReport(null, accessToken, refreshToken, expireSeconds * 1000L, new MySQLCoreProvider.MySQLUserSession(user)); } @Override @@ -98,17 +131,20 @@ public AuthManager.AuthReport authorize(String login, AuthResponse.AuthContext c } } MySQLUserSession session = new MySQLUserSession(postgresSQLUser); + var accessToken = LegacySessionHelper.makeAccessJwtTokenFromString(postgresSQLUser, LocalDateTime.now().plusSeconds(expireSeconds), server.keyAgreementManager.ecdsaPrivateKey); + var refreshToken = LegacySessionHelper.makeRefreshTokenFromPassword(postgresSQLUser.username, postgresSQLUser.password, server.keyAgreementManager.legacySalt); if (minecraftAccess) { String minecraftAccessToken = SecurityHelper.randomStringToken(); updateAuth(postgresSQLUser, minecraftAccessToken); - return AuthManager.AuthReport.ofMinecraftAccessToken(minecraftAccessToken, session); + return AuthManager.AuthReport.ofOAuthWithMinecraft(minecraftAccessToken, accessToken, refreshToken, expireSeconds * 1000L, session); } else { - return AuthManager.AuthReport.ofMinecraftAccessToken(null, session); + return AuthManager.AuthReport.ofOAuth(accessToken, refreshToken, expireSeconds * 1000L, session); } } @Override public void init(LaunchServer server) { + this.server = server; if (postgresSQLHolder == null) logger.error("postgresSQLHolder cannot be null"); if (uuidColumn == null) logger.error("uuidColumn cannot be null"); if (usernameColumn == null) logger.error("usernameColumn cannot be null"); diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/helper/LegacySessionHelper.java b/LaunchServer/src/main/java/pro/gravit/launchserver/helper/LegacySessionHelper.java new file mode 100644 index 00000000..29e7918d --- /dev/null +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/helper/LegacySessionHelper.java @@ -0,0 +1,45 @@ +package pro.gravit.launchserver.helper; + +import io.jsonwebtoken.Jwts; +import pro.gravit.launchserver.auth.core.User; +import pro.gravit.utils.helper.SecurityHelper; + +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.Date; +import java.util.UUID; + +public class LegacySessionHelper { + public static String makeAccessJwtTokenFromString(User user, LocalDateTime expirationTime, ECPrivateKey privateKey) { + return Jwts.builder() + .setIssuer("LaunchServer") + .setSubject(user.getUsername()) + .claim("uuid", user.getUUID().toString()) + .setExpiration(Date.from(expirationTime + .toInstant(ZoneOffset.UTC))) + .signWith(privateKey) + .compact(); + } + + public static JwtTokenInfo getJwtInfoFromAccessToken(String token, ECPublicKey publicKey) { + var parser = Jwts.parserBuilder() + .requireIssuer("LaunchServer") + .setSigningKey(publicKey) + .build(); + var claims = parser.parseClaimsJws(token); + var uuid = UUID.fromString(claims.getBody().get("uuid", String.class)); + var username = claims.getBody().getSubject(); + return new JwtTokenInfo(username, uuid); + } + + public static String makeRefreshTokenFromPassword(String username, String rawPassword, String secretSalt) { + return SecurityHelper.toHex(SecurityHelper.digest(SecurityHelper.DigestAlgorithm.SHA256, + String.format("%s.%s.%s.%s", secretSalt, username, rawPassword, secretSalt))); + } + + public record JwtTokenInfo(String username, UUID uuid) { + } +} diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/manangers/KeyAgreementManager.java b/LaunchServer/src/main/java/pro/gravit/launchserver/manangers/KeyAgreementManager.java index cdb33947..8658b907 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/manangers/KeyAgreementManager.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/manangers/KeyAgreementManager.java @@ -6,6 +6,7 @@ import pro.gravit.utils.helper.SecurityHelper; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.security.KeyPair; import java.security.SecureRandom; @@ -20,13 +21,15 @@ public class KeyAgreementManager { public final ECPrivateKey ecdsaPrivateKey; public final RSAPublicKey rsaPublicKey; public final RSAPrivateKey rsaPrivateKey; + public final String legacySalt; private transient final Logger logger = LogManager.getLogger(); - public KeyAgreementManager(ECPublicKey ecdsaPublicKey, ECPrivateKey ecdsaPrivateKey, RSAPublicKey rsaPublicKey, RSAPrivateKey rsaPrivateKey) { + public KeyAgreementManager(ECPublicKey ecdsaPublicKey, ECPrivateKey ecdsaPrivateKey, RSAPublicKey rsaPublicKey, RSAPrivateKey rsaPrivateKey, String legacySalt) { this.ecdsaPublicKey = ecdsaPublicKey; this.ecdsaPrivateKey = ecdsaPrivateKey; this.rsaPublicKey = rsaPublicKey; this.rsaPrivateKey = rsaPrivateKey; + this.legacySalt = legacySalt; } public KeyAgreementManager(Path keyDirectory) throws IOException, InvalidKeySpecException { @@ -62,5 +65,12 @@ public KeyAgreementManager(Path keyDirectory) throws IOException, InvalidKeySpec IOHelper.write(rsaPublicKeyPath, rsaPublicKey.getEncoded()); IOHelper.write(rsaPrivateKeyPath, rsaPrivateKey.getEncoded()); } + Path legacySaltPath = keyDirectory.resolve("legacySalt"); + if(IOHelper.isFile(legacySaltPath)) { + legacySalt = new String(IOHelper.read(legacySaltPath), StandardCharsets.UTF_8); + } else { + legacySalt = SecurityHelper.randomStringToken(); + IOHelper.write(legacySaltPath, legacySalt.getBytes(StandardCharsets.UTF_8)); + } } } diff --git a/LauncherCore/src/main/java/pro/gravit/utils/Version.java b/LauncherCore/src/main/java/pro/gravit/utils/Version.java index af759962..476c2e85 100644 --- a/LauncherCore/src/main/java/pro/gravit/utils/Version.java +++ b/LauncherCore/src/main/java/pro/gravit/utils/Version.java @@ -6,9 +6,9 @@ public final class Version implements Comparable { public static final int MAJOR = 5; public static final int MINOR = 2; - public static final int PATCH = 8; + public static final int PATCH = 9; public static final int BUILD = 1; - public static final Version.Type RELEASE = Type.STABLE; + public static final Version.Type RELEASE = Type.EXPERIMENTAL; public final int major; public final int minor; public final int patch;