Skip to content

Commit 2d70d3a

Browse files
[controller] make sure build verifies sha256 for binaries sums
1 parent cc55015 commit 2d70d3a

1 file changed

Lines changed: 83 additions & 39 deletions

File tree

‎controller/app/build.gradle‎

Lines changed: 83 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -154,12 +154,13 @@ dependencies {
154154
}
155155

156156
import groovy.json.JsonSlurper
157+
import java.security.MessageDigest
157158

158159
// =========================================================
159160
// IIAB SUPPLY CHAIN HOOK (GITHUB RELEASES SYNC)
160161
// =========================================================
161162
task syncNativeArtifacts {
162-
description = 'Downloads and extracts rolling native binaries from GitHub Releases'
163+
description = 'Downloads, extracts, and cryptographically verifies rolling native binaries'
163164

164165
// Repo name
165166
def repoName = "appdevforall/iiab-android"
@@ -171,6 +172,19 @@ task syncNativeArtifacts {
171172
def unzipDir = file("${buildDir}/temp_artifacts")
172173
def tagTrackerFile = file("src/main/assets/current_binaries_tag.txt")
173174

175+
// Helper to calculate SHA256
176+
def calculateSha256 = { File file ->
177+
def digest = MessageDigest.getInstance("SHA-256")
178+
file.withInputStream { is ->
179+
byte[] buffer = new byte[8192]
180+
int read
181+
while ((read = is.read(buffer)) > 0) {
182+
digest.update(buffer, 0, read)
183+
}
184+
}
185+
return digest.digest().encodeHex().toString()
186+
}
187+
174188
doLast {
175189
println ">> [IIAB Hook] Querying GitHub API for latest rolling release..."
176190

@@ -218,57 +232,87 @@ task syncNativeArtifacts {
218232
if (!allBinariesExist) break // Exit outer loop
219233
}
220234

221-
if (currentTag == targetRelease.tag_name && allBinariesExist) {
235+
// We also verify that the manifest exists in order to be able to audit.
236+
def manifestFile = new File(assetsDir, "ninja_manifest.json")
237+
def isCached = (currentTag == targetRelease.tag_name && allBinariesExist && manifestFile.exists())
238+
239+
if (isCached) {
222240
println " -> [CACHED] Artifacts are up to date (${currentTag}) and physically present. Skipping download."
223-
return // Exits the task immediately, saving time!
224241
} else {
225-
println " -> Cache miss or missing physical files. Proceeding with download..."
226-
}
242+
println " -> Cache miss, missing physical files, or missing manifest. Proceeding with download..."
227243

228-
// 4. Find the zip asset URL in the targeted release
229-
def zipAsset = targetRelease.assets.find { it.name == 'termux-binaries-latest.zip' }
230-
if (zipAsset == null) {
231-
throw new GradleException(">> [IIAB Hook] Zip asset not found in release ${targetRelease.tag_name}")
232-
}
244+
// 4. Find the zip asset URL in the targeted release
245+
def zipAsset = targetRelease.assets.find { it.name == 'termux-binaries-latest.zip' }
246+
if (zipAsset == null) {
247+
throw new GradleException(">> [IIAB Hook] Zip asset not found in release ${targetRelease.tag_name}")
248+
}
233249

234-
println " -> Found matching binaries: ${targetRelease.tag_name}"
235-
println " -> Downloading ZIP (approx ~15MB)..."
250+
println " -> Found matching binaries: ${targetRelease.tag_name}"
251+
println " -> Downloading ZIP (approx ~15MB)..."
236252

237-
// 5. Download the ZIP to the build/ directory
238-
if (!zipFile.parentFile.exists()) zipFile.parentFile.mkdirs()
239-
ant.get(src: zipAsset.browser_download_url, dest: zipFile, skipexisting: false)
253+
// 5. Download the ZIP to the build/ directory
254+
if (!zipFile.parentFile.exists()) zipFile.parentFile.mkdirs()
255+
ant.get(src: zipAsset.browser_download_url, dest: zipFile, skipexisting: false)
240256

241-
// 6. Extract the ZIP to a temporary folder
242-
println " -> Extracting binaries..."
243-
if (unzipDir.exists()) unzipDir.deleteDir() // Clean temp folder
244-
copy {
245-
from zipTree(zipFile)
246-
into unzipDir
247-
}
257+
// 6. Extract the ZIP to a temporary folder
258+
println " -> Extracting binaries..."
259+
if (unzipDir.exists()) unzipDir.deleteDir() // Clean temp folder
260+
copy {
261+
from zipTree(zipFile)
262+
into unzipDir
263+
}
248264

249-
// 7. Route the files to their correct Android folders
250-
// Move jniLibs/*.so to src/main/jniLibs
251-
copy {
252-
from "${unzipDir}/jniLibs"
253-
into jniLibsDir
254-
}
255-
// Move the config/manifest files to src/main/assets
256-
copy {
257-
from unzipDir
258-
include "cacert.pem", "ninja_manifest.json"
259-
into assetsDir
265+
// 7. Route the files to their correct Android folders
266+
copy {
267+
from "${unzipDir}/jniLibs"
268+
into jniLibsDir
269+
}
270+
copy {
271+
from unzipDir
272+
include "cacert.pem", "ninja_manifest.json"
273+
into assetsDir
274+
}
275+
276+
// 8. Update our local tracker file with the new tag
277+
tagTrackerFile.text = targetRelease.tag_name
260278
}
261279

262-
// 8. Update our local tracker file with the new tag
263-
tagTrackerFile.text = targetRelease.tag_name
280+
// --- 9. STRICT CRYPTOGRAPHIC AUDIT (SHA256) ---
281+
if (manifestFile.exists()) {
282+
println ">> [IIAB Hook] Performing strict SHA256 integrity audit..."
283+
def manifest = slurper.parse(manifestFile)
284+
285+
// Iterate over each architecture in the JSON
286+
manifest.binaries.each { archName, binariesMap ->
287+
binariesMap.each { binName, binMeta ->
288+
def physicalBinFile = new File(jniLibsDir, "${archName}/${binName}")
289+
290+
if (!physicalBinFile.exists()) {
291+
throw new GradleException(">> [IIAB Hook] INTEGRITY FATAL: Expected binary missing! -> ${archName}/${binName}")
292+
}
293+
294+
def expectedSha = binMeta.sha256
295+
def actualSha = calculateSha256(physicalBinFile)
296+
297+
if (expectedSha != actualSha) {
298+
// IF THERE IS CORRUPTION, STOP THE COMPILATION IMMEDIATELY
299+
throw new GradleException(">> [IIAB Hook] INTEGRITY FATAL: CORRUPTED BINARY DETECTED!\nFile: ${archName}/${binName}\nExpected: ${expectedSha}\nActual: ${actualSha}")
300+
}
301+
}
302+
}
303+
println " -> [OK] All binaries passed cryptographic SHA256 audit."
304+
} else {
305+
throw new GradleException(">> [IIAB Hook] INTEGRITY FATAL: ninja_manifest.json is missing! Cannot verify binaries.")
306+
}
307+
// ---------------------------------------------------
264308

265-
println ">> [IIAB Hook] Successfully synced and extracted native artifacts!"
309+
println ">> [IIAB Hook] Successfully synced and verified native artifacts!"
266310

267311
} catch (Exception e) {
268-
System.err.println ">> [IIAB Hook] ERROR: Failed to sync artifacts from GitHub"
312+
System.err.println ">> [IIAB Hook] ERROR: Failed to sync or verify artifacts"
269313
System.err.println ">> [IIAB Hook] Details: ${e.message}"
270-
// Throwing the exception ensures the build fails if critical binaries are missing
271-
throw new GradleException("Artifact sync failed", e)
314+
// Throwing the exception ensures the build fails if critical binaries can't be verified
315+
throw new GradleException("Artifact sync/verification failed", e)
272316
}
273317
}
274318
}

0 commit comments

Comments
 (0)