fix: contrib: allow multi-sig binary verification #23020

pull jamesob wants to merge 1 commits into bitcoin:master from jamesob:2021-09-binary-verify changing 3 files +645 −112
  1. jamesob commented at 6:57 pm on September 17, 2021: member

    Related: https://github.com/bitcoin-core/bitcoincore.org/issues/793

    This changeset improves the contrib/verifybinaries/verify.py script to account for the new binary verification procedure introduced in the 22.0 release; for details on this new process, see the note in #22634.

    In short, instead of relying on a single signature from the lead maintainer attesting to the expected hashes of binary releases, this verify script now supports validating that a minimum threshold of trusted identities have signed the checksum file.

    image

    Both the threshold and identities to trust are configurable by the end user, but sensible defaults are provided: identities are inferred from local GPG trust and the builder-keys file and a minimum threshold of 4 trusted signatures is configured by default. There are options for overriding these.

    Various improvements have been made to the script for allow for easier programmatic use; a --json option is introduced and logging output is now directed to stderr.

    Automatic pubkey import

    Pubkeys that are referenced in checksum signature files can now be automatically downloaded based on a user prompt. This behavior can be disabled with the --noninteractive flag to support CI use.

    I have built in functionality for elevating local GPG trust of the imported keys, but it isn’t quite working yet; I think instead of manually modifying the GPG trust database I have to use the --sign-key command. But this is optional functionality and fixing the broken process is the priority here, so I can enable this nicety in a follow-up.

    Builder-key diffing

    Builder keys (as listed in ./contrib/builder-keys/keys.txt) are used by default to establish trust in a pubkey signature. This can be disabled with --no-builder-keys.

    If builder keys are locally supplied (by running this command from the root of the repository), they are diffed with the remote version obtained over HTTPS from Github. A diff is reported if it exists, e.g.

    image

    Granular JSON output

    Because binary verification is now a gradient on the basis of an end user’s trust in a set of pubkeys, users of this script may want a granular report on which signatures were used. Stderr logging can be parsed for this information, but a convenient JSON blob is also accessible using the --json flag:

     0% ./contrib/verifybinaries/verify.py 22.0-x86 --noninteractive --json 2>/dev/null
     1
     2{
     3  "good_trusted_sigs": [
     4    "SigData('9D3CC86A72F8494342EA5FD10A41BDC3F4FAFF1C', 'Aaron Clauson (sipsorcery) <aaron@sipsorcery.com>', trusted=False, status='unknown')",
     5    "SigData('637DB1E23370F84AFF88CCE03152347D07DA627C', 'Stephan Oeste (it) <it@oeste.de>', trusted=True, status='unknown')",
     6    "SigData('9DEAE0DC7063249FB05474681E4AED62986CD25D', 'Wladimir J. van der Laan <laanwj@visucore.com>', trusted=True, status='unknown')",
     7    "SigData('0AD83877C1F0CD1EE9BD660AD7CC770B81FD22A8', 'Ben Carman <benthecarman@live.com>', trusted=False, status='unknown')",
     8    "SigData('152812300785C96444D3334D17565732E08E5E41', 'Andrew Chow (Official New Key) <achow101@gmail.com>', trusted=True, status='expired')",
     9    "SigData('D1DBF2C4B96F2DEBF4C16654410108112E7EA81F', 'Hennadii Stepanov (GitHub key) <32963518+hebasto@users.noreply.github.com>', trusted=True, status='unknown')",
    10    "SigData('590B7292695AFFA5B672CBB2E13FC145CD3F4304', 'Antoine Poinsot <darosior@protonmail.com>', trusted=True, status='unknown')"
    11  ],
    12  "good_untrusted_sigs": [
    13    "SigData('0CCBAAFD76A2ECE2CCD3141DE2FFD5B1D88CA97D', '.0xB10C <0xb10c@gmail.com>', trusted=False, status='unknown')",
    14    "SigData('28F5900B1BB5D1A4B6B6D1A9ED357015286A333D', 'Duncan Dean <duncangleeddean@gmail.com>', trusted=False, status='unknown')",
    15    "SigData('CFB16E21C950F67FA95E558F2EEB9F5CC09526C1', 'Michael Ford (bitcoin-otc) <fanquake@gmail.com>', trusted=False, status='unknown')",
    16    "SigData('6E01EEC9656903B0542B8F1003DB6322267C373B', 'Oliver Gugger <gugger@gmail.com>', trusted=False, status='unknown')",
    17    "SigData('74E2DEF5D77260B98BC19438099BAD163C70FBFA', 'Will Clark <will8clark@gmail.com>', trusted=False, status='unknown')"
    18  ],
    19  "unknown_sigs": [
    20    "SigData('82921A4B88FD454B7EB8CE3C796C4109063D4EAF', '', trusted=False, status='')"
    21  ],
    22  "bad_sigs": [],
    23  "verified_binaries": [
    24    "bitcoin-22.0-x86_64-linux-gnu.tar.gz"
    25  ]
    26}
    

    image

    Backwards incompatibility

    Note that I have broken the interface; the script is no longer invoked in the same way and output differs. I initially tried to retain the old format, but I found it made less and less sense for the new signature scheme, and was not easily parsed sensibly. If there are users that rely on the old output structure, they can copy the script from an old version of the source tree.

    Examples

    Validate releases with default settings:

    0./contrib/verifybinaries/verify.py 22.0
    1./contrib/verifybinaries/verify.py 22.0-rc2-x86_64
    2./contrib/verifybinaries/verify.py bitcoin-core-0.13.0-rc3
    

    Get JSON output and don’t prompt for user input (no auto key import):

    0./contrib/verifybinaries/verify.py 22.0-x86 --json --noninteractive
    

    Don’t trust builder-keys by default, and rely only on local GPG state and manually specified keys, while requiring a threshold of at least 10 trusted signatures:

    0./contrib/verifybinaries/verify.py 22.0 \
    1    --no-builder-keys \
    2    --trusted-keys 74E2DEF5D77260B98BC19438099BAD163C70FBFA,9D3CC86A72F8494342EA5FD10A41BDC3F4FAFF1C \
    3    --min-trusted-sigs 10
    

    Followups

    New CLI interface

     0% ./contrib/verifybinaries/verify.py --help
     1
     2usage: verify.py [-h] [--verbose] [--cleanup] [--noninteractive]
     3                 [--require-all-hosts] [--bitcoin-src-path [BITCOIN_SRC_PATH]]
     4                 [--skip-import-builders]
     5                 [--min-trusted-sigs [MIN_TRUSTED_SIGS]]
     6                 [--keyserver [KEYSERVER]] [--trusted-keys [TRUSTED_KEYS]]
     7                 [--no-builder-keys] [--json]
     8                 version
     9
    10Script for verifying Bitcoin Core release binaries. This script attempts to
    11download the sum file SHA256SUMS and corresponding signature file
    12SHA256SUMS.asc from bitcoincore.org and bitcoin.org and compares them. The
    13sum-signature file is signed by a number of builder keys. This script ensures
    14that there is a minimum threshold of signatures from pubkeys that we trust.
    15This trust is articulated on the basis of configuration options here, but by
    16default is based upon a unionof (i) local GPG trust settings, and (ii) keys
    17which appear in the builder-keys/keys.txt file. If a minimum good, trusted
    18signature threshold is met on the sum file, we then download the files
    19specified in SHA256SUMS, and check if the hashes of these files match those
    20that are specified. The script returns 0 if everything passes the checks. It
    21returns 1 if either the signature check or the hash check doesn't pass. If an
    22error occurs the return value is >= 2. Logging output goes to stderr and final
    23binary verification data goes to stdout. JSON output can by obtained by
    24setting env BINVERIFY_JSON=1.
    25
    26positional arguments:
    27  version               version of the bitcoin release to download; of the
    28                        format <major>.<minor>[.<patch>][-rc[0-9]][-platform].
    29                        Example: 22.0-x86_64 or 0.21.0-rc2-osx
    30
    31options:
    32  -h, --help            show this help message and exit
    33  --verbose
    34  --cleanup             if specified, clean up files afterwards
    35  --noninteractive      if specified, do not block for user input
    36  --require-all-hosts   If set, require all hosts (https://bitcoincore.org,
    37                        https://bitcoin.org) to provide signatures. (Sometimes
    38                        bitcoin.org lags behind bitcoincore.org.)
    39  --bitcoin-src-path [BITCOIN_SRC_PATH]
    40                        specify path to bitcoin repository. Used to find
    41                        builder keys.
    42  --skip-import-builders
    43                        If set, do not prompt to import builder pubkeys
    44  --min-trusted-sigs [MIN_TRUSTED_SIGS]
    45                        The minimum number of good signatures from recognized
    46                        keys to require successful termination.
    47  --keyserver [KEYSERVER]
    48                        which keyserver to use
    49  --trusted-keys [TRUSTED_KEYS]
    50                        A list of trusted builder GPG keys, specified as CSV
    51  --no-builder-keys     If set, do not trust the builder-keys from the bitcoin
    52                        repo by default
    53  --json                If set, output the result as JSON
    
  2. DrahtBot added the label Scripts and tools on Sep 17, 2021
  3. shahriyar9839 referenced this in commit 05b5831aa0 on Sep 17, 2021
  4. theStack commented at 8:52 pm on September 17, 2021: contributor

    Concept ACK

    Thanks for working on this! Will review and test within the next days.

  5. harding referenced this in commit 4bf8149e60 on Sep 20, 2021
  6. jonatack commented at 8:18 pm on September 20, 2021: contributor
    Concept ACK
  7. Rspigler commented at 4:47 am on September 21, 2021: contributor

    Concept ACK

    This is great! Thanks

  8. laanwj commented at 10:29 am on September 21, 2021: member

    Concept ACK

    Have you considered using the gpgme library (like https://github.com/bitcoin-core/bitcoin-maintainer-tools/blob/master/gitian-verify.py#L66 does) instead of parsing gpg command text output? I tried this approach and it became an awful mess, using the library is more robust, though it does introduce another Python dependency.

  9. Sjors commented at 5:21 pm on September 21, 2021: member

    I guess I’m doing something wrong (on macOS 11.6)?

     0% contrib/verifybinaries/verify.py 22.0  
     1WARNING: https://bitcoin.org failed to provide signature file. Continuing based solely upon https://bitcoincore.org.
     2WARNING: https://bitcoin.org failed to provide SHA256SUMS file. Continuing based solely upon https://bitcoincore.org.
     3Traceback (most recent call last):
     4  File "contrib/verifybinaries/verify.py", line 568, in <module>
     5    sys.exit(main(sys.argv[1:]))
     6  File "contrib/verifybinaries/verify.py", line 418, in main
     7    good, unknown, bad = parse_gpg_result(output.splitlines())
     8  File "contrib/verifybinaries/verify.py", line 271, in parse_gpg_result
     9    f"failed to evaluate all signatures: found {all_found} "
    10RuntimeError: failed to evaluate all signatures: found 13 but expected 10
    

    For 0.21.1 the SHA256SUMS file is missing, only the signatures are there: https://bitcoincore.org/bin/bitcoin-core-0.21.1/

    0% contrib/verifybinaries/verify.py 0.21.1
    1Error: couldn't fetch SHA256SUMS file.
    2wget output:
    3  --2021-09-21 19:18:24--  https://bitcoincore.org/bin/bitcoin-core-0.21.1/SHA256SUMS
    4  Herleiden van bitcoincore.org (bitcoincore.org)... 198.251.83.116, 107.191.99.5
    5  Verbinding maken met bitcoincore.org (bitcoincore.org)|198.251.83.116|:443... verbonden.
    6  HTTP-verzoek is verzonden; wachten op antwoord... 404 Not Found
    7  2021-09-21 19:18:25 Fout 404: Not Found.
    

    PS it would be nice to check the timestamp as well if opentimestamp is present.

  10. jamesob commented at 5:52 pm on September 21, 2021: member

    Have you considered using the gpgme library @laanwj this is a good idea, I’ll look into it. Initially I didn’t want end users to have to futz with pip or extra Python package installation since it can get very confusing very fast for semi-technical users (and even just plain technical users), but it might be worth the complexity. I’ll give that a shot and post the diff.

    I guess I’m doing something wrong (on macOS 11.6)? @Sjors hm, interesting… I’m not able to reproduce on my Linux machine, but I’m betting there may be some formatting differences on different versions of GPG and/or macOS (which speaks to @laanwj’s point about not relying on parsing output). Can you rerun with --verbose and send me the GPG output, either here or in DM?

    For 0.21.1 the SHA256SUMS file is missing, only the signatures are there: https://bitcoincore.org/bin/bitcoin-core-0.21.1/

    Oh, that’s a really good point. We probably need to retain the legacy behavior for old releases. I’ll work on this.

  11. jamesob commented at 5:56 pm on September 21, 2021: member

    PS it would be nice to check the timestamp as well if opentimestamp is present.

    Love that idea.

  12. jamesob force-pushed on Sep 22, 2021
  13. jamesob commented at 6:27 pm on September 22, 2021: member
    I’ve pushed a fix for the first issue @Sjors discovered (signature miscounting) as well as a logging refactoring. A commit for legacy process support is coming soon, but this can still be tested with 22.0.
  14. Sjors commented at 6:31 pm on September 22, 2021: member

    Much better, modulo somewhat scary warnings:

     0% contrib/verifybinaries/verify.py 22.0                                
     1[WARNING] https://bitcoin.org failed to provide signature file. Continuing based solely upon https://bitcoincore.org.
     2[WARNING] https://bitcoin.org failed to provide SHA256SUMS file. Continuing based solely upon https://bitcoincore.org.
     3[WARNING] removing *-unsigned binaries (bitcoin-22.0-osx-unsigned.dmg, bitcoin-22.0-osx-unsigned.tar.gz, bitcoin-22.0-win-unsigned.tar.gz, bitcoin-22.0-win64-setup-unsigned.exe) from verification since https://bitcoincore.org does not host *-unsigned binaries
     4[WARNING] removing *-debug binaries (bitcoin-22.0-aarch64-linux-gnu-debug.tar.gz, bitcoin-22.0-arm-linux-gnueabihf-debug.tar.gz, bitcoin-22.0-powerpc64-linux-gnu-debug.tar.gz, bitcoin-22.0-powerpc64le-linux-gnu-debug.tar.gz, bitcoin-22.0-riscv64-linux-gnu-debug.tar.gz, bitcoin-22.0-x86_64-linux-gnu-debug.tar.gz, bitcoin-22.0-win64-debug.zip) from verification since https://bitcoincore.org does not host *-debug binaries
     5[WARNING] removing *-codesignatures binaries (bitcoin-22.0-codesignatures-22.0.tar.gz) from verification since https://bitcoincore.org does not host *-codesignatures binaries
     6VERIFIED: bitcoin-22.0-aarch64-linux-gnu.tar.gz
     7VERIFIED: bitcoin-22.0-arm-linux-gnueabihf.tar.gz
     8VERIFIED: bitcoin-22.0.tar.gz
     9VERIFIED: bitcoin-22.0-powerpc64-linux-gnu.tar.gz
    10VERIFIED: bitcoin-22.0-powerpc64le-linux-gnu.tar.gz
    11VERIFIED: bitcoin-22.0-riscv64-linux-gnu.tar.gz
    12VERIFIED: bitcoin-22.0-osx-signed.dmg
    13VERIFIED: bitcoin-22.0-osx64.tar.gz
    14VERIFIED: bitcoin-22.0-x86_64-linux-gnu.tar.gz
    15VERIFIED: bitcoin-22.0-win64-setup.exe
    16VERIFIED: bitcoin-22.0-win64.zip
    
  15. jamesob force-pushed on Sep 22, 2021
  16. jamesob commented at 11:32 pm on September 22, 2021: member

    I’ve pushed an update (thanks to @Sjors’ testing) that preserves the ability to verify binaries for releases older than 22.0, but still outputs verification results for everything in a uniform way.

    I’ve also included a change that associates the sha256 hash values along with the binary filenames in JSON output, which seems potentially useful.

    22.0 output

     0% ./contrib/verifybinaries/verify.py 22.0 --json --quiet --noninteractive
     1[WARNING] https://bitcoin.org failed to provide file (https://bitcoin.org/bin/bitcoin-core-22.0/SHA256SUMS.asc). Continuing based solely upon https://bitcoincore.org.
     2[WARNING] https://bitcoin.org failed to provide file (https://bitcoin.org/bin/bitcoin-core-22.0/SHA256SUMS). Continuing based solely upon https://bitcoincore.org.
     3[WARNING] key 152812300785C96444D3334D17565732E08E5E41 for Andrew Chow (Official New Key) <achow101@gmail.com> is expired
     4[WARNING] UNKNOWN SIGNATURE: SigData('82921A4B88FD454B7EB8CE3C796C4109063D4EAF', '', trusted=False, status='')
     5{
     6  "good_trusted_sigs": [
     7    "SigData('9DEAE0DC7063249FB05474681E4AED62986CD25D', 'Wladimir J. van der Laan <laanwj@visucore.com>', trusted=True, status='unknown')",
     8    "SigData('152812300785C96444D3334D17565732E08E5E41', 'Andrew Chow (Official New Key) <achow101@gmail.com>', trusted=True, status='expired')",
     9    "SigData('637DB1E23370F84AFF88CCE03152347D07DA627C', 'Stephan Oeste (it) <it@oeste.de>', trusted=True, status='unknown')",
    10    "SigData('590B7292695AFFA5B672CBB2E13FC145CD3F4304', 'Antoine Poinsot <darosior@protonmail.com>', trusted=True, status='unknown')",
    11    "SigData('D1DBF2C4B96F2DEBF4C16654410108112E7EA81F', 'Hennadii Stepanov (GitHub key) <32963518+hebasto@users.noreply.github.com>', trusted=True, status='unknown')",
    12    "SigData('0AD83877C1F0CD1EE9BD660AD7CC770B81FD22A8', 'Ben Carman <benthecarman@live.com>', trusted=False, status='unknown')",
    13    "SigData('9D3CC86A72F8494342EA5FD10A41BDC3F4FAFF1C', 'Aaron Clauson (sipsorcery) <aaron@sipsorcery.com>', trusted=False, status='unknown')"
    14  ],
    15  "good_untrusted_sigs": [
    16    "SigData('0CCBAAFD76A2ECE2CCD3141DE2FFD5B1D88CA97D', '.0xB10C <0xb10c@gmail.com>', trusted=False, status='unknown')",
    17    "SigData('28F5900B1BB5D1A4B6B6D1A9ED357015286A333D', 'Duncan Dean <duncangleeddean@gmail.com>', trusted=False, status='unknown')",
    18    "SigData('CFB16E21C950F67FA95E558F2EEB9F5CC09526C1', 'Michael Ford (bitcoin-otc) <fanquake@gmail.com>', trusted=False, status='unknown')",
    19    "SigData('6E01EEC9656903B0542B8F1003DB6322267C373B', 'Oliver Gugger <gugger@gmail.com>', trusted=False, status='unknown')",
    20    "SigData('74E2DEF5D77260B98BC19438099BAD163C70FBFA', 'Will Clark <will8clark@gmail.com>', trusted=False, status='unknown')"
    21  ],
    22  "unknown_sigs": [
    23    "SigData('82921A4B88FD454B7EB8CE3C796C4109063D4EAF', '', trusted=False, status='')"
    24  ],
    25  "bad_sigs": [],
    26  "verified_binaries": {
    27    "bitcoin-22.0-aarch64-linux-gnu.tar.gz": "ac718fed08570a81b3587587872ad85a25173afa5f9fbbd0c03ba4d1714cfa3e",
    28    "bitcoin-22.0-arm-linux-gnueabihf.tar.gz": "b8713c6c5f03f5258b54e9f436e2ed6d85449aa24c2c9972f91963d413e86311",
    29    "bitcoin-22.0.tar.gz": "d0e9d089b57048b1555efa7cd5a63a7ed042482045f6f33402b1df425bf9613b",
    30    "bitcoin-22.0-powerpc64-linux-gnu.tar.gz": "2cca5f99007d060aca9d8c7cbd035dfe2f040dd8200b210ce32cdf858479f70d",
    31    "bitcoin-22.0-powerpc64le-linux-gnu.tar.gz": "91b1e012975c5a363b5b5fcc81b5b7495e86ff703ec8262d4b9afcfec633c30d",
    32    "bitcoin-22.0-riscv64-linux-gnu.tar.gz": "9cc3a62c469fe57e11485fdd32c916f10ce7a2899299855a2e479256ff49ff3c",
    33    "bitcoin-22.0-osx-signed.dmg": "3b3e2680f7d9304c13bfebaf6445ada40d72324b4b3e0a07de9db807389a6c5b",
    34    "bitcoin-22.0-osx64.tar.gz": "2744d199c3343b2d94faffdfb2c94d75a630ba27301a70e47b0ad30a7e0155e9",
    35    "bitcoin-22.0-x86_64-linux-gnu.tar.gz": "59ebd25dd82a51638b7a6bb914586201e67db67b919b2a1ff08925a7936d1b16",
    36    "bitcoin-22.0-win64-setup.exe": "9169989d649937c0f9ebccd3ab088501328aa319fe9e91fc7ea8e8cf0fcccede",
    37    "bitcoin-22.0-win64.zip": "9485e4b52ed6cebfe474ab4d7d0c1be6d0bb879ba7246a8239326b2230a77eb1"
    38  }
    39}
    

    0.21.0 output

    Note that this fails because the SHA256SUMS.asc files differ between bitcoincore.org and bitcoin.org. This is the behavior of the script before this changeset (though now we display a unified diff of file contents), but we may want to change things to just ignore bitcoin.org in a case like this.

     0 % ./contrib/verifybinaries/verify.py 0.21.0 --json --quiet --noninteractive
     1[WARNING] found diff in files (SHA256SUMS.asc, SHA256SUMS.asc.2):
     2  ---
     3  +++
     4  @@ -7,23 +7,23 @@
     5   6223fd23d07133a6bfa2aa3d2554a09dc1d790d28ce67b0085d3fdcc1c126e05  bitcoin-0.21.0-osx.dmg
     6   f8b2adfeae021a672effbc7bd40d5c48d6b94e53b2dd660f787340bf1a52e4e9  bitcoin-0.21.0-riscv64-linux-gnu.tar.gz
     7   1a91202c62ee49fb64d57a52b8d6d01cd392fffcbef257b573800f9289655f37  bitcoin-0.21.0.tar.gz
     8  -385a67cc4cf45558c05eb85fc500046cf033f816637fe0bff80d30debfca0128  bitcoin-0.21.0-win64-setup-unsigned.exe
     9  +54050748ef4d4f000ea1ece472491b3e5fd546efc74ed52119354b2893f6624b  bitcoin-0.21.0-win64-setup.exe
    10   1d0052c4ce80227fb6d0bc1c4e673ba21033e219c1f935d25f130ef7f43360d4  bitcoin-0.21.0-win64.zip
    11   da7766775e3f9c98d7a9145429f2be8297c2672fe5b118fd3dc2411fb48e0032  bitcoin-0.21.0-x86_64-linux-gnu.tar.gz
    12   -----BEGIN PGP SIGNATURE-----
    13   Version: GnuPG v1.4.11 (GNU/Linux)
    14
    15  -iQIcBAEBCAAGBQJgXPg5AAoJEJDIAZ42wulkkl4P/jq29Je7Uqn75jkpvT8ilB8M
    16  -5nM60ORBSsCyJHryS5sJeljLx/i8JTYkL10nUTpGDlFA3DOjM5MXgAHMJdYles7c
    17  -PFO6TAqjrDWHgpc/HuPUCiF84N47u8xXcyWuoUjcgfLuV0K6l/1unCw6ISXZEdhy
    18  -WTDZuqy+9lw9nWQA7VyIP+gfgMzgiH9P6x+ru9cYcXmimCv0TUtKp8iwUnW7QmrT
    19  -aDtQJPs9sNWxjIa4WP5l4FKFlRErAyiHA24apQkOhn8YLSrIEATFE2t89HwvmIoV
    20  -kv2BmQDrCUceC1OneFzmDP3JXJ0qa8wisw2gbUyO3EoSHfs/dlEG4+4NUQxtIwwX
    21  -JQmaB7HII6UI86kIVIBWKA19hNiAxljSKgG7blP7+J2EKC+IDqZhuYHjpYCiJg5V
    22  -2NSB3YE0sgKNyLPJN4OSA0h9nsBbEzHWm+BRgnkpPSsi2HSnczKzZwES31p9EGX1
    23  -PYi4A1nk/kOFKCZh0uvaBKWR4h3MrLB9ABv3QqmkLeEJamLDfZPDC/35WRs9Z7kQ
    24  -CUER5yMoJxq7aGjYSdd0jMxDL4akXRwf5DSN++voil3zWAqWS6XaH+9RtGmqFrZr
    25  -uso8UR3RkAwgC5HQwgtEcyewA9xxz4MHx5UN5D+YzaejJvdDJBEa9oLry21EA34U
    26  -071d1dTERe21zpnLj1EL
    27  -=yP9G
    28  +iQIcBAEBCAAGBQJgADqTAAoJEJDIAZ42wulkjtkQAJwlSTDinKsxZIMky3MeVhwB
    29  +CmxxYiMLPQA8bwgxyc4RaTxUrqL2oExPOtfcDzcR1WbQe12niG40N/2yrtf66lG9
    30  +KbSsQD6nKat9B3mCk9/jNkJHWmq5JJbOyfRs2mex75Lj7UHaPPrqh2rMfEewljed
    31  +kHkDuaqeqYlTAh981WqLD+l5jnpQZqBSrcz3YTTvXWd7xKfFSVzqF/tD4CQFrPX2
    32  +9b2BLzA/u+29Z3s+zio1l5c7fikNDd404T5U/y+NMOyCmgT4eiDGLQPlEpoGNq3w
    33  +GYU7FNZUO9xXeatx4PI8qiq5mIK46UwfPUTeruTzNrHsME7YioUa87uSYKM8jqwP
    34  +FSnbhYoUqCB/wPaKZwEF+2WzG88yj2+PzalVt8cnjRnTQ77COtHJqs8AjLWnVACF
    35  +LluplM16xyiLn0FWkrEHyi5HlI+X+cqIiTtehojMBXIkHugIYMnT5XB9llh5OWXg
    36  +Bp1UGupojLXYuMNF5R6cU5Iq+xJjbUiQ/PDm38MBlFlQ9RzRCYyZpMYZE3K9p789
    37  +jpjdYdMPtzkYlIKD87S89JtE1s6i/SkTPhebyu/328rqkqnNKSCHnuB7Fy2iEKJj
    38  +5kLs/LjY8yxSMuGeNl6LhWGKVZKy0AS/BztSHr2jgThfhN1BemFRcViSvcXhMeNw
    39  +ka8Z9KLt/N0ziabBexAw
    40  +=bi4p
    41   -----END PGP SIGNATURE-----
    42
    43
    44[ERROR] files not equal: SHA256SUMS.asc and SHA256SUMS.asc.2
    

    0.20.0 output

    Legacy binary verifications rely solely on a successful GPG code, as was the behavior before this changeset.

     0% ./contrib/verifybinaries/verify.py 0.20.0 --json --quiet --noninteractive
     1{
     2  "good_trusted_sigs": [],
     3  "good_untrusted_sigs": [
     4    "SigData('0x90C8019E36C2E964', 'Wladimir J. van der Laan (Bitcoin Core binary release signing key) <laanwj@gmail.com>', trusted=False, status='unknown')"
     5  ],
     6  "unknown_sigs": [],
     7  "bad_sigs": [],
     8  "verified_binaries": {
     9    "bitcoin-0.20.0-aarch64-linux-gnu.tar.gz": "081b30b0f1af95656242c83eef30bbf7216b1a30fa8e8f29b3b160fe520d28f6",
    10    "bitcoin-0.20.0-arm-linux-gnueabihf.tar.gz": "05014c7ff00f4496b1f389f0961d807e04505d8721d5c6f69567f2a0ec1985cc",
    11    "bitcoin-0.20.0-osx64.tar.gz": "34f377fee2c7adf59981dde7e41215765d47b466f773cf2673137d30495b2675",
    12    "bitcoin-0.20.0-osx.dmg": "a6e44b928d9ac04f11d43e920f4971fbdf1e77a8c28f7c14fafdd741ca7bc99f",
    13    "bitcoin-0.20.0-riscv64-linux-gnu.tar.gz": "5b9cae3aa4197d1e557ff236754f489bce877ebba2267ce33af79b2ca4a13af6",
    14    "bitcoin-0.20.0.tar.gz": "ec5a2358ee868d845115dc4fc3ed631ff063c57d5e0a713562d083c5c45efb28",
    15    "bitcoin-0.20.0-win64-setup.exe": "0f1ea61a9aa9aba383a43bcdb5755b072cfff016b9c6bb0afa772a8685bcf7b0",
    16    "bitcoin-0.20.0-win64.zip": "3e9ddfa05b7967e43fb502b735b6c4d716ec06f63ab7183df2e006ed4a6a431f",
    17    "bitcoin-0.20.0-x86_64-linux-gnu.tar.gz": "35ec10f87b6bc1e44fd9cd1157e5dfa483eaf14d7d9a9c274774539e7824c427"
    18  }
    19}
    
  17. jamesob force-pushed on Sep 22, 2021
  18. jamesob force-pushed on Sep 23, 2021
  19. fanquake commented at 8:46 am on September 23, 2021: member
    Concept ACK - thanks for working on this.
  20. jamesob commented at 7:31 pm on September 24, 2021: member

    Have you considered using the gpgme library (like https://github.com/bitcoin-core/bitcoin-maintainer-tools/blob/master/gitian-verify.py#L66 does) instead of parsing gpg command text output?

    So I spent some time thinking about this and looking around at the various libraries that’re available for GPG use. While using one of them would definitely save on a little complexity here, I’m somewhat wary of the trade-offs.

    On top of the added installation complexity (apparently gpgme isn’t just a pip install but requires libgpgme development headers to be installed as well), I have broader concerns about relying on Python dependencies for uses that are this sensitive.

    If we use a third-party Python lib, we’re opening up the trust model to encompass not just the Core repo and the Python runtime, but the author of the Python library and the PyPI servers. Obviously we could pin hashes, vendor the library, etc. etc. but I think that will ultimately be more work than the 70 odd lines of code that munge GPG output.

    It strikes me that dealing with possible (and probably explicit) failure of this script on less common platforms because of unexpected GPG formatting is probably preferable to opening ourselves up to vulnerabilities based on third-party dependencies and packaging infrastructure. At least when it comes to something as important as verifying Bitcoin binaries.

  21. Sjors commented at 9:34 am on September 27, 2021: member
    I tend to agree that it’s worth a bit of hassle to not have too many dependencies for this verify script.
  22. theStack commented at 7:39 pm on October 2, 2021: contributor

    Recently I worked on a Linux machine where gpg printed its verification results in its local language (German, in my case). In such a case, the verification script would obviously fail, as it expects English as output language. Can we find a way to cope with that? I didn’t look deeper yet on what the output language depends on (I guess some typical locale environment variable like LANGUAGE or LC_MESSAGES). Maybe there is a command line flag that can enforce a certain language. In the worst case, we can always start up gpg with a shell script that overrides the locale settings temporarily.

    This is one concern that we wouldn’t need to take care of when using a library like gpgme, though I generally agree with the opinion that we want to keep the dependencies minimal (as expressed by @jamesob and @Sjors).

  23. laanwj commented at 12:59 pm on October 13, 2021: member

    This is one concern that we wouldn’t need to take care of when using a library like gpgme, though I generally agree with the opinion that we want to keep the dependencies minimal (as expressed by

    Yes it would never run into language issues with libgpgme because it uses the GPG code directly, not through parsing text. And FWIW, libgpgme generally seems to already be installed on Ubuntu-ish machines (it’s not like bitcoin would be the only thing using this). I agree about dependency concerns otherwise of course.

    but I think that will ultimately be more work than the 70 odd lines of code that munge GPG output.

    What I’m mostly concerned about is that munging GPG output is conceptually brittle. It’s not a good machine interface. A change in the text output from one version to another may cause the script to misbehave (likely it would fail safely, not dangerously, due to the exit code, but okay).

  24. jamesob commented at 7:09 pm on October 13, 2021: member

    In such a case, the verification script would obviously fail, as it expects English as output language.

    Are you able to actually trigger such a failure at the moment? I think I saw some dutch in @Sjors’ output, but the script worked fine. If you are I’d be curious to see it.

    What I’m mostly concerned about is that munging GPG output is conceptually brittle.

    I don’t want to push back too hard, but right now the script in master is plain broken. This actually works (despite maybe being brittle), and so I think it’s worth considering for merge as-is.

    I’m happy to do a follow-up if we actually get reports of failures, or perhaps even before then if we find a decent way to handle the additional dependencies. Nothing in this approach precludes that, but incorporating libgpgme seems like a decent chunk of additional work given we’d now have to deal with packaging considerations. It’d be a shame for the perfect to be the enemy of the good in this case.

  25. jamesob commented at 7:50 pm on October 13, 2021: member
    One easy mitigation might be to specify the envvar LANG=en before each gpg call (https://stackoverflow.com/a/33961763/1611953).
  26. jamesob force-pushed on Oct 14, 2021
  27. jamesob commented at 5:38 pm on October 14, 2021: member

    Okay, I’ve verified that LANGUAGE=x does control output language for (my version of) gpg:

     013:33:26 james@teller /home/james % LANGUAGE=en gpg --verify sigs.txt
     1gpg: Signature made Thu 02 Sep 2021 01:52:57 PM EDT
     2gpg:                using RSA key 4589779ADFC14F3327534EA8A3A31BAD5A2A5B10
     3gpg: Good signature from "Peter D. Gray <peter@coinkite.com>" [unknown]
     4gpg: WARNING: This key is not certified with a trusted signature!
     5gpg:          There is no indication that the signature belongs to the owner.
     6Primary key fingerprint: 4589 779A DFC1 4F33 2753  4EA8 A3A3 1BAD 5A2A 5B10
     7
     813:33:27 james@teller /home/james % LANGUAGE=fr gpg --verify sigs.txt
     9gpg: Signature faite le Thu 02 Sep 2021 01:52:57 PM EDT
    10gpg:                avec la clef RSA 4589779ADFC14F3327534EA8A3A31BAD5A2A5B10
    11gpg: Bonne signature de « Peter D. Gray <peter@coinkite.com> » [inconnu]
    12gpg: Attention : cette clef n'est pas certifiée avec une signature de confiance.
    13gpg:             Rien n'indique que la signature appartient à son propriétaire.
    14Empreinte de clef principale : 4589 779A DFC1 4F33 2753  4EA8 A3A3 1BAD 5A2A 5B10
    

    and also demonstrated that the script would have broken in that case, albeit explicitly.

    I’ve added a commit that specifies LANGUAGE=en before the gpg command and so avoids this issue. Thanks for raising the point! And FWIW I’m still not averse to using libgpgme at some point, but I do think more thought is required about how to manage the packaging complexity of that. I really don’t like the idea of introducing PyPI and its servers into our process.

  28. jamesob commented at 5:00 pm on November 30, 2021: member

    Gentle reminder here that the script in master is broken for 22.0 verification. It’d be great to either get this reviewed/merged or get some clear direction on what’s needed to move this forward.

    I understand that parsing GPG text output is less than ideal, but I’m still of the opinion that the alternative of introducing a reliance on gpgme and all the packaging considerations that come along with it is less appealing. The downside of parsing text is potentially having explicit errors occur which users can report - this is opposed to the implicit risks of gpgme packaging and the disincentives that come along with a more onerous install process.

  29. in contrib/verifybinaries/README.md:38 in a92ec88cbc outdated
    41+
    42+#### Usage
    43+
    44+This script attempts to download the checksum file (`SHA256SUMS`) and corresponding
    45+signature file `SHA256SUMS.asc` from a number of sources, but chiefly
    46+https://bitcoincore.org.
    


    ryanofsky commented at 10:19 pm on December 8, 2021:
    Would maybe just say bitcoincore.org and bitcoin.org to be less mysterious
  30. in contrib/verifybinaries/verify.py:203 in a92ec88cbc outdated
    198+    """Get (success, text lines of a file) over HTTP."""
    199+    try:
    200+        return True, [
    201+            line.strip().decode() for line in urllib.request.urlopen(url).readlines()]
    202+    except urllib.request.HTTPError:
    203+        log.warning(f"HTTP request to {url} failed (HTTPError)")
    


    ryanofsky commented at 10:26 pm on December 8, 2021:
    Print the actual error? {e}
  31. in contrib/verifybinaries/verify.py:219 in a92ec88cbc outdated
    214+    # Use string-shell form (vs. args as list) because passing LANGUAGE via `env` doesn't
    215+    # seem to work.
    216+    args = (
    217+        'LANGUAGE=en gpg --yes --decrypt '
    218+        + (f' --output {output_filename} ' if output_filename else '')
    219+        + signature_filename)
    


    ryanofsky commented at 10:34 pm on December 8, 2021:

    Previous version of this that used an arg list seemed better, because it avoided shell syntax that wouldn’t work on windows and would have trouble with spaces and special characters.

    I don’t think there should be an issue passing the LANGUAGE value if you just add , env=dict(os.environ, LANGUAGE="en") to the subprocess call.

  32. in contrib/verifybinaries/verify.py:253 in a92ec88cbc outdated
    248+    bad_sigs = []
    249+    total_resolved_sigs = 0
    250+    curr_key = None
    251+
    252+    for i, line in enumerate(output):
    253+        if 'using RSA key' in line:
    


    ryanofsky commented at 10:48 pm on December 8, 2021:

    Would seem more readable and also more reassuring to do a fuller parse of GPG lines, instead of picking out little pieces of text. E.g.

    0m = re.match(^" *using RSA key (0x[0-9a-f]{18}|[0-9a-f]{40}$")
    

    If you’re just doing fragments like 'using RSA key' in line, who is to say the script won’t match the text NOT using RSA key and you just gave me a fake binary and ran away with my secret keys.


    jamesob commented at 7:20 pm on December 14, 2021:
    Excellent point, thanks!
  33. ryanofsky commented at 10:54 pm on December 8, 2021: contributor
    Started to review this (a92ec88cbc6e153eabf5e809d8960454488cc391). Looks good, there’s just a lot to go over
  34. in contrib/verifybinaries/verify.py:535 in a92ec88cbc outdated
    586+        if gpg_retval == 2:
    587+            log.critical(
    588+                "gpg error. Do you have the Bitcoin Core binary release "
    589+                "signing key installed?")
    590+        else:
    591+            log.critical("unexpected GPG exit code")
    


    ryanofsky commented at 6:04 pm on December 9, 2021:
    Add exit code {gpg_retval}?
  35. in contrib/verifybinaries/verify.py:89 in a92ec88cbc outdated
     99+
    100+def bool_from_env(key, default=False) -> bool:
    101+    if key not in os.environ:
    102+        return default
    103+    raw = os.environ[key]
    104+    return raw in ['1', 'True', 'true']
    


    ryanofsky commented at 7:07 pm on December 9, 2021:

    Does not seem great if BINVERIFY_REQUIRE_ALL_HOSTS=yes is treated as false. It would be better to raise an error if value is unrecognized.

    0if value.lower() in ("1', "true"):
    1    return True
    2if value.lower() in ("0", "false"):
    3    return False
    4raise ValueError(f"Unrecognized environment value {key}={value}")
    
  36. in contrib/verifybinaries/verify.py:483 in a92ec88cbc outdated
    492+        os.chdir(Path.home())
    493+        shutil.rmtree(WORKINGDIR)
    494 
    495     # determine remote dir dependent on provided version string
    496-    version_base, version_rc, os_filter = parse_version_string(args[0])
    497+    version_base, version_rc, os_filter = parse_version_string(args.version)
    


    ryanofsky commented at 7:21 pm on December 9, 2021:
    Version is the main required argument so if it isn’t parsed correctly it might be good to print a helpful error like “Version {args.version!r} can’t be parsed” instead of just printing the exception
  37. in contrib/verifybinaries/verify.py:451 in a92ec88cbc outdated
    455+    if unknown and not args.noninteractive and not args.skip_import_builders:
    456+        for unsig in unknown:
    457+            name = builder_key_map.get(unsig.key, '<unknown>')
    458+            if input(f" ? Retrieve key {unsig.key} for {name}? (y/N) ").lower() == "y":
    459+                ran = subprocess.run(
    460+                    f"gpg --keyserver {args.keyserver} --recv-keys {unsig.key}", shell=True)
    


    ryanofsky commented at 7:24 pm on December 9, 2021:

    Another use of shell=True here doesn’t handle spaces and special characters and would be easy to eliminate with

    0["gpg", "--keyserver", args.keyserver, "--recv-keys", unsig.key]
    
  38. in contrib/verifybinaries/verify.py:449 in a92ec88cbc outdated
    453+
    454+    # Retrieve unknown keys that are in the repo builder list and then try GPG again.
    455+    if unknown and not args.noninteractive and not args.skip_import_builders:
    456+        for unsig in unknown:
    457+            name = builder_key_map.get(unsig.key, '<unknown>')
    458+            if input(f" ? Retrieve key {unsig.key} for {name}? (y/N) ").lower() == "y":
    


    ryanofsky commented at 7:26 pm on December 9, 2021:
    It would be better if this loop if an unrecognized response was given, instead of assuming a response like “yes” means no.
  39. in contrib/verifybinaries/verify.py:538 in a92ec88cbc outdated
    628+
    629+    for sig in bad:
    630+        log.warning(f"BAD SIGNATURE: {sig}")
    631+
    632+    for sig in unknown:
    633+        log.warning(f"UNKNOWN SIGNATURE: {sig}")
    


    ryanofsky commented at 7:32 pm on December 9, 2021:
    This main() function has a lot of verification logic mixed in with a lot of printing code like this. Would be good to move informational parts of main() to separate print_signature_info or similar functions, so it’s easy for someone trying to see how verification works to see what this is doing.

    jamesob commented at 7:00 pm on December 14, 2021:
    While this is good feedback and could be done, I don’t have time to do this refactoring - may be a good follow-up for a newer contrib looking for a task.
  40. in contrib/verifybinaries/verify.py:490 in a92ec88cbc outdated
    500         remote_dir += f"test.{version_rc}/"
    501-    remote_sigfile = remote_dir + SIGNATUREFILENAME
    502+    remote_sigs_path = remote_dir + SIGNATUREFILENAME
    503+    remote_sums_path = remote_dir + SUMS_FILENAME
    504 
    505+    bitcoin_dir_maybe = Path(args.bitcoin_src_path) if args.bitcoin_src_path else Path.cwd()
    


    ryanofsky commented at 7:49 pm on December 9, 2021:

    Instead of making behavior depend on directory where script was called from, I think it would be more stable to use the script location like:

    0local_keys = (Path(args.bitcoin_src_path) / 'contrib' if args.bitcoin_src_path else
    1              Path(__file__).resolve().parent.parent) / 'builder-keys' / 'keys.txt'
    
  41. in contrib/verifybinaries/verify.py:135 in a92ec88cbc outdated
    145+)
    146+parser.add_argument(
    147+    '--skip-import-builders', action='store_true',
    148+    default=bool_from_env('BINVERIFY_SKIP_IMPORT_BUILDERS'),
    149+    help='If set, do not prompt to import builder pubkeys',
    150+)
    


    ryanofsky commented at 8:11 pm on December 9, 2021:

    This optiony seems awkard currently and not useful. Would suggest dropping or replacing with a ‘–import-builder-keys’ bool option.

    Right now this option is redundant and does the exact same thing as –noninteractive. It’s also indirect, choosing whether or not to prompt about receiving keys, instead of just choosing whether or not to receive keys.

    I think it would be better have a plain --import-builder-keys option (defaulting to false) that would provide a way to import keys with --noninteractive, and confirm before importing each key without --noninteractive


    jamesob commented at 7:11 pm on December 14, 2021:
    I’ll just remove --skip-import-builders for now and we can revisit in a follow-up if desired.
  42. in contrib/verifybinaries/verify.py:141 in a92ec88cbc outdated
    151+parser.add_argument(
    152+    '--min-trusted-sigs', type=int, action='store', nargs='?',
    153+    default=int(os.environ.get('BINVERIFY_MIN_TRUSTED_SIGS', 4)),
    154+    help=(
    155+        'The minimum number of good signatures from recognized keys to '
    156+        'require successful termination.'),
    


    ryanofsky commented at 8:16 pm on December 9, 2021:
    Would be good to say this option is ignored when verifying older releases with only one signature.
  43. in contrib/verifybinaries/verify.py:151 in a92ec88cbc outdated
    161+    help='which keyserver to use',
    162+)
    163+parser.add_argument(
    164+    '--trusted-keys', action='store', nargs='?',
    165+    default=os.environ.get('BINVERIFY_TRUSTED_KEYS', ''),
    166+    help='A list of trusted builder GPG keys, specified as CSV',
    


    ryanofsky commented at 8:21 pm on December 9, 2021:

    Would be good to say this has nothing to with GPG trusted keys

    Also might be good to say “separated by commas” instead of “specified as CSV”. CSV implies extra complexity around escaping

  44. in contrib/verifybinaries/verify.py:154 in a92ec88cbc outdated
    164+    '--trusted-keys', action='store', nargs='?',
    165+    default=os.environ.get('BINVERIFY_TRUSTED_KEYS', ''),
    166+    help='A list of trusted builder GPG keys, specified as CSV',
    167+)
    168+parser.add_argument(
    169+    '--no-builder-keys', action='store_true',
    


    ryanofsky commented at 8:34 pm on December 9, 2021:

    Would call this --trust-builder-keys to be consistent with --trusted-keys and --min-trusted-sigs.

    Current double negation and interaction with environment variable also seems awkward and has weird edge cases, like there’s no way to set the argument to false if the environment variable sets it to true.

    Maybe this would be better:

    0parser.add_argument('--trust-builder-keys', dest='trust_builder_keys', action='store_true', help="Treat keys in local builder-keys/keys.txt file as trusted")
    1parser.add_argument('--no-trust-builder-keys', dest='trust_builder_keys', action='store_false')
    2parser.set_defaults(trust_builder_keys=bool_from_env('BINVERIFY_TRUST_BUILDER_KEYS', True))
    
  45. in contrib/verifybinaries/verify.py:63 in a92ec88cbc outdated
    73+    FILE_GET_FAILED = 4
    74+    FILE_MISSING_FROM_ONE_HOST = 5
    75+    FILES_NOT_EQUAL = 6
    76+    NO_BINARIES_MATCH = 7
    77+    NOT_ENOUGH_GOOD_SIGS = 9
    78+    BINARY_DOWNLOAD_FAILED = 10
    


    ryanofsky commented at 8:47 pm on December 9, 2021:

    There are gaps in numbering here, so maybe would renumber, but I think it would be better to simplify semantics and just return SUCCESS-Verification succeeded, binary is trusted, FAILED-Verification failed, binary is not trusted, or ERROR-Not possible to verify, need to fix errors or retry

    More detailed errors and status information would seem more appropriate to include in text and json output than condense into a status byte.


    jamesob commented at 7:15 pm on December 14, 2021:
    I’m going to leave this as-is to marginally aid people migrating from the old script. If we want we can add a generic failure code, but I don’t think there’s any harm in having specific non-zero error codes.
  46. in contrib/verifybinaries/verify.py:79 in a92ec88cbc outdated
    88+    console.setFormatter(formatter)
    89+    log.addHandler(console)
    90+    return log
    91+
    92+
    93+log = set_up_logger()
    


    ryanofsky commented at 8:51 pm on December 9, 2021:
    Why is this being called here instead of in main? Would be more straightforward if log.setLevel was just called once in set_up_logger instead of changing later in main()

    jamesob commented at 7:17 pm on December 14, 2021:
    It’s called here so that the log reference can be used in functions defined before main.
  47. in contrib/verifybinaries/verify.py:152 in a92ec88cbc outdated
    172+)
    173+parser.add_argument(
    174+    '--json', action='store_true',
    175+    default=bool_from_env('BINVERIFY_JSON'),
    176+    help='If set, output the result as JSON',
    177+)
    


    ryanofsky commented at 8:55 pm on December 9, 2021:
    I guess this is preexisting code but parse_version_string below seems to silently accept len(parts) >= 3 instead of treating it like an error. Would seem more reliable to parse the full string and not silently ignore any parts. Otherwise script might return success while not verifying the thing it was supposed to verify.

    jamesob commented at 7:38 pm on December 14, 2021:
    Don’t have time to think about the best way to handle this, so will leave for a follow-up.
  48. in contrib/verifybinaries/verify.py:602 in a92ec88cbc outdated
    661     # download binaries
    662     for _, binary_filename in hashes_to_verify:
    663-        print(f"Downloading {binary_filename}")
    664-        download_with_wget(HOST1 + remote_dir + binary_filename)
    665+        log.info(f"downloading {binary_filename}")
    666+        success, output = download_with_wget(HOST1 + remote_dir + binary_filename)
    


    ryanofsky commented at 9:05 pm on December 9, 2021:
    This is calling wget without -O so if wget decides to write a .1, .2 file then then code below will verify the old existing file not the new downloaded file. Would be better to pass binary_filename as an explicit second argument to download_with_wget

    jamesob commented at 7:20 pm on December 14, 2021:
    Good catch!
  49. ryanofsky commented at 9:16 pm on December 9, 2021: contributor
    More review of a92ec88cbc6e153eabf5e809d8960454488cc391. Mostly done I think. Will continue
  50. bitcoin deleted a comment on Dec 9, 2021
  51. jamesob commented at 7:38 pm on December 14, 2021: member
    Thanks (as always) for the great review and feedback @ryanofsky. To avoid painful review invalidation (and an even more painful rebase process), I’ve addressed the majority of your feedback in an additional commit.
  52. jamesob force-pushed on Dec 15, 2021
  53. jamesob commented at 4:15 pm on December 15, 2021: member
    Pushed a rebase that ports the functional tests from .sh to .py - immediately to fix the shellcheck failures, but I’d been meaning to do this anyway because the tests are easier to write and maintain in Python.
  54. laanwj commented at 2:06 pm on December 18, 2021: member

    Pushed a rebase that ports the functional tests from .sh to .py

    Thank you!

  55. Rspigler commented at 6:01 pm on December 21, 2021: contributor
    tACK 396cb35a2205a625ce3ba1a86d97754fbb983e85. I really like this and IMO should be very simple to explain to end users.
  56. jamesob commented at 3:33 pm on July 22, 2022: member
    Should I just close this?
  57. Rspigler commented at 3:37 pm on July 22, 2022: contributor
    I would prefer not, I really like this solution
  58. in contrib/verifybinaries/verify.py:190 in 0bd3220200 outdated
    77+        return True, [l.strip().decode() for l in urllib.request.urlopen(url).readlines()]
    78+    except urllib.request.HTTPError:
    79+        print_warn(f"HTTP request to {url} failed (HTTPError)")
    80+    except Exception as e:
    81+        print_warn(f"HTTP request to {url} failed ({e})")
    82+    return (False, [])
    


    willcl-ark commented at 1:19 pm on September 6, 2022:
    Pico-nit in case you re-touch: fn returns an unparenthesized tuple on L77 and parenthesized here. There’s no behavioural difference in Python, but could be nice to have them the same (as “the comma makes the tuple”, perhaps both unparenthesized?)
  59. in contrib/verifybinaries/verify.py:264 in 396cb35a22 outdated
    279+        if line_begins_with(r'Good signature from (".+")(\s+)(\[.+\])$', line):
    280+            if not curr_key:
    281+                raise RuntimeError("failed to detect signature being resolved")
    282+            name, status = parse_gpg_from_line(line)
    283+
    284+            # It's safe to index output[i + 1] because if we saw a good sig, there should
    


    willcl-ark commented at 9:02 pm on September 6, 2022:

    There may well be another line in the good case, however my output looks like this for many keys:

    0  gpg: Good signature from "Antoine Poinsot <darosior@protonmail.com>" [unknown]
    1  gpg:                 aka "darosior <darosior@protonmail.com>" [unknown]
    2  gpg:                 aka "Antoine Poinsot <antoine@revault.dev>" [unknown]
    3  gpg:                 aka "darosior <darosior@ln.dev>" [unknown]
    4  gpg: WARNING: This key is not certified with a trusted signature!
    

    So it shows up as trusted = True, when in fact it should not be


    willcl-ark commented at 9:05 pm on September 6, 2022:

    Perhaps keep indexing until we reach a line which does not start with aka, and then check that? This does feel quite brittle though.

    Actually it seems like we might be able to use gpg --verify-options show-primary-uid-only to hide the aka lines, according to https://www.gnupg.org/documentation/manuals/gnupg/GPG-Configuration-Options.html

  60. in contrib/verifybinaries/verify.py:215 in 396cb35a22 outdated
    218+def verify_with_gpg(
    219+    signature_filename,
    220+    output_filename: t.Optional[str] = None
    221+) -> t.Tuple[int, str]:
    222+    args = [
    223+        'gpg', '--yes', '--decrypt',
    


    willcl-ark commented at 9:16 pm on September 6, 2022:
    0        'gpg', '--yes', '--decrypt', '--verify-options', 'show-primary-uid-only',
    

    willcl-ark commented at 9:17 pm on September 6, 2022:
    This fixes my comment about not strictly checking trusted for keys displaying akas (https://github.com/bitcoin/bitcoin/pull/23020/files#r964163444)
  61. in contrib/verifybinaries/verify.py:257 in 396cb35a22 outdated
    260+    # from fooling the parser.
    261+    def line_begins_with(patt: str, line: str) -> bool:
    262+        return re.match(r'^\s*(gpg:)?(\s+)' + patt, line)
    263+
    264+    for i, line in enumerate(output):
    265+        if line_begins_with(r"using RSA key (0x[0-9a-fA-F]{16}|[0-9a-fA-F]{40})$", line):
    


    willcl-ark commented at 9:40 pm on September 6, 2022:

    For v23.0 one user now has an ECDSA key which seems to break this script (output[70:80])

     0gpg: Signature made Sat 23 Apr 2022 13:39:56 BST
     1gpg:                using ECDSA key C388F6961FB972A95678E327F62711DBDCA8AE56
     2gpg:                issuer "kvaciral@protonmail.com"
     3gpg: key 0x2F9867672542AC32: "kvaciral@protonmail.com <kvaciral@protonmail.com>" not changed
     4gpg: Total number processed: 1
     5gpg:              unchanged: 1
     6gpg: requesting key 0xF62711DBDCA8AE56 from hkps server keys.openpgp.org
     7gpg: key 0xF62711DBDCA8AE56: new key but contains no user ID - skipped
     8gpg: Total number processed: 1
     9gpg:           w/o user IDs: 1
    10gpg: Can't check signature: No public key
    

    After this block is skipped curr_key is not set, which then causes the RuntimeError on L269 to hit


    willcl-ark commented at 9:41 pm on September 6, 2022:
    0        if line_begins_with(r"using (ECDSA|RSA) key (0x[0-9a-fA-F]{16}|[0-9a-fA-F]{40})$", line):
    
  62. in contrib/verifybinaries/README.md:76 in 396cb35a22 outdated
    86 ```sh
    87-./verify.py bitcoin-core-0.11.2
    88-./verify.py bitcoin-core-0.12.0
    89-./verify.py bitcoin-core-0.13.0-rc3
    90+./contrib/verifybinaries/verify.py 22.0-x86 \
    91+    --no-builder-keys \
    


    willcl-ark commented at 9:54 pm on September 6, 2022:

    Perhaps this option name was changed?

    0    --no-trust-builder-keys \
    
  63. in contrib/verifybinaries/test.py:43 in 396cb35a22 outdated
    38+def run_verify(extra: str) -> subprocess.CompletedProcess:
    39+    maybe_here = Path.cwd() / 'verify.py'
    40+    path = maybe_here if maybe_here.exists() else Path.cwd() / 'contrib' / 'verifybinaries' / 'verify.py'
    41+
    42+    return subprocess.run(
    43+        f"{path} --noninteractive --cleanup {extra}", capture_output=True, shell=True)
    


    willcl-ark commented at 10:02 pm on September 6, 2022:

    Capture_output is python 3.7. Can just capture both with subprocess.PIPE though:

    0        f"{path} --noninteractive --cleanup {extra}", stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
    
  64. willcl-ark commented at 10:04 pm on September 6, 2022: contributor
    tACK with the changes I suggested inline previously.
  65. jamesob commented at 10:32 pm on September 6, 2022: member
    @willcl-ark thanks very much for the review; pushed your feedback.
  66. DrahtBot commented at 3:20 pm on September 23, 2022: contributor

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    Concept ACK theStack, jonatack, laanwj, fanquake, willcl-ark, achow101
    Stale ACK Rspigler

    If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #27358 (contrib: allow multi-sig binary verification v2 by theuni)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

  67. jamesob commented at 6:48 pm on November 8, 2022: member

    Review tally:

    Friendly reminder that the existing script is broken for modern releases.

  68. Sjors commented at 11:39 am on November 9, 2022: member
    I’ll take this for another spin after the v24.0 release.
  69. willcl-ark commented at 1:42 pm on November 25, 2022: contributor

    Works with 24.0 for me:

      0 ./contrib/verifybinaries/verify.py --verbose 24.0-x86_64
      1[INFO] got file https://bitcoincore.org/bin/bitcoin-core-24.0/SHA256SUMS.asc as SHA256SUMS.asc
      2[WARNING] https://bitcoin.org failed to provide file (https://bitcoin.org/bin/bitcoin-core-24.0/SHA256SUMS.asc). Continuing based solely upon https://bitcoincore.org.
      3[WARNING] found diff (local vs. GH) in builder keys:
      4  ---
      5
      6  +++
      7
      8  @@ -1,3 +1,4 @@
      9
     10  +982A193E3CE0EED535E09023188CBB2648416AD5 0xB10C (0xb10c)
     11   9D3CC86A72F8494342EA5FD10A41BDC3F4FAFF1C Aaron Clauson (sipsorcery)
     12   617C90010B3BD370B0AC7D424BB42E31C79111B8 Akira Takizawa (akx20000)
     13   E944AE667CF960B1004BC32FCA662BE18B877A60 Andreas Schildbach (aschildbach)
     14
     15[INFO] got file https://bitcoincore.org/bin/bitcoin-core-24.0/SHA256SUMS as SHA256SUMS
     16[WARNING] https://bitcoin.org failed to provide file (https://bitcoin.org/bin/bitcoin-core-24.0/SHA256SUMS). Continuing based solely upon https://bitcoincore.org.
     17[INFO] gpg output:
     18  gpg: assuming signed data in 'SHA256SUMS'
     19  gpg: Signature made Thu 17 Nov 2022 19:10:20 GMT
     20  gpg:                using RSA key 152812300785C96444D3334D17565732E08E5E41
     21  gpg:                issuer "andrew@achow101.com"
     22  gpg: Good signature from "Andrew Chow <andrew@achow101.com>" [unknown]
     23  gpg: WARNING: This key is not certified with a trusted signature!
     24  gpg:          There is no indication that the signature belongs to the owner.
     25  Primary key fingerprint: 1528 1230 0785 C964 44D3  334D 1756 5732 E08E 5E41
     26  gpg: Signature made Fri 18 Nov 2022 17:38:13 GMT
     27  gpg:                using RSA key 0AD83877C1F0CD1EE9BD660AD7CC770B81FD22A8
     28  gpg:                issuer "benthecarman@live.com"
     29  gpg: Good signature from "Ben Carman <benthecarman@live.com>" [unknown]
     30  gpg: WARNING: This key is not certified with a trusted signature!
     31  gpg:          There is no indication that the signature belongs to the owner.
     32  Primary key fingerprint: 0AD8 3877 C1F0 CD1E E9BD  660A D7CC 770B 81FD 22A8
     33  gpg: Signature made Thu 17 Nov 2022 20:36:14 GMT
     34  gpg:                using RSA key 101598DC823C1B5F9A6624ABA5E0907A0380E6C3
     35  gpg: requesting key 0xA5E0907A0380E6C3 from hkp server keyserver.ubuntu.com
     36  gpg: key 0xA5E0907A0380E6C3: public key "CoinForensics (SigningKey) <59567284+coinforensics@users.noreply.github.com>" imported
     37  gpg: Total number processed: 1
     38  gpg:               imported: 1
     39  gpg: Good signature from "CoinForensics (SigningKey) <59567284+coinforensics@users.noreply.github.com>" [unknown]
     40  gpg: WARNING: This key is not certified with a trusted signature!
     41  gpg:          There is no indication that the signature belongs to the owner.
     42  Primary key fingerprint: 1015 98DC 823C 1B5F 9A66  24AB A5E0 907A 0380 E6C3
     43  gpg: Signature made Thu 17 Nov 2022 18:59:53 GMT
     44  gpg:                using RSA key CFB16E21C950F67FA95E558F2EEB9F5CC09526C1
     45  gpg:                issuer "fanquake@gmail.com"
     46  gpg: Good signature from "Michael Ford (bitcoin-otc) <fanquake@gmail.com>" [unknown]
     47  gpg: WARNING: This key is not certified with a trusted signature!
     48  gpg:          There is no indication that the signature belongs to the owner.
     49  Primary key fingerprint: E777 299F C265 DD04 7930  70EB 944D 35F9 AC3D B76A
     50       Subkey fingerprint: CFB1 6E21 C950 F67F A95E  558F 2EEB 9F5C C095 26C1
     51  gpg: Signature made Fri 18 Nov 2022 04:30:59 GMT
     52  gpg:                using RSA key F19F5FF2B0589EC341220045BA03F4DBE0C63FB4
     53  gpg: Good signature from "Gloria Zhao <gloriazhao@berkeley.edu>" [unknown]
     54  gpg: WARNING: This key is not certified with a trusted signature!
     55  gpg:          There is no indication that the signature belongs to the owner.
     56  Primary key fingerprint: 6B00 2C6E A3F9 1B1B 0DF0  C9BC 8F61 7F12 00A6 D25C
     57       Subkey fingerprint: F19F 5FF2 B058 9EC3 4122  0045 BA03 F4DB E0C6 3FB4
     58  gpg: Signature made Thu 17 Nov 2022 19:11:54 GMT
     59  gpg:                using RSA key F4FC70F07310028424EFC20A8E4256593F177720
     60  gpg:                issuer "gugger@gmail.com"
     61  gpg: Good signature from "Oliver Gugger <gugger@gmail.com>" [unknown]
     62  gpg: WARNING: This key is not certified with a trusted signature!
     63  gpg:          There is no indication that the signature belongs to the owner.
     64  Primary key fingerprint: F4FC 70F0 7310 0284 24EF  C20A 8E42 5659 3F17 7720
     65  gpg: Signature made Mon 21 Nov 2022 09:40:28 GMT
     66  gpg:                using RSA key D1DBF2C4B96F2DEBF4C16654410108112E7EA81F
     67  gpg:                issuer "hebasto@gmail.com"
     68  gpg: Good signature from "Hennadii Stepanov (GitHub key) <32963518+hebasto@users.noreply.github.com>" [unknown]
     69  gpg: WARNING: This key is not certified with a trusted signature!
     70  gpg:          There is no indication that the signature belongs to the owner.
     71  Primary key fingerprint: D1DB F2C4 B96F 2DEB F4C1  6654 4101 0811 2E7E A81F
     72  gpg: Signature made Sat 19 Nov 2022 09:07:19 GMT
     73  gpg:                using RSA key 287AE4CA1187C68C08B49CB2D11BD4F33F1DB499
     74  gpg: Good signature from "jackielove4u <jackielove4u@hotmail.com>" [unknown]
     75  gpg: WARNING: This key is not certified with a trusted signature!
     76  gpg:          There is no indication that the signature belongs to the owner.
     77  Primary key fingerprint: 287A E4CA 1187 C68C 08B4  9CB2 D11B D4F3 3F1D B499
     78  gpg: Signature made Thu 24 Nov 2022 09:34:31 GMT
     79  gpg:                using RSA key 9DEAE0DC7063249FB05474681E4AED62986CD25D
     80  gpg: Good signature from "Wladimir J. van der Laan <laanwj@protonmail.com>" [unknown]
     81  gpg: WARNING: This key is not certified with a trusted signature!
     82  gpg:          There is no indication that the signature belongs to the owner.
     83  Primary key fingerprint: 71A3 B167 3540 5025 D447  E8F2 7481 0B01 2346 C9A6
     84       Subkey fingerprint: 9DEA E0DC 7063 249F B054  7468 1E4A ED62 986C D25D
     85  gpg: Signature made Mon 21 Nov 2022 15:16:20 GMT
     86  gpg:                using RSA key 3EB0DEE6004A13BE5A0CC758BF2978B068054311
     87  gpg: Good signature from "Pieter Wuille <pieter@wuille.net>" [unknown]
     88  gpg: WARNING: This key is not certified with a trusted signature!
     89  gpg:          There is no indication that the signature belongs to the owner.
     90  Primary key fingerprint: 133E AC17 9436 F14A 5CF1  B794 860F EB80 4E66 9320
     91       Subkey fingerprint: 3EB0 DEE6 004A 13BE 5A0C  C758 BF29 78B0 6805 4311
     92  gpg: Signature made Sat 19 Nov 2022 13:10:12 GMT
     93  gpg:                using RSA key 9D3CC86A72F8494342EA5FD10A41BDC3F4FAFF1C
     94  gpg:                issuer "aaron@sipsorcery.com"
     95  gpg: Good signature from "Aaron Clauson (sipsorcery) <aaron@sipsorcery.com>" [unknown]
     96  gpg: WARNING: This key is not certified with a trusted signature!
     97  gpg:          There is no indication that the signature belongs to the owner.
     98  Primary key fingerprint: 9D3C C86A 72F8 4943 42EA  5FD1 0A41 BDC3 F4FA FF1C
     99  gpg: Signature made Thu 17 Nov 2022 21:23:22 GMT
    100  gpg:                using RSA key ED9BDF7AD6A55E232E84524257FF9BDBCC301009
    101  gpg: Good signature from "Sjors Provoost <sjors@sprovoost.nl>" [unknown]
    102  gpg: WARNING: This key is not certified with a trusted signature!
    103  gpg:          There is no indication that the signature belongs to the owner.
    104  Primary key fingerprint: ED9B DF7A D6A5 5E23 2E84  5242 57FF 9BDB CC30 1009
    105  gpg: Signature made Tue 22 Nov 2022 11:04:28 GMT
    106  gpg:                using RSA key 6A8F9C266528E25AEB1D7731C2371D91CB716EA7
    107  gpg:                issuer "sebastian.falbesoner@gmail.com"
    108  gpg: requesting key 0xC2371D91CB716EA7 from hkp server keyserver.ubuntu.com
    109  gpg: key 0xC2371D91CB716EA7: public key "Sebastian Falbesoner (theStack) <sebastian.falbesoner@gmail.com>" imported
    110  gpg: Total number processed: 1
    111  gpg:               imported: 1
    112  gpg: Good signature from "Sebastian Falbesoner (theStack) <sebastian.falbesoner@gmail.com>" [unknown]
    113  gpg: WARNING: This key is not certified with a trusted signature!
    114  gpg:          There is no indication that the signature belongs to the owner.
    115  Primary key fingerprint: 6A8F 9C26 6528 E25A EB1D  7731 C237 1D91 CB71 6EA7
    116  gpg: Signature made Thu 17 Nov 2022 19:35:52 GMT
    117  gpg:                using RSA key 28E72909F1717FE9607754F8A7BEB2621678D37D
    118  gpg:                issuer "vertion@protonmail.com"
    119  gpg: Good signature from "vertion <vertion@protonmail.com>" [unknown]
    120  gpg: WARNING: This key is not certified with a trusted signature!
    121  gpg:          There is no indication that the signature belongs to the owner.
    122  Primary key fingerprint: 28E7 2909 F171 7FE9 6077  54F8 A7BE B262 1678 D37D
    123[INFO] got 8 good, trusted signatures
    124[INFO] GOOD SIGNATURE: SigData('D1DBF2C4B96F2DEBF4C16654410108112E7EA81F', 'Hennadii Stepanov (GitHub key) <32963518+hebasto@users.noreply.github.com>', trusted=False, status='unknown')
    125[INFO] GOOD SIGNATURE: SigData('6A8F9C266528E25AEB1D7731C2371D91CB716EA7', 'Sebastian Falbesoner (theStack) <sebastian.falbesoner@gmail.com>', trusted=False, status='unknown')
    126[INFO] GOOD SIGNATURE: SigData('152812300785C96444D3334D17565732E08E5E41', 'Andrew Chow <andrew@achow101.com>', trusted=False, status='unknown')
    127[INFO] GOOD SIGNATURE: SigData('F4FC70F07310028424EFC20A8E4256593F177720', 'Oliver Gugger <gugger@gmail.com>', trusted=False, status='unknown')
    128[INFO] GOOD SIGNATURE: SigData('101598DC823C1B5F9A6624ABA5E0907A0380E6C3', 'CoinForensics (SigningKey) <59567284+coinforensics@users.noreply.github.com>', trusted=False, status='unknown')
    129[INFO] GOOD SIGNATURE: SigData('0AD83877C1F0CD1EE9BD660AD7CC770B81FD22A8', 'Ben Carman <benthecarman@live.com>', trusted=False, status='unknown')
    130[INFO] GOOD SIGNATURE: SigData('ED9BDF7AD6A55E232E84524257FF9BDBCC301009', 'Sjors Provoost <sjors@sprovoost.nl>', trusted=False, status='unknown')
    131[INFO] GOOD SIGNATURE: SigData('9D3CC86A72F8494342EA5FD10A41BDC3F4FAFF1C', 'Aaron Clauson (sipsorcery) <aaron@sipsorcery.com>', trusted=False, status='unknown')
    132[INFO] GOOD SIGNATURE (untrusted): SigData('CFB16E21C950F67FA95E558F2EEB9F5CC09526C1', 'Michael Ford (bitcoin-otc) <fanquake@gmail.com>', trusted=False, status='unknown')
    133[INFO] GOOD SIGNATURE (untrusted): SigData('F19F5FF2B0589EC341220045BA03F4DBE0C63FB4', 'Gloria Zhao <gloriazhao@berkeley.edu>', trusted=False, status='unknown')
    134[INFO] GOOD SIGNATURE (untrusted): SigData('287AE4CA1187C68C08B49CB2D11BD4F33F1DB499', 'jackielove4u <jackielove4u@hotmail.com>', trusted=False, status='unknown')
    135[INFO] GOOD SIGNATURE (untrusted): SigData('9DEAE0DC7063249FB05474681E4AED62986CD25D', 'Wladimir J. van der Laan <laanwj@protonmail.com>', trusted=False, status='unknown')
    136[INFO] GOOD SIGNATURE (untrusted): SigData('3EB0DEE6004A13BE5A0CC758BF2978B068054311', 'Pieter Wuille <pieter@wuille.net>', trusted=False, status='unknown')
    137[INFO] GOOD SIGNATURE (untrusted): SigData('28E72909F1717FE9607754F8A7BEB2621678D37D', 'vertion <vertion@protonmail.com>', trusted=False, status='unknown')
    138[INFO] removing *-unsigned binaries (bitcoin-24.0-x86_64-apple-darwin-unsigned.dmg, bitcoin-24.0-x86_64-apple-darwin-unsigned.tar.gz) from verification since https://bitcoincore.org does not host *-unsigned binaries
    139[INFO] removing *-debug binaries (bitcoin-24.0-x86_64-linux-gnu-debug.tar.gz) from verification since https://bitcoincore.org does not host *-debug binaries
    140[INFO] downloading bitcoin-24.0-x86_64-apple-darwin.dmg
    141[INFO] downloading bitcoin-24.0-x86_64-apple-darwin.tar.gz
    142[INFO] downloading bitcoin-24.0-x86_64-linux-gnu.tar.gz
    143[INFO] did not clean up /tmp/bitcoin_verify_binaries.24.0-x86_64
    144VERIFIED: bitcoin-24.0-x86_64-apple-darwin.dmg
    145VERIFIED: bitcoin-24.0-x86_64-apple-darwin.tar.gz
    146VERIFIED: bitcoin-24.0-x86_64-linux-gnu.tar.gz
    
  70. Sjors commented at 1:07 pm on November 28, 2022: member

    Tested dff50dc68a54799df4415be6fea52a1e6fc88fc1 on v23.0 and v24.0. It noticed some new keys were added. It asked me to fetch keys I didn’t have yet. It output a bunch of comforting VERIFIED lines :-)

    I also tested that editing a file right after it’s downloaded, will indeed cause it to fail the checksum ([CRITICAL] Hashes don't match.).

    I ran contrib/verifybinaries/test.py locally:

     0% contrib/verifybinaries/test.py 
     1 'Nonexistent version should fail' passed
     2 'Malformed version should fail' passed
     3 '--min-trusted-sigs 20 should fail' passed
     4- testing multisig verification (22.0)
     5 '22.0 should succeed' passed
     6- testing single-sig verification (0.20.0)
     7Traceback (most recent call last):
     8  File "contrib/verifybinaries/test.py", line 62, in <module>
     9    main()
    10  File "contrib/verifybinaries/test.py", line 33, in main
    11    result = json.loads(_20.stdout.decode())
    12  File "/Users/sjors/.pyenv/versions/3.6.15/lib/python3.6/json/__init__.py", line 354, in loads
    13    return _default_decoder.decode(s)
    14  File "/Users/sjors/.pyenv/versions/3.6.15/lib/python3.6/json/decoder.py", line 339, in decode
    15    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
    16  File "/Users/sjors/.pyenv/versions/3.6.15/lib/python3.6/json/decoder.py", line 357, in raw_decode
    17    raise JSONDecodeError("Expecting value", s, err.value) from None
    18json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
    

    (tried twice)

    This is consistent with me not being able to verify 0.20.0. The error is a bit cryptic.

     0./contrib/verifybinaries/verify.py 0.20.0
     1[INFO] got file https://bitcoincore.org/bin/bitcoin-core-0.20.0/SHA256SUMS.asc as SHA256SUMS.asc
     2[INFO] got file https://bitcoin.org/bin/bitcoin-core-0.20.0/SHA256SUMS.asc as SHA256SUMS.asc.2
     3Traceback (most recent call last):
     4  File "./contrib/verifybinaries/verify.py", line 687, in <module>
     5    sys.exit(main(sys.argv[1:]))
     6  File "./contrib/verifybinaries/verify.py", line 560, in main
     7    SIGNATUREFILENAME, args)
     8  File "./contrib/verifybinaries/verify.py", line 481, in check_single_sig
     9    good, unknown, bad = parse_gpg_result(output.splitlines())
    10  File "./contrib/verifybinaries/verify.py", line 273, in parse_gpg_result
    11    raise RuntimeError("failed to detect signature being resolved")
    12RuntimeError: failed to detect signature being resolved
    
  71. maflcko commented at 1:39 pm on November 28, 2022: member

    This is consistent with me not being able to verify 0.20.0. The error is a bit cryptic.

    0.y.x isn’t using guix, only y.x, so I guess this is expected?

  72. in contrib/verifybinaries/README.md:24 in dff50dc68a outdated
    27+  - (most laborious) through a browser using a key server (e.g. https://keyserver.ubuntu.com/),
    28+  - manually using the `gpg --keyserver <url> --recv-keys <key>` command, or
    29+  - (easiest) you can run the packaged `verifybinaries.py` script to have it automatically
    30+    retrieve unrecognized keys.
    31+
    32+#### Legacy verification
    


    maflcko commented at 1:49 pm on November 28, 2022:

    Maybe this can be removed, given that the releases are EOL and it doesn’t seem to be working #23020 (comment)

    If someone really wants to do this, they can just use the previous script, no?


    Sjors commented at 1:56 pm on November 28, 2022:
    Dropping the legacy checks would also make review and testing easier.
  73. jamesob commented at 2:57 pm on November 28, 2022: member

    This is consistent with me not being able to verify 0.20.0. The error is a bit cryptic.

    Do you have Wladimir’s old release signing key imported?

    0gpg --keyserver [...] --recv-key 01EA5486DE18A882D4C2684590C8019E36C2E964
    

    When reproducing, I found that that fixed both the test failure as well as the inability to verify 0.20.0.

  74. jamesob commented at 3:20 pm on November 28, 2022: member

    Thanks for testing @Sjors and for the look @MarcoFalke. I’ve pushed a small commit that improves error diagnostics for the test script, and ensures that the old release key is installed.

    I looked at removing single-sig (legacy) verification, but I’d actually like to keep it in. I think it’s a useful way to retrieve old releases of Bitcoin, and the marginal code to review/maintain is relatively little.

  75. Sjors commented at 10:12 am on November 29, 2022: member

    With this new commit I get the same error message when fetching 0.20.0.

    When running the test I get “In order to run tests, you should first import the old release signing key:”, however importing that key has no effect since I already have it.

    I have the old release key, though it’s marked as expired. I tried pulling from the default key server that gpgtools.org uses (hkps://keys.openpgp.org), but that doesn’t change the status. Also tried gpg --keyserver hkp://keyserver.ubuntu.com --recv-keys 01EA5486DE18A882D4C2684590C8019E36C2E964.

    I assume the key has intentionally not been renewed, so we have to handle the expiration somehow.

  76. in contrib/verifybinaries/test.py:11 in f6cfcb7cf9 outdated
     7@@ -8,19 +8,24 @@
     8 
     9 def main():
    10     """Tests ordered roughly from faster to slower."""
    11+    if subprocess.run("gpg --list-keys | grep 0x90C8019E36C2E964 >/dev/null", shell=True).returncode != 0:
    


    Sjors commented at 10:22 am on November 29, 2022:

    On macOS gpg (GnuPG/MacGPG2) 2.2.40 has a slightly different output format for --list-keys. It won’t include the 0x. The manual also recommends adding --with-colons for a machine-readable format that’s meant to stay consistent.

    So maybe try: gpg --list-keys --with-colons | grep 90C8019E36C2E964 >/dev/null

    On Ubuntu 22.10 with gpg (GnuPG) 2.2.35 it also doesn’t like the 0x prefix.


    Sjors commented at 10:28 am on November 29, 2022:
    With that change, the 0.22.0 test fails for me as before.

    willcl-ark commented at 10:41 am on November 29, 2022:

    So maybe try: gpg --list-keys --with-colons | grep 90C8019E36C2E964 >/dev/null

    Tested and also working on Ubuntu


    Sjors commented at 10:56 am on November 29, 2022:
    Test also fails on a (relatively) fresh Ubuntu 22.10 machine. As does contrib/verifybinaries/verify.py 0.20.0.
  77. in contrib/verifybinaries/test.py:13 in f6cfcb7cf9 outdated
     7@@ -8,19 +8,24 @@
     8 
     9 def main():
    10     """Tests ordered roughly from faster to slower."""
    11+    if subprocess.run("gpg --list-keys | grep 0x90C8019E36C2E964 >/dev/null", shell=True).returncode != 0:
    12+        print("In order to run tests, you should first import the old release signing key:")
    13+        print("  gpg --recv-key 01EA5486DE18A882D4C2684590C8019E36C2E964")
    


    Sjors commented at 10:25 am on November 29, 2022:

    Let’s add --keyserver hkps://keyserver.ubuntu.com to save the user a Google search for gpg: key 90C8019E36C2E964: new key but contains no user ID - skipped.

    Or we could just add it to the repo at this point, especially handy if key servers ever decide to drop expired keys entirely.

  78. in contrib/verifybinaries/README.md:13 in f6cfcb7cf9 outdated
    16-pub   4096R/36C2E964 2015-06-24 [expires: YYYY-MM-DD]
    17-      Key fingerprint = 01EA 5486 DE18 A882 D4C2  6845 90C8 019E 36C2 E964
    18-uid                  Wladimir J. van der Laan (Bitcoin Core binary release signing key) <laanwj@gmail.com>
    19-```
    20+First, you have to figure out which public keys to recognize. Browse the [list of frequent
    21+builder-keys](https://github.com/bitcoin/bitcoin/tree/master/contrib/builder-keys) and
    


    maflcko commented at 11:21 am on December 21, 2022:

    Not sure if it makes sense to pick the trust based on this list. It is outdated and there are many keys, such as mine, which have never contributed a signature for any guix release (https://github.com/bitcoin/bitcoin/pull/26598#issuecomment-1337066475). Also, it doesn’t seem ideal to blindly trust the list, when the only requirement to get on the list, is to upload one or two attestations.

    It might be better (and less code) to just treat all good signatures as good, but untrusted, unless they are trusted by the local gpg.

  79. fanquake added this to the milestone 25.0 on Feb 16, 2023
  80. fanquake commented at 3:29 pm on February 16, 2023: member

    Assigned this to the 25.x milestone. We either need to make these scripts useful/usable, or we should just delete them.

    I looked at removing single-sig (legacy) verification, but I’d actually like to keep it in.

    Given that the legacy signing key has been revoked:

    Please remove it from verification pipelines.

    probably makes more sense to remove.

  81. DrahtBot added the label Needs rebase on Feb 16, 2023
  82. jamesob commented at 3:48 pm on February 16, 2023: member

    We either need to make these scripts useful/usable, or we should just delete them.

    Agreed - right now in master this script is useless for contemporary versions.

    Unless anyone thinks otherwise, I think the best way forward here is for me to remove support for pre-multi-sig binaries and compress this change down to a single commit for easier review.

  83. jamesob force-pushed on Feb 23, 2023
  84. jamesob commented at 5:33 pm on February 23, 2023: member

    I’ve rebased/squashed/simplified the script here to

    • incorporate the removal of builder keys from this repo, and
    • remove support for single-sig release signing (users are referred to old versions of this script for that).
  85. DrahtBot removed the label Needs rebase on Feb 23, 2023
  86. contrib: verifybinaries: allow multisig verification
    This commit adds the functionality necessary to transition from
    doing binary verification on the basis of a single signature to
    requiring a minimum threshold of trusted signatures.
    
    A signature can appear as "good" from GPG output, but it may not come
    from an identity the user trusts. We call these "good, untrusted"
    signatures.
    
    We report bad signatures but do not necessarily fail in their presence,
    since a bad signature might coexist with enough good, trusted signatures
    to fulfill our criteria.
    
    If "--import-keys" is enabled, we will prompt the user to
    optionally try to retrieve unknown keys. Marking them as trusted locally
    is a WIP, but keys which are retrieved successfully and appear on the
    builder-keys list will immediately count as being useful towards
    fulfilling the threshold.
    
    Logging is improved and an option to output JSON that summarizes the
    whole sum signature and binary verification processes has been added.
    
    Co-authored-by: Russ Yanofsky <russ@yanofsky.org>
    Co-authored-by: willcl-ark <will8clark@gmail.com>
    b42d8e5238
  87. jamesob force-pushed on Feb 23, 2023
  88. in contrib/verifybinaries/README.md:26 in b42d8e5238
    25+    have it automatically retrieve unrecognized keys.
    26 
    27+#### Usage
    28 
    29+This script attempts to download the checksum file (`SHA256SUMS`) and corresponding
    30+signature file `SHA256SUMS.asc` from https://bitcoincore.org and https://bitcoin.org.
    


    fanquake commented at 2:03 pm on February 24, 2023:
    Note that bitcoin.org only currently offers 22.0, and it would seem unlikely that it will offer newer versions any in the near future. It also doesn’t actually provide https://bitcoin.org/bin/bitcoin-core-22.0/SHA256SUMS.asc, so can’t even be used for verification. Could make sense to drop it from this script for now?

    theStack commented at 5:21 pm on February 26, 2023:
    If we decide to drop it, the --require-all-hosts option should probably also be reconsidered. Not saying that it should also be dropped (we could and probably should have more than one host in the future), but at least the help text has to be adapted, which currently explicitly mentions both bitcoin.org and bitcoincore.org.
  89. in contrib/verifybinaries/README.md:47 in b42d8e5238
    46+
    47+Validate releases with default settings:
    48+```sh
    49+./contrib/verifybinaries/verify.py 22.0
    50+./contrib/verifybinaries/verify.py 22.0-rc2
    51+./contrib/verifybinaries/verify.py bitcoin-core-23.0
    


    fanquake commented at 2:03 pm on February 24, 2023:
    Do we need to offer both ways of doing this? Can we just pick either ./contrib/verifybinaries/verify.py xx.y or ./contrib/verifybinaries/verify.py bitcoin-core-xx.y? My preference would be just the version number, as I’d think anyone running this script already knows they are downloading and verifying Bitcoin Core?
  90. in contrib/verifybinaries/README.md:73 in b42d8e5238
    78-./verify.py bitcoin-core-0.13.0-rc3-win64
    79+./contrib/verifybinaries/verify.py bitcoin-core-22.0-osx
    80+./contrib/verifybinaries/verify.py bitcoin-core-22.0-rc2-win64
    81 ```
    82 
    83 If you do not want to keep the downloaded binaries, specify anything as the second parameter.
    


    fanquake commented at 2:05 pm on February 24, 2023:
    Should we just add an explicit --delete argument to the script, rather than “append a random anything”, as was proposed in #26985.
  91. in contrib/verifybinaries/README.md:36 in b42d8e5238
    35+
    36+If we encounter pubkeys in the signature file that we do not recognize, the script
    37+can prompt the user as to whether they'd like to download the pubkeys. To enable
    38+this behavior, use the `--import-keys` flag.
    39+
    40+The script returns 0 if everything passes the checks. It returns 1 if either the
    


    fanquake commented at 2:08 pm on February 24, 2023:
    Is there a reason to split this into return codes of 1 & 2? I think failing to verify is just much of an error as anything else?
  92. in contrib/verifybinaries/README.md:63 in b42d8e5238
    65-./verify.py bitcoin-core-0.12.0
    66-./verify.py bitcoin-core-0.13.0-rc3
    67+./contrib/verifybinaries/verify.py 22.0-x86 \
    68+    --no-trust-builder-keys \
    69+    --trusted-keys 74E2DEF5D77260B98BC19438099BAD163C70FBFA,9D3CC86A72F8494342EA5FD10A41BDC3F4FAFF1C \
    70+    --min-trusted-sigs 10
    


    fanquake commented at 2:10 pm on February 24, 2023:
    0    --min-good-sigs 10
    
  93. in contrib/verifybinaries/README.md:61 in b42d8e5238
    63 ```sh
    64-./verify.py bitcoin-core-0.11.2
    65-./verify.py bitcoin-core-0.12.0
    66-./verify.py bitcoin-core-0.13.0-rc3
    67+./contrib/verifybinaries/verify.py 22.0-x86 \
    68+    --no-trust-builder-keys \
    


    fanquake commented at 2:10 pm on February 24, 2023:
    Where is the --no-trust-builder-keys option?
  94. in contrib/verifybinaries/README.md:37 in b42d8e5238
    36+If we encounter pubkeys in the signature file that we do not recognize, the script
    37+can prompt the user as to whether they'd like to download the pubkeys. To enable
    38+this behavior, use the `--import-keys` flag.
    39+
    40+The script returns 0 if everything passes the checks. It returns 1 if either the
    41+signature check or the hash check doesn't pass. An exit code of >2 indicates an error.
    


    theStack commented at 4:34 pm on February 26, 2023:
    0signature check or the hash check doesn't pass. An exit code of >=2 indicates an error.
    

    non-blocking nit: could list all the individual return code’s meaning here, or at least mention something like “(see the ReturnCode class for individual error reasons)”.

  95. in contrib/verifybinaries/verify.py:488 in b42d8e5238
    547+        if gpg_retval == 1:
    548+            log.critical(f"Bad signature (code: {gpg_retval}).")
    549+        if gpg_retval == 2:
    550+            log.critical(
    551+                "gpg error. Do you have the Bitcoin Core binary release "
    552+                "signing key installed?")
    


    theStack commented at 5:05 pm on February 26, 2023:
    Looks like this is currently dead code, as the condition for if 3 lines above wouldn’t be true if the retval is 2 (gpg_allowed_codes is set to contains 0 and 2 on line 469).
  96. theStack commented at 5:16 pm on February 26, 2023: contributor

    Tested lightly on OpenBSD 7.2 (amd64) with versions 22.1 / 23.1 / 24.0.1 and didn’t run into any issues so far. Automatic key import (--import-keys) and JSON output mode (--json) work as expected. :heavy_check_mark: Planning to code-review and test a bit more in-depth (i.e. intentionally triggering a failed verification and other individual errors) within the following days.

    (Not sure if it has to do with my connection but fetching the binaries from bitcoincore.org was extra slow today, taking several minutes per file).

  97. TheBlueMatt commented at 5:38 pm on March 21, 2023: contributor
    Can you (here or in a separate PR) add support for validating release artifacts without actually downloading them (ie pre-downloaded release artifacts)?
  98. in contrib/verifybinaries/verify.py:199 in b42d8e5238
    202+    signature_filename,
    203+    output_filename: t.Optional[str] = None
    204+) -> t.Tuple[int, str]:
    205+    args = [
    206+        'gpg', '--yes', '--decrypt', '--verify-options', 'show-primary-uid-only',
    207+        '--output', output_filename if output_filename else '', signature_filename]
    


    achow101 commented at 7:21 pm on March 21, 2023:

    GPG’s manpage suggests using --status-fd (or --status-file) to for unattended usage of gpg. This outputs machine parseable lines to the specified file descriptor (or file) that look like:

     0[GNUPG:] NEWSIG fanquake@gmail.com
     1[GNUPG:] KEY_CONSIDERED E777299FC265DD04793070EB944D35F9AC3DB76A 0
     2[GNUPG:] SIG_ID IpS4EHolPjGh7he2GCBYNha53aY 2022-11-17 1668711593
     3[GNUPG:] KEY_CONSIDERED E777299FC265DD04793070EB944D35F9AC3DB76A 0
     4[GNUPG:] GOODSIG 2EEB9F5CC09526C1 Michael Ford (bitcoin-otc) <fanquake@gmail.com>
     5[GNUPG:] VALIDSIG CFB16E21C950F67FA95E558F2EEB9F5CC09526C1 2022-11-17 1668711593 0 4 0 1 8 00 E777299FC265DD04793070EB944D35F9AC3DB76A
     6[GNUPG:] KEY_CONSIDERED E777299FC265DD04793070EB944D35F9AC3DB76A 0
     7[GNUPG:] KEY_CONSIDERED E777299FC265DD04793070EB944D35F9AC3DB76A 0
     8[GNUPG:] TRUST_UNDEFINED 0 pgp
     9[GNUPG:] NEWSIG
    10[GNUPG:] KEY_CONSIDERED 6B002C6EA3F91B1B0DF0C9BC8F617F1200A6D25C 0
    11[GNUPG:] SIG_ID TVTce/SMh2FBof7NtPId5HC5MBo 2022-11-18 1668745859
    12[GNUPG:] KEY_CONSIDERED 6B002C6EA3F91B1B0DF0C9BC8F617F1200A6D25C 0
    13[GNUPG:] GOODSIG BA03F4DBE0C63FB4 Gloria Zhao <gloriazhao@berkeley.edu>
    14[GNUPG:] VALIDSIG F19F5FF2B0589EC341220045BA03F4DBE0C63FB4 2022-11-18 1668745859 0 4 0 1 8 00 6B002C6EA3F91B1B0DF0C9BC8F617F1200A6D25C
    15[GNUPG:] KEY_CONSIDERED 6B002C6EA3F91B1B0DF0C9BC8F617F1200A6D25C 0
    16[GNUPG:] KEY_CONSIDERED 6B002C6EA3F91B1B0DF0C9BC8F617F1200A6D25C 0
    17[GNUPG:] TRUST_UNDEFINED 0 pgp
    

    This should make the parsing done in parse_gpg_output simpler as the output should be consistently formatted for machine parsing.

    A description of this format can be found at https://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob;f=doc/DETAILS;h=e064c9d214506b4b9cea95448677afb2f885caee;hb=refs/heads/STABLE-BRANCH-2-2#l414

    The docs also suggest using --with-colons, although it doesn’t seem to make a difference in my testing.

  99. achow101 commented at 7:30 pm on March 21, 2023: member

    Concept ACK

    It would be nice to have this in, given how long our current verification script has been broken.

    I think it would be better to also allow the user to specify the paths to the files to verify instead of automatically downloading them. This would be particularly useful when the files haven’t been uploaded yet, e.g. on the website server itself prior to publishing the binaries.

    The linter is unhappy for some reason.

  100. achow101 commented at 4:59 am on March 23, 2023: member
    https://github.com/achow101/bitcoin/tree/pr23020-direct-bins-gpg-parse is a branch which implements a subcommand for verifying binaries directly and to use --status-file for the machine parseable output.
  101. jamesob commented at 11:52 am on March 23, 2023: member
    I probably won’t have time in the next few days to attend to this, so feel free to open a replacement PR with @achow101’s branch if I’m holding anything up.
  102. theuni commented at 9:22 pm on March 23, 2023: member

    https://github.com/achow101/bitcoin/tree/pr23020-direct-bins-gpg-parse is a branch which implements a subcommand for verifying binaries directly and to use --status-file for the machine parseable output.

    Testing your branch now. Let me know if you’d like to take review elsewhere.

    Sorry I don’t speak much python. Some of these problems may already exist as opposed to being introduced here.

    First, testing with python3.8: verify.py pub 23.0-x86_64

    0  File "/home/cory/dev/bitcoin2/contrib/verifybinaries/verify.py", line 192, in parse_gpg_result
    1    def line_begins_with(patt: str, line: str) -> t.Optional[re.Match[str]]:
    2TypeError: 'type' object is not subscriptable
    

    python3.9 gets past this, but runs into this:

    verify.py pub 23.0-x86_64

    0  File "/home/cory/dev/bitcoin2/contrib/verifybinaries/verify.py", line 228, in parse_gpg_result
    1    curr_sigdata.key, _, _, _, _, _, _ = line.split()[2:8]
    2ValueError: not enough values to unpack (expected 7, got 6)
    

    I was able to get past that with the following naive change:

    0index 51c8aa584f..407030a71c 100755
    1--- a/contrib/verifybinaries/verify.py
    2+++ b/contrib/verifybinaries/verify.py
    3@@ -227,3 +227,3 @@ def parse_gpg_result(
    4         elif line_begins_with(r"ERRSIG(?:\s|$)", line):
    5-            curr_sigdata.key, _, _, _, _, _, _ = line.split()[2:8]
    6+            curr_sigdata.key, _, _, _, _, _ = line.split()[2:8]
    7             curr_sigs = unknown_sigs
    

    At that point, verification succeeds.

    Testing local binaries:

    Again, fails with python3.8 for the same reason. Works as expected with python3.9.

    It would be nice if: if no binary is named, check for everything listed in SHA256SUMS. I suspect that would make it easier to automate its use.

  103. achow101 commented at 10:09 pm on March 23, 2023: member

    I probably won’t have time in the next few days to attend to this, so feel free to open a replacement PR with @achow101’s branch if I’m holding anything up.

    Unfortunately I will be away for the next two weeks soon, so I won’t have any time to maintain a replacement PR either.

    First, testing with python3.8: verify.py pub 23.0-x86_64

    0  File "/home/cory/dev/bitcoin2/contrib/verifybinaries/verify.py", line 192, in parse_gpg_result
    1    def line_begins_with(patt: str, line: str) -> t.Optional[re.Match[str]]:
    2TypeError: 'type' object is not subscriptable
    

    This is an issue with the original in this PR, but I’ve added a commit to my branch that fixes it.

    verify.py pub 23.0-x86_64

    0  File "/home/cory/dev/bitcoin2/contrib/verifybinaries/verify.py", line 228, in parse_gpg_result
    1    curr_sigdata.key, _, _, _, _, _, _ = line.split()[2:8]
    2ValueError: not enough values to unpack (expected 7, got 6)
    

    Fixed

    It would be nice if: if no binary is named, check for everything listed in SHA256SUMS. I suspect that would make it easier to automate its use.

    I’ve updated my branch to allow this.

    I’ve also updated it to allow specifying only the SHA256SUMS and it will assume the signature file name.

  104. fanquake commented at 6:30 am on March 29, 2023: member
    Closing for now. In favour of #27358, which incorporated this, and other changes.
  105. fanquake closed this on Mar 29, 2023

  106. fanquake removed this from the milestone 25.0 on Mar 29, 2023
  107. fanquake referenced this in commit db720b5a70 on Apr 7, 2023
  108. bitcoin locked this on Mar 28, 2024

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2024-12-22 03:12 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me