Verify the minecraft jar signature (#1282)

* Verify the minecraft jar cert

* Don't verify old server jars.

* Checkstyle

* Unit test fixes

* Add a list of known version hashes for versions that we cannot verify the jar signature.

Either the versions arent signed, or are signed with a SHA-1.

* Only verify minecraft jars when they were actually downloaded again.

* Add property to disable verification

* Fix import

* Fix bundled jars
This commit is contained in:
modmuss
2025-04-07 11:53:55 +01:00
committed by GitHub
parent dbe1408a72
commit 186b774a2e
31 changed files with 2402 additions and 33 deletions

View File

@@ -155,6 +155,9 @@ dependencies {
}
testImplementation testLibs.mockito
testImplementation testLibs.java.debug
testImplementation testLibs.bcprov
testImplementation testLibs.bcutil
testImplementation testLibs.bcpkix
compileOnly runtimeLibs.jetbrains.annotations
testCompileOnly runtimeLibs.jetbrains.annotations

View File

@@ -5,6 +5,7 @@ javalin = "6.3.0"
mockito = "5.14.2"
java-debug = "0.52.0"
mixin = "0.15.3+mixin.0.8.7"
bouncycastle = "1.80"
gradle-nightly = "8.14-20250225001625+0000"
fabric-loader = "0.16.9"
@@ -18,4 +19,7 @@ mockito = { module = "org.mockito:mockito-core", version.ref = "mockito" }
java-debug = { module = "com.microsoft.java:com.microsoft.java.debug.core", version.ref = "java-debug" }
mixin = { module = "net.fabricmc:sponge-mixin", version.ref = "mixin" }
gradle-nightly = { module = "org.gradle:dummy", version.ref = "gradle-nightly" }
fabric-loader = { module = "net.fabricmc:fabric-loader", version.ref = "fabric-loader" }
fabric-loader = { module = "net.fabricmc:fabric-loader", version.ref = "fabric-loader" }
bcprov = { module = "org.bouncycastle:bcprov-jdk18on", version.ref = "bouncycastle" }
bcpkix = { module = "org.bouncycastle:bcpkix-jdk18on", version.ref = "bouncycastle" }
bcutil = { module = "org.bouncycastle:bcutil-jdk18on", version.ref = "bouncycastle" }

View File

@@ -93,7 +93,6 @@ public final class MergedMinecraftProvider extends MinecraftProvider {
File minecraftServerJar = getMinecraftServerJar();
if (getServerBundleMetadata() != null) {
extractBundledServerJar();
minecraftServerJar = getMinecraftExtractedServerJar();
}

View File

@@ -29,6 +29,7 @@ import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import com.google.common.base.Preconditions;
import org.gradle.api.JavaVersion;
@@ -41,9 +42,12 @@ import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.ConfigContext;
import net.fabricmc.loom.configuration.providers.BundleMetadata;
import net.fabricmc.loom.configuration.providers.minecraft.verify.MinecraftJarVerification;
import net.fabricmc.loom.configuration.providers.minecraft.verify.SignatureVerificationFailure;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.download.DownloadExecutor;
import net.fabricmc.loom.util.download.GradleDownloadProgressListener;
import net.fabricmc.loom.util.gradle.GradleUtils;
import net.fabricmc.loom.util.gradle.ProgressGroup;
public abstract class MinecraftProvider {
@@ -88,10 +92,18 @@ public abstract class MinecraftProvider {
}
}
downloadJars();
boolean didDownload = downloadJars();
if (provideServer()) {
serverBundleMetadata = BundleMetadata.fromJar(minecraftServerJar.toPath());
if (serverBundleMetadata != null) {
extractBundledServerJar();
}
}
if (didDownload) {
verifyJars();
}
final MinecraftLibraryProvider libraryProvider = new MinecraftLibraryProvider(this, configContext.project());
@@ -109,7 +121,34 @@ public abstract class MinecraftProvider {
}
}
private void downloadJars() throws IOException {
private void verifyJars() throws IOException, SignatureVerificationFailure {
if (GradleUtils.getBooleanProperty(getProject(), Constants.Properties.DISABLE_MINECRAFT_VERIFICATION)) {
LOGGER.info("Skipping Minecraft jar verification!");
}
LOGGER.info("Verifying Minecraft jars");
MinecraftJarVerification verification = getProject().getObjects().newInstance(MinecraftJarVerification.class, minecraftVersion());
if (provideClient()) {
verification.verifyClientJar(minecraftClientJar.toPath());
}
if (provideServer()) {
if (serverBundleMetadata == null) {
verification.verifyServerJar(minecraftServerJar.toPath());
} else {
verification.verifyServerJar(getMinecraftExtractedServerJar().toPath());
}
}
LOGGER.info("Jar verification complete");
}
// Returns true when a file was downloaded
private boolean downloadJars() throws IOException {
AtomicBoolean didDownload = new AtomicBoolean(false);
try (ProgressGroup progressGroup = new ProgressGroup(getProject(), "Download Minecraft jars");
DownloadExecutor executor = new DownloadExecutor(2)) {
if (provideClient()) {
@@ -117,7 +156,12 @@ public abstract class MinecraftProvider {
getExtension().download(client.url())
.sha1(client.sha1())
.progress(new GradleDownloadProgressListener("Minecraft client", progressGroup::createProgressLogger))
.downloadPathAsync(minecraftClientJar.toPath(), executor);
.downloadPathAsync(minecraftClientJar.toPath(), executor)
.thenAccept(downloadResult -> {
if (downloadResult.didDownload()) {
didDownload.set(true);
}
});
}
if (provideServer()) {
@@ -125,12 +169,25 @@ public abstract class MinecraftProvider {
getExtension().download(server.url())
.sha1(server.sha1())
.progress(new GradleDownloadProgressListener("Minecraft server", progressGroup::createProgressLogger))
.downloadPathAsync(minecraftServerJar.toPath(), executor);
.downloadPathAsync(minecraftServerJar.toPath(), executor)
.thenAccept(downloadResult -> {
if (downloadResult.didDownload()) {
didDownload.set(true);
}
});
}
}
if (didDownload.get()) {
LOGGER.info("Downloaded new Minecraft jars");
return true;
}
LOGGER.info("Using cached Minecraft jars");
return false;
}
protected final void extractBundledServerJar() throws IOException {
private void extractBundledServerJar() throws IOException {
Preconditions.checkArgument(provideServer(), "Not configured to provide server jar");
Objects.requireNonNull(getServerBundleMetadata(), "Cannot bundled mc jar from none bundled server jar");

View File

@@ -139,14 +139,13 @@ public abstract sealed class SingleJarMinecraftProvider extends MinecraftProvide
}
@Override
public Path getInputJar(SingleJarMinecraftProvider provider) throws Exception {
public Path getInputJar(SingleJarMinecraftProvider provider) {
BundleMetadata serverBundleMetadata = provider.getServerBundleMetadata();
if (serverBundleMetadata == null) {
return provider.getMinecraftServerJar().toPath();
}
provider.extractBundledServerJar();
return provider.getMinecraftExtractedServerJar().toPath();
}

View File

@@ -74,8 +74,6 @@ public final class SplitMinecraftProvider extends MinecraftProvider {
throw new UnsupportedOperationException("Only Minecraft versions using a bundled server jar can be split, please use a merged jar setup for this version of minecraft");
}
extractBundledServerJar();
final Path clientJar = getMinecraftClientJar().toPath();
final Path serverJar = getMinecraftExtractedServerJar().toPath();

View File

@@ -0,0 +1,194 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2025 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.configuration.providers.minecraft.verify;
import java.io.IOException;
import java.io.InputStream;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.Nullable;
/**
* A node in the certificate chain.
*/
public interface CertificateChain {
/**
* The certificate itself.
*/
X509Certificate certificate();
/**
* The issuer of this certificate, or null if this is a root certificate.
*/
@Nullable CertificateChain issuer();
/**
* The children of this certificate, or an empty list if this is a leaf certificate.
*/
List<CertificateChain> children();
/**
* Verify that this certificate chain matches exactly with another one.
* @param other the other certificate chain
*/
void verifyChainMatches(CertificateChain other) throws SignatureVerificationFailure;
/**
* Recursively visit all certificates in the chain, including this one.
*/
static void visitAll(CertificateChain chain, CertificateConsumer consumer) throws SignatureVerificationFailure {
consumer.accept(chain.certificate());
for (CertificateChain child : chain.children()) {
visitAll(child, consumer);
}
}
/**
* Load certificate chain from the classpath, returning the root certificate.
*/
static CertificateChain getRoot(String name) throws IOException {
try (InputStream is = JarVerifier.class.getClassLoader().getResourceAsStream("certs/" + name + ".cer")) {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Collection<X509Certificate> certificates = cf.generateCertificates(is).stream()
.map(c -> (X509Certificate) c)
.toList();
return getRoot(certificates);
} catch (CertificateException e) {
throw new RuntimeException("Failed to load certificate: " + name, e);
}
}
/**
* Takes an unordered collection of certificates and builds a tree structure.
*/
static CertificateChain getRoot(Collection<X509Certificate> certificates) {
Map<String, Impl> certificateNodes = new HashMap<>();
for (X509Certificate certificate : certificates) {
Impl node = new Impl();
node.certificate = certificate;
certificateNodes.put(certificate.getSubjectX500Principal().getName(), node);
}
for (X509Certificate certificate : certificates) {
String subject = certificate.getSubjectX500Principal().getName();
String issuer = certificate.getIssuerX500Principal().getName();
if (subject.equals(issuer)) {
continue; // self-signed
}
Impl parent = certificateNodes.get(issuer);
Impl self = certificateNodes.get(subject);
if (parent == self) {
throw new IllegalStateException("Certificate " + subject + " is its own issuer");
}
if (parent == null) {
throw new IllegalStateException("Certificate " + subject + " defines issuer " + issuer + " which is not in the chain");
}
parent.children.add(self);
self.issuer = parent;
}
List<Impl> roots = certificateNodes.values()
.stream()
.filter(node -> node.issuer == null)
.toList();
if (roots.size() != 1) {
throw new IllegalStateException("Expected exactly one root certificate, but found " + roots.size());
}
return roots.get(0);
}
@FunctionalInterface
interface CertificateConsumer {
void accept(X509Certificate certificate) throws SignatureVerificationFailure;
}
class Impl implements CertificateChain {
X509Certificate certificate;
@Nullable CertificateChain.Impl issuer;
List<CertificateChain> children = new ArrayList<>();
private Impl() {
}
@Override
public X509Certificate certificate() {
return certificate;
}
@Override
public @Nullable CertificateChain issuer() {
return issuer;
}
@Override
public List<CertificateChain> children() {
return children;
}
@Override
public void verifyChainMatches(CertificateChain other) throws SignatureVerificationFailure {
if (!this.certificate().equals(other.certificate())) {
throw new SignatureVerificationFailure("Certificate mismatch: " + this + " != " + other);
}
if (this.children().size() != other.children().size()) {
throw new SignatureVerificationFailure("Certificate mismatch: " + this + " has " + this.children().size() + " children, but " + other + " has " + other.children().size());
}
if (this.children.isEmpty()) {
// Fine, leaf certificate
return;
}
if (this.children.size() != 1) {
// TODO support this, not needed currently
throw new UnsupportedOperationException("Validating Certificate chain with multiple children is not supported");
}
this.children.get(0).verifyChainMatches(other.children().get(0));
}
@Override
public String toString() {
return certificate.getSubjectX500Principal().getName();
}
}
}

View File

@@ -0,0 +1,122 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2025 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.configuration.providers.minecraft.verify;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.cert.CRLException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509CRL;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.gradle.api.Project;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.util.download.DownloadException;
public record CertificateRevocationList(Collection<X509CRL> crls, boolean downloadFailure) {
/**
* Hardcoded CRLs for Mojang's certificate, we don't want to add a large dependency just to parse this each time.
*/
public static final List<String> CSC3_2010 = List.of(
"http://crl.verisign.com/pca3-g5.crl",
"http://crl.verisign.com/pca3.crl",
"http://csc3-2010-crl.verisign.com/CSC3-2010.crl"
);
private static final Logger LOGGER = LoggerFactory.getLogger(CertificateRevocationList.class);
/**
* Attempt to download the CRL from the given URL, if we fail to get it its not the end of the world.
*/
public static CertificateRevocationList create(Project project, List<String> urls) throws IOException {
List<X509CRL> crls = new ArrayList<>();
boolean downloadFailure = false;
for (String url : urls) {
try {
crls.add(download(project, url));
} catch (DownloadException e) {
LOGGER.warn("Failed to download CRL from {}: {}", url, e.getMessage());
LOGGER.warn("Loom will not be able to verify the integrity of the minecraft jar");
downloadFailure = true;
}
}
return new CertificateRevocationList(crls, downloadFailure);
}
static X509CRL download(Project project, String url) throws IOException {
final LoomGradleExtension extension = LoomGradleExtension.get(project);
final String name = url.substring(url.lastIndexOf('/') + 1);
final Path path = extension.getFiles().getUserCache().toPath()
.resolve("crl")
.resolve(name);
LOGGER.info("Downloading CRL from {} to {}", url, path);
extension.download(url)
.allowInsecureProtocol()
.maxAge(Duration.ofDays(7)) // Cache the CRL for a week
.downloadPath(path);
return parse(path);
}
static X509CRL parse(Path path) throws IOException {
try (InputStream inStream = Files.newInputStream(path)) {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
return (X509CRL) cf.generateCRL(inStream);
} catch (CRLException | CertificateException e) {
throw new RuntimeException(e);
}
}
/**
* Verify that none of the certs in the chain are revoked.
* @throws SignatureVerificationFailure if the certificate is revoked
*/
public void verify(CertificateChain certificateChain) throws SignatureVerificationFailure {
CertificateChain.visitAll(certificateChain, this::verify);
}
private void verify(X509Certificate certificate) throws SignatureVerificationFailure {
for (X509CRL crl : crls) {
if (crl.isRevoked(certificate)) {
throw new SignatureVerificationFailure("Certificate " + certificate.getSubjectX500Principal().getName() + " is revoked");
}
}
}
}

View File

@@ -0,0 +1,92 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2025 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.configuration.providers.minecraft.verify;
import java.io.IOException;
import java.nio.file.Path;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.fabricmc.loom.util.ZipReprocessorUtil;
public final class JarVerifier {
private static final Logger LOGGER = LoggerFactory.getLogger(JarVerifier.class);
private JarVerifier() {
}
public static void verify(Path jarPath, CertificateChain certificateChain) throws IOException, SignatureVerificationFailure {
Objects.requireNonNull(jarPath, "jarPath");
Objects.requireNonNull(certificateChain, "certificateChain");
if (certificateChain.issuer() != null) {
throw new IllegalStateException("Can only verify jars from a root certificate");
}
Set<X509Certificate> jarCertificates = new HashSet<>();
try (JarFile jarFile = new JarFile(jarPath.toFile(), true)) {
for (JarEntry jarEntry : Collections.list(jarFile.entries())) {
if (ZipReprocessorUtil.isSpecialFile(jarEntry.getName())
|| jarEntry.getName().equals("META-INF/MANIFEST.MF")
|| jarEntry.isDirectory()) {
continue;
}
try {
// Must read the entire entry to trigger the signature verification
byte[] bytes = jarFile.getInputStream(jarEntry).readAllBytes();
} catch (SecurityException e) {
throw new SignatureVerificationFailure("Jar entry " + jarEntry.getName() + " failed signature verification", e);
}
Certificate[] entryCertificates = jarEntry.getCertificates();
if (entryCertificates == null) {
throw new SignatureVerificationFailure("Jar entry " + jarEntry.getName() + " does not have a signature");
}
Arrays.stream(entryCertificates)
.map(c -> (X509Certificate) c)
.forEach(jarCertificates::add);
}
}
CertificateChain jarCertificateChain = CertificateChain.getRoot(jarCertificates);
jarCertificateChain.verifyChainMatches(certificateChain);
LOGGER.debug("Jar {} is signed by the expected certificate", jarPath);
}
}

View File

@@ -0,0 +1,57 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2025 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.configuration.providers.minecraft.verify;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UncheckedIOException;
import java.util.Map;
import java.util.Objects;
import java.util.function.Supplier;
import com.google.common.base.Suppliers;
import net.fabricmc.loom.LoomGradlePlugin;
/**
* The know versions keep track of the versions that are signed using SHA1 or not signature at all.
* The maps are the Minecraft version to sha256 hash of the jar file.
*/
public record KnownVersions(
Map<String, String> client,
Map<String, String> server) {
public static final Supplier<KnownVersions> INSTANCE = Suppliers.memoize(KnownVersions::load);
private static KnownVersions load() {
try (InputStream is = KnownVersions.class.getClassLoader().getResourceAsStream("certs/known_versions.json");
Reader reader = new InputStreamReader(Objects.requireNonNull(is))) {
return LoomGradlePlugin.GSON.fromJson(reader, KnownVersions.class);
} catch (IOException e) {
throw new UncheckedIOException("Failed to load known versions", e);
}
}
}

View File

@@ -0,0 +1,113 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2025 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.configuration.providers.minecraft.verify;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
import javax.inject.Inject;
import com.google.common.base.Function;
import org.gradle.api.Project;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.fabricmc.loom.util.Checksum;
public abstract class MinecraftJarVerification {
private static final Logger LOGGER = LoggerFactory.getLogger(MinecraftJarVerification.class);
private final String minecraftVersion;
@Inject
protected abstract Project getProject();
@Inject
public MinecraftJarVerification(String minecraftVersion) {
this.minecraftVersion = minecraftVersion;
}
public void verifyClientJar(Path path) throws IOException, SignatureVerificationFailure {
verifyJarSignature(path, KnownJarType.CLIENT);
}
public void verifyServerJar(Path path) throws IOException, SignatureVerificationFailure {
verifyJarSignature(path, KnownJarType.SERVER);
}
private void verifyJarSignature(Path path, KnownJarType type) throws IOException, SignatureVerificationFailure {
CertificateChain chain = CertificateChain.getRoot("mojangcs");
CertificateRevocationList revocationList = CertificateRevocationList.create(getProject(), CertificateRevocationList.CSC3_2010);
try {
revocationList.verify(chain);
JarVerifier.verify(path, chain);
} catch (SignatureVerificationFailure e) {
if (isValidKnownVersion(path, minecraftVersion, type)) {
LOGGER.info("Minecraft {} signature verification failed, but is a known version", path.getFileName());
return;
}
LOGGER.error("Verification of Minecraft {} signature failed: {}", path.getFileName(), e.getMessage());
throw e;
}
}
private boolean isValidKnownVersion(Path path, String version, KnownJarType type) throws IOException, SignatureVerificationFailure {
Map<String, String> knownVersions = type.getKnownVersions();
String expectedHash = knownVersions.get(version);
if (expectedHash == null) {
return false;
}
LOGGER.info("Found executed hash ({}) for known version: {}", expectedHash, version);
String hash = Checksum.sha256Hex(Files.readAllBytes(path));
if (hash.equalsIgnoreCase(expectedHash)) {
LOGGER.info("Minecraft {} hash matches known version", path.getFileName());
return true;
}
throw new SignatureVerificationFailure("Hash mismatch for known Minecraft version " + version + ": expected " + expectedHash + ", got " + hash);
}
private enum KnownJarType {
CLIENT(KnownVersions::client),
SERVER(KnownVersions::server),;
private final Function<KnownVersions, Map<String, String>> knownVersions;
KnownJarType(Function<KnownVersions, Map<String, String>> knownVersions) {
this.knownVersions = knownVersions;
}
private Map<String, String> getKnownVersions() {
return knownVersions.apply(KnownVersions.INSTANCE.get());
}
}
}

View File

@@ -0,0 +1,35 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2025 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.configuration.providers.minecraft.verify;
public final class SignatureVerificationFailure extends Exception {
public SignatureVerificationFailure(String message) {
super(message);
}
public SignatureVerificationFailure(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -149,6 +149,10 @@ public class Constants {
public static final String RUNTIME_JAVA_COMPATIBILITY_VERSION = "fabric.loom.runtimeJavaCompatibilityVersion";
public static final String DECOMPILE_CACHE_MAX_FILES = "fabric.loom.decompileCacheMaxFiles";
public static final String DECOMPILE_CACHE_MAX_AGE = "fabric.loom.decompileCacheMaxAge";
/**
* Skip the signature verification of the Minecraft jar after downloading it.
*/
public static final String DISABLE_MINECRAFT_VERIFICATION = "fabric.loom.disableMinecraftVerification";
}
public static final class Manifest {

View File

@@ -46,7 +46,7 @@ public class ZipReprocessorUtil {
private static final String META_INF = "META-INF/";
// See https://docs.oracle.com/en/java/javase/20/docs/specs/jar/jar.html#signed-jar-file
private static boolean isSpecialFile(String zipEntryName) {
public static boolean isSpecialFile(String zipEntryName) {
if (!zipEntryName.startsWith(META_INF)) {
return false;
}

View File

@@ -145,13 +145,13 @@ public final class Download {
}
}
void downloadPath(Path output) throws DownloadException {
DownloadResult downloadPath(Path output) throws DownloadException {
boolean downloadRequired = requiresDownload(output);
if (!downloadRequired) {
// Does not require download, we are done here.
progressListener.onEnd();
return;
return new DownloadResultImpl(false);
}
try {
@@ -162,6 +162,8 @@ public final class Download {
} finally {
progressListener.onEnd();
}
return new DownloadResultImpl(true);
}
private void doDownload(Path output) throws DownloadException {
@@ -483,4 +485,6 @@ public final class Download {
private DownloadException error(Throwable throwable, String message, Object... args) {
return new DownloadException(message.formatted(args), throwable);
}
private record DownloadResultImpl(boolean didDownload) implements DownloadResult { }
}

View File

@@ -33,6 +33,7 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.Locale;
import java.util.concurrent.CompletableFuture;
@SuppressWarnings("UnusedReturnValue")
public class DownloadBuilder {
@@ -115,15 +116,12 @@ public class DownloadBuilder {
return new Download(this.url, this.expectedHash, this.useEtag, this.forceDownload, this.offline, maxAge, progressListener, httpVersion, downloadAttempt);
}
public void downloadPathAsync(Path path, DownloadExecutor executor) {
executor.runAsync(() -> downloadPath(path));
public CompletableFuture<DownloadResult> downloadPathAsync(Path path, DownloadExecutor executor) {
return executor.runAsync(() -> downloadPath(path));
}
public void downloadPath(Path path) throws DownloadException {
withRetries((download) -> {
download.downloadPath(path);
return null;
});
public DownloadResult downloadPath(Path path) throws DownloadException {
return withRetries((download) -> download.downloadPath(path));
}
public String downloadString() throws DownloadException {

View File

@@ -24,10 +24,11 @@
package net.fabricmc.loom.util.download;
import java.io.UncheckedIOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@@ -40,20 +41,20 @@ public class DownloadExecutor implements AutoCloseable {
executorService = Executors.newFixedThreadPool(threads);
}
void runAsync(DownloadRunner downloadRunner) {
CompletableFuture<DownloadResult> runAsync(DownloadRunner downloadRunner) {
if (!downloadExceptions.isEmpty()) {
return;
return CompletableFuture.failedFuture(new DownloadException("Download blocked due to previous errors"));
}
executorService.execute(() -> {
return CompletableFuture.supplyAsync(() -> {
try {
downloadRunner.run();
return downloadRunner.run();
} catch (DownloadException e) {
executorService.shutdownNow();
downloadExceptions.add(e);
throw new UncheckedIOException(e);
throw new CompletionException(e);
}
});
}, executorService);
}
@Override
@@ -79,6 +80,6 @@ public class DownloadExecutor implements AutoCloseable {
@FunctionalInterface
public interface DownloadRunner {
void run() throws DownloadException;
DownloadResult run() throws DownloadException;
}
}

View File

@@ -0,0 +1,29 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2025 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.util.download;
public interface DownloadResult {
boolean didDownload();
}

View File

@@ -0,0 +1,682 @@
{
"client": {
"14w18b": "8403c9fb03b1e9c60cb6fb2f97e25dd60041aba4f0a62596f4e63eed49cfa9c0",
"14w18a": "8b18e1c6bcc01d9a96e44171e8ed05bd8193a2bb6a3c9c9f5fecce7492b04cac",
"14w17a": "db07fed9bed91d5de8f48c3906b87b7c05aa2b7242a16000c24009eabcf3516e",
"1.7.9": "38f8d799a9b42fb539ca7250e317dd6546910c8ac7718a720c11aad79780e8d8",
"1.7.8": "09d06a078aaedc075682440a5d87d473ee5ebfb35270aa8f085575f133029a31",
"1.7.7": "3e41fd81092a1e9bb7d3d268ebd8c8700b5906065974dc6a962ba8417d57ba73",
"14w10c": "542a846d2ca4975069230c1b8e3da143731966190ff307cc202fd75117fa7c83",
"14w10b": "02367e9efcbd9feff8c41afb9e1e2801076190821fd259de61d0d49137c9c078",
"14w10a": "267dcc0da9deff6a5e8ad4a80b3ecf1da0f17c9184b7c236c3b11e2350f92d53",
"14w08a": "5fefa25e18becd6d4a522b21488f835100dde75d1e20612ff1864586453f8ab9",
"1.7.5": "5f83b944b59c48ea7fa8f92fefd491ecb6d1e8d6c9b412fc849f6457c8cee27b",
"14w07a": "298f9b762fdd56fab0aa250385629f8971cebc7054c37d48c8f3f65454f62116",
"14w06b": "f1e87881adf7e02af3dc69a2f57631c32aed31d709b7820037771af28faf55cc",
"14w06a": "047db6da1fb6a5d6d9f8500c4f37fc274d351dc3aeec5b4b5a5879565617265c",
"14w05b": "7c9d9fbdcdca7160bef96cdb1ada2e8cc4edad73d0f996e36305a79d3a1790bc",
"14w05a": "893f6b8129a1fdee79c0aec3f912a11a6f3af3240debdf75a685aad2a00b118b",
"14w04b": "0278bdc4c5ba4e7035555fc5e5b90b8d005478ad12be32613ecc6fe03aee4da7",
"14w04a": "a09bae6167d85fc4cabef338a5f9f4b710b029079354eb9c36097fa9dc137244",
"14w03b": "f027f1aaded0445f769aad31770cc93abdeb6bb7b66cf23b1b32d68feaf638b6",
"14w03a": "d640a5a69510251fe8417cb96acd9f2ee2e302e671adb33581abcbe6e27eb1cd",
"14w02c": "1189a21527efd3e2bc9936787d6697b865b84f917e271fa29edd8b1e6ddfb66d",
"14w02b": "02080a3cea941c7fdedebada32315c62a121a2d372310f80ba119c4ba2fd8d2d",
"14w02a": "4b08784409fbf168a8d27a74f8a4c992fe50ace58021434a1fd2d22d6df2bc01",
"1.7.4": "c51494c52a612648cbcb7ebcdaedbcb61f6cd287f6215f90113a43136cb62526",
"1.7.3": "8b1d90ae662b235072d05dbc27d38ae5fef8b322a21d10302703fd798feb05c5",
"13w49a": "63e598023cbdbf77f4ec08b7fae4c25ab276b6dd17f3f59ff1086d1334ebdb00",
"13w48b": "d606b7eb41bdcbedb678bb5aa87d05e9e0fb13b31e16dac845ea658b3e970d51",
"13w48a": "92edd6cfa4ec6dd46f361bfbc2a537803f92177c80a643def663d2c7be376385",
"13w47e": "0cf206856ebaf09f08fa27faa5cedce3230fe837b5486806ea8354b952aae7c9",
"13w47d": "19d5f72e2aa1779a02335a3a4692c1a1a2a04be8d0329c15bf675bcd74464fdf",
"13w47c": "0a69ec62aca3a4b5c6f2956df4cabe465d4a7f6f2fc80a7ac1444936f07a5dd3",
"13w47b": "11216038d4c85cbf1cb2d3bf61808b0d26ca287b064b5fd8d511e11323414bf2",
"13w47a": "e8b0204b3d35b2a6c381417d8a32d3163c9e98caa4c733b4e9914989fecc7282",
"1.7.2": "507fb3660e77ab1a3000424d9b08c61606ae1e5a9be41caa3728bddd9597365f",
"1.7.1": "0ae4bb959d5d916ce7a6a83882aa154bb4233ea8a3624d3fb0970a5fd3720fa8",
"1.7": "af3ce46c2b91d5ae61bc5c590fb8c72b00b538875c8de225e9f2f2dd465afb50",
"13w43a": "ae0fe25f37d971787e73116fd091c1e1edbce197552bf15be70862ac7b4e5c40",
"13w42b": "e5ec51369856028631bb973cffdb13e5b83ba3fdd466314469ddedaa5c1725ba",
"13w42a": "1c1d7ee75225aedba2c04f871f7f1de6261f42debbc1219024bb2ec4af0e47ae",
"13w41b": "3e9eaef7162673b5fb062017ee02e847cc7e53395bd9366ce1d7db95d808dd6e",
"13w41a": "ef428b96e3550b7c733d43544950553971503bf99dc68942971d35c5269d73a3",
"13w39b": "cf804e8ed8c0589df08baa76f2f77eb89046b1df51b5e7e7a6ec198012a06bb1",
"13w39a": "a4cbdea94098d07c30c2ed4941344ee0fa2019edfc6e483826cc7e075e9934e1",
"13w38c": "d28cb5d42a8bd4bd9e870f13ce6f6cb2cf5427b43efb631a71751cff56c32288",
"13w38b": "0ea32cb82f5f49db167987907e4ff14e5358a42cc202c794a8cd4525cc286009",
"13w38a": "941e72596782c260e2cc8687757c141060b3f3d1b77c1a75e7c4e4e2b04504e4",
"1.6.4": "f4513e51c766cdd1d32ca8acd0835d7e16e2851ebea3f2a3b4ce5ae696baf3ae",
"1.6.3": "e6dc707af0a0cd050d5f66da38476c81f2ad6437074e46c852bae410ee2a30d3",
"13w37b": "998240601ae985884f7899933410188075320e432ca2257748b56f17e8d09770",
"13w37a": "61252567fe990c23689a1bbd3514d82460442b3c6822dafc2fdd06a2e5746809",
"13w36b": "00e81ac87bfe31a47f2b7f5aeb532649e0b75b847a237b3bcd2f6b101a161667",
"13w36a": "796e2540899dac97bcdc873a84f08d74a41212ec5648e5f70ae1e87f75dcf692",
"1.6.2": "08406549a47412294923705bf023b1080ae14da22ec2770d16d920ce420c3092",
"1.6.1": "1d8a2945e196db1226930ca51f20f8dbe2d9578884949c569c5cc8dc5fbdcc53",
"1.6": "1362475725c495e34fba6cd8bd10af1e621d9425180c8428b9cd8efca168e611",
"13w26a": "71e49cfd67aade77449ee898bdbdd719149ff142be501d29312d702db107d530",
"13w25c": "26c2259621d151b29ffcda4b4f2f39d044b835a4d5152cc3f87138b81a6ab080",
"13w25b": "18d9e3ff2ae3184c5d2be0ea6ad5ebac496e2ef8454a8911b036b078087c8c35",
"13w25a": "8ebc73a02b8b3c952d678b9cadd3250bdf47218513baabe6b7529e8871e19b9e",
"13w24b": "bfa6fc3154afad3ce6f3e416f7bf8b7dcc93f21b8d67fc1cb8872346462e73c9",
"13w24a": "391edb763fa015d2893ddf9345ffa676322e939719b16ff77fd442fcac873282",
"13w23b": "53ba4cb9f0e2ad5ddddb6cebddca18cfd85db3968ae8b3ac4f36ba93169faeac",
"13w23a": "f99593e38e106713d022c43cca369ec08187a062b8112b34cfb6f484534d342a",
"13w22a": "cd3b4e88804f7e6ba6ca604f3e53c49770d8b5a967c9f069c78b637f9a79d407",
"13w21b": "6790b306bdc554d5485edbee9cbb434707f8f45c85d36041079d80588dd2d96b",
"13w21a": "7a267523ed4f42e654bd80817368dead64b3e6c3a4afb8f4b2070d87441a004a",
"13w19a": "0cc59566ee89deb80090c8752c35cc532667921332ed4b4d5b2b136e6131388c",
"13w18c": "45d3b2ff10dfbb55f5f4d719b26cdd3c5e232261d0a3d9d66c2536f7a1c9695a",
"13w18b": "bc54c2f86339d489a0f20376fbb23f19dcf8464645a675cf0eeb623b249c2934",
"13w18a": "1750c66d6c4aaeb75061ef1cb8f55821553ad469ea3a8dbdf1f27c70af2ac53d",
"13w17a": "d9b3725b86e69b04eb55f24aacd92cb339d77e1083f6c986acfd5ae8ae1511d3",
"1.5.2": "dc0fa48951f61c12eafede5e46e248aa86ab86d1e4c28cd880c1d9c348ec44d6",
"13w16b": "9a7108fbefc3e4a85b82100eaa57f725668540d98a5b0160a55ada148d5e80a7",
"13w16a": "54eeeb77ef7813415bf31551ea14005d612d64fee0d04e5b543bff4b5c2bc787",
"1.5.1": "e3164ae3a18954b0e09aefa20afd9c6713d9ca3b63d603fc8ae2502026149922",
"1.5": "4ad020a9c3fd95850370a45ea3511a8fd98728f7dffda68007ca2e99276b67c4",
"1.4.7": "c5f4972bd775e03b1d3a1255d962ffc00f8c1f6ad98a30a149d5f107153c0da6",
"1.4.5": "b7f06f2019ccfe5d8aaf7e4fb5f5e8e2bdc3ce6509c4ab4f559a5ce4ffe011a8",
"1.4.6": "c4bb5fd8f98ac7160b34fff7a65f809e0930ee39c5d94f463f0884a6477b25ca",
"1.4.4": "9e155ec17c574488c0bf361aedb1c485d1f08b31fa9d21ddcbcdd441a0d41f53",
"1.4.3": "06d60d55d1c16f60884f0def32fa249145bf7e0b8f5058ad885e3aeaecf798fb",
"1.4.2": "199f44e06dfbc20567f9b6db0a965776b38208359a62956b43906bdc51118cf2",
"1.4.1": "773e2bbe53db57ff8ba9dd530ad62db546f6d9f4c37b0dcb7631e337d26641e3",
"1.4": "3a70045649d016e8026e98ed0488b1578910b9bef0dd48a744690501b369f43c",
"1.3.2": "24ec25082cf2bafad90518b5e24d23fdfec55f1bff7f2ead391ab40501eb8354",
"1.3.1": "26a5586a1dd5555e918813284ab6cf05af1fb997f66b99c93035b03ca066cc52",
"1.3": "a71f49a4a9fae65db94da535e5b0318606151927dcacb68908ec937c71ac7b71",
"1.2.5": "c1c3740a912ef523a8bd46605ab5708643498330140cba175c7ce6f177e468e1",
"1.2.4": "ece050b625d1836a035197ed312dc59caf26429f4b386c81bdd84cdff3ca80cb",
"1.2.3": "ea15a60614f96a6bb9aecf865de213635c62d664124092d84bce240622c4a9d3",
"1.2.2": "5cc415005abc4931238c1af056f7f7aa650990951a58142d7ee855532b2b0fd8",
"1.2.1": "3c519ce303504c2f112f6d2f94f81cf1570c83ce81348df7db93ada317e33054",
"1.1": "e065e9681462aa1b78f3cc589ba880d9aae51994bcd4af8c67bcab5c47391ca0",
"1.0": "136e3dd54454e96175badf50bee2cdebdab9e7d66fee4fd6d135f39ead99eb58",
"b1.8.1": "3a61a9cb5b8b6cade30a8bfe21f6793122c7349d0c3bbe1cd0fc2a93add36f4a",
"b1.8": "5d861a5ae1ada4f659905dfde3ff76859a8fd687b2b040b2cc97b0e4e43d9358",
"b1.7.3": "af1fa04b8006d3ef78c7e24f8de4aa56f439a74d7f314827529062d5bab6db4c",
"b1.7.2": "16c8f63be9e5039f2ce974974a5688d772e93c9dba53731deac4b2434efb8d12",
"b1.7": "43efdd70ac7c7b1b7d5c2ccb6c1aad8d9fb7af110187fb4d1070b21d04648b7c",
"b1.6.6": "7764cb6cd9832d3b270d749a9ebf44e5ec8297faf5f14753146ab70cbd316b66",
"b1.6.5": "b203d6d62f5b97671d262d88c7bf8891b8501b401fe9ababc9e4ee2d960cd0a8",
"b1.6.4": "02d02cd94905c4a2c771b6c1696d236cb38150600a3dfb9946393d3e1431df0d",
"b1.6.3": "a88794d52654321527669ac38cb39764ab41cef804ad98eae8e7e00861bf845f",
"b1.6.2": "b3a6cff53774e1c0f7e9faee014f49728b72a7b1d1301fc9d39eb12cc20fc310",
"b1.6.1": "5108cdc9f7f9c5514ad2d16ee1b2a7bbfaaa13f1816aafdcd24e9ee975a4707a",
"b1.6": "a084d22ea2c872a564ebef4e5b99be9ec0727e3f7be6cfb4f6cb2f9dea4522f8",
"b1.5_01": "088e96109679889c62cf8a91c499bc44ca292252872c242ffefcad95be077618",
"b1.5": "d99cba867acc5aabf383f8252f047d5d1e9afb4f531c9b7205d1b845cd4fa7f0",
"b1.4_01": "36170e61a2ce9dcb6dda5af63440070d1bad82c0846d779f25c2fb0bdc14992e",
"b1.4": "c48c430cb60d3197ca4b9c0aa33821ea56fa10edba5a10a46b77aa46883a6908",
"b1.3_01": "e993b789dd450e1538667def644e1376bd702fe26eebec3197598d5157042684",
"b1.3b": "335ed6e306324ba4b7eb8ae16479839ae5678f0c1da62757e515d640801e1159",
"b1.2_02": "c803cd9c4a0815b98317efaee3dcc84863049a5a3b5d250c56776669c8996131",
"b1.2_01": "55df77ca089baa030c5a4e62905ea78f755485ea3a8e968a2e0ff549bfe13bc0",
"b1.2": "a0996c12b0bfd5a9c85a03542cecf02f573f4cd4dd60f16b05323e897750cc9c",
"b1.1_02": "3439c894641b07bb1b31b89209e9dc5818755353d1f646ee48e583610d6670f1",
"b1.1_01": "f5a4cf4b631c67cafc490dcb43c17dbbab0383d3ec1ffb946f0eb217374a8368",
"b1.0.2": "82e28d25b493ade968cc0015e5b1c2e9145bbbe247d7f1ea7d603e7ddfe056bd",
"b1.0_01": "61829173ce6d311246db4f68bf649bab5ef9f8e29c436cd7c9f2ae494db7f1bb",
"b1.0": "ad62df9cc678bfa15ff981f56aa8aa7ad0d60a1e584c39b981f9ec089ed450f0",
"a1.2.6": "63276bf2617068ffbaf2a1992d1f06f9339c96c21991eafc9583d5f3e7074b9c",
"a1.2.5": "fdde933363df3a1d95a76d180dbdb14464f8390f954d272613ed25ce42155787",
"a1.2.4_01": "047a05550de80c186a3f23d7c4c8b25056dcc0a775e73f4e4d919f39d8bd1c37",
"a1.2.3_04": "e80e49d2ea895198fcd2d3866e4541a4d464aa5263f3923d540cbaf1053f9eb9",
"a1.2.3_02": "bc23d23764761484472060a49353598bbaa33d574e6953015e75e192e03bb9f9",
"a1.2.3_01": "17c0463e56800aa257de3e76011d7f3db015b44fe2f3e846275fcee7f6129571",
"a1.2.3": "76b99d9a0d884bb0de99257740fd391c319abebc2ec30174a39bb63e5ebe69b1",
"a1.2.2b": "fe33a245d0c1a995ffc82f2673436029fd0c4f04b9597fa094a1fd6157cd65d7",
"a1.2.2a": "054c311b6ac2181f8d362d579ab8d3992ebff1867f19c480191e417c030e531a",
"a1.2.1_01": "7eab51320f26cf68ff9b06fcf34b64ee60aa7aa0ee5c0421e8fb0a265e811c18",
"a1.2.1": "7eab51320f26cf68ff9b06fcf34b64ee60aa7aa0ee5c0421e8fb0a265e811c18",
"a1.2.0_02": "cb4d712cbbe51a6a8b375ca771fdc4ff3fc70bc7bdd640e53a020ae7e687af56",
"a1.2.0_01": "e658a3a6eceac9eed89bd1e2a00768f72dd5aa8ad085fde59a9e1cff2f3c43b9",
"a1.2.0": "2139af187a74e4c60af45110665f38777fe6df07da0b72930fa08245c732e149",
"a1.1.2_01": "167a9cef74eb60417ab9670df10953634c0dba8371ebae1e44588e0ba0a7b07d",
"a1.1.2": "6beaee2c909ed33591a985ab6bd03d6962958c0b3d0aeb7a7a3cc144adf7f50d",
"a1.1.0": "0723b15a4b56b202a46574f2bf039da2760f838e8ea3ce5aac683b26dbf38e8e",
"a1.0.17_04": "34105933da180843e575909978246fb373f2db53b00905b92c8110cd19fad6f2",
"a1.0.17_02": "9505e19f30b79ce2964504de29594e18afeed7fb5dcb3f0960177705880dde0e",
"a1.0.16": "fd14110691b14ea51f9320a949e2b3c1855e38af2668bf4e743c19b08de00234",
"a1.0.15": "c9018315807056c610a30ff08d6d9515712a50997d6c06ff7d923676d61e87ce",
"a1.0.14": "0859315d26dab43e004454f19f51301351e826acf3ffaed636d7d2a5f6d5a584",
"a1.0.11": "a11120202e2ae0b474a60c2944b03cc5dac5c6d659a0926f8c881619de70c17c",
"a1.0.5_01": "5ef06e9c7e0421505e3cecc732f2d192a9e04b5a55840d91418a301bae375c70",
"a1.0.4": "e464928fdfc445de13b91b635d31c1aadf212c656f9d7a9f3ae54da5c2783f5f",
"inf-20100618": "26c18bbdb55c0c7f5858a8094ce082a76f28d76e7f6f3a035383129f75a365c0",
"c0.30_01c": "3bfaa9ceccfd49f62d4c1863d5cd565c24de1f83bab982a38e4811ac369993a5",
"c0.0.13a": "b3b5a88834c2351c948950c6d60c822d0d1d60f88c5adad4794f8ad6fe5b3a33",
"c0.0.13a_03": "40595f0c37adb1c581d0d4d836733806ff75af977836f815f1a5600fef0a43be",
"c0.0.11a": "2ca13b43ea3efc0388c5c1d4613854f6412f97d4c02f4b18729f648e46f02d1f",
"rd-161348": "cb8bda0074ac44d8d26bfe0c101f08edeb2291915f32ad1d2909831cd84934e0",
"rd-160052": "bbd6e24a276c5082f8aafae152bed450c161d56ff776472821de56c0daa7fe1e",
"rd-20090515": "cb8bda0074ac44d8d26bfe0c101f08edeb2291915f32ad1d2909831cd84934e0",
"rd-132328": "0627a893265fd697fff165626e000a2640c6a6ece6b64b75c3ab0194f960ed8b",
"rd-132211": "407460840eaeab01260b9e7951bd518f7c31e2e3f12a352e436502fa7050a6e7"
},
"server": {
"21w38a": "56ebc9c2a2997a983df5777d7fa1e2d25ba38fa3b740b3cd1db8308819449e5a",
"21w37a": "051cf95cc9aa199f4872bcfaf873ab39841c0a266ac09093d2cad6c93e14d7b0",
"1.17.1": "e8c211b41317a9f5a780c98a89592ecb72eb39a6e475d4ac9657e5bc9ffaf55f",
"1.17.1-rc2": "b54452f67071054983935a02ec344a5d9e0c7ade5a7a4f7966c5f8cceb62335f",
"1.17.1-rc1": "643c414bfa6493f2e644b64bda0e558fb209a12bde27bcaaa508e2976006660a",
"1.17.1-pre3": "b4a23854646c7631aa0e39520184e2a36eaeb4674139264b480cc46d60ca88de",
"1.17.1-pre2": "dbe08754799fc8661f0140d34391885f367a64048437080cc2b3048b11032e19",
"1.17.1-pre1": "c710f543d60df1d0218848c34ba240044dc2eaaa7f210d825b83eb9a5c6af9fa",
"1.17": "7b390d8d9f6b5649b226d82686aec7f11bd9aa4430bb5cac9072ffd32f3c1f4b",
"1.17-rc2": "038782cd1017e75513c6954086b423e227691e91d76567ddeea749119af3756f",
"1.17-rc1": "64a0f55142ff835deac34b7d949f4173e01791e13bfc3825561b1f72156603e8",
"1.17-pre5": "b6ec94e542dc0783bfd76f822f44f0a3434a4dbc7205c545e3c6f42a4bb9b323",
"1.17-pre4": "885a42fb4243b6dc4d4f6fb4f32797f27b88bc74e402b1ae19b69a77b789a257",
"1.17-pre3": "3db83b98c5afb4a49ad2d5e8b1dde30db8edd35d470602b878e5c4b01bfd30a7",
"1.17-pre2": "58bc609735b32aade48a857b95ec92c16202eace828e2d6c6b5384e68083c5bc",
"1.17-pre1": "f2e66bc5d818f1afbf80d46cbcd178cd14fba10ad1ce21330fcf2426e5295b60",
"21w20a": "ea47315ba2b9abfe3ddc08a0e0da4713434b2a3cc8a404a907c1e12267ff6120",
"21w19a": "4242ddb3304917206ee57d4f8733ac6f271bfcd789c9b93cbe5ebb684bba366c",
"21w18a": "991df5a799aad616244022e52ce058c1b52c48eb67c2388f0e9bf514aaff8073",
"21w17a": "79c5fe68415b0198e6716706cdbb7f900b146b678af2883b5ca6edc42e783207",
"21w16a": "6d5fab818c5928c9bb9060f7b2d9968110c620a62def1ce92aaefc39ddc1a4f5",
"21w15a": "afb63475c6e2ba324fcdc4630ae9fefdc422ff86bd6a962c9ca3094ba6a6bff1",
"21w14a": "03467ee277f483cdadfa2f603e6473105f277b0f3fd5f9d5c77ffb2c34159a5c",
"21w13a": "fcd97c853dc0edc5c07cd4753b73bfb9a1ae72c616ba811c044f6075272eeb68",
"21w11a": "b5d0f819e6361327ca96661eed042f92751ff282a137b03acfca763707d6c7e5",
"21w10a": "cd8f9846c42332d4859a35f7645e325dc07b59d4ebdce76a3ff50d73a81b3ab5",
"21w08b": "d8ec1e31705409c641f3ed293b26207f62091b524e3364dbea344dec0a63f386",
"21w08a": "59ea0491b83285061bcfd8ecda579d2dc204f9182a093c4f1f539311a046f3bd",
"21w07a": "292e9f641eaf137b51cc32e68000ad96cd2bb6b6e89b3f31a82de2e2501f0f9a",
"21w06a": "18dac09c4014641ddd6bd17c84425a614a27290dc9cf499671750f7746a4f9f5",
"21w05b": "91583bcc0639113cec87b536de9d94da88cc8381e769d97c319b98a8a14ea17e",
"21w05a": "2c833407945fb4a21f845fe1e2611bdf940573ab6d2df9a307b1a6d3ffdb7079",
"21w03a": "ca3c83d4747ef8e9f458ecc5814561c6042095842aa4a7b604101dc1dca03e05",
"1.16.5": "58f329c7d2696526f948470aa6fd0b45545039b64cb75015e64c12194b373da6",
"1.16.5-rc1": "77417618470294df7df7e10ee1969441287c32f9a6807ced290c2b048fb3808b",
"20w51a": "311493da9f55ee4ba3d17f6615e44167abd69749f917758994952a17a15c665e",
"20w49a": "374f5a9fcc4d72b3a5b094fe04e7a075fdce2d27558d4115ad323d74fa6e577c",
"20w48a": "cd5167235cee40ee8b5487015b0e172964472c1753b9034de19058c31daaa332",
"20w46a": "d66328d61a0442b37ad7e125f2bf69ec4f38295e525e4d42c83bf5d990ffb764",
"20w45a": "0647b8dce42f02c807c7bd83a06c6d68845f917bb9ac1260529a83636b403675",
"1.16.4": "444d30d903a1ef489b6737bb9d021494faf23434ca8568fd72ce2e3d40b32506",
"1.16.4-rc1": "78e7cf12a0e07ae1f5c314720b36cc44ac936fde89263d57927dadd9889f18ba",
"1.16.4-pre2": "5131ae739be591e23e779beca823bcff1df8dc764b6eda2abec3c7f2a9c9df27",
"1.16.4-pre1": "0ae53adc1154fda746dc373dfb5af7b4d1c253f418c84118880d1840f362c468",
"1.16.3": "32e450e74c081aec06dcfbadfa5ba9aa1c7f370bd869e658caec0c3004f7ad5b",
"1.16.3-rc1": "e0b7fb222eb8c4225bf66441045f491da06de851382ece144bcaff38044ef88a",
"1.16.2": "2902ed3ff84e4f810a2c0620c6b6df9c3ef8488b272c61274d5eac2433876f39",
"1.16.2-rc2": "fe0cbac64b7d62ccc50519aa4b8c58a780d8aafca4629d964bbab9903774a37f",
"1.16.2-rc1": "bed63f508d1aa1dd7293ee7d965c62237ffe9e11a753164ede274759758cadfd",
"1.16.2-pre3": "fd81170c5abc101e41a79a218cca9ef59d44821a7cea540b3704d1a79159f098",
"1.16.2-pre2": "a35a434a4bce236c95828e95a4814c2c2938c39feffbcb707bedc430c08eb39d",
"1.16.2-pre1": "3f0eb69be02920a4ba5006047fba2534d3d2142e90df25de4fdee105f7e4894e",
"20w30a": "127b624cbd83fac5931f753ecc16ade2c6324d163ed6370c75b3561ed2381799",
"20w29a": "b8a34501e01b1f2e5cc7e34d90ec8fd54cd2f99557c7fb464ca5bbb6ab4d4237",
"20w28a": "c00f686fa78732a1446fc17d7c3a76695df64b8085005e6c816aea19622b3019",
"20w27a": "a01d3784a033cfee18eb43069f73f450f8a57ef64615ccff8d819af4d2394d85",
"1.16.1": "2782d547724bc3ffc0ef6e97b2790e75c1df89241f9d4645b58c706f5e6c935b",
"1.16": "7d2d2d127b90baf2bd8fc61092cbff42bea1bdfae30a2838f45edb31294979b9",
"1.16-rc1": "372963701a2a7ae47eccc8bf9879a11f2e283c2ef5f33a0275c44aa26daf9883",
"1.16-pre8": "ac4a92fad98af7b65e09e6d4a833860242b9f4938c94c74b8e7661ed5139ab5e",
"1.16-pre7": "d99c608ab3d8aa84fe9851684518fa97967a50d8d2c7ad45b449c4ed72e72a73",
"1.16-pre6": "b33afa7c5ff0586334e2961cc5d81700a1c5d8e7bfacbc155534d41fe4d20e4c",
"1.16-pre5": "cf7999b76c7659bf4a277943b61c0571b8bd7c3e8764f7bbce7a16d461ab7b2c",
"1.16-pre4": "70da4865676df62aae67a897b47684efcfbebef491f30bdfde0962e821b03727",
"1.16-pre3": "9c9b0163aaea79e9c437e7c27befa27482ec21284a77e0f07b2e7765d44e1b00",
"1.16-pre2": "cdba37b0fcb9ed958cf999d3f763d146738e2ec339ee7c9052db053b47e09f8c",
"1.16-pre1": "c9c62c1410601fdd9b14cf0e2545251577512fab586bc75c10c748ee1d9a8ed9",
"20w22a": "c155d45a4bbf6150d7b187ecce11eed199040299f9ccc789cf3e6d89e667a786",
"20w21a": "68fecc31f9ee1952d099269a946e7be8742b04ee90a447e9cb42dbc41ec46883",
"20w20b": "933a424ad1e82d33b0d782b54158e877969dd0893329f190495ca3ba287e8358",
"20w20a": "67c6a4ef7a0cfdf1212b6d9ef28de92a855a78a9b053e3ef5e6bca5e5bf01a41",
"20w19a": "bcc4c321cbe9c1d95ca4d46677f93488a10549337d224495ca2cc1b2fe01bf17",
"20w18a": "9deb9a207c9f4ecf43bd16fbd7d948a3f85642981d65e62715312a4f9e36114e",
"20w17a": "fc40e4759636430547f22df66d6431a99abe16406d29035283d8fb60b7286a55",
"20w16a": "3026f0a3e750b16b5a08c1e7a5172ebdb908ec662af52759228f00d1275a7c62",
"20w15a": "eecca6cee337657ece906dd0055b275283e0b9bf82e9703bebc2ad88b45328a4",
"20w14a": "0dfd9a8d5b09f0f5abf9b297855c8332e490b266c21e77ccfc3c60560dd1e5ed",
"20w14infinite": "1b31cb0c36471632c97b3ea30962c4b8f210f73094a81d9dab8cf9c6f15871fb",
"20w13b": "66fca31e1d3979161e2ac5114647c44faf3347e2eba2a1793c6769e71d2a50da",
"20w13a": "f216eded69f2f22cfbe3c3ed8baafea4a33b2c0f94a04778f09d2dc20236cf86",
"20w12a": "461a870208ff3ff51a5327d6067c8734f9b8cfef9633a70d3dc550cad31b3e17",
"20w11a": "bc2de25797bce59b753507cc9f3cda660c1065999a46c7ef16c3e1f51abc9413",
"20w10a": "fd52aa742e806c6fcfb7afab7f7c94528ff369995df47b474c9a6e5596527887",
"20w09a": "b5417d9821ecddb112eff4dfe5b5b456c2f000896518b10f7ecc0f767600f531",
"20w08a": "fec35ad3e793348ad344afb493cb22d17938a98caa27913da4c37a9782a98e74",
"20w07a": "16def0cf2d6db7f251496772ee44dd68c5af0c71512facd29765ff2ca16852f5",
"20w06a": "cbdee7195fb76e87d39d57fe07e6e10a82b5e9ce376129edb113e2b9cb6ce7e1",
"1.15.2": "80cf86dc2004ec6a2dc0183d1c75a9af3ba0669f7c332e4247afb1d76fb67e8a",
"1.15.2-pre2": "7271730b514ae2520bdefa4fc58614c40511c43ac0d9079e664ff68811226c30",
"1.15.2-pre1": "0452ef9adc2a75394474d7fbd84a043f24659e1a8ae885c4b1d61301e53ca858",
"1.15.1": "a0c062686bee5a92d60802ca74d198548481802193a70dda6d5fe7ecb7207993",
"1.15.1-pre1": "6a2a5c42bc79b24f13659fe90a37b30d470c4011f3477bdaedcdbc429ecdc548",
"1.15": "e0fe1749263b5ec211b358b598b46e787645bffa8411414f0c812a92bdc70c84",
"1.15-pre7": "4bdc556ac0deec5fee4d1ad13abbf5ba5798992f60381747767f33f6c3262653",
"1.15-pre6": "d574de39dd9897e3d6c616a04313fa54ac394c027e25f212defb99e4210f222a",
"1.15-pre5": "1a78bed6ad7c6c4e2f78ef5552b1085a98ace4190968f0d78a1663510b0116d6",
"1.15-pre4": "2313cc6f9e2b6d5111c513c68fcd7162fce255f836e67ac21473dc3a5c812924",
"1.15-pre3": "549bc3993c77ca5cfafbcc224b66dda0d8f01e194cd3df80daf7cdae66632934",
"1.15-pre2": "3a4bd1e1635e3cd56abe7903036b1fb70514204ca4f3d9e4195703e1a9cc324e",
"1.15-pre1": "7c9c58700f70bd8be4e07ff9ba10e6b0eae64735e7b7c784f277dbd7d26e9f61",
"19w46b": "e108ffa6fab53c29d159a3d38ea24fea0f5d2f7783dbf707a4059bc364748604",
"19w46a": "9823fa660bc1ec137f1b8f166267a36ce355dcd20a64f48e1ad36a6b915e3551",
"19w45b": "6d4583a47adbc27463e173880cbcaf55a35537806857105c7eafa7f808c73cae",
"19w45a": "76caafcd6f41017f2c53409b22927a720313ed2b6e6600dd2fcfc3a3569669d5",
"19w44a": "36f8714c89b3f2be48a36bd8e57a751abe3ad6b32bc7e5431cc8923d1f1d774a",
"19w42a": "a729692962823fbede0c86f03aa9e15517f55e7b99ca970415433263be08a88f",
"19w41a": "dcd0755feffdccb93ffabef6a70c85992c3549a0f3868a38187d6b05f7137820",
"19w40a": "6043b833358370fbb7455ab66f5384d37ce8efd5df1578f1426aa389bcece864",
"19w39a": "0ffd910953ab50a736dcc0ab214a0659966713857422e9b7211440b7b181a427",
"19w38b": "61966431ad3d6d70f01582d0e44a6fb88d397da45038b14e08739107fed27906",
"19w38a": "fd23eb1dde29585ba9fd483b13efbf5cbf0a363178fc7207d2754efb8fa68dcf",
"19w37a": "c5d58c9ee416dfa2ffb5523062eeec002048f3a3cd1d3512f3a1f7938a43278c",
"19w36a": "392ec1912c4ff94db122584a32110e2ac4b2a7bc0ac7ce01fc7fb956748890ae",
"19w35a": "5a32b44741e23f43aad504cf83be944b5c5bb75cae1d640f7ecfb7f7e339e744",
"19w34a": "e90576934e580b87b8467cbec0c2a8614e8ed4b829aa4a409d89b7442ea610cc",
"1.14.4": "5ecdedab3a6e129321a444490d0a467c25ea702a24a99cebe3b6aed41f8f5729",
"1.14.4-pre7": "23aba0515ddc24f0936461275651f25ab2b94c927302fdd58cbddd40b2114260",
"1.14.4-pre6": "f18763257d0f23e17906c1a45c1f0ccb123ea68b6b916d2725a8516409cbc110",
"1.14.4-pre5": "b9acac7a5963bbce7ca45d2dc70a4444b69965d007795d32ded9f5beb680aa13",
"1.14.4-pre4": "4b280aa3bba0bf8213c7347daf043bb2943d9a692da359beee841a9149bd3e80",
"1.14.4-pre3": "d86b1ca6eedd93c3b6c5768ab9901176c95fe07976ce949e4d01354698ad0b53",
"1.14.4-pre2": "1f894e747df7106ef604d9000462e5b8a0fb8347fcbda4ac72e534fec3f704f2",
"1.14.4-pre1": "f326a95cf33d4744258da423659dd4feb06d4c5bcfc1e7d5a3282063b8084102",
"1.14.3": "942256f0bfec40f2331b1b0c55d7a683b86ee40e51fa500a2aa76cf1f1041b38",
"1.14.3-pre4": "0f14bd6bcfb987bb98e1e445f1eaf0a330fe8c3f0dce577707bb6611e600af53",
"1.14.3-pre3": "4230cf4f2ed3940dce187cbc6d6f5ed5286b3cc0ec51cdb190bfb0f696b6a563",
"1.14.3-pre2": "6285e75d6d63e82ca07308369465640b5570eda46cfa28aa63dc47a83105e86a",
"1.14.3-pre1": "ca7ecc41eb29dfda1fec8a4d0a2122b1550ae8129aa7c6cb3e831140dcf90438",
"1.14.2": "b47fd85155ae77c2bc59e62a215310c4dce87c7dfdf7588385973fa20ff4655b",
"1.14.2 Pre-Release 4": "8be46e24663acb8a7905029d352af180b39fb98ef7123f609ce2ad208981340a",
"1.14.2 Pre-Release 3": "efb78b0d2f20ac0ec4ec2a27a1eeed7c39d6324060950f8f17eda81a32cf5329",
"1.14.2 Pre-Release 2": "33589482861ad3423c36625f76205498d4e55796956d44f20025ab9e972d34f7",
"1.14.2 Pre-Release 1": "47ff9fff64aea5460c1efbd4dd62c7477e2f28ecbb357edadedf7da558cc1cc5",
"1.14.1": "f822f0b730b7e1f05fca84248a6873400bac4ca449ff6762a55cab3d68b1f03e",
"1.14.1 Pre-Release 2": "0b8271aa0f4c0cdefd311d4d878747405efe60c95710e0a93518f1bc996ced9c",
"1.14.1 Pre-Release 1": "6aa6978e91f9b89c9ce4e878e4cf93ec3d4adffa1fdd276924ea544ba282cd28",
"1.14": "671e3d334dd601c520bf1aeb96e49038145172bef16bc6c418e969fd8bf8ff6c",
"1.14 Pre-Release 5": "dcd1365d9031c17c64a1aca227e0ea1b339f92f162b83aed4c30d784fe3dd690",
"1.14 Pre-Release 4": "008f97dd87b89afc8eaf672e72bf3c37ea4d7961cadda8fe502b51130c418136",
"1.14 Pre-Release 3": "b689a062f9958121da1f656862a5239c3ab70169f6e1851f824621da78636db2",
"1.14 Pre-Release 2": "c2868040da54c422bcd037aa139526558b0abd0aa9d25b995384b0a9322b60c5",
"1.14 Pre-Release 1": "89b5b5fcf3602657b65cacd7358923fb0a921dd9e3aef0a57fee89f3acfea6ef",
"19w14b": "dffe87aee391720976d3d9badff28f56886c50fc6bd9cc80e4248a6a6cb683eb",
"19w14a": "16f04769de4092d9fec1285fddc9d41432f09c93acc88eed1b817204d5109624",
"3D Shareware v1.34": "90d4fc6471ff11ddaff3f981327f851e26891c184716af74c84c6e2fbfd22e67",
"19w13b": "14e08085fc7b42936a9d7567c821cf94b4fe8b4e631ae71132a71e8e8cc189b3",
"19w13a": "60c5eb086681dc5b0f82b42ae10f940c6215c69e1dde558356cf04c8c63a03ce",
"19w12b": "db02070f79fd42d659bc2de8e373836a77504bd4624a62acf1f6b61a6a967614",
"19w12a": "7241d81af90bc685ef1b4b500119cfdbce97f60363a5286b863b00102ad33a2b",
"19w11b": "a95568c5f2255eb3ec1b6125574db35f4f0eca1c1ee1276748d2019d277d17be",
"19w11a": "c9326a467b8d503812035c9000fe527632dcfbc0feab3d3012f4e8c8def0755d",
"19w09a": "10623b38e5343c90aa5b56afa85b6a9a5508c509586afcba3ce75ff3de04b781",
"19w08b": "7674fb600ebcaaeec29ce4ae0975b85b8cfe768eb2b2dafaab1d2840c6d976d8",
"19w08a": "cee4a8ce0645e04ba95a5a7c3f7d98679979c1f781881aeb870a36d303993d24",
"19w07a": "ca225e2ef6b5f39c4c7558fe8d58931d6eb3c9cff8b69b24583b70443e83baeb",
"19w06a": "f3913592807eedd540faa160b914785ce39eaecfc908cdb5170f68e0b442973c",
"19w05a": "efa7ec1b4ab2102146969b9055fdf055f0ae651fca785fd83d32c419647d6a7e",
"19w04b": "1088d3c424dc084f1d10acf4da07315b61617ae7545dad2e0a0d99b5e4afda8d",
"19w04a": "4a70a201366bf22639b9e535ba7fe418a082bde36d4882acef6373589b9c793a",
"19w03c": "420a09a8011532b5f14aff99c4ffc3eaa63ac55732f7701998d9eea62ed8f16f",
"19w03b": "3b5f4bb672b9881bbf29edc935fab991124d5dc3d533cad5b4789f94d6678919",
"19w03a": "39af3a62ac674bab60af54559cad3dd1f37e8b3d097908173849551f0050b8eb",
"19w02a": "60a8947079785b4dfd6398b5fe5213205a18704953dfa9779ba1a76cb68cdf6d",
"18w50a": "a3a6e4519823971b04b8ec719d78443a5b2482161d0dd5d00711cb49cb342ffe",
"18w49a": "926ab61e8064b90d35f8f48d85703b1862056eb781769934551c8a20f4b37e13",
"18w48b": "1b03939ba45778497638df11582525ab9c436c3f53fc9b1d35f229240ec14fe1",
"18w48a": "86db5f64d2bba30a29d3c89dfc5eb1c3fa69db15ee864bbb159980105020cb17",
"18w47b": "e88d8a372a02c1c917f90e330c0b84425c60ebec79c323ebba93b5da2e1faa2a",
"18w47a": "c43648005b63737e2d62a9978a8ac3a6fa218952d64660deb9aade56d1ae20ee",
"18w46a": "0d1688a0aa0e5ccd5689949138fde0585e2039b775a1665e762f9a98d8d3860f",
"18w45a": "d9652f4958f840a34746e2dd349d8e91fb92b1d5193cfed0b799267a447d5c0f",
"18w44a": "f623d5a5f2c0360eef8397320f8784b4d25e7d513e05d27745e23696212196c0",
"18w43c": "91d8eeaef10c01de935fbd8018e9360b4606b4dfd000cfa25d99ceaf7faa1310",
"18w43b": "d64207c164f66248b168e6b8db1daad034f0e796ffdaec8dc3e9bf671eeac2b9",
"18w43a": "0d400f0ad9f31d789d779abaeaa455f120dfede7fc9aa4b429e4f63c7a20cc0f",
"1.13.2": "ffd3aa2c25c5ba68a706b59f2abdc69ac1748e115ca9d3b47941e197736f088e",
"1.13.2-pre2": "5d7e47d3dbe2f464dd4118ffedd37d5aafa09f601bab9806aeb61dc9f42a41f7",
"1.13.2-pre1": "029fab08d8ebd36d77c79e9649d88f5fde1d6d71ca50beb40ec754f95ab8ed43",
"1.13.1": "2ea6047e7651c429228340acd7d1e35f4f6c7af42f59f92b0b1cd476561253d1",
"1.13.1-pre2": "358feb0457aec2940f0a53252cd9ed66f5837710e97871f0d4d25dcfdf00b5f7",
"1.13.1-pre1": "7a6cb5c06dc582e2b5799fec44e8cc2d105ed1229c0576273749e2d067aafe44",
"18w33a": "b116e0785bd7e4853468a9e9f300c07d872f2e2339d682e7237ec62076174dd1",
"18w32a": "99de375c834c939347f025b1afd93d6a464fdf8c3d431cb70701cb535a11c90f",
"18w31a": "d27eb76f6143fe2cd6e641c388b588299e9417c156c498cd39be97c91639608b",
"18w30b": "6127da70d52b47d51adccb0c1627f692f56678ea68de404e9dd5e6a15e6a3ae8",
"18w30a": "6e83809a5a308e16ce266909424b8a6f539af47c82f2b964a57e355a30c65325",
"1.13": "e76f3927904d331c969a2c437d5661ac02f24be86062dd1c607bfd4ebdc550b9",
"1.13-pre10": "a8ff609f7891618b63f1f163d2416650779d2c4fcd6300e296fd994983736b5f",
"1.13-pre9": "33a1812ea0f128551cb2cad28f28808b11434e16c55f4af7b42ba9f9eee787d2",
"1.13-pre8": "a658c769b8f29ac4fc103a4c7b24cada1f0c48400658bd2a7def60b1152711b9",
"1.13-pre7": "21086e3ee7ab37f91dca33a57e682276f8f28d3dff37a5029d756d3dc36da0d3",
"1.13-pre6": "be788e95a3b60545a506e7841c091651f1f32d5a078ad12f9b4c74e57121bec9",
"1.13-pre5": "96acdd0a6389fdc049ba597818a1d27a4b6747385c43b7db2d1e9acd2ea3529a",
"1.13-pre4": "45a8d618b2b8771d91f8f689871cb2163228461eb7e637acf95dcf419f830b94",
"1.13-pre3": "3c9da719f5501ed27fb54cfa19e4fd96cbb69e07b92c0def37fdcb45782f4780",
"1.13-pre2": "c3bc9ef540ecb5819252c4ab85cd7bf492a5e077e250d3b4958c8ea0774cc8ee",
"1.13-pre1": "3fad9696de535038e90d2b4338d817256cde2d9b23cdead190ee8900aa9a2a83",
"18w22c": "4a176e787d8635afc5ddce5477a8fa992c8da07df7284062dd1e6139511be83e",
"18w22b": "d95f737959b708b7af63fcf71eed9d8266e668ca522a7eeb67a38460692bf6b4",
"18w22a": "c534aa4cd9251a005c701a22b60e69e35bb39d7479962dae3f3aff9b4ade0eb9",
"18w21b": "0bc312dbe2c1ee8fc411210708dea05ad51da13784d94c6445922e6541f29184",
"18w21a": "b38caf0c824db0a4fc3d3056a2f47d93ffa9f9aaca1bd4bca8d8ba0a35e0118c",
"18w20c": "24ace4e82c4b907bb3caa6953cefcdd33f60d4cc00f8f474365a9f1402ceceb3",
"18w20b": "37561559343a5fa0354d1d4408bbb5da190e2b5cf31d79cc939cdd494c27c34b",
"18w20a": "e9cd263e1608c8d40841a9254214cd3c93479711056cea004265a4dd322f2996",
"18w19b": "2c048d0accef680ffef7cd2722682f65765d2d085633bc8b5b7e5baf1d378207",
"18w19a": "548def192939baddf8717619ffb157d95cb22f069b6627e4f26008298cee4e10",
"18w16a": "16696c021eecb5150656f6041874a5ef7f40f1a01f1c53c68885b3f17ca249b1",
"18w15a": "b9b11bab9b5d1fef199fc277bd0b68533581d3150f49a5ecc7f8e39dcf627af1",
"18w14b": "b38223a2452ce41aa7e2266a7b05d2015c99faae2426b39713c9f1648665dfa2",
"18w14a": "58cfdfb099ac2bcc7fc67536b0a8b94488ac0acd099dba10bd280f82285c71ab",
"18w11a": "4b11887faa3725becf4385f8b0c014cbfb24d6745216a69177deb91c168250a1",
"18w10d": "c1b13e09fed92138b04151b65b6144be42fdf28f71d8a5035980422114302e0f",
"18w10c": "949ace0cc5a1193f4c1d5a54ea16b7db4ea5cf36643f6bc521df4b82ff12a990",
"18w10b": "d88febaf435c7858ef4faca46550f8cb8f06df2448332a1afaccb54a1385cf64",
"18w10a": "11ef7c590536ba0496e6d9aa089df5ad2b47d2ea1531bf62a30cfb6ea841c225",
"18w09a": "17b0dd2f4f75d30868b2bbe5251cef7d9a82e3c06a843aef54934f93f27ac139",
"18w08b": "1dc97992a9ec2558e348f5e4f67d55415cb906d494b2e8f5e44d7613cd2567b1",
"18w08a": "116d6ec30088b552a2c8f38da270ac11e49d4e0f6226716df81e023ab4dbb749",
"18w07c": "b25ec60a1cd18c878c99865db5767ab86c54d7e18e9aa0b6e5d379feffcd6f64",
"18w07b": "990777b050e015d99483a902990247c55ef402fe6a18aed4b53bfea01a92ef35",
"18w07a": "e40a0b64b3e8372cf8673953abd32b625c64115b0a482d4f16989bf340c15127",
"18w06a": "5f4d89418606c8c14be0d428e991b5cd62b58fd2762a6040284877f29eafc60b",
"18w05a": "399767cd2cbe45b4dfb2a3746b885a989151ca4fa4c2a22cede14333be28f8ea",
"18w03b": "a8d380b9a28a07e544308ab55e0c86f01dd509c890409a6a4a7d0a88f7b51fe8",
"18w03a": "5cf4d2680901e4483f3d7c09b7aecff612a07954eca35e12b4645640334e0b92",
"18w02a": "58bb24f857ecb851c0b34ec711225642fd75252ce62b55ac39a7650f89462631",
"18w01a": "d017bfc7ee82317140cac4c0f722da3f7e4f6bbe73f45f10ae8bd2cf84787f55",
"17w50a": "46e7c69769cf1da826258d9d6b17592273c2728bf91e21fd26b3508dca0bebb9",
"17w49b": "a68135f67ca398bbb9d74942f94db015458c0fba7f94968753decabf56dd9518",
"17w49a": "b1f37d282f5100b1ff46a40c1f25465983f9e1fa24fd511247c18f69bb2cc80a",
"17w48a": "af1cd881b372f8460467b4d55120f89fe39a2410c6d15f5ae34ba8d0fe65e12e",
"17w47b": "6d88ff769dade25bcfeb56c6292061487ba2e3613c29a313b8e97e624aafe02c",
"17w47a": "376f208c9f43356697f5e28d0a3f50c112b78da35358c3b0f89c99e171b6cd30",
"17w46a": "417b0b0b39f5c8f1775ec3dbb8cfdda116d3175f4b68fafa87092644ac8a22fb",
"17w45b": "79ca13a3ae15b69a665762dc7859c50769b6fcd2f4d3f55797a242851155d253",
"17w45a": "a230593306d30e11d2e3d33bacf33873461d060270e138dc083871ceaee96032",
"17w43b": "2f4d74b3fa01d970289e9c4a8e5e00b2930d8edb621b2d693ddd6bfbf7df8b99",
"17w43a": "0362895039ad160810c7a96886938cf01d79aef040da65feefa3e9ed8e60cc82",
"1.12.2": "fe1f9274e6dad9191bf6e6e8e36ee6ebc737f373603df0946aafcded0d53167e",
"1.12.2-pre2": "724f1b2560afceda2f0bae37b4b9d3cfada7203edab2f4e1ad5fba8d9085c67c",
"1.12.2-pre1": "c4413733ed6d43f77706c7c8888b9dd4301d613a96f9e1d91d11d3e0c3cb6380",
"1.12.1": "848912640bccfa7ea34a2cc1c76cb2b35f8467c4216d9603917c991660f91a8b",
"1.12.1-pre1": "95a52cd874e32fbc4e4e490c64a1ccb47628f693a910c49a132dab9907694c9e",
"17w31a": "cdee889556c01336f33040c24adb6d59e474f8008f93aeeb7cb09a38d08f487e",
"1.12": "feebff3834e41cc096522525707d2dd27adc2431b1f3145b9d0ccfc4c8a3dc09",
"1.12-pre7": "670fe62bc16e28dfac35816e72ea639cc6cee2c11113d5c777f7916bbf0a3e68",
"1.12-pre6": "0eaa41ac44f2821fadb8f795e0b1081588418972a4c00e700dfc943ce19497a4",
"1.12-pre5": "53860ea9b4c0635da1246af27349b2e76912b1fa74790a2ecd8b60b99b9a15fa",
"1.12-pre4": "ba153a70a4775e03149abf4d93ff4f5272fdba005f5a3b24f9c781fa31c8c965",
"1.12-pre3": "9441df4d269ac3f7adc4229c00bb16c1f6292badaf89b425a0ba936ac98de051",
"1.12-pre2": "7c049cca26ee35fb3758487019c9c6d4784400089283ea85c6874e7d4d1db3cb",
"1.12-pre1": "1ff015429a46b8d6433fbb775c5b3d93863bacd30e085b1ac52e32e3fbbe6c62",
"17w18b": "acd52df80ad8c90463184e2b0e2355695aa3857ebd65902f8b82fcb029a23310",
"17w18a": "4db1fb4e55b4d87c85e2b7580606c9104065ad4a5ebe09f3f9c9269099967540",
"17w17b": "a0cc570906ac5cda9995edd7648189f3df1c42b11e6ff48ee304fa6f0406b366",
"17w17a": "9a6f5985c4612a903b875c09a2ad058440e4c14ecd03b929d1cd83822c536988",
"17w16b": "1d0c3f8028495275fa58c765f55081ea07462c33bdd0860b3d7d726367b739dd",
"17w16a": "8eeead591b5cb4660a0a800af02800816c34e6d221d989e21d20d0b2e1cf87f2",
"17w15a": "1f5125fa4829159297073245aa26fdfeef19f75df958877a0ad980f85d051276",
"17w14a": "1bf64744a93be9efd6eb866bd66875e81dc0af93b0ef0d190c4ea2ff3b8d8dc4",
"17w13b": "24d86a3d9fde9ef9ac748732f641a5a3f600c4c3331c3e1994a7403c5f917926",
"17w13a": "407832c1add81e2f1d1c5f4349df90ab4d0206060399ca5fc5e27cf15029ac2a",
"17w06a": "2f9b96bcfc7b19f51c26e623e22c76d7acbd009ca00ae1483123baaeea97258d",
"1.11.2": "dec47d36b429fd05076b90b1f42c2a25138bc39204aa51b9674ef2a98d64d88a",
"1.11.1": "8002bc32fdba21bf73fa30d94524b0c823bf7256e9c71598d2f18fb319e72c98",
"16w50a": "1fcbc53dfbfcf653c7e27621168a2cc4b204f0c7b07f371ec19fd90a8ac4f4b1",
"1.11": "3277965fd83d26944dfbd1b9740d95cf206985da330a1b3733868e4de7dc6f83",
"1.11-pre1": "41a5a5d9ecbdb9876adb834fa44d17a617b263c2a909dcdd806182b1be47e053",
"16w44a": "5414d2c025591ab3b30e70054582ee21a8a4d1ef1607fec39f09ca2ab3a69820",
"16w43a": "4a9d0c7f6a1c3c78b864039078d5c5c0aa6e4dcf0571bebb32b93dc9e8d18649",
"16w42a": "ad5d72d4b40cde7ece1196d93518419822576bb9fb94ab914c82a1db22aa3606",
"16w41a": "44b8a5de5cca66562e6da8afbe195760292e65cae1ccdde2a0311adc9426695b",
"16w40a": "803679e3afb2ad21366b7b95f19d7e882a2009ff9d8e09220b75c3c220bf2b8f",
"16w39c": "400f18ed5ca26d870e8ff1cb143b02dc1fbcf262647ef2ef9fb3478b90f140d3",
"16w39b": "f3e2d0a4b1215b21eea13b11321c520493ff68d95a867334c16d25e188fa87d1",
"16w39a": "5d7fbc59215e694d8f6814426c47b79b28040081d30ad1d3c10814d6966fb85f",
"16w38a": "34dc55b0c358e68d22831f9da261939f407bc995e93b95d5c3c26c6bf443392e",
"16w36a": "69aec3df1fe9b087be60573111382c3c7fbbfc5e54f8a70e839bcf17ab9b1345",
"16w35a": "24f92327cdf6e836c2e36884a20f5d6b9eb62b2c326e73ec8c816b8ac8f91cd2",
"16w33a": "7ee55ace0109b7f31eab8496838e8b001da154c8c848b2d2cbc26952ef44107b",
"16w32b": "7c59216c96eb750e9e5a18dc255139645b90553942ec9569ea2618e5a88a9849",
"16w32a": "ff7cfbbe3d93e0ddd7d79b9185051d0a607c2d98bf3240c3663e74f9d219d6d8",
"1.10.2": "195f468227c5f9218f3919538b9b16ba34adced67fc7d7b652c508a5e8d07a21",
"1.10.1": "623c8d67e7357c7078f30cee450562c52a05a42c394f064474c7f2d45b4a7d3f",
"1.10": "dd44a72e920a01dedf57507b73642f4a9dd8c6052e1f42ff6cc0635008014201",
"1.10-pre2": "80970a1ad42fc434d306205dad3e71d74d2af057eca8a49756d742628b3725cd",
"1.10-pre1": "920f899b57892424a9c8117f8c21563a76f5fe91ca70f94da467efc7772dbba6",
"16w21b": "52d3b46dac4f555930879f5eb7d326e55dd79a90eef381bd178fcfca42a263da",
"16w21a": "4d9ecda93f23af7f9efef333a4b9ba4d0af2484b4664940c1b6f18f613002c4d",
"16w20a": "9b0b5b31b198e67bdd316b1de23c2df587692d064df1b20746d40d4394891105",
"1.9.4": "13fea7aa10d804dd14ed7ebde2493dc64c7d3c8173369309bd7f6ea4c0ea40ad",
"1.9.3": "0eb669eeca23bad6d4ee5237aa24cd51274dfbda15813477f8414f4bfc2f1f27",
"1.9.3-pre3": "fd57a52c156192c06490a9775d694433065fc8bf93f41d6286ebe68db3fb213f",
"1.9.3-pre2": "7a21a84b472a50ef865872c7eef5ed8c2d0a94aed8a459a11562ed6b1086a5d8",
"1.9.3-pre1": "367f81f9a5c3d8983db29e6531c048d8abb5e62eec666fe1c2725b2ea455b3c3",
"16w15b": "0141cc7bf999021b14be167e087806135878410aa283a1f904a86937593aedee",
"16w15a": "748877dfaedaf531cb0173b39e54d9f005628a19e89e30204cad5ce3823bd6ef",
"16w14a": "37047a269614bf730a6bd942cd1180f7cffff1c33aa3e962ec895d7b5a34b7b4",
"1.RV-Pre1": "b85a55a0fc78771d2c3571dc7c4d9c8f1a2aab9edcfb3e9fe3825dff5befc817",
"1.9.2": "a972d127be3b9d5fafe5bd610a173563cb24331b6664a3dc5f73b3cc76d77081",
"1.9.1": "fcc5393c191afbcc0b706bd94616fb171e2fadaa107a3e5f36298ed2abf76c41",
"1.9.1-pre3": "b62800a3e74c3be7ee99503a848dd523e56c762f69c23d7ebab99d22538a4fa5",
"1.9.1-pre2": "eb162b8f05c638bc5ab296ddc56dfb598849ea29ba349e0dff38a09398904e1a",
"1.9.1-pre1": "20dca2343649d91ef2e017ca61d71c5cace0d73159ac020ec44e156c583509da",
"1.9": "38a797f50c71f55202e2135a30302cf3a5c8cb494c6d225b88599542957d3a7d",
"1.9-pre4": "e55154e2b238be686385dd3a29f98a8aef4b2175cacbc68432037def340fb6fb",
"1.9-pre3": "d652eb5de18eddd95de594d9c45b0aca76c80b72b30ce7b929ce97a512f662b2",
"1.9-pre2": "f5037617e8b0235544f546b178799305e3f32829996e0827c9f1b496fc231047",
"1.9-pre1": "84644f7ea596b7f730cc98018660ba6629e6392323ccdf5c71fb85332bc088f9",
"16w07b": "342bab138d633ddd2470b9b88e65dcc39da5fbb8b65b7fc917600587f084ac54",
"16w07a": "6ecdb84ba29e8478464997f2a0f8774776e209052e9e124e5c015a02b6caa149",
"16w06a": "88d71f7a3eb597b06c0519de221746ecdaab2cd82707ce443828c3c0becdc0f6",
"16w05b": "344ee31868913e095301a9153d118ec1b87bc624251231ca5032978d726e6515",
"16w05a": "e070b7a3a46ec6bd03d950f745e0d298e68a88b360d242797f50c9295e0f7e41",
"16w04a": "dbe527c539d7a3fc2bd6a50b11988bc3e484cd941f93bf8e6c852f4ae194aae5",
"16w03a": "992f0ac1b195a538f4b84ebcbe137935a714658d8acaae197b11508f7744e23d",
"16w02a": "aba2d1579db0afef8064747402622741bd771195d6377bd3629f7dd73a8a5cf8",
"15w51b": "8c8e8a435c8bd832a2c85358124f86ed9ccc327c5c630c28a10b29bd01526c0b",
"15w51a": "2b046d0728a74c0b016c9448cffc6bd49c05d1642156b3133ffcd937b98298a6",
"15w50a": "e8b92400015342eabadcfba5e6f31372a1c71d07dd868e0139277c0de5400d85",
"15w49b": "e948e5aa52ea52e323fbfd25091c7bfc687ff75a529e12efe791ee2b7b6342aa",
"1.8.9": "c18e4245073aaff580eb7359902f0251436568b1647a9e443a924cdb73fa8312",
"15w49a": "8f281199d271ea365542523e486d9b623d061867709833046442d3512c4b4720",
"15w47c": "0e70fb9243c774f12cded32a7c50b812cbf341cafa637fb1be46f788a117b9d4",
"15w47b": "f38c0d3ab4f7a9d7ba75e02cbdaa0ca2761a7406249ee03d51dac130f52c8234",
"15w47a": "ca07d91f61fd9661c52be2fd56270900e2d2d6659ccc8166dc4dfc89bab5f6e7",
"15w46a": "ccdc14e21259818d824c1498f035347bbcb74e9c4d6dc20169d61f3896eeb963",
"15w45a": "863eebd07e30d5d4d9f18f419d6cf123f75ac2b6f2c12dfcd61decd4202d269c",
"15w44b": "bb56195201a68a4966efe30c3c317d69d74ceff756276e159340d17b2b9c44aa",
"15w44a": "0a13803768cb8097648131d681c17f99f75c2948d6e5c6567c220f33c9e579fc",
"15w43c": "dd85afaca19fe0d335819dfd59e6162ad2675b0c59300a27c644b535bf359a8a",
"15w43b": "5efe6b619b1ca31a63657b6b58a1b9d4c27e4f40ae7d50d1970c1d3e692b4ce8",
"15w43a": "074212c0b20aba62419277b7ebcefc64c68d3a80813e95db7966a72ad67f6679",
"15w42a": "a16bbe888735e68872ce6863d93cc9d89df39ed2cc9e227b425e2048c6bc9343",
"15w41b": "8926af4de6224a9fef9f010fe7eb114da54fd4af86e21b055faca20c6f61c2db",
"15w41a": "4ab8a919feb53f5f8cb7325c001ebad6f3d721f0387983459da883c8563af87a",
"15w40b": "e2569a83a80ba0fcdf5c1c495b677c3652d28a3bbc3b14f91a3cbde5a8da0e7a",
"15w40a": "2a4b746ea50cb855710dd0dc3e04bdafc7ffe02ec9501e45b7782a9eaf0c0b55",
"15w39c": "41ed25f97e6e767d47185c5e73e16b288f71398c895f8874fdab620de951a305",
"15w39b": "a840c2fa3f11a3d4591aeeb9235d46cff269959e5c03f40207e00bbbb997abee",
"15w39a": "ed755605c41790de977c07c3bdd283e1eb79d6ee2b4ca33e476a3ff87f8154dd",
"15w38b": "8f7729e2c9a433e4786a04074d78f78d9a8496cab4a28d993558b31d1b8442d4",
"15w38a": "b46119ef3d5c5c2509108f86466cbfc15b5bf87d21358f5d787241f9168b61cb",
"15w37a": "e3e16fdb2149a1cd1fe3a286ae822a84581db90cf79f8f3646d3a3d394a55f34",
"15w36d": "3bcca5c4b0bc4281fbaf9bb24d64c75f90e003fa742f5de996aed0b21a19afd2",
"15w36c": "7ee42fe9f4b8c4df8918c8cdaa806d07b67ec0ecc6a0ea3ebd45cf084a88cee3",
"15w36b": "0641d3294b319a1116633f8b6658433ff6a5d46106f0027529bbaaf73fd0d8fa",
"15w36a": "88b420ae14500ece0164b70470ae114732175ba242c792379d006c9a8b09e92b",
"15w35e": "74bf64ef314fee221d5e330be910aa4a6bf55a8c1ec65d2077fb89be41bd64fd",
"15w35d": "55e871006e45e156de6a0dd4e3516ff7ab41c6e6803ecc9a648117f8fb512b1e",
"15w35c": "3c9afb5bcf6fef5933314ad09dda469be9e6cfbd07b8ab5789e468e4f3f93833",
"15w35b": "c31ee139dcfb4cecfb08d12313cee5bf60dd0f05194d4ddf804df471a0e76446",
"15w35a": "f17463ced603699eeb075916a97ec3de3171c145a619a498fcc8a7f045f4da7e",
"15w34d": "a1b9a9a1fa47bc8ff7e1efc8f3603921ec150abef71f8fefa5f209bbbe079859",
"15w34c": "62a1ae63cbf38ff9f94288b5d3dcdd462918cd4c2d465a809fce3c47ac05baf5",
"15w34b": "9e3c2f29682f2be1a4c628eb94040ac4d67ba4270e98ba8330aa3ce33423ce12",
"15w34a": "46875a087599b89618ee5502dcfebaa33307babb03b221aa9d924d83c5ce3f48",
"15w33c": "f36b658c781660cf4218e3ccc45aacaa2800afd3bc81f136cbe9c13364b6963c",
"15w33b": "c2a8bed5a76a95afb35950c2ee3605bdd3fa774a905103edafe915283e9fb1c6",
"15w33a": "4422eaf4702b96b40a58e7e6d440ca29289669f562575ea4016971971eca8be3",
"15w32c": "e8f3c78a60b659297f4ebda8351779a183385eaa2edab954920eaee184f0ab88",
"15w32b": "5aed03f99346ed70cb2f63279382822abe754f047db3f4820f1581754a0bb843",
"15w32a": "dcdcebf422252abc2fb9f2ff8835c39e3bbd0a0197b293de93eda5818ab3beac",
"15w31c": "3c502fad30a1bcd1b1ffa077825885754bf524b9412e2cbf65628b8609efaccf",
"15w31b": "e0de11fe8a2c9f1020e35d0b178b0eba37476dc38e7f7a0a15bda52cdadd60b2",
"15w31a": "e4f9e7b1ebbef58f1ae99c35c6cd3d0fb13d7f4aa6b68995501f512561c6a63f",
"1.8.8": "39aef720dc5309476f56f2e96a516f3dd3041bbbf442cbfd47d63acbd06af31e",
"1.8.7": "5cf4a49762c996c94f6b8b119f1c80b4de3c12b2f5c53268801905bb7daa0644",
"1.8.6": "7fc66b2b54f0f4d65fdd6d6484a50f432c144ef02072d3435d5660f120f58e0d",
"1.8.5": "6a412e89009acfcd5c56084ddab4f9676c5561bb58a3f22d5ea4ba4ac5d3503e",
"1.8.4": "394a9d0d5bcd03272a58f036b8736a47d26d63b45a4e7c820629114876e72107",
"15w14a": "9920b744ac1f7de76ebf4f33fae5d0b53baef35bb175529a3633f53ae17f2e99",
"1.8.3": "a26751b18ccc80ceef488da645c3b785aa528e2ae20a6a6dbd46f6dc754e62bd",
"1.8.2": "d99f3b3478018cb454fbee36fd60e3c4acbe1132f1cb26b3657f9ad291e7035c",
"1.8.2-pre7": "d19c50683a17c43fb64563a5fe75b6417626c57e2ebd76d04f71316794499b72",
"1.8.2-pre6": "01a28ada45d313a1ef59757496ae0c50b79f9152d5ab9fd83b90e9628345a114",
"1.8.2-pre5": "b9694f042fc6028e3d36160383169fe6d9a5455a05d002733f547466e5bce69f",
"1.8.2-pre4": "0511885adad9255c4d8e1d0b193f8b0fe3cf5fb323629235571d337c46f6a342",
"1.8.2-pre3": "9e5d0000101e61f3c5de520743850954a62d4b0b85df0d75a58f9f9384941e8a",
"1.8.2-pre2": "c4acd0660a05bc76c24c6bd7dec0c8c05fafd03a6218071cda989dfc2ec9d6b8",
"1.8.2-pre1": "00514ad81d46b19a8b8e066f0f4716c2cb0a3275183cbff9db119b01ea5e48fd",
"1.8.1": "ef5f5a1a1a78087859b18153acf97efc6ecb12540ac08d82b9c95024249b9845",
"1.8.1-pre5": "f492628c8a192e9ec8f9f5a4283c3c36748fe703d6733d4304c0cbc725d20ee0",
"1.8.1-pre4": "970cdaccb4ff3acdf2bbc5b4cab07e2ea9a1b20271f09829ffbecaba7bcaf93a",
"1.8.1-pre3": "dac84ad1dbd16365e48d59b6772dde5758256c2c9a315d2bb61dd29854595e70",
"1.8.1-pre2": "0e19fd1db175aa8a957c9cf7ce60dfc430090ed71d4f981af502856f69227630",
"1.8.1-pre1": "c581dc7475b45f35f573ca6ba90e9cf7df9b95ebac007594c7d10b24c781550c",
"1.8": "40e23f3823d6f0e3cbadc491cedb55b8ba53f8ab516b68182ddd1536babeb291",
"1.8-pre3": "da969b62ffde078c8890c40b05c47082bc691ad53b026534c87057a2f4cd0118",
"1.8-pre2": "5863a5256894335787f9a048c96650b1d28ce7e049641268bec89fe6e083a310",
"1.8-pre1": "0927b56f23c480d94889c0a837932a9d44ed848059d04835764f78365ef27660",
"14w34d": "05d4a426e75245475d3c88cbbe913658964661df2d8605a0598200ece76b81de",
"14w34c": "069dd43e6c3235d488f945b675798f5c522a0e9cf811a7487a6fcf48db52a3ca",
"14w34b": "602e9e03cc853f9291aed021f3cbcdc87377d2afbfe19a7de314ba0b759676b4",
"14w34a": "19bdb3b1366ccfac7fab918b9f02f75f420dcdfc210129bf909c720f2cf7a51b",
"14w33c": "594aaf7505dbc447b2cf3b67cc026c4885581e45841107d065544824e6e34667",
"14w33b": "b935e611d9a55f464fe29d6b40ea628d7d5d3e87e03c2ee84a055aed40757b06",
"14w33a": "9f6dc04ea65c2404e3c910efbba33ba057428c873b55ea21b98ade7ebaf8b025",
"14w32d": "a59c224c33d0a5b24d41d13e21509fa79053e413e22b05471118e221cbf65864",
"14w32c": "d673d52b400459ed6b5003af36897149f9917c5146452b87bf806a851661378b",
"14w32b": "c2c4ff5940197e7ab6ef922cccce04f9cee0ef118052c4d607d9351b930f3ae5",
"14w32a": "6969688ad556ee91ae078d31e7d5d84b24dd907ea0ee55976b90a1b16981ddbb",
"14w31a": "a58dd363035c79925a5ce03ef3da2ba5a25d514122e842d0188d4e1055259375",
"14w30c": "55b92a96054b8f50c7b0d4a67fffa4ca9004fc87d88402699d66afe230b42773",
"14w30b": "ebf69ac57cfa9ed3146bcc5b4757cf3faf2b6729b61e016c12e25f8ce4bf01d0",
"14w30a": "a9fa0fe45a96412e1e3dcc065de6b0802f659836e366115c6f9557c572560ad8",
"14w29b": "445d21e51f68b5bd30fd905c5739662f2f42fd6bcff8bf9a8fedc2926d94b407",
"14w29a": "d2f52276df97dc039ffbb6dd2b9a2adfc6fe8227b394e98efdf125646a4c0e8d",
"14w28b": "30974fb655414499f186018c4c68dc970f0ae21cae868ce1b7ee1debaf58b6d3",
"14w28a": "b894cd9051fe26d0619b9a717237ce770f8c622b16b39e514ee9e804fd9afe90",
"14w27b": "05511f5e092e94ac7342ddf9685ec3ca820cda5dcc072ed9a9deadcc6c2856d8",
"14w27a": "a8efb5015780dd070b8fb92a11dd876a20b653031f4314b717c972608003bede",
"14w26c": "e6793c31489f25a76cbb64edb9b675e13db5e9d7e7d48ec12aa2c3f16812c25d",
"14w26b": "f65db21b7e44208ccdfdeec9b57d6e4ea640cbbd8481503ede4a27f3613522c4",
"14w26a": "e998161bd9dbb3ce60c3fc757d227dcabb3a95836ddebc1d8740dd2888c06638",
"14w25b": "96bb81e322c8ad08e6b085cc6f2eca068a069db44fde78afb4760e060bc4cb70",
"14w25a": "decd0e814c06860fc66a8642fccfb1deeaf60bebd7980cbf975a0797051f6509",
"14w21b": "2c9e15b5d47eed678da1172edd0e05f07fca9b2b9d21a91c6d3dc92558c3aac0",
"14w21a": "387c81b1aaffb6982cbf1ba1926620235dccddb27cadc14449cdb5b3befadf1b",
"14w20b": "66543e6776a72fa407cae0e804e9fca642ea41f22b54fcfefbef76f584763a1b",
"14w20a": "0bbb5bd0038c09401033fe5d28f4c207c44bec6920afe5d6cc3eaff1a7b49ac1",
"1.7.10": "c70870f00c4024d829e154f7e5f4e885b02dd87991726a3308d81f513972f3fc",
"1.7.10-pre4": "882648310f8e370b7fcb71a2e4f0dc578d59d3c23f6d24e0509810fc8ae3edf4",
"1.7.10-pre3": "376cf7df05a1d1f265a0aec0129040e9ca75ba43c279e929b9e43ae5adb781b8",
"1.7.10-pre2": "21a9c212e91c0af5b4d56bf0dd411227732554804b009a4fbd36d7b15a4fcbd8",
"1.7.10-pre1": "c0e908addbceaf60d42a424baa1b413264b9618584948794ec5ae151a81f8068",
"14w19a": "632fd260c009bcd29e5d0412657911cfffd8b7664b5f51802bbfa169684a7cfd",
"14w18b": "24d6d49d9f3b332bb3c5a530561279077f986c6e19e8a4b6f7be3842080afaf2",
"14w18a": "86ebb97a1b18657f8468c45c64d7de6ce3ef19e9897564a4adb17abd5dc88fb6",
"14w17a": "c7f77b08a3df25c30386daaf75ec36b3c8968e6621c558c995c427adbd327863",
"14w11b": "131a37f8f960c06bf119d435682bc40ceead6de48b73aef2bda25a418c1c239a",
"1.7.9": "f7b9150d05c2cf8c48541527de310557e6bd9bde73e8ac9479e8ffe722c60a21",
"1.7.8": "5907ef1103acf15952d6d50cde3db01e4fc5a95b9f5fec0be25fa56a5ef0d121",
"1.7.7": "74646f88ed76d878eaaf2b28c4ebd4043bb11255999e389345dc55d2f11c19a6",
"1.7.6": "5cdc6ab6168ae496ce1d1ae96c0b165360184518d030417429ec7687c0b9d527",
"14w11a": "508e7a5c272b0428414ea6a84ca4b22c11d48ff4cecca26ae083390de8983655",
"1.7.6-pre2": "c4e8751d2b38e4ddb9d72189d5d3f0bfb82d5b76ce2d4792878cf37f4162bda8",
"1.7.6-pre1": "bf0407bd78a9583c0957cc6e1657dec4d19813b8006c6ae1e8f1c49f64807981",
"14w10c": "9dae5fe55d939a5837887a1f69844d9154888331fd362b1c3f722516d4f4f5cc",
"14w10b": "83d4a315981a07e1f1951a95c02f4d0d7a0b7d5f7fe476c60af78b6d6410ae2c",
"14w10a": "4b2d17daf2a41a336abdf1e098825a5cbf3b163bfb8d992b9d8c4fc99fd418ff",
"14w08a": "a84139d1887b20fa3363f6b94dd93de41b26c8a1ba1697967ebf26d65de6879d",
"1.7.5": "caa9e13aee32112b3b5305c02ecc01c05502fe244cdb83faa7a01832937542e4",
"14w07a": "44a814b0e306a0368e569e0596719ff07897b355df131b21acbe85199e6f9ff0",
"14w06b": "7d0d40ec07935c79c46a2f1689b0e983dc1214b7f57b9a91c5f4c47b1aed1353",
"14w06a": "5d2f23dc66faf6ab6d1b7196e30c0016d324e3c3a4f2b1facffee2677f8415b7",
"14w05b": "08dd19438e280481c2b83bc912b162250709ff523b127f365cc35c696c452c87",
"14w05a": "0c61c3c1d4069c29cd7db31593061a4b4abbe2b74623b54fa86ef73eac9daf05",
"14w04b": "5492e4ec31762bcd059d1d7238adf5a33e2bb5b5816a5d72a9eee6dbbec8800e",
"14w04a": "d24129f6e93e69706b465134177186d22e404067d58099ccc07f3bec81941085",
"14w03b": "0342ee40cb4bc3e2f2142bc4efd27033093c68c856f8d08f10afef3d0f5f31af",
"14w03a": "7a2a49d7985a9d82bc2026ae0465705cfa12f32e65f9bc1bc4d05de7e3de4f69",
"14w02c": "13095b3c5871fbbe1ee3dee8908b53ba2ab05b50f51dde0681b58c3f24777742",
"14w02b": "d39db2835ff2b2b2f6d6cbaad7dd626a2ffa8097343bf82c72a6203306208ae2",
"14w02a": "aada0c408d7776a08617748cbf0a3fd4abea36474d87fca1639b453512b48980",
"1.7.4": "796d6ca283861a3185f2e87ec321b1233540cdf2638da6e00f1d96d47791031f",
"1.7.3": "027b0bf027910d9ed3c49ff643ee81fa875e95052cd66ab7648a9e602973f266",
"13w49a": "94218edaaed13d8ddffb83cdf2f2f9e4d6b1e43d2094fe0a9e8c231f610a867f",
"13w48b": "9d66f65d85ffecf0c90fdc751f3ae1151f90dcbe175811f198c7320f1b37ee0a",
"13w48a": "8dd6c1fde2cc68f314bb50d7a0eb63a33d2d8ddcf1e5e0d92475529d2006b656",
"13w47e": "8123fad577fae5a92744847f203c3dfc8937de1f42ee21154c9e4d656d783c5f",
"13w47d": "ed8a11184ad77069a92500d0e90ff2f05e0d3ac4f0f40b7e51dd3b6198f869bc",
"13w47c": "aa0ef4490c66e4eb9d013d15a6368c38a658a3706bd9d6ca74ad1e5939879317",
"13w47b": "e4ea5ea684c7cd3c2a435fa35d351e5eb33d5a74b8d6c091001ea3bf3aac6d98",
"13w47a": "e2e288bc12fc7823dcc69d6e8dfbcf9c5e41c7f7032af5f2bb23d1f684977485",
"1.7.2": "b4139899700c1bdbf72880eec4bdb9e46c2cf22d1724a48a018ee0330035462e",
"1.7.1": "78ddbd2dee1c68b5f1d92ff4752cfc6dec3cc15320b41df142e2049c6e3c0ed6",
"1.7": "5a4e9b5a4cdfd7a681195c6016c94d8e5f49e18d91ba0ececa1d81ba5fb15aa7",
"13w43a": "7542353677246babc76d0ab2de4f2c9770c3685ce31f7f08143583b3c7f4f2d7",
"13w42b": "bbbff097c83f1dbb9120eb9f3af0457e339ca58f54b3be612ed4eec09be0b03d",
"13w42a": "0f62c56b10d7b52df893377b60ee9417c956513b8e1f1606b8cb7bee59404642",
"13w41b": "e9ef9fc8304460c9b0efc73e7a07b6b183d2a90d21c4ed13f4087e41742b0aab",
"13w41a": "494126c0143fa85a1b28ac03ee36d1d404931acad8364ee5f5e2acdebb53ed73",
"13w39b": "99229c20fc69fce7994e6db1f0b22224ebc75d5489fbddb4ba85a503ba6ad0ef",
"13w39a": "b738f75209bc5acb3ce9baebd707ceb98306ca59395c7a60e69370d61b48a0b4",
"13w38c": "d42dc65f9a173815a67933b12ef0abed77428142c6559336155a7f9b231b3498",
"13w38b": "571b16ffc98af57ef0f197318b6b3674a07bac40c541acbb90b5e5cca64deebc",
"13w38a": "dd58e97a800b63d88f3a9599120fec9bcae0643dbc70f3aa602e2f620f99fa56",
"1.6.4": "81841a2fedfe0ce19983156a06fa5294335284beeb95c8ca872d3c1a5fcf5774",
"1.6.3": "5a7b3f4ec258b55c8e4feb956d7861b48501a61a618f5c6495fc86ae4985f0e0",
"13w37b": "75090b7f800c690df76b8ada5d31ff96f54e33a24f6337e1c13e71dff625a938",
"13w37a": "3531513b752a1eb65a3f500e14023c04ff9711c61f4769b48e75d31d92a57bad",
"13w36b": "d868871077dba9094990274b8791882ee60374c2696a3b19d04fab5aea6da399",
"13w36a": "1db816ff10df3f0d5a8e9728360ddc8090f6ba7bb5e8c17066ea7ed614303a88",
"1.6.2": "99a7f4088226f5574ec47fa69fda4779376499e5c9c5b8c2342563c7ac35368e",
"1.6.1": "d58a6cc07305bc3bc3915fd8a81ceb07ba4bede3111c971115815789e5674611",
"1.6": "7e6fd851b7646aa32964b0d3370ec33ddc64695074e5c26634d3ff0951617be1",
"13w26a": "3dd2c0ea0ce08c1569acd40e1f09145c620325eafa65ef522551139e1e039fa0",
"13w25c": "949f32be91f32f85f17511abd47b79cacc7bd5d8d8a5d4eb99f282b80bcfd156",
"13w25b": "0dbe9d6cd69551c8f17bcbaf2adecffad6a9c5bc1c0f0e189c2005fb5fa73fc5",
"13w25a": "a9143baab9a1f0d693ebab2420d9e876ff1d62274781a2b2d3112bc0f3da2540",
"13w24b": "b7cd0305793f8a61363781b8dd9800afe0c5bb65e9542407a0c1d15a573b14c2",
"13w24a": "99e504f1ee5dc1b11bf9a084a8d8afaa03f413471f987a39d99cdd561af7b5f2",
"13w23b": "ac28f91f4d9dbe4a2c2098e1231345cb9d15d52df4f0405e4d2beeab30943886",
"13w23a": "005b3ba8d7c26c370be32503868b83984fc67ad88dc5e46b9208bf980335919b",
"13w22a": "a5e21402916da0fb6663a0d0d5584de2b3bf58c2620d4aae6235efb74747fb33",
"13w21b": "3ed24ca37e193d647737ceee6e5f3acb1b40d2209b46bdec9d87df688ecceb77",
"13w21a": "7f4d1dd9cf844fa590cc30051040e1fd8c3c115cb4b48a4b9a200cbe10a5bc0d",
"13w19a": "3d0083272afe405518daa345de20824272669bb91b0568b14e28cad06f4de4d6",
"13w18c": "f0b5bb768e87d88334da5d30c8cbd1503448e248091aaacd8cb8b56bb30c3a76",
"13w18b": "a251665c17145c9f5b0a7aa9f6ed2b7bbb007c48fc7ff4e5dc4cd3b2efb8c4fa",
"13w18a": "46e96fff19bada9e994e20fbe044ec4bc1b226a31f855b4a1760825425676076",
"13w17a": "f2fa011daed1647006df3859147c08d5935f4a9700c0fbd300524c3d800d9fde",
"1.5.2": "4f0c7b79ca2b10716703436550f75fa14e784b999707ffad0ea4e9c38cc256a0",
"13w16b": "ee358d4f84f91f1623f746dcdf157bd526bb2897d5c5293a099ec7ecf72dab51",
"13w16a": "3d3d2f3d9480ef2cb1abd31a1c5879603af0260bd95fd298fed6f026bf42f7ef",
"1.5.1": "e8dc60c93992e495c3a9e6dac7517e6811af7a803a3419eea50dc78e97f51297",
"1.5": "71239880440f8f22a96aac5c00d954401859ddc8847b4f973f563de0b6a5d781",
"1.4.7": "96b7512aead2fb20ddf780d7dd74208d77f209e16058ea8944150179e65b4dd3",
"1.4.5": "b8af871d6b0a03dd2fe65ee9238bb52c60dd5e30d3ded0f37a9eb860e5df206d",
"1.4.6": "90b3b9cd466abcd6ed9e932e1b81f8e34c5771f536670ed9ac493188b021000b",
"1.4.4": "2ea46e24c3c2931dbce11e4d79a83668cd6f002b5bfe131645a98ece099430a3",
"1.4.3": "283c15e256ad4776906e6832de90cecc9a5fd2c28651c6800024c3bc90f5f9fe",
"1.4.2": "16bc7305231d5ceba8b81e43cca8bdcd19cc6d92a488ed270baa5ab827b0fa40",
"1.4.1": "a6ff759d3161ceb3dd9997daaa53c3916f2c8b8b61e38f850a4d9577ea0678ab",
"1.4": "49c50a2c9ad4ab78c1ff9048c1a7f000a4f4d0628000902190ddc1a64d293b71",
"1.3.2": "0795e098d970b459832750d4c6c2c4f58ef55a333a67281418318275e6026eba",
"1.3.1": "62b8c8a3691fb5f51af3bd7efc34d1bc5a227e6162072e9827f439744df994f2",
"1.3": "64ba1cc32240cf12c76b6a235b299c16110e90a535f9c83fc08d9e2e766da0a9",
"1.2.5": "19285d7d16aee740f5a0584f0d80a4940f273a97f5a3eaf251fc1c6c3f2982d1"
}
}

View File

@@ -0,0 +1,120 @@
subject=C=US, O=VeriSign, Inc., OU=Class 3 Public Primary Certification Authority
issuer=C=US, O=VeriSign, Inc., OU=Class 3 Public Primary Certification Authority
-----BEGIN CERTIFICATE-----
MIICPDCCAaUCEDyRMcsf9tAbDpq40ES/Er4wDQYJKoZIhvcNAQEFBQAwXzELMAkG
A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz
cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2
MDEyOTAwMDAwMFoXDTI4MDgwMjIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV
BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt
YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN
ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE
BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is
I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G
CSqGSIb3DQEBBQUAA4GBABByUqkFFBkyCEHwxWsKzH4PIRnN5GfcX6kb5sroc50i
2JhucwNhkcV8sEVAbkSdjbCxlnRhLQ2pRdKkkirWmnWXbj9T/UWZYB2oK0z5XqcJ
2HUw19JlYD1n1khVdWk/kfVIC0dpImmClr7JyDiGSnoscxlIaU5rfGW/D/xwzoiQ
-----END CERTIFICATE-----
subject=C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=(c) 2006 VeriSign, Inc. - For authorized use only, CN=VeriSign Class 3 Public Primary Certification Authority - G5
issuer=C=US, O=VeriSign, Inc., OU=Class 3 Public Primary Certification Authority
-----BEGIN CERTIFICATE-----
MIIE0DCCBDmgAwIBAgIQJQzo4DBhLp8rifcFTXz4/TANBgkqhkiG9w0BAQUFADBf
MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xNzA1BgNVBAsT
LkNsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkw
HhcNMDYxMTA4MDAwMDAwWhcNMjExMTA3MjM1OTU5WjCByjELMAkGA1UEBhMCVVMx
FzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVz
dCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2lnbiwgSW5jLiAtIEZv
ciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAz
IFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzUwggEi
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvJAgIKXo1nmAMqudLO07cfLw8
RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKzj/i5Vbext0uz/o9+B1fs70Pb
ZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIzSdhDY2pSS9KP6HBR
TdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQGBO+QueQA5N06tRn/
Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+rCpSx4/VBEnkjWNH
iDxpg8v+R70rfk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/NIeWiu5T6CUVAgMB
AAGjggGbMIIBlzAPBgNVHRMBAf8EBTADAQH/MDEGA1UdHwQqMCgwJqAkoCKGIGh0
dHA6Ly9jcmwudmVyaXNpZ24uY29tL3BjYTMuY3JsMA4GA1UdDwEB/wQEAwIBBjA9
BgNVHSAENjA0MDIGBFUdIAAwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cudmVy
aXNpZ24uY29tL2NwczAdBgNVHQ4EFgQUf9Nlp8Ld7LvwMAnzQzn6Aq8zMTMwbQYI
KwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEwHzAHBgUrDgMCGgQU
j+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVyaXNpZ24uY29t
L3ZzbG9nby5naWYwNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzABhhhodHRwOi8v
b2NzcC52ZXJpc2lnbi5jb20wPgYDVR0lBDcwNQYIKwYBBQUHAwEGCCsGAQUFBwMC
BggrBgEFBQcDAwYJYIZIAYb4QgQBBgpghkgBhvhFAQgBMA0GCSqGSIb3DQEBBQUA
A4GBABMC3fjohgDyWvj4IAxZiGIHzs73Tvm7WaGY5eE43U68ZhjTresY8g3JbT5K
lCDDPLq9ZVTGr0SzEK0saz6r1we2uIFjxfleLuUqZ87NMwwq14lWAyMfs77oOghZ
tOxFNfeKW/9mz1Cvxm1XjRl4t7mi0VfqH5pLr7rJjhJ+xr3/
-----END CERTIFICATE-----
subject=C=SE, ST=Stockholm, L=Stockholm, O=Mojang, OU=Digital ID Class 3 - Java Object Signing, CN=Mojang
issuer=C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=Terms of use at https://www.verisign.com/rpa (c)10, CN=VeriSign Class 3 Code Signing 2010 CA
-----BEGIN CERTIFICATE-----
MIIFRzCCBC+gAwIBAgIQWAyDGhMqlzv+buZKWtQ52DANBgkqhkiG9w0BAQUFADCB
tDELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTswOQYDVQQLEzJUZXJtcyBvZiB1c2Ug
YXQgaHR0cHM6Ly93d3cudmVyaXNpZ24uY29tL3JwYSAoYykxMDEuMCwGA1UEAxMl
VmVyaVNpZ24gQ2xhc3MgMyBDb2RlIFNpZ25pbmcgMjAxMCBDQTAeFw0xMjA0MDUw
MDAwMDBaFw0xNTA0MDUyMzU5NTlaMIGKMQswCQYDVQQGEwJTRTESMBAGA1UECBMJ
U3RvY2tob2xtMRIwEAYDVQQHEwlTdG9ja2hvbG0xDzANBgNVBAoUBk1vamFuZzEx
MC8GA1UECxMoRGlnaXRhbCBJRCBDbGFzcyAzIC0gSmF2YSBPYmplY3QgU2lnbmlu
ZzEPMA0GA1UEAxQGTW9qYW5nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
AQEApK2feVemdO2gfn5ewbbUZGeRvSygG+bHwIj4fK8c/epUh169x+hDUCrrSI97
7mrulhegTTI+zqsF36SRaWOxlDu75LUGSqp/WKCRQyBqiUB+ZIJXaemWIZipBcWv
rPRYm0bZLMJERT1W+KlshCQSkXDcof8zlFnV4HQ9X9zxlk+9uKhYlCuM1c09sjlK
7xegZUIDiu92g/sRIpVHrtyXLbnSpHRxDzFYZkJDPFhVDjK2x8NIuK4yNOf1nWoM
QYu5V/8tD7uG+HVFTiIg1SkOLNW4XCn1+0vUt2TANga+/NZxLSrlR0Zwtm8KPTNj
o7aOZ40dCfbZqQlqk9wS+2X6awIDAQABo4IBezCCAXcwCQYDVR0TBAIwADAOBgNV
HQ8BAf8EBAMCB4AwQAYDVR0fBDkwNzA1oDOgMYYvaHR0cDovL2NzYzMtMjAxMC1j
cmwudmVyaXNpZ24uY29tL0NTQzMtMjAxMC5jcmwwRAYDVR0gBD0wOzA5BgtghkgB
hvhFAQcXAzAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy52ZXJpc2lnbi5jb20v
cnBhMBMGA1UdJQQMMAoGCCsGAQUFBwMDMHEGCCsGAQUFBwEBBGUwYzAkBggrBgEF
BQcwAYYYaHR0cDovL29jc3AudmVyaXNpZ24uY29tMDsGCCsGAQUFBzAChi9odHRw
Oi8vY3NjMy0yMDEwLWFpYS52ZXJpc2lnbi5jb20vQ1NDMy0yMDEwLmNlcjAfBgNV
HSMEGDAWgBTPmanqeyb0S8mOj9fwBSbv49KnnTARBglghkgBhvhCAQEEBAMCBBAw
FgYKKwYBBAGCNwIBGwQIMAYBAQABAf8wDQYJKoZIhvcNAQEFBQADggEBAHT+RhnF
LoqUlSvo0bxl3eUj81FZg0neyCnpGZV1bFqmDwcwHAWRqOSkrOYxTed6v9cJl0q1
FPXU/6ic0lfUWNqcn0uaS5vfVRpAhRnliLrGlfE5fQfE4lguOUQ4cILK6AxJpeKU
JVDUoeObG2ven83yIy0guevE/1so2VXnV1bFLTtdS5r6iqqMGCshDZMFVleYMo0S
uhfubZDtKIhd9pRLkpg3MzchYLmri5NB67vYZizW11W86QZIWoDIJG8NAWyz6HOJ
rS+ecFa1TBo4gkcKDrd6DT8dMBNUUTTECo6bTGpSfkxjaRK82ZZwS7ui2DMb7K7e
K2GyWhR2PnMg09o=
-----END CERTIFICATE-----
subject=C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=Terms of use at https://www.verisign.com/rpa (c)10, CN=VeriSign Class 3 Code Signing 2010 CA
issuer=C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=(c) 2006 VeriSign, Inc. - For authorized use only, CN=VeriSign Class 3 Public Primary Certification Authority - G5
-----BEGIN CERTIFICATE-----
MIIGCjCCBPKgAwIBAgIQUgDlqiVW/BqG7ZbJ1EszxzANBgkqhkiG9w0BAQUFADCB
yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp
U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW
ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0
aG9yaXR5IC0gRzUwHhcNMTAwMjA4MDAwMDAwWhcNMjAwMjA3MjM1OTU5WjCBtDEL
MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW
ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTswOQYDVQQLEzJUZXJtcyBvZiB1c2UgYXQg
aHR0cHM6Ly93d3cudmVyaXNpZ24uY29tL3JwYSAoYykxMDEuMCwGA1UEAxMlVmVy
aVNpZ24gQ2xhc3MgMyBDb2RlIFNpZ25pbmcgMjAxMCBDQTCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAPUjS16l14q7MunUV/fv5Mcmfq0ZmP6onX2U9jZr
ENd1gTB/BGh/yyt1Hs0dCIzfaZSnN6Oce4DgmeHuN01fzjsU7obU0PUnNbwlCzin
jGOdF6MIpauw+81qYoJM1SHaG9nx44Q7iipPhVuQAU/Jp3YQfycDfL6ufn3B3fkF
vBtInGnnwKQ8PEEAPt+W5cXklHHWVQHHACZKQDy1oSapDKdtgI6QJXvPvz8c6y+W
+uWHd8a1VrJ6O1QwUxvfYjT/HtH0WpMoheVMF05+W/2kk5l/383vpHXv7xX2R+f4
GXLYLjQaprSnTH69u08MPVfxMNamNo7WgHbXGS6lzX40LYkCAwEAAaOCAf4wggH6
MBIGA1UdEwEB/wQIMAYBAf8CAQAwcAYDVR0gBGkwZzBlBgtghkgBhvhFAQcXAzBW
MCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy52ZXJpc2lnbi5jb20vY3BzMCoGCCsG
AQUFBwICMB4aHGh0dHBzOi8vd3d3LnZlcmlzaWduLmNvbS9ycGEwDgYDVR0PAQH/
BAQDAgEGMG0GCCsGAQUFBwEMBGEwX6FdoFswWTBXMFUWCWltYWdlL2dpZjAhMB8w
BwYFKw4DAhoEFI/l0xqGrI2Oa8PPgGrUSBgsexkuMCUWI2h0dHA6Ly9sb2dvLnZl
cmlzaWduLmNvbS92c2xvZ28uZ2lmMDQGA1UdHwQtMCswKaAnoCWGI2h0dHA6Ly9j
cmwudmVyaXNpZ24uY29tL3BjYTMtZzUuY3JsMDQGCCsGAQUFBwEBBCgwJjAkBggr
BgEFBQcwAYYYaHR0cDovL29jc3AudmVyaXNpZ24uY29tMB0GA1UdJQQWMBQGCCsG
AQUFBwMCBggrBgEFBQcDAzAoBgNVHREEITAfpB0wGzEZMBcGA1UEAxMQVmVyaVNp
Z25NUEtJLTItODAdBgNVHQ4EFgQUz5mp6nsm9EvJjo/X8AUm7+PSp50wHwYDVR0j
BBgwFoAUf9Nlp8Ld7LvwMAnzQzn6Aq8zMTMwDQYJKoZIhvcNAQEFBQADggEBAFYi
5jSkxGHLSLkBrVaoZA/ZjJHEu8wM5a16oCJ/30c4Si1s0X9xGnzscKmx8E/kDwxT
+hVe/nSYSSSFgSYckRRHsExjjLuhNNTGRegNhSZzA9CpjGRt3HGS5kUFYBVZUTn8
WBRr/tSk7XlrCAxBcuc3IgYJviPpP0SaHulhncyxkFz8PdKNrEI9ZTbUtD1AKI+b
EM8jJsxLIMuQH12MTDTKPNjlN9ZvpSC9NOsm2a4N58Wa96G0IZEzb4boWLslfHQO
WP51G2M/zjF8m48blp7FU3aEW5ytkfqs7ZO6XcghU8KCU2OvEg1QhxEbPVRSloos
nD2SGgiaBS7Hk6VIkdM=
-----END CERTIFICATE-----

View File

@@ -0,0 +1,5 @@
# Minecraft certificate chain
Exported from the vanilla jar by extracting MOJANGCS.RSA from the jar and then running:
`openssl pkcs7 -inform DER -in MOJANGCS.RSA -print_certs -out cert.pem`

View File

@@ -38,6 +38,7 @@ import net.fabricmc.loom.util.download.Download
import net.fabricmc.loom.util.download.DownloadException
import net.fabricmc.loom.util.download.DownloadExecutor
import net.fabricmc.loom.util.download.DownloadProgressListener
import net.fabricmc.loom.util.download.DownloadResult
class DownloadFileTest extends DownloadTest {
@IgnoreIf({ os.windows }) // Requires admin on windows.
@@ -115,16 +116,20 @@ class DownloadFileTest extends DownloadTest {
}
def output = new File(File.createTempDir(), "file.txt").toPath()
def results = [] as List<DownloadResult>
when:
for (i in 0..<2) {
Download.create("$PATH/sha1.txt")
def result = Download.create("$PATH/sha1.txt")
.sha1("0a4d55a8d778e5022fab701977c5d840bbc486d0")
.downloadPath(output)
results << result
}
then:
requestCount == 1
results[0].didDownload()
!results[1].didDownload()
}
def "Invalid Sha1"() {
@@ -365,6 +370,28 @@ class DownloadFileTest extends DownloadTest {
Files.readString(dir.resolve("4.txt")) == "Hello World"
}
def "File: Async result"() {
setup:
server.get("/async1") {
it.result("Hello World")
}
def dir = File.createTempDir().toPath()
when:
boolean didDownload = false
new DownloadExecutor(2).withCloseable {
Download.create("$PATH/async1")
.downloadPathAsync(dir.resolve("1.txt"), it)
.thenAccept {
didDownload = it.didDownload()
}
}
then:
didDownload
}
def "File: Async Error"() {
setup:
server.get("/async2") {

View File

@@ -0,0 +1,110 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2025 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.test.unit.providers
import spock.lang.Specification
import net.fabricmc.loom.configuration.providers.minecraft.verify.CertificateChain
import net.fabricmc.loom.configuration.providers.minecraft.verify.SignatureVerificationFailure
import net.fabricmc.loom.test.util.CertificateUtils
class CertificateChainTest extends Specification {
def "load mojang's cert chain"() {
when:
def chain = CertificateChain.getRoot("mojangcs")
then:
chain.certificate().issuerX500Principal.name == "OU=Class 3 Public Primary Certification Authority,O=VeriSign\\, Inc.,C=US"
}
def "load certificate chain"() {
given:
def keyPair = CertificateUtils.generateKeyPair()
def root = CertificateUtils.createCert(keyPair, "CN=Test Root Certificate")
def intermediate = CertificateUtils.createCert(keyPair, "CN=Test Intermediate Certificate", root)
def leaf = CertificateUtils.createCert(keyPair, "CN=Test Leaf Certificate", intermediate)
when:
def chain = CertificateChain.getRoot([root, intermediate, leaf])
then:
chain.issuer() == null
chain.certificate() == root
chain.children().size() == 1
chain.children()[0].issuer().certificate() == root
chain.children()[0].certificate() == intermediate
chain.children()[0].children().size() == 1
}
def "matching cert chain"() {
given:
def keyPair = CertificateUtils.generateKeyPair()
def root = CertificateUtils.createCert(keyPair, "CN=Test Root Certificate")
def intermediate = CertificateUtils.createCert(keyPair, "CN=Test Intermediate Certificate", root)
def leaf = CertificateUtils.createCert(keyPair, "CN=Test Leaf Certificate", intermediate)
when:
def chain1 = CertificateChain.getRoot([root, intermediate, leaf])
def chain2 = CertificateChain.getRoot([root, intermediate, leaf])
then:
chain1.verifyChainMatches(chain2)
}
def "different leaf cert"() {
given:
def keyPair = CertificateUtils.generateKeyPair()
def root = CertificateUtils.createCert(keyPair, "CN=Test Root Certificate")
def intermediate = CertificateUtils.createCert(keyPair, "CN=Test Intermediate Certificate", root)
def leaf1 = CertificateUtils.createCert(keyPair, "CN=Test Leaf 1 Certificate", intermediate)
def leaf2 = CertificateUtils.createCert(keyPair, "CN=Test Leaf 2 Certificate", intermediate)
when:
def chain1 = CertificateChain.getRoot([root, intermediate, leaf1])
def chain2 = CertificateChain.getRoot([root, intermediate, leaf2])
chain1.verifyChainMatches(chain2)
then:
thrown SignatureVerificationFailure
}
def "different cert"() {
given:
def keyPair = CertificateUtils.generateKeyPair()
def root = CertificateUtils.createCert(keyPair, "CN=Test Root Certificate")
def root2 = CertificateUtils.createCert(keyPair, "CN=Test Root 2 Certificate")
when:
def chain1 = CertificateChain.getRoot([root])
def chain2 = CertificateChain.getRoot([root2])
chain1.verifyChainMatches(chain2)
then:
thrown SignatureVerificationFailure
}
}

View File

@@ -0,0 +1,110 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2025 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.test.unit.providers
import spock.lang.Specification
import net.fabricmc.loom.configuration.providers.minecraft.verify.CertificateChain
import net.fabricmc.loom.configuration.providers.minecraft.verify.CertificateRevocationList
import net.fabricmc.loom.configuration.providers.minecraft.verify.SignatureVerificationFailure
import net.fabricmc.loom.test.util.CertificateUtils
import net.fabricmc.loom.test.util.GradleTestUtil
class CertificateRevocationListTest extends Specification {
// Test to make sure that the CRL URL is correct for the mojang cert chain
// As we don't want to depend on bouncycastle in the main project just to extract the same crl url each time
def "crl url matches"() {
given:
def cert = CertificateChain.getRoot("mojangcs")
when:
def crls = CertificateUtils.getCrls(cert)
then:
crls.sort() == CertificateRevocationList.CSC3_2010
}
def "valid cert"() {
given:
def keyPair = CertificateUtils.generateKeyPair()
def root = CertificateUtils.createCert(keyPair, "CN=Test Root Certificate")
def intermediate = CertificateUtils.createCert(keyPair, "CN=Test Intermediate Certificate", root)
def validLeaf = CertificateUtils.createCert(keyPair, "CN=Test Valid Leaf Certificate", intermediate)
def revokedLeaf = CertificateUtils.createCert(keyPair, "CN=Test Revoked Leaf Certificate", intermediate)
def x509crl = CertificateUtils.createCrl(keyPair, intermediate, [revokedLeaf])
def chain = CertificateChain.getRoot([root, intermediate, validLeaf])
when:
def crl = new CertificateRevocationList([x509crl], false)
then:
crl.verify(chain)
}
def "revoked cert"() {
given:
def keyPair = CertificateUtils.generateKeyPair()
def root = CertificateUtils.createCert(keyPair, "CN=Test Root Certificate")
def intermediate = CertificateUtils.createCert(keyPair, "CN=Test Intermediate Certificate", root)
def revokedLeaf = CertificateUtils.createCert(keyPair, "CN=Test Revoked Leaf Certificate", intermediate)
def x509crl = CertificateUtils.createCrl(keyPair, intermediate, [revokedLeaf])
def chain = CertificateChain.getRoot([
root,
intermediate,
revokedLeaf
])
when:
def crl = new CertificateRevocationList([x509crl], false)
crl.verify(chain)
then:
thrown SignatureVerificationFailure
}
def "Verify Mojang cert"() {
given:
def project = GradleTestUtil.mockProject()
def cert = CertificateChain.getRoot("mojangcs")
when:
def crl = CertificateRevocationList.create(project, CertificateRevocationList.CSC3_2010)
then:
!crl.downloadFailure()
crl.verify(cert)
}
def "Invalid URL"() {
given:
def project = GradleTestUtil.mockProject()
def cert = CertificateChain.getRoot("mojangcs")
when:
def crl = CertificateRevocationList.create(project, ["http://invalid.url"])
then:
crl.downloadFailure()
crl.verify(cert)
}
}

View File

@@ -0,0 +1,127 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2025 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.test.unit.providers
import java.nio.file.Files
import java.nio.file.Path
import spock.lang.Specification
import net.fabricmc.loom.configuration.providers.minecraft.verify.CertificateChain
import net.fabricmc.loom.configuration.providers.minecraft.verify.JarVerifier
import net.fabricmc.loom.configuration.providers.minecraft.verify.SignatureVerificationFailure
import net.fabricmc.loom.test.LoomTestConstants
import net.fabricmc.loom.util.ZipUtils
import net.fabricmc.loom.util.download.Download
class JarVerifierTest extends Specification {
public static final String CLIENT_JAR_URL = "https://launcher.mojang.com/v1/objects/7e46fb47609401970e2818989fa584fd467cd036/client.jar"
public static final String INSTALLER_JAR_URL = "https://maven.fabricmc.net/net/fabricmc/fabric-installer/1.0.3/fabric-installer-1.0.3.jar"
public static final File mcJarDir = new File(LoomTestConstants.TEST_DIR, "jar-verifier")
def "verify Minecraft Jar"() {
setup:
def clientJar = downloadJarIfNotExists(CLIENT_JAR_URL, "client.jar")
def cert = CertificateChain.getRoot("mojangcs")
when:
JarVerifier.verify(clientJar, cert)
then:
true == true
}
def "invalid Minecraft Jar, extra entry"() {
setup:
def clientJar = downloadJarIfNotExists(CLIENT_JAR_URL, "client.jar")
Path tempDir = Files.createTempDirectory("test")
def tempJar = tempDir.resolve("client.jar")
Files.copy(clientJar, tempJar)
ZipUtils.add(tempJar, "extra.txt", "Hello World".bytes)
def cert = CertificateChain.getRoot("mojangcs")
when:
JarVerifier.verify(tempJar, cert)
then:
def e = thrown SignatureVerificationFailure
e.message == "Jar entry extra.txt does not have a signature"
}
def "invalid Minecraft Jar, modified entry"() {
setup:
def clientJar = downloadJarIfNotExists(CLIENT_JAR_URL, "client.jar")
Path tempDir = Files.createTempDirectory("test")
def tempJar = tempDir.resolve("client.jar")
Files.copy(clientJar, tempJar)
ZipUtils.replace(tempJar, "version.json", "Hello World".bytes)
def cert = CertificateChain.getRoot("mojangcs")
when:
JarVerifier.verify(tempJar, cert)
then:
def e = thrown SignatureVerificationFailure
e.message == "Jar entry version.json failed signature verification"
}
def "invalid Minecraft Jar, not signed"() {
setup:
Path tempDir = Files.createTempDirectory("test")
def tempJar = tempDir.resolve("client.jar")
ZipUtils.add(tempJar, "hello.txt", "Hello World".bytes)
def cert = CertificateChain.getRoot("mojangcs")
when:
JarVerifier.verify(tempJar, cert)
then:
def e = thrown SignatureVerificationFailure
e.message == "Jar entry hello.txt does not have a signature"
}
def "not minecraft"() {
setup:
def installerJar = downloadJarIfNotExists(INSTALLER_JAR_URL, "installer.jar")
def cert = CertificateChain.getRoot("mojangcs")
when:
JarVerifier.verify(installerJar, cert)
then:
def e = thrown SignatureVerificationFailure
e.message == "Certificate mismatch: CN=Fabric,OU=CI,O=Fabric,L=Unknown,ST=Unknown,C=Unknown != OU=Class 3 Public Primary Certification Authority,O=VeriSign\\, Inc.,C=US"
}
static Path downloadJarIfNotExists(String url, String name) {
File dst = new File(mcJarDir, name)
if (!dst.exists()) {
dst.parentFile.mkdirs()
Download.create(url)
.defaultCache()
.downloadPath(dst.toPath())
}
return dst.toPath()
}
}

View File

@@ -0,0 +1,40 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2025 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.test.unit.providers
import spock.lang.Specification
import net.fabricmc.loom.configuration.providers.minecraft.verify.KnownVersions
class KnownVersionsTest extends Specification {
// Just a simple test to make sure we can load the known versions
def "check known versions"() {
when:
def versions = KnownVersions.INSTANCE.get()
then:
versions.client().get("1.2.5") == "c1c3740a912ef523a8bd46605ab5708643498330140cba175c7ce6f177e468e1"
versions.server().get("1.16.5") == "58f329c7d2696526f948470aa6fd0b45545039b64cb75015e64c12194b373da6"
}
}

View File

@@ -0,0 +1,157 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2025 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.test.unit.providers
import java.nio.file.Files
import java.nio.file.Path
import spock.lang.Specification
import net.fabricmc.loom.LoomGradlePlugin
import net.fabricmc.loom.configuration.providers.BundleMetadata
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftVersionMeta
import net.fabricmc.loom.configuration.providers.minecraft.VersionsManifest
import net.fabricmc.loom.configuration.providers.minecraft.verify.MinecraftJarVerification
import net.fabricmc.loom.configuration.providers.minecraft.verify.SignatureVerificationFailure
import net.fabricmc.loom.test.util.GradleTestUtil
import net.fabricmc.loom.util.Constants
import net.fabricmc.loom.util.ZipUtils
import net.fabricmc.loom.util.download.Download
class MinecraftJarVerificationTest extends Specification {
static Path dir = Path.of(".gradle", "test-files", "jar-verification")
def "check client verified"() {
setup:
def jar = getJar(version, "client")
def project = GradleTestUtil.mockProject()
def verification = project.objects.newInstance(MinecraftJarVerification.class, version)
when:
verification.verifyClientJar(jar)
then:
true == true
where:
version | _
"1.21.5" | _
"1.16.5" | _
"1.14.4" | _
"1.7.10" | _
"1.7.9" | _ // Sha1 signed
"b1.5" | _ // Not signed
}
def "check bundled server verified"() {
setup:
def jar = getJar(version, "server")
def unpackedJar = jar.resolveSibling(jar.fileName.toString() + ".unpacked")
def project = GradleTestUtil.mockProject()
def verification = project.objects.newInstance(MinecraftJarVerification.class, version)
def bundle = BundleMetadata.fromJar(jar)
bundle.versions().get(0).unpackEntry(jar, unpackedJar, project)
when:
verification.verifyServerJar(unpackedJar)
then:
true == true
where:
version | _
"1.21.5" | _
}
def "check standalone server verified"() {
setup:
def jar = getJar(version, "server")
def project = GradleTestUtil.mockProject()
def verification = project.objects.newInstance(MinecraftJarVerification.class, version)
when:
verification.verifyServerJar(jar)
then:
true == true
where:
version | _
"1.16.5" | _
"1.14.4" | _
"1.7.10" | _
"1.7.9" | _ // Sha1 signed
"1.2.5" | _
}
def "hash mismatch"() {
setup:
def jar = getJar("1.2.5", "client")
def project = GradleTestUtil.mockProject()
def verification = project.objects.newInstance(MinecraftJarVerification.class, "1.2.4")
when:
verification.verifyClientJar(jar)
then:
thrown SignatureVerificationFailure
}
def "unverified jar"() {
setup:
Path tempDir = Files.createTempDirectory("test")
def jar = tempDir.resolve("client.jar")
ZipUtils.add(jar, "hello.txt", "Hello World".bytes)
def project = GradleTestUtil.mockProject()
def verification = project.objects.newInstance(MinecraftJarVerification.class, "blah")
when:
verification.verifyClientJar(jar)
then:
thrown SignatureVerificationFailure
}
private static Path getJar(String id, String type) {
def versionManifest = Download.create(Constants.VERSION_MANIFESTS)
.downloadString(dir.resolve("manifest.json"))
final VersionsManifest versions = LoomGradlePlugin.GSON.fromJson(versionManifest, VersionsManifest.class)
def version = versions.getVersion(id)
def manifest = Download.create(version.url)
.sha1(version.sha1)
.downloadString(dir.resolve(version.id + ".json"))
def meta = LoomGradlePlugin.GSON.fromJson(manifest, MinecraftVersionMeta.class)
def download = meta.download(type)
Path jarPath = dir.resolve(download.sha1() + ".jar")
Download.create(download.url())
.sha1(download.sha1())
.downloadPath(jarPath)
return jarPath
}
}

View File

@@ -0,0 +1,131 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2025 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.test.util
import java.security.KeyPair
import java.security.KeyPairGenerator
import java.security.cert.X509CRL
import java.security.cert.X509Certificate
import org.bouncycastle.asn1.DERIA5String
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x509.CRLDistPoint
import org.bouncycastle.asn1.x509.CRLNumber
import org.bouncycastle.asn1.x509.CRLReason
import org.bouncycastle.asn1.x509.DistributionPointName
import org.bouncycastle.asn1.x509.Extension
import org.bouncycastle.asn1.x509.GeneralName
import org.bouncycastle.asn1.x509.GeneralNames
import org.bouncycastle.cert.jcajce.JcaX509CRLConverter
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils
import org.bouncycastle.cert.jcajce.JcaX509v2CRLBuilder
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
import net.fabricmc.loom.configuration.providers.minecraft.verify.CertificateChain
/**
* Test code, not for production use.
*/
class CertificateUtils {
private static final JcaContentSignerBuilder SIGNER_BUILDER = new JcaContentSignerBuilder("SHA384withECDSA")
private static final JcaX509CertificateConverter CERT_CONVERTER = new JcaX509CertificateConverter()
private static final JcaX509CRLConverter CRL_CONVERTER = new JcaX509CRLConverter()
static KeyPair generateKeyPair() {
def keyPairGenerator = KeyPairGenerator.getInstance("EC")
keyPairGenerator.initialize(384)
return keyPairGenerator.generateKeyPair()
}
static X509Certificate createCert(KeyPair keyPair, String name, X509Certificate parent = null) {
def issuerName = new X500Name(parent ? parent.subjectX500Principal.name : name)
def subjectName = new X500Name(name)
def notBefore = new Date()
def notAfter = new Date(notBefore.getTime() + 365 * 24 * 60 * 60 * 1000) // 1 year
def serialNumber = BigInteger.valueOf(System.currentTimeMillis())
def builder = new JcaX509v3CertificateBuilder(
issuerName,
serialNumber,
notBefore,
notAfter,
subjectName,
keyPair.getPublic()
)
def contentSigner = SIGNER_BUILDER.build(keyPair.getPrivate())
return CERT_CONVERTER.getCertificate(builder.build(contentSigner))
}
static X509CRL createCrl(KeyPair keyPair, X509Certificate issuerCert, List<X509Certificate> revokedCerts) {
def builder = new JcaX509v2CRLBuilder(issuerCert, new Date())
for (final def revoked in revokedCerts) {
assert revoked.getIssuerX500Principal() == issuerCert.getSubjectX500Principal()
builder.addCRLEntry(revoked.getSerialNumber(), new Date(), CRLReason.keyCompromise)
}
builder.addExtension(Extension.authorityKeyIdentifier, false,
new JcaX509ExtensionUtils().createAuthorityKeyIdentifier(keyPair.getPublic()))
builder.addExtension(Extension.cRLNumber, false, new CRLNumber(BigInteger.ONE))
def crlSigner = SIGNER_BUILDER.build(keyPair.getPrivate())
return CRL_CONVERTER.getCRL(builder.build(crlSigner))
}
static List<String> getCrls(CertificateChain certificateChain) {
def crls = [] as Set
getCrls(certificateChain, crls)
return crls.toList()
}
static void getCrls(CertificateChain certificateChain, Set<String> crls) {
crls.addAll(getCrls(certificateChain.certificate()))
certificateChain.children().each { child ->
getCrls(child, crls)
}
}
static ArrayList<String> getCrls(X509Certificate certificate) {
byte[] crlDistributionPointsValue = certificate.getExtensionValue(Extension.cRLDistributionPoints.getId())
if (crlDistributionPointsValue == null) {
return []
}
return CRLDistPoint
.getInstance(JcaX509ExtensionUtils.parseExtensionValue(crlDistributionPointsValue))
.getDistributionPoints()
.findAll { it.getDistributionPoint().type == DistributionPointName.FULL_NAME }
.collectMany { distPoint ->
GeneralNames.getInstance(distPoint.getDistributionPoint().getName()).getNames()
.findAll { it.tagNo == GeneralName.uniformResourceIdentifier }
.collect { DERIA5String.getInstance(it.name).getString() }
}
}
}

View File

@@ -33,6 +33,7 @@ import org.gradle.api.internal.tasks.DefaultSourceSet
import org.gradle.api.model.ObjectFactory
import org.gradle.api.plugins.ExtensionContainer
import org.gradle.api.provider.Property
import org.gradle.api.provider.ProviderFactory
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.util.PatternFilterable
import org.jetbrains.annotations.Nullable
@@ -63,9 +64,10 @@ class GradleTestUtil {
}
static Project mockProject() {
def objectFactory = TestServiceFactory.objectFactory
def providerFactory = TestServiceFactory.providerFactory
def mock = mock(Project.class)
def serviceRegistry = TestServiceFactory.createServiceRegistry(mock)
def objectFactory = serviceRegistry.get(ObjectFactory)
def providerFactory = serviceRegistry.get(ProviderFactory)
def extensions = mockExtensionContainer()
when(mock.getExtensions()).thenReturn(extensions)
when(mock.getObjects()).thenReturn(objectFactory)

View File

@@ -0,0 +1,143 @@
/*
* This file is part of fabric-loom, licensed under the MIT License (MIT).
*
* Copyright (c) 2025 FabricMC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.fabricmc.loom.test.util
import java.nio.file.Path
import java.security.MessageDigest
import net.fabricmc.loom.LoomGradlePlugin
import net.fabricmc.loom.configuration.providers.BundleMetadata
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftVersionMeta
import net.fabricmc.loom.configuration.providers.minecraft.VersionsManifest
import net.fabricmc.loom.configuration.providers.minecraft.verify.CertificateChain
import net.fabricmc.loom.configuration.providers.minecraft.verify.JarVerifier
import net.fabricmc.loom.configuration.providers.minecraft.verify.KnownVersions
import net.fabricmc.loom.configuration.providers.minecraft.verify.SignatureVerificationFailure
import net.fabricmc.loom.util.Constants
import net.fabricmc.loom.util.download.Download
import net.fabricmc.loom.util.download.DownloadExecutor
/**
* A quick and dirty script to generate the known_versions.json file
* This file contains a list of all the unsigned versions of Minecraft and their sha1 hashes
* Note: Running this will take a while as it downloads all the versions of Minecraft
*/
class KnownVersionsGenerator {
static Path dir = Path.of(".gradle", "test-files", "unsigned")
static CertificateChain chain = CertificateChain.getRoot("mojangcs")
static void main(String[] args) {
def versionManifest = Download.create(Constants.VERSION_MANIFESTS)
.downloadString()
final VersionsManifest manifest = LoomGradlePlugin.GSON.fromJson(versionManifest, VersionsManifest.class)
// Download all the minecraft jars
new DownloadExecutor(10).withCloseable {
for (def version in manifest.versions()) {
downloadVersion(version, it)
}
}
println("Downloaded all versions")
def unsignedClientVersions = [:] as Map<String, String>
def unsignedServerVersions = [:] as Map<String, String>
for (def version in manifest.versions()) {
println("Checking version " + version.id)
checkVersion(version, unsignedClientVersions, unsignedServerVersions)
}
def json = LoomGradlePlugin.GSON.toJson(new KnownVersions(unsignedClientVersions, unsignedServerVersions))
println(json)
}
static void downloadVersion(VersionsManifest.Version version, DownloadExecutor downloadExecutor) {
def manifest = Download.create(version.url)
.sha1(version.sha1)
.downloadString(dir.resolve(version.id + ".json"))
def meta = LoomGradlePlugin.GSON.fromJson(manifest, MinecraftVersionMeta.class)
def client = meta.download("client")
def server = meta.download("server")
download(client, downloadExecutor)
if (server != null) {
download(server, downloadExecutor)
}
}
static void download(MinecraftVersionMeta.Download download, DownloadExecutor executor) {
Path jarPath = dir.resolve(download.sha1() + ".jar")
Download.create(download.url())
.sha1(download.sha1())
.downloadPathAsync(jarPath, executor)
}
static void checkVersion(VersionsManifest.Version version, Map<String, String> unsignedClientVersions, Map<String, String> unsignedServerVersions) {
def manifest = Download.create(version.url)
.sha1(version.sha1)
.downloadString(dir.resolve(version.id + ".json"))
def meta = LoomGradlePlugin.GSON.fromJson(manifest, MinecraftVersionMeta.class)
def client = meta.download("client")
def server = meta.download("server")
def clientJar = dir.resolve(client.sha1() + ".jar")
if (!isSigned(clientJar)) {
unsignedClientVersions.put(version.id, sha256(clientJar))
}
if (server != null) {
def serverJar = dir.resolve(server.sha1() + ".jar")
if (BundleMetadata.fromJar(serverJar) == null) {
unsignedServerVersions.put(version.id, sha256(serverJar))
}
}
}
static boolean isSigned(Path jarPath) {
try {
JarVerifier.verify(jarPath, chain)
return true
} catch (SignatureVerificationFailure ignored) {
return false
}
}
static String sha256(Path path) {
MessageDigest md = MessageDigest.getInstance("SHA-256")
path.withInputStream { inputStream ->
byte[] buffer = new byte[8192]
int bytesRead
while ((bytesRead = inputStream.read(buffer)) != -1) {
md.update(buffer, 0, bytesRead)
}
}
return md.digest().encodeHex().toString()
}
}

View File

@@ -25,6 +25,7 @@
package net.fabricmc.loom.test.util
import groovy.transform.CompileStatic
import org.gradle.api.Project
import org.gradle.api.internal.CollectionCallbackActionDecorator
import org.gradle.api.internal.MutationGuard
import org.gradle.api.internal.MutationGuards
@@ -71,7 +72,7 @@ class TestServiceFactory {
public static final ObjectFactory objectFactory = serviceRegistry.get(ObjectFactory)
public static final ProviderFactory providerFactory = serviceRegistry.get(ProviderFactory)
private static ServiceRegistry createServiceRegistry() {
static ServiceRegistry createServiceRegistry(Project project = null) {
def services = new DefaultServiceRegistry()
services.register {
it.add(DefaultPropertyFactory)
@@ -83,6 +84,11 @@ class TestServiceFactory {
it.add(FileCollectionFactory, fileCollectionFactory())
it.add(DefaultDomainObjectCollectionFactory)
it.add(CrossBuildInMemoryCacheFactory, new DefaultCrossBuildInMemoryCacheFactory(mock(ListenerManager)))
if (project != null) {
it.add(Project, project)
}
//noinspection unused
it.addProvider(new ServiceRegistrationProvider() {
@Provides