Skip to content

Commit db720b5

Browse files
committed
Merge bitcoin/bitcoin#27358: contrib: allow multi-sig binary verification v2
754fb6b verifybinaries: fix argument type error pointed out by mypy (Cory Fields) 8a65e51 verifybinaries: catch the correct exception (Cory Fields) 4b23b48 verifybinaries: fix OS download filter (Cory Fields) 8cdadd1 verifybinaries: use recommended keyserver by default (Cory Fields) 4e03968 verifybinaries: remove unreachable code (Cory Fields) 5668c64 verifybinaries: Don't delete shasums file (Cory Fields) 46c73b5 verifybinaries: README cleanups (Cory Fields) 6d11830 verifybinaries: remove awkward bitcoin-core prefix handling (Cory Fields) c44323a verifybinaries: move all current examples to the pub subcommand (Cory Fields) 7a6e7ff contrib: Use machine parseable GPG output in verifybinaries (Andrew Chow) 6b2cebf contrib: Add verifybinaries command for specifying files to verify (Andrew Chow) e4d5778 contrib: Specify to GPG the SHA256SUMS file that is detached signed (Andrew Chow) 17575c0 contrib: Refactor verifbinaries to support subcommands (Andrew Chow) 37c9fb7 contrib: verifybinaries: allow multisig verification (James O'Beirne) Pull request description: Following up on #23020 from jamesob with achow101's additional features on top. Both mentioned that they will be away for the next few weeks, so this is intended to keep review going. All credit to the jamesob and achow101. See #23020 for the original description and [here](bitcoin/bitcoin#23020 (comment)) for the added features. I squashed the last commit from https://github.com/achow101/bitcoin/tree/pr23020-direct-bins-gpg-parse into the first commit here. Fetching and local verification seem to work as intended for me. ACKs for top commit: josibake: ACK bitcoin/bitcoin@754fb6b Tree-SHA512: b310c57518daa690a00126308a3e7e94b978ded56d13da15d5189e9e90b71c93888d854f64179150586b0a915db8dadd43c92b716613913c198128db8867257b
2 parents d6c2a46 + 754fb6b commit db720b5

File tree

3 files changed

+774
-127
lines changed

3 files changed

+774
-127
lines changed

contrib/verifybinaries/README.md

Lines changed: 70 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,88 @@
11
### Verify Binaries
22

3-
#### Usage:
3+
#### Preparation
44

