Skip to content

Commit

Permalink
Moved JWT code into separate class to allow exclusion of the JWT library
Browse files Browse the repository at this point in the history
  • Loading branch information
RaphiMC committed Nov 6, 2024
1 parent 09c84d3 commit 98805b6
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@

import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import lombok.*;
import lombok.experimental.NonFinal;
import lombok.experimental.PackagePrivate;
Expand All @@ -33,6 +30,7 @@
import net.raphimc.minecraftauth.step.xbl.StepXblXstsToken;
import net.raphimc.minecraftauth.util.CryptUtil;
import net.raphimc.minecraftauth.util.JsonContent;
import net.raphimc.minecraftauth.util.JwtUtil;
import net.raphimc.minecraftauth.util.logging.ILogger;
import org.jetbrains.annotations.ApiStatus;

Expand All @@ -45,8 +43,6 @@
import java.util.Map;
import java.util.UUID;

import static net.raphimc.minecraftauth.util.TimeUtil.MAX_JWT_CLOCK_SKEW;

public class StepMCChain extends AbstractStep<StepXblXstsToken.XblXsts<?>, StepMCChain.MCChain> {

public static final String MINECRAFT_LOGIN_URL = "https://multiplayer.minecraft.net/authentication";
Expand Down Expand Up @@ -80,11 +76,11 @@ protected MCChain execute(final ILogger logger, final HttpClient httpClient, fin
throw new IllegalStateException("Invalid chain size");
}

final Jws<Claims> mojangJwt = Jwts.parser().clockSkewSeconds(MAX_JWT_CLOCK_SKEW).verifyWith(MOJANG_PUBLIC_KEY).build().parseSignedClaims(chain.get(0).getAsString());
final ECPublicKey mojangJwtPublicKey = CryptUtil.publicKeyEcFromBase64(mojangJwt.getPayload().get("identityPublicKey", String.class));
final Jws<Claims> identityJwt = Jwts.parser().clockSkewSeconds(MAX_JWT_CLOCK_SKEW).verifyWith(mojangJwtPublicKey).build().parseSignedClaims(chain.get(1).getAsString());
final JwtUtil.Jwt mojangJwt = JwtUtil.parseSignedJwt(chain.get(0).getAsString(), MOJANG_PUBLIC_KEY);
final ECPublicKey mojangJwtPublicKey = CryptUtil.publicKeyEcFromBase64(mojangJwt.getClaim("identityPublicKey", String.class));
final JwtUtil.Jwt identityJwt = JwtUtil.parseSignedJwt(chain.get(1).getAsString(), mojangJwtPublicKey);

final Map<String, Object> extraData = identityJwt.getPayload().get("extraData", Map.class);
final Map<String, Object> extraData = identityJwt.getClaim("extraData", Map.class);
final String xuid = (String) extraData.get("XUID");
final UUID id = UUID.fromString((String) extraData.get("identity"));
final String displayName = (String) extraData.get("displayName");
Expand Down Expand Up @@ -186,9 +182,9 @@ public boolean isExpired() {

this.lastExpireCheckTimeMs = System.currentTimeMillis();
try {
final Jws<Claims> mojangJwt = Jwts.parser().clockSkewSeconds(MAX_JWT_CLOCK_SKEW).verifyWith(MOJANG_PUBLIC_KEY).build().parseSignedClaims(this.mojangJwt);
final ECPublicKey mojangJwtPublicKey = CryptUtil.publicKeyEcFromBase64(mojangJwt.getPayload().get("identityPublicKey", String.class));
Jwts.parser().clockSkewSeconds(MAX_JWT_CLOCK_SKEW).verifyWith(mojangJwtPublicKey).build().parseSignedClaims(this.identityJwt);
final JwtUtil.Jwt mojangJwt = JwtUtil.parseSignedJwt(this.mojangJwt, MOJANG_PUBLIC_KEY);
final ECPublicKey mojangJwtPublicKey = CryptUtil.publicKeyEcFromBase64(mojangJwt.getClaim("identityPublicKey", String.class));
JwtUtil.parseSignedJwt(this.identityJwt, mojangJwtPublicKey);
this.lastExpireCheckResult = false;
} catch (Throwable e) { // Any error -> The jwts are expired or invalid
this.lastExpireCheckResult = true;
Expand Down
5 changes: 1 addition & 4 deletions src/main/java/net/raphimc/minecraftauth/util/CryptUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,11 @@
package net.raphimc.minecraftauth.util;

import com.google.gson.JsonObject;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.impl.security.DefaultSecureRequest;
import net.lenni0451.commons.httpclient.content.HttpContent;
import net.lenni0451.commons.httpclient.model.HttpHeader;
import net.lenni0451.commons.httpclient.requests.HttpContentRequest;
import net.lenni0451.commons.httpclient.requests.HttpRequest;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
Expand Down Expand Up @@ -130,7 +127,7 @@ public static HttpHeader getSignatureHeader(final HttpRequest httpRequest, final
ecdsaSignature.update(signatureContent.toByteArray());
signature = ecdsaSignature.sign();
} catch (NoSuchAlgorithmException e) { // Fallback for Java 8
signature = Jwts.SIG.ES256.digest(new DefaultSecureRequest<>(new ByteArrayInputStream(signatureContent.toByteArray()), null, null, privateKey));
signature = JwtUtil.signES256(privateKey, signatureContent.toByteArray());
}
data.write(signature); // Signature
} catch (Throwable e) {
Expand Down
60 changes: 60 additions & 0 deletions src/main/java/net/raphimc/minecraftauth/util/JwtUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* This file is part of MinecraftAuth - https://github.com/RaphiMC/MinecraftAuth
* Copyright (C) 2022-2024 RK_01/RaphiMC and contributors
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.raphimc.minecraftauth.util;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.impl.security.DefaultSecureRequest;
import org.jetbrains.annotations.ApiStatus;

import java.io.ByteArrayInputStream;
import java.security.PrivateKey;
import java.security.PublicKey;

import static net.raphimc.minecraftauth.util.TimeUtil.MAX_JWT_CLOCK_SKEW;

/**
* Utility class for handling JWTs. Code is intentionally kept separate from the rest of the project to allow for the exclusion of the JWT library.
*/
@ApiStatus.Internal
public class JwtUtil {

public static Jwt parseSignedJwt(final String jwt, final PublicKey publicKey) {
final Jws<Claims> parsedJwt = Jwts.parser().clockSkewSeconds(MAX_JWT_CLOCK_SKEW).verifyWith(publicKey).build().parseSignedClaims(jwt);
return new Jwt() {

@Override
public <T> T getClaim(final String claimName, final Class<T> requiredType) {
return parsedJwt.getPayload().get(claimName, requiredType);
}

};
}

public static byte[] signES256(final PrivateKey privateKey, final byte[] data) {
return Jwts.SIG.ES256.digest(new DefaultSecureRequest<>(new ByteArrayInputStream(data), null, null, privateKey));
}

public interface Jwt {

<T> T getClaim(final String claimName, final Class<T> requiredType);

}

}

0 comments on commit 98805b6

Please sign in to comment.