1
0
mirror of https://github.com/veracrypt/VeraCrypt.git synced 2026-05-21 21:30:48 -05:00
Files
VeraCrypt/src/Build/Tools/makeself_repro_finalize.py
T
curious-rabbit 9535e65bd8 Ensure reproducible builds on Linux (#1731)
* ensure reproducible builds

* improve patch

* improve patch

* Narrow reproducibility scope to legacy and DEB

Keep the verified Linux legacy Makefile and DEB reproducibility paths, but remove the unverified RPM/openSUSE timestamp changes and AppImage reproducibility behavior from this PR.

The CPack mtime/mode clamp is now installed only for Debian/Ubuntu packaging, matching the scope covered by the provided reproducibility logs.

Retain umask 022 in the RPM/openSUSE wrappers so staged package permissions do not depend on a restrictive caller umask.

* Harden reproducible build cleanup

Validate SOURCE_DATE_EPOCH before interpolating it into Make, CMake or shell packaging paths.

Refuse live DESTDIR values in the CPack mtime clamp and pass makeself options through normal argv construction instead of eval.

---------

Co-authored-by: curious-rabbit <curious-rabbit@local>
Co-authored-by: Mounir IDRASSI <mounir.idrassi@amcrypto.jp>
2026-05-18 20:54:13 +09:00

56 lines
2.2 KiB
Python
Executable File

#!/usr/bin/env python3
#
# Copyright (c) 2026 VeraCrypt
# Governed by the Apache License 2.0.
#
# Zero the gzip mtime in a makeself archive and refresh its integrity
# fields. makeself runs `gzip -c9 < tmpfile' which writes tmpfile's
# mtime into the gzip header (gzip ignores SOURCE_DATE_EPOCH for
# redirected stdin), so the installer is otherwise not reproducible.
#
# After editing the payload the recorded checksums are refreshed:
# - CRCsum is set to "0000000000". Makeself stores a POSIX cksum(1)
# value there, not a zlib CRC-32 (the two differ); an all-zero
# CRCsum makes its extractor skip the redundant CRC check.
# - MD5 is recomputed, which the extractor still verifies.
#
# Usage: makeself_repro_finalize.py <archive>
import hashlib
import re
import sys
def finalize(path):
with open(path, "rb") as f:
raw = bytearray(f.read())
text = raw.decode("latin1", errors="replace")
# Locate payload start by line count, mirroring makeself's own extractor.
m = re.search(r'^skip="(\d+)"', text, re.MULTILINE)
if not m:
sys.exit(f"{path}: no skip= line in makeself header")
skip = int(m.group(1))
header_text = "\n".join(text.split("\n")[:skip]) + "\n"
offset = len(header_text.encode("latin1"))
if bytes(raw[offset:offset + 3]) != b"\x1f\x8b\x08":
sys.exit(f"{path}: no gzip magic at payload offset {offset}")
# gzip header mtime: 4-byte LE uint at offset+4 (RFC 1952 section 2.3.1).
raw[offset + 4:offset + 8] = b"\x00\x00\x00\x00"
payload = bytes(raw[offset:])
new_md5 = hashlib.md5(payload).hexdigest()
# CRCsum -> all zeros (extractor then skips the CRC check); MD5 -> fresh.
new_header = re.sub(r'CRCsum="[^"]*"', 'CRCsum="0000000000"', header_text)
new_header = re.sub(r'MD5="[0-9a-fA-F]+"', f'MD5="{new_md5}"', new_header)
new_bytes = new_header.encode("latin1")
# Line count must stay the same so makeself's "skip=" remains accurate.
if new_bytes.count(b"\n") != skip:
sys.exit(f"{path}: header line count changed during rewrite")
with open(path, "wb") as f:
f.write(new_bytes + payload)
if __name__ == "__main__":
if len(sys.argv) != 2:
sys.exit("Usage: makeself_repro_finalize.py <archive>")
finalize(sys.argv[1])