muse_artifact.py
python
sha256:106ef9e12cca7f3be3ffb2a6bd3abfa76f032dce453c9e5010d732a8d86f2e14
Reconcile reviewed credential compatibility 32254cd939885d9…
Human
patch
8 days ago
| 1 | """Reproduce or verify the explicitly local Muse rc5 recovery distribution. |
| 2 | |
| 3 | No upstream release is claimed. This stdlib-only tool neither installs nor |
| 4 | downloads anything. The reviewed manifest covers source, data and metadata. |
| 5 | """ |
| 6 | import argparse |
| 7 | import base64 |
| 8 | import csv |
| 9 | import hashlib |
| 10 | import io |
| 11 | import json |
| 12 | from pathlib import Path, PurePosixPath |
| 13 | import stat |
| 14 | import zipfile |
| 15 | |
| 16 | HERE = Path(__file__).resolve().parent |
| 17 | DIST = 'muse-0.2.1rc5.dist-info' |
| 18 | SOURCE_NAME = 'muse-0.2.1rc5-overseer-recovery1-source.zip' |
| 19 | WHEEL_NAME = 'muse-0.2.1rc5-1overseerrecovery-py3-none-any.whl' |
| 20 | |
| 21 | |
| 22 | def sha(raw): |
| 23 | return hashlib.sha256(raw).hexdigest() |
| 24 | |
| 25 | |
| 26 | def json_bytes(value): |
| 27 | return (json.dumps(value, indent=2, sort_keys=True) + '\n').encode() |
| 28 | |
| 29 | |
| 30 | def contract(): |
| 31 | lock = json.loads((HERE / 'muse-rc5-artifact.json').read_bytes()) |
| 32 | raw = (HERE / 'muse-rc5-source-files.json').read_bytes() |
| 33 | if sha(raw) != lock['payload_manifest_sha256']: |
| 34 | raise ValueError('reviewed payload manifest digest differs') |
| 35 | return lock, json.loads(raw) |
| 36 | |
| 37 | |
| 38 | def validate_payload(files, manifest, source_sha256): |
| 39 | if files.keys() != manifest.keys(): |
| 40 | raise ValueError('payload paths differ from reviewed manifest') |
| 41 | source = hashlib.sha256() |
| 42 | for name, expected in sorted(manifest.items()): |
| 43 | path = PurePosixPath(name) |
| 44 | if (path.is_absolute() or '..' in path.parts or str(path) != name |
| 45 | or not name.startswith(('muse/', DIST + '/'))): |
| 46 | raise ValueError('invalid payload path: ' + name) |
| 47 | raw = files[name] |
| 48 | if len(raw) != expected['size'] or sha(raw) != expected['sha256']: |
| 49 | raise ValueError('payload differs: ' + name) |
| 50 | if name.startswith('muse/') and name.endswith('.py'): |
| 51 | source.update(name[5:].encode() + b'\0' + raw + b'\0') |
| 52 | if source.hexdigest() != source_sha256: |
| 53 | raise ValueError('package source differs from reviewed rc5') |
| 54 | |
| 55 | |
| 56 | def read_installed(root, manifest): |
| 57 | files = {} |
| 58 | for name in manifest: |
| 59 | path = root / name |
| 60 | if any(p.is_symlink() for p in [path, *path.parents]): |
| 61 | raise ValueError('linked input: ' + name) |
| 62 | files[name] = path.read_bytes() |
| 63 | # Installation-generated metadata/bytecode are not distribution inputs. |
| 64 | # Do not silently omit added package code or data. |
| 65 | observed = {p.relative_to(root).as_posix() for p in (root / 'muse').rglob('*') |
| 66 | if p.is_file() and p.suffix != '.pyc'} |
| 67 | if observed != {n for n in manifest if n.startswith('muse/')}: |
| 68 | raise ValueError('installed package has missing or extra payload files') |
| 69 | return files |
| 70 | |
| 71 | |
| 72 | def read_archive(raw, manifest): |
| 73 | with zipfile.ZipFile(io.BytesIO(raw)) as archive: |
| 74 | names = archive.namelist() |
| 75 | if len(names) != len(set(names)) or set(names) != set(manifest): |
| 76 | raise ValueError('source archive paths differ from reviewed manifest') |
| 77 | for info in archive.infolist(): |
| 78 | if info.file_size != manifest[info.filename]['size']: |
| 79 | raise ValueError('source archive entry size differs') |
| 80 | return {name: archive.read(name) for name in names} |
| 81 | |
| 82 | |
| 83 | def archive_bytes(files): |
| 84 | # Stored entries avoid zlib/version-dependent compression; metadata/order |
| 85 | # never depend on wall clock, filesystem mode, umask, host or source mtime. |
| 86 | output = io.BytesIO() |
| 87 | with zipfile.ZipFile(output, 'w', compression=zipfile.ZIP_STORED) as archive: |
| 88 | for name, raw in sorted(files.items()): |
| 89 | info = zipfile.ZipInfo(name, date_time=(1980, 1, 1, 0, 0, 0)) |
| 90 | info.create_system = 3 |
| 91 | info.external_attr = (stat.S_IFREG | 0o644) << 16 |
| 92 | archive.writestr(info, raw) |
| 93 | return output.getvalue() |
| 94 | |
| 95 | |
| 96 | def artifacts(files, lock): |
| 97 | source = archive_bytes(files) |
| 98 | wheel_files = dict(files) |
| 99 | wheel_files[DIST + '/WHEEL'] = ( |
| 100 | 'Wheel-Version: 1.0\nGenerator: overseer-rc5-recovery-1\n' |
| 101 | 'Root-Is-Purelib: true\nBuild: 1overseerrecovery\nTag: py3-none-any\n' |
| 102 | ).encode() |
| 103 | wheel_files[DIST + '/OVERSEER_RECOVERY.json'] = json_bytes({ |
| 104 | 'origin': lock['origin'], 'upstream_release': False, |
| 105 | 'version': '0.2.1rc5', 'build': '1overseerrecovery', |
| 106 | 'source_sha256': lock['package_source_sha256'], |
| 107 | 'payload_manifest_sha256': lock['payload_manifest_sha256'], |
| 108 | 'recovery_source_archive_sha256': sha(source), |
| 109 | 'original_sdist_sha256_recorded_by_pip': lock['original_sdist_sha256'], |
| 110 | 'original_sdist_recovered': False, |
| 111 | }) |
| 112 | record = io.StringIO() |
| 113 | writer = csv.writer(record, lineterminator='\n') |
| 114 | for name, raw in sorted(wheel_files.items()): |
| 115 | digest = base64.urlsafe_b64encode(hashlib.sha256(raw).digest()).decode().rstrip('=') |
| 116 | writer.writerow([name, 'sha256=' + digest, len(raw)]) |
| 117 | writer.writerow([DIST + '/RECORD', '', '']) |
| 118 | wheel_files[DIST + '/RECORD'] = record.getvalue().encode() |
| 119 | return {SOURCE_NAME: source, WHEEL_NAME: archive_bytes(wheel_files)} |
| 120 | |
| 121 | |
| 122 | def main(): |
| 123 | parser = argparse.ArgumentParser(description=__doc__) |
| 124 | commands = parser.add_subparsers(dest='command', required=True) |
| 125 | rebuild = commands.add_parser('rebuild') |
| 126 | inputs = rebuild.add_mutually_exclusive_group(required=True) |
| 127 | inputs.add_argument('--installed-root', type=Path) |
| 128 | inputs.add_argument('--source-archive', type=Path) |
| 129 | rebuild.add_argument('--output-dir', type=Path, required=True) |
| 130 | verify = commands.add_parser('verify') |
| 131 | verify.add_argument('--wheel', type=Path, required=True) |
| 132 | args = parser.parse_args() |
| 133 | try: |
| 134 | lock, manifest = contract() |
| 135 | if args.command == 'verify': |
| 136 | if args.wheel.name != WHEEL_NAME or sha(args.wheel.read_bytes()) != lock['artifacts'][WHEEL_NAME]: |
| 137 | raise ValueError('wheel name/digest differs from reviewed recovery artifact') |
| 138 | print('Verified exact local rc5 recovery wheel; not an upstream release.') |
| 139 | return |
| 140 | if args.installed_root: |
| 141 | files = read_installed(args.installed_root.resolve(), manifest) |
| 142 | else: |
| 143 | raw = args.source_archive.read_bytes() |
| 144 | if sha(raw) != lock['artifacts'][SOURCE_NAME]: |
| 145 | raise ValueError('recovery source archive digest differs') |
| 146 | files = read_archive(raw, manifest) |
| 147 | validate_payload(files, manifest, lock['package_source_sha256']) |
| 148 | built = artifacts(files, lock) |
| 149 | if {name: sha(raw) for name, raw in built.items()} != lock['artifacts']: |
| 150 | raise ValueError('rebuilt artifact digests differ from reviewed contract') |
| 151 | args.output_dir.mkdir(parents=True, exist_ok=False) |
| 152 | for name, raw in built.items(): |
| 153 | (args.output_dir / name).write_bytes(raw) |
| 154 | print(json.dumps({name: sha(raw) for name, raw in built.items()}, indent=2)) |
| 155 | except (ValueError, OSError, zipfile.BadZipFile) as error: |
| 156 | parser.exit(1, str(error) + '\n') |
| 157 | |
| 158 | |
| 159 | if __name__ == '__main__': |
| 160 | main() |
File History
1 commit
sha256:106ef9e12cca7f3be3ffb2a6bd3abfa76f032dce453c9e5010d732a8d86f2e14
Reconcile reviewed credential compatibility 32254cd939885d9…
Human
patch
8 days ago