diff --git a/.gitignore b/.gitignore index 0e09cdbccde7987d66a0bf8c3d6ba63aed2b3865..77f105a03c53c3e9a99764a7d3f46681bb38a1fa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .idea *.apk -*.jar +build/ briar/ +briar-mailbox/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 71895e22d568c6d2bceb9ae5490dcb5225df9889..972e139021ca11347ad4b6256387ae119ef956ee 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -25,13 +25,17 @@ build: except: - triggers -test_success: +test_briar_success: stage: test script: - # Consider adding the cap and the device directly to the CI config - # https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runners-docker-section - # TODO replace alpha-1.4.3 with release version - - docker run --cap-add SYS_ADMIN --device /dev/fuse ${TEST_IMAGE} ./reproduce.py alpha-1.4.3 + - docker run --cap-add SYS_ADMIN --device /dev/fuse ${TEST_IMAGE} ./reproduce.py briar release-1.4.20 + except: + - triggers + +test_mailbox_success: + stage: test + script: + - docker run --cap-add SYS_ADMIN --device /dev/fuse ${TEST_IMAGE} ./reproduce.py briar-mailbox alpha-1.0.2 except: - triggers @@ -57,6 +61,8 @@ release: check: stage: check script: + # TODO update RELEASE_TAG in briar and mailbox repo to include app name prefix: "briar $tag" + # https://code.briarproject.org/briar/briar/-/blob/4253bbaaf5b63e90d74a54df3c15cf6c53e555d4/.gitlab-ci.yml#L86 - docker run --cap-add SYS_ADMIN --device /dev/fuse ${RELEASE_IMAGE} ./reproduce.py ${RELEASE_TAG} - docker tag ${RELEASE_IMAGE} ${TAG_IMAGE} - docker push ${TAG_IMAGE} diff --git a/Dockerfile b/Dockerfile index dc55722edb97869e014ff73bb0a6c19051f2c9a9..53ca65fbb8abd5806574fb73fef9bc14dece255b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,13 +3,13 @@ FROM debian:bullseye-slim ENV LANG=C.UTF-8 ENV DEBIAN_FRONTEND=noninteractive ENV ANDROID_HOME=/opt/android-sdk -ENV REPO_URL=https://code.briarproject.org/briar/briar.git WORKDIR /opt/briar-reproducer # add required files to WORKDIR ADD install*.sh ./ ADD *.py ./ +ADD apps.json ./ RUN ./install.sh diff --git a/README.md b/README.md index 528dfb1a3b24bdd69f3acf31c1ee92c10feebb96..2913dad0f68d1dae0b6265cb2125ade6374f4653 100644 --- a/README.md +++ b/README.md @@ -103,3 +103,5 @@ If the build fails with `fuse: mount failed: Permission denied`, you may need to which broke reproducibility. An [old version of briar-reproducer](https://code.briarproject.org/briar/briar-reproducer/tags/pre-1.1.7) can be used to verify these releases. +* Before version `1.1.0`, the path to the APK (`gradle_apk_path` in `apps.json`) was + `briar-android/build/outputs/apk/release/briar-android-release-unsigned.apk` diff --git a/apps.json b/apps.json new file mode 100644 index 0000000000000000000000000000000000000000..24cc27872b67b1c99e5b4065fbf87d5f2d0145cc --- /dev/null +++ b/apps.json @@ -0,0 +1,18 @@ +{ + "briar": { + "repo_url": "https://code.briarproject.org/briar/briar.git", + "repo_default_branch": "master", + "reference_url": "https://briarproject.org/apk/briar-%s.apk", + "reference_apk": "briar-%s.apk", + "gradle_task": "briar-android:assembleRelease", + "gradle_apk_path": "briar-android/build/outputs/apk/official/release/briar-android-official-release-unsigned.apk" + }, + "briar-mailbox": { + "repo_url": "https://code.briarproject.org/briar/briar-mailbox.git", + "repo_default_branch": "main", + "reference_url": "https://briarproject.org/apk/mailbox-%s.apk", + "reference_apk": "mailbox-%s.apk", + "gradle_task": "mailbox-android:assembleRelease", + "gradle_apk_path": "mailbox-android/build/outputs/apk/release/mailbox-android-release-unsigned.apk" + } +} diff --git a/install.sh b/install.sh index 5097b15d68643adbad8da6f6ead3f6755139d98e..bde1779f9d2c594f2da37978ff951da52d666d85 100755 --- a/install.sh +++ b/install.sh @@ -3,7 +3,7 @@ set -e set -x # use snapshot repos for deterministic package versions -DATE="20211214T000000Z" +DATE="20220208T000000Z" cat << EOF > /etc/apt/sources.list deb http://snapshot.debian.org/archive/debian/${DATE}/ bullseye main deb http://snapshot.debian.org/archive/debian-security/${DATE}/ bullseye-security main diff --git a/reproduce.py b/reproduce.py index 7b1cb753bb0a4d508b8cdfd4cad5b078b996ea67..43da5145a9a4fa67a92335c94344478c16f2852f 100755 --- a/reproduce.py +++ b/reproduce.py @@ -5,135 +5,74 @@ import os import sys from subprocess import call, check_call, check_output from verify_apk import verify_apk -from verify_jar import verify_jar -from utils import fail - -REPO_DIR = "briar" -REFERENCE_APK_URL = 'https://briarproject.org/apk/briar-%s.apk' -REFERENCE_JAR_URL = 'https://briarproject.org/jar/briar-headless-%s.jar' -GRADLE_APK_TASK = "briar-android:assembleRelease" -GRADLE_JAR_TASK = "briar-headless:%s" -APK_PATH = "briar-android/build/outputs/apk/official/release/" + \ - "briar-android-official-release-unsigned.apk" -OLD_APK_PATH = "briar-android/build/outputs/apk/release/briar-android-release-unsigned.apk" -JAR_PATH= "briar-headless/build/libs/briar-headless-%s.jar" -BUILD_DIR = "briar-build" - -JAR_FLAVOURS = [ - { - "task": "x86LinuxJar", - "file": "linux-x86_64" - }, - { - "task": "aarch64LinuxJar", - "file": "linux-aarch64" - }, - { - "task": "armhfLinuxJar", - "file": "linux-armhf" - } - ] +from utils import get_app_info, fail + +BUILD_DIR = "build" def main(): - if len(sys.argv) > 2: - fail("Usage: %s [tag]" % sys.argv[0]) - tag = sys.argv[1] if len(sys.argv) > 1 else None + if len(sys.argv) > 3: + fail("Usage: %s briar [tag]" % sys.argv[0]) + app_name = sys.argv[1] + tag = sys.argv[2] if len(sys.argv) > 2 else None + + # get app info + app = get_app_info(app_name) # clone/reset repo and checkout tag - tag = prepare_repo(tag) + tag = prepare_repo(app["repo_url"], app["repo_default_branch"], app_name, tag) + version = tag.split('-')[1] # download reference binaries (before building to detect missing file early on) - version = tag.split('-')[1] - apk_url = REFERENCE_APK_URL % version - reference_apk = "briar-%s.apk" % version + apk_url = app["reference_url"] % version + reference_apk = app["reference_apk"] % version check_call(['wget', '--no-verbose', apk_url, '-O', reference_apk]) - reference_jars = list() - if version >= "1.2.17": - for flavour in JAR_FLAVOURS: - suffix = flavour['file'] + "-" + version - jar_url = REFERENCE_JAR_URL % suffix - reference_jar = "briar-headless-%s.jar" % suffix - reference_jars.append(reference_jar) - if version >= "1.2.10": - check_call(['wget', '--no-verbose', jar_url, '-O', reference_jar]) - else: - jar_url = REFERENCE_JAR_URL % version - reference_jar = "briar-headless-%s.jar" % version - reference_jars.append(reference_jar) - if version >= "1.2.10": - check_call(['wget', '--no-verbose', jar_url, '-O', reference_jar]) - # use non-deterministic file system for building the app to detect issues if not os.path.exists(BUILD_DIR): os.makedirs(BUILD_DIR) - check_call(['disorderfs', '--shuffle-dirents=yes', REPO_DIR, BUILD_DIR]) + check_call(['disorderfs', '--shuffle-dirents=yes', app_name, BUILD_DIR]) atexit.register(unmount) # build the app - if version >= "1.2.17": - tasks = list() - for flavour in JAR_FLAVOURS: - tasks.append(GRADLE_JAR_TASK % flavour["task"]) - check_call(["./gradlew", "--no-daemon", GRADLE_APK_TASK, *tasks], cwd=BUILD_DIR) - else: - gradle_jar_task = "briar-headless:assemble" - check_call(["./gradlew", "--no-daemon", GRADLE_APK_TASK, gradle_jar_task], cwd=BUILD_DIR) + check_call(["./gradlew", "--no-daemon", app["gradle_task"]], cwd=BUILD_DIR) # check if both APKs match - apk = os.path.join(BUILD_DIR, APK_PATH if version >= "1.1.0" else OLD_APK_PATH) + apk = os.path.join(BUILD_DIR, app["gradle_apk_path"]) if not verify_apk(reference_apk, apk): sys.exit(1) - # check if jars match pairwise - if version >= "1.2.10": - if version >= "1.2.17": - for i in range(len(JAR_FLAVOURS)): - flavour = JAR_FLAVOURS[i] - jar = os.path.join(BUILD_DIR, JAR_PATH % flavour["file"]) - if not verify_jar(reference_jars[i], jar): - sys.exit(1) - else: - jar = os.path.join(BUILD_DIR, "briar-headless/build/libs/briar-headless.jar") - if not verify_jar(reference_jars[0], jar): - sys.exit(1) - print("Version '%s' was built reproducible! :)" % tag) -def prepare_repo(tag): - if os.path.isdir(REPO_DIR): +def prepare_repo(repo_url, repo_branch, repo_dir, tag): + if os.path.isdir(repo_dir): # get latest commits and tags from remote - repo_call(['git', 'fetch', 'origin']) - # checkout to latest master HEAD - repo_call(['git', 'checkout', '-f', 'master']) + check_call(['git', 'fetch', 'origin'], cwd=repo_dir) + # checkout to latest main HEAD + check_call(['git', 'checkout', '-f', repo_branch], cwd=repo_dir) else: # clone repo - check_call(['git', 'clone', os.environ.get("REPO_URL"), REPO_DIR]) + check_call(['git', 'clone', repo_url, repo_dir]) # undo all changes - repo_call(['git', 'reset', '--hard']) + check_call(['git', 'reset', '--hard'], cwd=repo_dir) # clean all untracked files and directories (-d) from repo - repo_call(['git', 'clean', '-dffx']) + check_call(['git', 'clean', '-dffx'], cwd=repo_dir) # use latest tag if none given if tag is None: - result = check_output(['git', 'describe', '--abbrev=0', '--tags'], cwd=REPO_DIR) + result = check_output(['git', 'describe', '--abbrev=0', '--tags'], cwd=repo_dir) tag = result.decode().rstrip() # strip away line-break # checkout tag - repo_call(['git', 'checkout', tag]) + check_call(['git', 'checkout', tag], cwd=repo_dir) # return the tag that was used for the checkout return tag -def repo_call(command): - check_call(command, cwd=REPO_DIR) - - def unmount(): call(['fusermount', '-u', BUILD_DIR]) diff --git a/utils.py b/utils.py index 1aa609d53cf4e3df6d3912c573f435fd7beb6e91..bd5014027a9ab77c414620c91505b631f3d621b8 100755 --- a/utils.py +++ b/utils.py @@ -1,9 +1,15 @@ #!/usr/bin/env python3 import hashlib +import json import sys +def get_app_info(app_name): + with open('apps.json', 'r') as f: + return json.load(f)[app_name] + + def calc_hash(filename, block_size=65536): sha512 = hashlib.sha512() with open(filename, 'rb') as f: @@ -15,4 +21,3 @@ def calc_hash(filename, block_size=65536): def fail(msg=""): sys.stderr.write(msg + "\n") sys.exit(1) - diff --git a/verify_jar.py b/verify_jar.py deleted file mode 100755 index a2f7c0875133fee61a6342f3a851290355df83f4..0000000000000000000000000000000000000000 --- a/verify_jar.py +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env python3 - -import os -import subprocess -import sys -from utils import calc_hash, fail - - -def main(): - if len(sys.argv) != 3: - fail("Usage: %s reference_jar built_jar" % sys.argv[0]) - if not verify_jar(sys.argv[1], sys.argv[2]): sys.exit(1) - - -def verify_jar(reference_jar, built_jar): - if not os.path.isfile(reference_jar): - sys.stderr.write("Cannot verify: Reference jar '%s' does not exist" % reference_jar) - return False - - if not os.path.isfile(built_jar): - sys.stderr.write("Cannot verify: Built jar '%s' does not exist" % built_jar) - return False - - if calc_hash(reference_jar) != calc_hash(built_jar): - sys.stderr.write("Mismatch detected. Trying to find reason...\n") - subprocess.call(['diffoscope', reference_jar, built_jar]) - sys.stderr.write("Files do NOT match! :(") - return False - else: - print("Files match! :)") - return True - - -if __name__ == "__main__": - main()