Skip to content

Commit

Permalink
Support multiple signaturePublickeys
Browse files Browse the repository at this point in the history
Allow authentication servers to specify an array of profile property
public keys instead of just one.

Adds a new field, `signaturePublickeys`, to the authlib-injector
metadata route at the root of the API. `signaturePublickeys` is an
array of strings containing public keys in the same format as the
`signaturePublickey`. Any of the keys specified either in
`signaturePublickeys` or as the `signaturePublickey` can be used to
verify the signature of a textures payload.

With this change, authentication server A can forward a texture payload
from authentication server B, and the client could accept a texture
signed by either authentication server.
  • Loading branch information
evan-goode committed Nov 11, 2023
1 parent da91095 commit 3c11492
Show file tree
Hide file tree
Showing 3 changed files with 23 additions and 9 deletions.
27 changes: 20 additions & 7 deletions src/main/java/moe/yushi/authlibinjector/APIMetadata.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,14 @@
import static java.text.MessageFormat.format;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap;
import static java.util.Collections.emptySet;
import static java.util.Collections.unmodifiableList;
import static java.util.Collections.unmodifiableMap;
import static java.util.Collections.unmodifiableSet;
import static java.util.Objects.requireNonNull;
import static java.util.Optional.ofNullable;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
import static moe.yushi.authlibinjector.util.JsonUtils.asJsonArray;
import static moe.yushi.authlibinjector.util.JsonUtils.asJsonObject;
import static moe.yushi.authlibinjector.util.JsonUtils.parseJson;
Expand All @@ -32,6 +35,7 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
import moe.yushi.authlibinjector.internal.org.json.simple.JSONObject;
import moe.yushi.authlibinjector.util.JsonUtils;
Expand All @@ -54,24 +58,33 @@ public static APIMetadata parse(String apiRoot, String metadataResponse) throws
.map(JsonUtils::asJsonString)
.map(KeyUtils::parseSignaturePublicKey);

Set<PublicKey> decodedPublickeys =
ofNullable(response.get("signaturePublickeys"))
.map(it -> asJsonArray(it).stream()
.map(JsonUtils::asJsonString)
.map(KeyUtils::parseSignaturePublicKey)
.collect(toSet()))
.orElse(emptySet());
decodedPublickey.ifPresent(decodedPublickeys::add);

Map<String, Object> meta =
ofNullable(response.get("meta"))
.map(it -> (Map<String, Object>) new TreeMap<>(asJsonObject(it)))
.orElse(emptyMap());

return new APIMetadata(apiRoot, unmodifiableList(skinDomains), unmodifiableMap(meta), decodedPublickey);
return new APIMetadata(apiRoot, unmodifiableList(skinDomains), unmodifiableMap(meta), unmodifiableSet(decodedPublickeys));
}

private String apiRoot;
private List<String> skinDomains;
private Optional<PublicKey> decodedPublickey;
private Set<PublicKey> decodedPublickeys;
private Map<String, Object> meta;

public APIMetadata(String apiRoot, List<String> skinDomains, Map<String, Object> meta, Optional<PublicKey> decodedPublickey) {
public APIMetadata(String apiRoot, List<String> skinDomains, Map<String, Object> meta, Set<PublicKey> decodedPublickeys) {
this.apiRoot = requireNonNull(apiRoot);
this.skinDomains = requireNonNull(skinDomains);
this.meta = requireNonNull(meta);
this.decodedPublickey = requireNonNull(decodedPublickey);
this.decodedPublickeys = requireNonNull(decodedPublickeys);
}

public String getApiRoot() {
Expand All @@ -86,12 +99,12 @@ public Map<String, Object> getMeta() {
return meta;
}

public Optional<PublicKey> getDecodedPublickey() {
return decodedPublickey;
public Set<PublicKey> getDecodedPublickeys() {
return decodedPublickeys;
}

@Override
public String toString() {
return format("APIMetadata [apiRoot={0}, skinDomains={1}, decodedPublickey={2}, meta={3}]", apiRoot, skinDomains, decodedPublickey, meta);
return format("APIMetadata [apiRoot={0}, skinDomains={1}, decodedPublickeys={2}, meta={3}]", apiRoot, skinDomains, decodedPublickeys, meta);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ private static ClassTransformer createTransformer(APIMetadata config) {
SkinWhitelistTransformUnit.getWhitelistedDomains().addAll(config.getSkinDomains());

transformer.units.add(new YggdrasilKeyTransformUnit());
config.getDecodedPublickey().ifPresent(YggdrasilKeyTransformUnit.PUBLIC_KEYS::add);
YggdrasilKeyTransformUnit.PUBLIC_KEYS.addAll(config.getDecodedPublickeys());
transformer.units.add(new VelocityProfileKeyTransformUnit());
transformer.units.add(new BungeeCordProfileKeyTransformUnit());
MainArgumentsTransformer.getArgumentsListeners().add(new AccountTypeTransformer()::transform);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap;
import static java.util.Collections.emptySet;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.Optional;
import org.junit.jupiter.api.Test;
Expand All @@ -27,7 +28,7 @@
public class DefaultURLRedirectorTest {

private String apiRoot = "https://yggdrasil.example.com/";
private DefaultURLRedirector redirector = new DefaultURLRedirector(new APIMetadata(apiRoot, emptyList(), emptyMap(), Optional.empty()));
private DefaultURLRedirector redirector = new DefaultURLRedirector(new APIMetadata(apiRoot, emptyList(), emptyMap(), emptySet()));

private void testTransform(String domain, String path, String output) {
assertEquals(redirector.redirect(domain, path).get(), output);
Expand Down

0 comments on commit 3c11492

Please sign in to comment.