From dc2c65ebd6effcabaeb19150d4a7a43a22d67eaf Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Thu, 7 Nov 2024 11:30:54 -0300 Subject: [PATCH] [download] check hash for images loaded with glide --- .../glide/AutoVerifyingInputStream.kt | 47 +++++++++++++++++++ .../org/fdroid/download/glide/HttpFetcher.kt | 7 ++- 2 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 libs/download/src/androidMain/kotlin/org/fdroid/download/glide/AutoVerifyingInputStream.kt diff --git a/libs/download/src/androidMain/kotlin/org/fdroid/download/glide/AutoVerifyingInputStream.kt b/libs/download/src/androidMain/kotlin/org/fdroid/download/glide/AutoVerifyingInputStream.kt new file mode 100644 index 000000000..69346198a --- /dev/null +++ b/libs/download/src/androidMain/kotlin/org/fdroid/download/glide/AutoVerifyingInputStream.kt @@ -0,0 +1,47 @@ +package org.fdroid.download.glide + +import org.fdroid.fdroid.isMatching +import java.io.IOException +import java.io.InputStream +import java.security.DigestInputStream +import java.security.MessageDigest + +/** + * An [InputStream] that automatically verifies the [expectedHash] in lower-case SHA-256 format + * and ensures that not more than [maxBytesToRead] are read from the stream. + * This is useful to put an upper bound on data read to not exhaust memory. + */ +internal class AutoVerifyingInputStream( + inputStream: InputStream, + private val expectedHash: String, + private val maxBytesToRead: Long = Runtime.getRuntime().maxMemory() / 2, +) : DigestInputStream(inputStream, MessageDigest.getInstance("SHA-256")) { + + private var bytesRead = 0 + + override fun read(): Int { + val readByte = super.read() + if (readByte != -1) { + bytesRead++ + if (bytesRead > maxBytesToRead) { + throw IOException("Read $bytesRead bytes, above maximum allowed.") + } + } else { + if (!digest.isMatching(expectedHash)) throw IOException("Hash not matching.") + } + return readByte + } + + override fun read(b: ByteArray?, off: Int, len: Int): Int { + val read = super.read(b, off, len) + if (read != -1) { + bytesRead += read + if (bytesRead > maxBytesToRead) { + throw IOException("Read $bytesRead bytes, above maximum allowed.") + } + } else { + if (!digest.isMatching(expectedHash)) throw IOException("Hash not matching.") + } + return read + } +} diff --git a/libs/download/src/androidMain/kotlin/org/fdroid/download/glide/HttpFetcher.kt b/libs/download/src/androidMain/kotlin/org/fdroid/download/glide/HttpFetcher.kt index fdd4b5868..f6a475ff1 100644 --- a/libs/download/src/androidMain/kotlin/org/fdroid/download/glide/HttpFetcher.kt +++ b/libs/download/src/androidMain/kotlin/org/fdroid/download/glide/HttpFetcher.kt @@ -26,7 +26,12 @@ internal class HttpFetcher( try { // glide should take care of closing this stream and the underlying channel val inputStream = httpManager.getChannel(downloadRequest).toInputStream() - callback.onDataReady(inputStream) + val sha256 = downloadRequest.indexFile.sha256 + if (sha256 == null) { + callback.onDataReady(inputStream) + } else { + callback.onDataReady(AutoVerifyingInputStream(inputStream, sha256)) + } } catch (e: Exception) { callback.onLoadFailed(e) }