From d2b0261bb95c6fbfa9b8cce5d9b649a32c549303 Mon Sep 17 00:00:00 2001 From: Zbigniew Jędrzejewski-Szmek Date: Apr 03 2016 19:16:17 +0000 Subject: Add verification of signatures using gpgv2 I looked into using pygpgme, but it seems that would be much more complicated. In particular setting the keyring is non-obvious, and the results have to verified manually, etc. Just doesn't seem worth the trouble. --- diff --git a/spectool b/spectool index 9772451..11aa626 100755 --- a/spectool +++ b/spectool @@ -207,6 +207,8 @@ def parseopts(): help='lists the expanded sources/patches (default)') mode1.add_argument('-g', '--get-files', '--gf', action='store_true', help='gets the sources/patches that are listed with a URL') + mode1.add_argument('--verify', action='store_true', + help='verify the signatures on files') mode.add_argument('-h', '--help', action='help', help="display this help screen") @@ -239,6 +241,9 @@ def parseopts(): misc.add_argument('-f', '--force', action='store_true', help="try to unlink and download if target files exist") + misc.add_argument('--keyring', + help="path to file or Source number for the keyring with trusted key") + misc.add_argument('-D', '--debug', action='store_true', help="output debug info, don't clean up when done") @@ -341,6 +346,10 @@ def is_downloadable(url): """Check that string is a valid URL of a protocol which we can handle.""" return url.split('://')[0] in {'http', 'https', 'ftp'} +def is_signature(url): + """Check that path looks like a signature.""" + return url.endswith(".gpg") or url.endswith(".sig") + def path_download_name(url): return url.split('/')[-1] @@ -369,6 +378,61 @@ def download_files(spec, opts, selected): if not opts.dryrun: download_file(asset, dest) +def verify_signature(path, signature, keyring): + if not os.path.exists(path): + print('{} not downloaded yet, not checking'.format(path)) + return + + cmdline = ['gpgv2', '--quiet', '--keyring', keyring, signature, path] + try: + proc = run(cmdline) + except ProcError as e: + print(e.stdout) + error('Error: signature verification failed for {}!'.format(path), e) + print('{} has a good signature'.format(path)) + +def verify_file(path, signatures, keyring): + for ext in ('.sig', '.gpg'): + if path + ext in signatures: + return verify_signature(path, path + ext, keyring) + else: + print('No signature for {}'.format(path)) + +def verify(spec, opts, selected): + """ + Verify signatures on files. + """ + dir = get_download_location(spec, opts) + + if opts.keyring: + try: + num = int(opts.keyring) + except ValueError: + keyring = opts.keyring + if '/' not in keyring: + # gpgv2 will look in ~/.gnupg for the keyring if it not a path + keyring = os.path.join('.', keyring) + else: + if num not in spec.sourcenums: + error("No source item {} (for the keyring)") + keyring = os.path.join(dir, path_download_name(spec.sources[num])) + else: + error("not implemented") + + sigs = set() + files = set() + for typ, num, asset in generate_asset_list(spec, opts, selected): + if typ == 'Error': + raise IndexError(asset) + dest = os.path.join(dir, path_download_name(asset)) + if is_signature(dest): + sigs.add(dest) + else: + files.add(dest) + + for path in files: + verify_file(path, sigs, keyring) + def show_parsed_data(spec, opts): print("Parsed these tags:") print("-> Name: {}".format(spec.name)) @@ -389,11 +453,12 @@ def main(): show_parsed_data(spec, opts) selected = Selections(spec, opts) - if opts.list_files or not opts.get_files: + if opts.list_files or not (opts.get_files or opts.verify): list_files(spec, opts, selected) if opts.get_files: download_files(spec, opts, selected) - + if opts.verify: + verify(spec, opts, selected) if __name__ == '__main__': main()