@@ -32,8 +32,15 @@ import kotlinx.coroutines.CoroutineName
3232import kotlinx.coroutines.launch
3333import net.minecraft.client.gui.GuiButton
3434import net.minecraft.client.gui.GuiScreen
35- import net.minecraft.util.EnumChatFormatting
35+ import org.bouncycastle.jce.provider.BouncyCastleProvider
36+ import org.bouncycastle.openpgp.PGPPublicKeyRingCollection
37+ import org.bouncycastle.openpgp.PGPSignatureList
38+ import org.bouncycastle.openpgp.PGPUtil
39+ import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory
40+ import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator
41+ import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider
3642import java.io.File
43+ import java.security.Security
3744import kotlin.math.floor
3845
3946/* *
@@ -46,12 +53,14 @@ class UpdateGui(restartNow: Boolean) : GuiScreen() {
4653 companion object {
4754 private val DOTS = arrayOf(" ." , " .." , " ..." , " ..." , " ..." )
4855 private const val DOT_TIME = 200 // ms between "." -> ".." -> "..."
49- var failed = false
5056 var complete = false
5157 }
5258
5359 private var backButton: GuiButton ? = null
5460 private var progress = 0.0
61+ private var stage = " Downloading"
62+ var failed = false
63+
5564 override fun initGui () {
5665 buttonList.add(GuiButton (0 , width / 2 - 100 , height / 3 * 2 , 200 , 20 , " " ).also { backButton = it })
5766 updateText()
@@ -63,14 +72,48 @@ class UpdateGui(restartNow: Boolean) : GuiScreen() {
6372 val url = UpdateChecker .updateDownloadURL
6473 val jarName = UpdateChecker .getJarNameFromUrl(url)
6574 IO .launch(CoroutineName (" Skytils-update-downloader-thread" )) {
66- downloadUpdate(url, directory)
75+ val updateFile = downloadUpdate(url, directory)
76+ val signFile = downloadUpdate(" $url .asc" , directory)
6777 if (! failed) {
68- UpdateChecker .scheduleCopyUpdateAtShutdown(jarName)
69- if (restartNow) {
70- mc.shutdown()
78+ if (updateFile != null && signFile != null ) {
79+ stage = " Verifying signature"
80+ val finger = JcaKeyFingerprintCalculator ()
81+
82+ fun getKeyRingCollection (fileName : String ): PGPPublicKeyRingCollection =
83+ this ::class .java.classLoader.getResourceAsStream(" assets/skytils/$fileName .gpg" )!! .use {
84+ PGPPublicKeyRingCollection (PGPUtil .getDecoderStream(it), finger)
85+ }
86+
87+ val keys = listOf (
88+ getKeyRingCollection(" my-name-is-jeff" ),
89+ getKeyRingCollection(" sychic" )
90+ )
91+
92+ val sig = (JcaPGPObjectFactory (PGPUtil .getDecoderStream(signFile.inputStream())).nextObject() as PGPSignatureList ).first()
93+ val key = keys.firstNotNullOfOrNull { it.getPublicKey(sig.keyID) }
94+ if (key != null ) {
95+ sig.init (JcaPGPContentVerifierBuilderProvider ().setProvider(Security .getProvider(" BC" ) ? : BouncyCastleProvider ().also (Security ::addProvider)), key)
96+ sig.update(updateFile.readBytes())
97+ if (sig.verify()) {
98+ signFile.deleteOnExit()
99+ UpdateChecker .scheduleCopyUpdateAtShutdown(jarName)
100+ if (restartNow) {
101+ mc.shutdown()
102+ }
103+ complete = true
104+ updateText()
105+ } else {
106+ failed = true
107+ println (" Signature verification failed" )
108+ }
109+ } else {
110+ println (" Key not found" )
111+ failed = true
112+ }
113+ } else {
114+ println (" Files are missing" )
115+ failed = true
71116 }
72- complete = true
73- updateText()
74117 }
75118 }
76119 } catch (ex: Exception ) {
@@ -82,7 +125,7 @@ class UpdateGui(restartNow: Boolean) : GuiScreen() {
82125 backButton!! .displayString = if (failed || complete) " Back" else " Cancel"
83126 }
84127
85- private suspend fun downloadUpdate (urlString : String , directory : File ) {
128+ private suspend fun downloadUpdate (urlString : String , directory : File ): File ? {
86129 try {
87130 val url = Url (urlString)
88131
@@ -102,25 +145,27 @@ class UpdateGui(restartNow: Boolean) : GuiScreen() {
102145 failed = true
103146 updateText()
104147 println (" $url returned status code ${st.status} " )
105- return
148+ return null
106149 }
107150 if (! directory.exists() && ! directory.mkdirs()) {
108151 failed = true
109152 updateText()
110153 println (" Couldn't create update file directory" )
111- return
154+ return null
112155 }
113156 val fileSaved = File (directory, url.pathSegments.last().decodeURLPart())
114157 if (mc.currentScreen != = this @UpdateGui || st.bodyAsChannel().copyTo(fileSaved.writeChannel()) == 0L ) {
115158 failed = true
116- return
159+ return null
117160 }
118161 println (" Downloaded update to $fileSaved " )
162+ return fileSaved
119163 } catch (ex: Exception ) {
120164 ex.printStackTrace()
121165 failed = true
122166 updateText()
123167 }
168+ return null
124169 }
125170
126171 public override fun actionPerformed (button : GuiButton ) {
@@ -134,14 +179,14 @@ class UpdateGui(restartNow: Boolean) : GuiScreen() {
134179 when {
135180 failed -> drawCenteredString(
136181 mc.fontRendererObj,
137- EnumChatFormatting . RED .toString() + " Update download failed" ,
182+ " §cUpdate download failed" ,
138183 width / 2 ,
139184 height / 2 ,
140185 - 0x1
141186 )
142187 complete -> drawCenteredString(
143188 mc.fontRendererObj,
144- EnumChatFormatting . GREEN .toString() + " Update download complete" ,
189+ " §aUpdate download complete" ,
145190 width / 2 ,
146191 height / 2 ,
147192 0xFFFFFF
@@ -162,16 +207,8 @@ class UpdateGui(restartNow: Boolean) : GuiScreen() {
162207 top + 3 ,
163208 - 0x1000000
164209 )
165- val x = (width - mc.fontRendererObj.getStringWidth(
166- String .format(
167- " Downloading %s" ,
168- DOTS [DOTS .size - 1 ]
169- )
170- )) / 2
171- val title = String .format(
172- " Downloading %s" ,
173- DOTS [(System .currentTimeMillis() % (DOT_TIME * DOTS .size)).toInt() / DOT_TIME ]
174- )
210+ val x = (width - mc.fontRendererObj.getStringWidth(" $stage ${DOTS [DOTS .size - 1 ]} " )) / 2
211+ val title = " $stage ${DOTS [(System .currentTimeMillis() % (DOT_TIME * DOTS .size)).toInt() / DOT_TIME ]} "
175212 drawString(mc.fontRendererObj, title, x, top - mc.fontRendererObj.FONT_HEIGHT - 2 , - 0x1 )
176213 }
177214 }
0 commit comments