Mercurial > hgrepos > Python > apps > py-cutils
changeset 12:5e2c9123f93f
Implemented digest verification: -c or --check option
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Fri, 04 Dec 2020 17:37:56 +0100 |
| parents | 15c3416d3677 |
| children | db64e282b049 |
| files | shasum.py |
| diffstat | 1 files changed, 105 insertions(+), 2 deletions(-) [+] |
line wrap: on
line diff
--- a/shasum.py Fri Dec 04 16:13:02 2020 +0100 +++ b/shasum.py Fri Dec 04 17:37:56 2020 +0100 @@ -14,6 +14,8 @@ import argparse import hashlib +import io +import re import sys @@ -36,6 +38,9 @@ "--bsd", "-B", action="store_true", dest="bsd", default=False, help="write BSD style output; also :command:`openssl dgst` style") aparser.add_argument( + "--check", "-c", action="store_true", + help="read digests from FILEs and check them") + aparser.add_argument( "--reverse", "-r", action="store_false", dest="bsd", default=False, help="explicitely select normal coreutils style output (to be option compatible with BSD style commands and :command:`openssl dgst -r`)") aparser.add_argument( @@ -58,7 +63,10 @@ if not opts.files: opts.files.append('-') - generate_digests(opts) + if opts.check: + return verify_digests_from_files(opts) + else: + return generate_digests(opts) def generate_digests(opts): @@ -87,11 +95,68 @@ fn, opts.algorithm[1], True) + return 0 + + +def verify_digests_from_files(opts): + exit_code = 0 + if len(opts.files) == 1 and opts.files[0] == '-': + for checkline in sys.stdin: + if not checkline: + continue + r, fn = handle_checkline(opts, checkline) + print("{}: {}".format(fn, r.upper())) + if r != "ok" and exit_code == 0: + exit_code = 1 + else: + for fn in opts.files: + with io.open(fn, "rt", encoding="utf-8") as checkfile: + for checkline in checkfile: + if not checkline: + continue + r, fn = handle_checkline(opts, checkline) + print("{}: {}".format(fn, r.upper())) + if r != "ok" and exit_code == 0: + exit_code = 1 + return exit_code + + +def handle_checkline(opts, line): + """ + :return: a tuple with static "ok", "missing", or "failed" and the filename + :rtype: tuple(str, str) + + """ + # determine checkfile format (BSD or coreutils) + # BSD? + mo = re.search(r"\A(\S+)\s*\((.*)\)\s*=\s*(.+)\n?\Z", line) + if mo: + algo = algotag2algotype(mo.group(1)) + fn = mo.group(2) + digest = mo.group(3) + else: + mo = re.search(r"([^\ ]+) [\*\ ]?(.+)\n?\Z", line) + if mo: + algo = opts.algorithm[0] + fn = mo.group(2) + digest = mo.group(1) + else: + raise ValueError( + "improperly formatted digest line: {}".format(line)) + try: + with open(fn, "rb") as input: + d = compute_digest(algo, input) + if d.lower() == digest.lower(): + return ("ok", fn) + else: + return ("failed", fn) + except EnvironmentError: + return ("missing", fn) def argv2algo(s): """Convert a commane line algorithm specifier into a tuple with the - type/factory of the digest and the algorithms tag for output purposes + type/factory of the digest and the algorithms tag for output purposes. :param str s: the specifier from the commane line :return: the internal digest specification @@ -130,6 +195,44 @@ "`{}' is not a recognized algorithm".format(s)) +def algotag2algotype(s): + """Convert the algorithm specifier in a BSD-style digest file to the + type/factory of the corresponding algorithm. + + :param str s: the tag (i.e. normalized name) or the algorithm + :return: the digest type or factory for `s` + + All string comparisons are case-sensitive. + + """ + if s == "SHA1": + return hashlib.sha1 + elif s == "SHA224": + return hashlib.sha224 + elif s == "SHA256": + return hashlib.sha256 + elif s == "SHA384": + return hashlib.sha384 + elif s == "SHA512": + return hashlib.sha512 + elif s == "SHA3-224": + return hashlib.sha3_224 + elif s == "SHA3-256": + return hashlib.sha3_256 + elif s == "SHA3-384": + return hashlib.sha3_384 + elif s == "SHA3-512": + return hashlib.sha3_512 + elif s == "BLAKE2b": + return hashlib.blake2b + elif s == "BLAKE2s": + return hashlib.blake2s + elif s == "MD5": + return hashlib.md5 + else: + raise ValueError("unknown algorithm: {}".format(s)) + + def out_bsd(dest, digest, filename, digestname, binary): """BSD format output, also :command:`openssl dgst` and :command:`b2sum --tag" format output
