From 8a0701e8549ad73a0fe4020937b07f72d2bc1de0 Mon Sep 17 00:00:00 2001 From: Moritz Zwerger Date: Wed, 17 Jan 2024 18:28:52 +0100 Subject: [PATCH] updater: download and verify update --- .../bixilon/minosoft/assets/util/FileUtil.kt | 32 +-------------- .../system/opengl/OpenGLRenderSystem.kt | 16 +++----- .../minosoft/updater/MinosoftUpdater.kt | 41 +++++++++++++++++-- .../minosoft/updater/UpdateProgress.kt | 14 +------ .../java/de/bixilon/minosoft/util/KUtil.kt | 33 ++++++++++++++- .../util/signature/SignatureSigner.kt | 2 +- 6 files changed, 78 insertions(+), 60 deletions(-) diff --git a/src/main/java/de/bixilon/minosoft/assets/util/FileUtil.kt b/src/main/java/de/bixilon/minosoft/assets/util/FileUtil.kt index 4a0e3a671..78bab5e53 100644 --- a/src/main/java/de/bixilon/minosoft/assets/util/FileUtil.kt +++ b/src/main/java/de/bixilon/minosoft/assets/util/FileUtil.kt @@ -14,13 +14,10 @@ package de.bixilon.minosoft.assets.util import com.github.luben.zstd.ZstdInputStream -import de.bixilon.kutil.buffer.BufferDefinition -import de.bixilon.kutil.random.RandomStringUtil.randomString import de.bixilon.minosoft.terminal.RunConfiguration -import de.bixilon.minosoft.util.KUtil import java.io.* +import java.nio.file.Files import java.nio.file.Path -import java.security.MessageDigest object FileUtil { @@ -60,31 +57,6 @@ object FileUtil { fun createTempFile(): File { - var file: File - - for (i in 0 until AssetsOptions.MAX_FILE_CHECKING) { - file = RunConfiguration.TEMPORARY_FOLDER.resolve(KUtil.RANDOM.randomString(32)).toFile() - if (!file.exists()) { - return file - } - } - - throw IOException("Can not find temporary file after ${AssetsOptions.MAX_FILE_CHECKING} tries!") - } - - @Deprecated("kutil 1.26") - fun InputStream.copy(vararg output: OutputStream, digest: MessageDigest?) { - val buffer = ByteArray(BufferDefinition.DEFAULT_BUFFER_SIZE) - - while (true) { - val length = read(buffer, 0, buffer.size) - if (length < 0) { - break - } - for (stream in output) { - stream.write(buffer, 0, length) - } - digest?.update(buffer, 0, length) - } + return Files.createTempFile(RunConfiguration.TEMPORARY_FOLDER, "", "").toFile() } } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/system/opengl/OpenGLRenderSystem.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/system/opengl/OpenGLRenderSystem.kt index 4452376d7..b53c91a58 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/system/opengl/OpenGLRenderSystem.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/system/opengl/OpenGLRenderSystem.kt @@ -1,6 +1,6 @@ /* * Minosoft - * Copyright (C) 2020-2023 Moritz Zwerger + * Copyright (C) 2020-2024 Moritz Zwerger * * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. * @@ -285,17 +285,11 @@ class OpenGLRenderSystem( } override fun getErrors(): List { - var error: Int = glGetError() - if (error == GL_NO_ERROR) { - return emptyList() - } - val errors: MutableList = mutableListOf() - do { - errors += OpenGLError(error) - error = glGetError() - } while (error != GL_NO_ERROR) + val error = glGetError() - return errors + if (error == GL_NO_ERROR) return emptyList() + // opengl only supports single error + return listOf(OpenGLError(error)) } private var polygonOffsetFactor: Float = 0.0f diff --git a/src/main/java/de/bixilon/minosoft/updater/MinosoftUpdater.kt b/src/main/java/de/bixilon/minosoft/updater/MinosoftUpdater.kt index 36e74a508..6f7e460fb 100644 --- a/src/main/java/de/bixilon/minosoft/updater/MinosoftUpdater.kt +++ b/src/main/java/de/bixilon/minosoft/updater/MinosoftUpdater.kt @@ -13,14 +13,20 @@ package de.bixilon.minosoft.updater +import com.google.common.io.Files +import de.bixilon.kutil.array.ByteArrayUtil.toHex +import de.bixilon.kutil.base64.Base64Util.fromBase64 import de.bixilon.kutil.concurrent.pool.DefaultThreadPool +import de.bixilon.kutil.hash.HashUtil import de.bixilon.kutil.observer.DataObserver.Companion.observed import de.bixilon.kutil.os.PlatformInfo import de.bixilon.kutil.string.StringUtil.formatPlaceholder import de.bixilon.kutil.url.URLUtil.toURL +import de.bixilon.minosoft.assets.util.FileUtil import de.bixilon.minosoft.config.profile.profiles.other.OtherProfileManager import de.bixilon.minosoft.properties.MinosoftProperties import de.bixilon.minosoft.terminal.RunConfiguration +import de.bixilon.minosoft.util.KUtil.copy import de.bixilon.minosoft.util.http.HTTP2.get import de.bixilon.minosoft.util.http.HTTPResponse import de.bixilon.minosoft.util.http.exceptions.HTTPException @@ -28,7 +34,11 @@ import de.bixilon.minosoft.util.json.Jackson import de.bixilon.minosoft.util.logging.Log import de.bixilon.minosoft.util.logging.LogLevels import de.bixilon.minosoft.util.logging.LogMessageType +import java.io.File +import java.io.FileOutputStream import java.net.URL +import java.security.MessageDigest +import java.security.SignatureException object MinosoftUpdater { var update: MinosoftUpdate? by observed(null) @@ -110,13 +120,36 @@ object MinosoftUpdater { val download = update.download if (download == null) { progress.log?.print("Update is unavailable for download. Please download it manually!") - progress.stage = UpdateProgress.UpdateStage.FAILED + progress.error = IllegalAccessError("Unavailable...") return } progress.log?.print("Downloading update...") - progress.log?.print("TODO :)") - progress.stage = UpdateProgress.UpdateStage.FAILED - // UpdateKey.require(data, signature) + try { + val stream = download.url.openStream() + val digest = MessageDigest.getInstance(HashUtil.SHA_512) + val temp = FileUtil.createTempFile() + val signature = UpdateKey.createInstance() + stream.copy(FileOutputStream(temp), digest = digest, signature = signature) + if (digest.digest().toHex() != download.sha512) throw SignatureException("Hash mismatch of downloaded file: Expected ${download.sha512}, got ${digest.digest().toHex()}") + if (!signature.verify(download.signature.fromBase64())) throw SignatureException("Signature of downloaded file mismatches!") + + progress.log?.print("Moving temporary file to final destination") + + // move to current directory + val output = File(("./Minosoft-${update.id}.jar")) + Files.move(temp, output) + progress.log?.print("Success, file saved to $output") + + // TODO: restart minosoft + } catch (error: Throwable) { + if (progress.log == null) { + error.printStackTrace() + } else { + progress.log?.print(error) + } + progress.error = error + throw error + } } } diff --git a/src/main/java/de/bixilon/minosoft/updater/UpdateProgress.kt b/src/main/java/de/bixilon/minosoft/updater/UpdateProgress.kt index c4949779e..49a62af1a 100644 --- a/src/main/java/de/bixilon/minosoft/updater/UpdateProgress.kt +++ b/src/main/java/de/bixilon/minosoft/updater/UpdateProgress.kt @@ -1,6 +1,6 @@ /* * Minosoft - * Copyright (C) 2020-2023 Moritz Zwerger + * Copyright (C) 2020-2024 Moritz Zwerger * * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. * @@ -15,23 +15,11 @@ package de.bixilon.minosoft.updater import de.bixilon.kutil.latch.AbstractLatch import de.bixilon.kutil.latch.SimpleLatch -import de.bixilon.kutil.observer.DataObserver.Companion.observed import de.bixilon.minosoft.commands.stack.print.PrintTarget class UpdateProgress( val progress: AbstractLatch = SimpleLatch(0), var log: PrintTarget? = null, ) { - var stage by observed(UpdateStage.WAITING) var error: Throwable? = null - - enum class UpdateStage { - WAITING, - DOWNLOADING, - VERIFYING, - STORING, - - DONE, - FAILED, - } } diff --git a/src/main/java/de/bixilon/minosoft/util/KUtil.kt b/src/main/java/de/bixilon/minosoft/util/KUtil.kt index 4923b1b91..228d6804b 100644 --- a/src/main/java/de/bixilon/minosoft/util/KUtil.kt +++ b/src/main/java/de/bixilon/minosoft/util/KUtil.kt @@ -1,6 +1,6 @@ /* * Minosoft - * Copyright (C) 2020-2023 Moritz Zwerger + * Copyright (C) 2020-2024 Moritz Zwerger * * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. * @@ -21,6 +21,7 @@ import de.bixilon.kotlinglm.vec2.Vec2t import de.bixilon.kotlinglm.vec3.Vec3d import de.bixilon.kotlinglm.vec3.Vec3t import de.bixilon.kotlinglm.vec4.Vec4t +import de.bixilon.kutil.buffer.BufferDefinition import de.bixilon.kutil.cast.CastUtil.unsafeCast import de.bixilon.kutil.collections.CollectionUtil.synchronizedListOf import de.bixilon.kutil.collections.CollectionUtil.synchronizedMapOf @@ -29,6 +30,7 @@ import de.bixilon.kutil.collections.CollectionUtil.toSynchronizedSet import de.bixilon.kutil.concurrent.pool.DefaultThreadPool import de.bixilon.kutil.concurrent.pool.runnable.ForcePooledRunnable import de.bixilon.kutil.concurrent.schedule.TaskScheduler +import de.bixilon.kutil.exception.ExceptionUtil import de.bixilon.kutil.primitive.BooleanUtil.decide import de.bixilon.kutil.primitive.DoubleUtil import de.bixilon.kutil.primitive.DoubleUtil.matches @@ -69,7 +71,11 @@ import io.netty.channel.SimpleChannelInboundHandler import javafx.application.Platform import org.kamranzafar.jtar.TarHeader import java.io.FileOutputStream +import java.io.InputStream +import java.io.OutputStream +import java.security.MessageDigest import java.security.SecureRandom +import java.security.Signature import java.util.* import javax.net.ssl.SSLContext @@ -355,4 +361,29 @@ object KUtil { stream.close() println("Packet dumped to $path") } + + + @Deprecated("kutil 1.26") + fun InputStream.copy(vararg output: OutputStream, digest: MessageDigest? = null, signature: Signature? = null, closeIn: Boolean = true, closeOut: Boolean = true) { + val buffer = ByteArray(BufferDefinition.DEFAULT_BUFFER_SIZE) + + while (true) { + val length = read(buffer, 0, buffer.size) + if (length < 0) { + break + } + for (stream in output) { + stream.write(buffer, 0, length) + } + digest?.update(buffer, 0, length) + signature?.update(buffer, 0, length) + } + + if (closeIn) ExceptionUtil.ignoreAll { close() } + if (closeOut) { + for (stream in output) { + ExceptionUtil.ignoreAll { stream.close() } + } + } + } } diff --git a/src/main/java/de/bixilon/minosoft/util/signature/SignatureSigner.kt b/src/main/java/de/bixilon/minosoft/util/signature/SignatureSigner.kt index 77405e843..db2e133d6 100644 --- a/src/main/java/de/bixilon/minosoft/util/signature/SignatureSigner.kt +++ b/src/main/java/de/bixilon/minosoft/util/signature/SignatureSigner.kt @@ -36,7 +36,7 @@ abstract class SignatureSigner( key = keyFactory.generatePublic(spec) } - protected fun createInstance(): Signature { + fun createInstance(): Signature { val instance = Signature.getInstance(algorithm) instance.initVerify(key)