@@ -154,12 +154,13 @@ dependencies {
154154}
155155
156156import groovy.json.JsonSlurper
157+ import java.security.MessageDigest
157158
158159// =========================================================
159160// IIAB SUPPLY CHAIN HOOK (GITHUB RELEASES SYNC)
160161// =========================================================
161162task 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!\n File: ${ archName} /${ binName} \n Expected: ${ expectedSha} \n Actual: ${ 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