5-
This script attempts to download the signature file `SHA256SUMS.asc` from https://bitcoin.org.
5+
As of Bitcoin Core v22.0, releases are signed by a number of public keys on the basis
6+
of the [guix.sigs repository](https://github.com/bitcoin-core/guix.sigs/). When
7+
verifying binary downloads, you (the end user) decide which of these public keys you
8+
trust and then use that trust model to evaluate the signature on a file that contains
9+
hashes of the release binaries. The downloaded binaries are then hashed and compared to
10+
the signed checksum file.
611

7-
It first checks if the signature passes, and then downloads the files specified in the file, and checks if the hashes of these files match those that are specified in the signature file.
12+
First, you have to figure out which public keys to recognize. Browse the [list of frequent
13+
builder-keys](https://github.com/bitcoin-core/guix.sigs/tree/main/builder-keys) and
14+
decide which of these keys you would like to trust. For each key you want to trust, you
15+
must obtain that key for your local GPG installation.
816

9-
The script returns 0 if everything passes the checks. It returns 1 if either the signature check or the hash check doesn't pass. If an error occurs the return value is 2.
17+
You can obtain these keys by
18+
- through a browser using a key server (e.g. keyserver.ubuntu.com),
19+
- manually using the `gpg --keyserver <url> --recv-keys <key>` command, or
20+
- you can run the packaged `verify.py ... --import-keys` script to
21+
have it automatically retrieve unrecognized keys.
1022

23+
#### Usage
1124

25+
This script attempts to download the checksum file (`SHA256SUMS`) and corresponding
26+
signature file `SHA256SUMS.asc` from https://bitcoincore.org and https://bitcoin.org.
27+
28+
It first checks if the checksum file is valid based upon a plurality of signatures, and
29+
then downloads the release files specified in the checksum file, and checks if the
30+
hashes of the release files are as expected.
31+
32+
If we encounter pubkeys in the signature file that we do not recognize, the script
33+
can prompt the user as to whether they'd like to download the pubkeys. To enable
34+
this behavior, use the `--import-keys` flag.
35+
36+
The script returns 0 if everything passes the checks. It returns 1 if either the
37+
signature check or the hash check doesn't pass. An exit code of >2 indicates an error.
38+
39+
See the `Config` object for various options.
40+
41+
#### Examples
42+
43+
Validate releases with default settings:
1244
```sh
13-
./verify.py bitcoin-core-0.11.2
14-
./verify.py bitcoin-core-0.12.0
15-
./verify.py bitcoin-core-0.13.0-rc3
45+
./contrib/verifybinaries/verify.py pub 22.0
46+
./contrib/verifybinaries/verify.py pub 22.0-rc2
47+
```
48+
49+
Get JSON output and don't prompt for user input (no auto key import):
50+
51+
```sh
52+
./contrib/verifybinaries/verify.py --json pub 22.0-x86
53+
```
54+
55+
Rely only on local GPG state and manually specified keys, while requiring a
56+
threshold of at least 10 trusted signatures:
57+
```sh
58+
./contrib/verifybinaries/verify.py \
59+
--trusted-keys 74E2DEF5D77260B98BC19438099BAD163C70FBFA,9D3CC86A72F8494342EA5FD10A41BDC3F4FAFF1C \
60+
--min-good-sigs 10 pub 22.0-x86
1661
```
1762

1863
If you only want to download the binaries of certain platform, add the corresponding suffix, e.g.:
1964

2065
```sh
21-
./verify.py bitcoin-core-0.11.2-osx
22-
./verify.py 0.12.0-linux
23-
./verify.py bitcoin-core-0.13.0-rc3-win64
66+
./contrib/verifybinaries/verify.py pub 22.0-osx
67+
./contrib/verifybinaries/verify.py pub 22.0-rc2-win64
68+
```
69+
70+
If you do not want to keep the downloaded binaries, specify the cleanup option.
71+
72+
```sh
73+
./contrib/verifybinaries/verify.py pub --cleanup 22.0
74+
```
75+
76+
Use the bin subcommand to verify all files listed in a local checksum file
77+
78+
```sh
79+
./contrib/verifybinaries/verify.py bin SHA256SUMS
2480
```
2581

26-
If you do not want to keep the downloaded binaries, specify anything as the second parameter.
82+
Verify only a subset of the files listed in a local checksum file
2783

2884
```sh
29-
./verify.py bitcoin-core-0.13.0 delete
85+
./contrib/verifybinaries/verify.py bin ~/Downloads/SHA256SUMS \
86+
~/Downloads/bitcoin-24.0.1-x86_64-linux-gnu.tar.gz \
87+
~/Downloads/bitcoin-24.0.1-arm-linux-gnueabihf.tar.gz
3088
```

contrib/verifybinaries/test.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#!/usr/bin/env python3
2+
3+
import json
4+
import sys
5+
import subprocess
6+
from pathlib import Path
7+
8+
9+
def main():
10+
"""Tests ordered roughly from faster to slower."""
11+
expect_code(run_verify("", "pub", '0.32'), 4, "Nonexistent version should fail")
12+
expect_code(run_verify("", "pub", '0.32.awefa.12f9h'), 11, "Malformed version should fail")
13+
expect_code(run_verify('--min-good-sigs 20', "pub", "22.0"), 9, "--min-good-sigs 20 should fail")
14+
15+
print("- testing multisig verification (22.0)", flush=True)
16+
_220 = run_verify("--json", "pub", "22.0")
17+
try:
18+
result = json.loads(_220.stdout.decode())
19+
except Exception:
20+
print("failed on 22.0 --json:")
21+
print_process_failure(_220)
22+
raise
23+
24+
expect_code(_220, 0, "22.0 should succeed")
25+
v = result['verified_binaries']
26+
assert result['good_trusted_sigs']
27+
assert v['bitcoin-22.0-aarch64-linux-gnu.tar.gz'] == 'ac718fed08570a81b3587587872ad85a25173afa5f9fbbd0c03ba4d1714cfa3e'
28+
assert v['bitcoin-22.0-osx64.tar.gz'] == '2744d199c3343b2d94faffdfb2c94d75a630ba27301a70e47b0ad30a7e0155e9'
29+
assert v['bitcoin-22.0-x86_64-linux-gnu.tar.gz'] == '59ebd25dd82a51638b7a6bb914586201e67db67b919b2a1ff08925a7936d1b16'
30+
31+
32+
def run_verify(global_args: str, command: str, command_args: str) -> subprocess.CompletedProcess:
33+
maybe_here = Path.cwd() / 'verify.py'
34+
path = maybe_here if maybe_here.exists() else Path.cwd() / 'contrib' / 'verifybinaries' / 'verify.py'
35+
36+
if command == "pub":
37+
command += " --cleanup"
38+
39+
return subprocess.run(
40+
f"{path} {global_args} {command} {command_args}",
41+
stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
42+
43+
44+
def expect_code(completed: subprocess.CompletedProcess, expected_code: int, msg: str):
45+
if completed.returncode != expected_code:
46+
print(f"{msg!r} failed: got code {completed.returncode}, expected {expected_code}")
47+
print_process_failure(completed)
48+
sys.exit(1)
49+
else:
50+
print(f"✓ {msg!r} passed")
51+
52+
53+
def print_process_failure(completed: subprocess.CompletedProcess):
54+
print(f"stdout:\n{completed.stdout.decode()}")
55+
print(f"stderr:\n{completed.stderr.decode()}")
56+
57+
58+
if __name__ == '__main__':
59+
main()

0 commit comments

Comments
 (0)