comparison shasum.py @ 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
comparison
equal deleted inserted replaced
11:15c3416d3677 12:5e2c9123f93f
12 12
13 from __future__ import print_function 13 from __future__ import print_function
14 14
15 import argparse 15 import argparse
16 import hashlib 16 import hashlib
17 import io
18 import re
17 import sys 19 import sys
18 20
19 21
20 PY2 = sys.version_info[0] < 3 22 PY2 = sys.version_info[0] < 3
21 23
34 help="read in binary mode (default)") 36 help="read in binary mode (default)")
35 aparser.add_argument( 37 aparser.add_argument(
36 "--bsd", "-B", action="store_true", dest="bsd", default=False, 38 "--bsd", "-B", action="store_true", dest="bsd", default=False,
37 help="write BSD style output; also :command:`openssl dgst` style") 39 help="write BSD style output; also :command:`openssl dgst` style")
38 aparser.add_argument( 40 aparser.add_argument(
41 "--check", "-c", action="store_true",
42 help="read digests from FILEs and check them")
43 aparser.add_argument(
39 "--reverse", "-r", action="store_false", dest="bsd", default=False, 44 "--reverse", "-r", action="store_false", dest="bsd", default=False,
40 help="explicitely select normal coreutils style output (to be option compatible with BSD style commands and :command:`openssl dgst -r`)") 45 help="explicitely select normal coreutils style output (to be option compatible with BSD style commands and :command:`openssl dgst -r`)")
41 aparser.add_argument( 46 aparser.add_argument(
42 "--tag", action="store_true", dest="bsd", default=False, 47 "--tag", action="store_true", dest="bsd", default=False,
43 help="alias for the `--bsd' option (to be compatible with :command:`b2sum`)") 48 help="alias for the `--bsd' option (to be compatible with :command:`b2sum`)")
56 if not opts.algorithm: 61 if not opts.algorithm:
57 opts.algorithm = argv2algo("1") 62 opts.algorithm = argv2algo("1")
58 63
59 if not opts.files: 64 if not opts.files:
60 opts.files.append('-') 65 opts.files.append('-')
61 generate_digests(opts) 66 if opts.check:
67 return verify_digests_from_files(opts)
68 else:
69 return generate_digests(opts)
62 70
63 71
64 def generate_digests(opts): 72 def generate_digests(opts):
65 if opts.bsd: 73 if opts.bsd:
66 out = out_bsd 74 out = out_bsd
85 out(sys.stdout, 93 out(sys.stdout,
86 compute_digest(opts.algorithm[0], source), 94 compute_digest(opts.algorithm[0], source),
87 fn, 95 fn,
88 opts.algorithm[1], 96 opts.algorithm[1],
89 True) 97 True)
98 return 0
99
100
101 def verify_digests_from_files(opts):
102 exit_code = 0
103 if len(opts.files) == 1 and opts.files[0] == '-':
104 for checkline in sys.stdin:
105 if not checkline:
106 continue
107 r, fn = handle_checkline(opts, checkline)
108 print("{}: {}".format(fn, r.upper()))
109 if r != "ok" and exit_code == 0:
110 exit_code = 1
111 else:
112 for fn in opts.files:
113 with io.open(fn, "rt", encoding="utf-8") as checkfile:
114 for checkline in checkfile:
115 if not checkline:
116 continue
117 r, fn = handle_checkline(opts, checkline)
118 print("{}: {}".format(fn, r.upper()))
119 if r != "ok" and exit_code == 0:
120 exit_code = 1
121 return exit_code
122
123
124 def handle_checkline(opts, line):
125 """
126 :return: a tuple with static "ok", "missing", or "failed" and the filename
127 :rtype: tuple(str, str)
128
129 """
130 # determine checkfile format (BSD or coreutils)
131 # BSD?
132 mo = re.search(r"\A(\S+)\s*\((.*)\)\s*=\s*(.+)\n?\Z", line)
133 if mo:
134 algo = algotag2algotype(mo.group(1))
135 fn = mo.group(2)
136 digest = mo.group(3)
137 else:
138 mo = re.search(r"([^\ ]+) [\*\ ]?(.+)\n?\Z", line)
139 if mo:
140 algo = opts.algorithm[0]
141 fn = mo.group(2)
142 digest = mo.group(1)
143 else:
144 raise ValueError(
145 "improperly formatted digest line: {}".format(line))
146 try:
147 with open(fn, "rb") as input:
148 d = compute_digest(algo, input)
149 if d.lower() == digest.lower():
150 return ("ok", fn)
151 else:
152 return ("failed", fn)
153 except EnvironmentError:
154 return ("missing", fn)
90 155
91 156
92 def argv2algo(s): 157 def argv2algo(s):
93 """Convert a commane line algorithm specifier into a tuple with the 158 """Convert a commane line algorithm specifier into a tuple with the
94 type/factory of the digest and the algorithms tag for output purposes 159 type/factory of the digest and the algorithms tag for output purposes.
95 160
96 :param str s: the specifier from the commane line 161 :param str s: the specifier from the commane line
97 :return: the internal digest specification 162 :return: the internal digest specification
98 :rtype: a tuple (digest_type_or_factory, name_in_output) 163 :rtype: a tuple (digest_type_or_factory, name_in_output)
99 164
128 else: 193 else:
129 raise argparse.ArgumentTypeError( 194 raise argparse.ArgumentTypeError(
130 "`{}' is not a recognized algorithm".format(s)) 195 "`{}' is not a recognized algorithm".format(s))
131 196
132 197
198 def algotag2algotype(s):
199 """Convert the algorithm specifier in a BSD-style digest file to the
200 type/factory of the corresponding algorithm.
201
202 :param str s: the tag (i.e. normalized name) or the algorithm
203 :return: the digest type or factory for `s`
204
205 All string comparisons are case-sensitive.
206
207 """
208 if s == "SHA1":
209 return hashlib.sha1
210 elif s == "SHA224":
211 return hashlib.sha224
212 elif s == "SHA256":
213 return hashlib.sha256
214 elif s == "SHA384":
215 return hashlib.sha384
216 elif s == "SHA512":
217 return hashlib.sha512
218 elif s == "SHA3-224":
219 return hashlib.sha3_224
220 elif s == "SHA3-256":
221 return hashlib.sha3_256
222 elif s == "SHA3-384":
223 return hashlib.sha3_384
224 elif s == "SHA3-512":
225 return hashlib.sha3_512
226 elif s == "BLAKE2b":
227 return hashlib.blake2b
228 elif s == "BLAKE2s":
229 return hashlib.blake2s
230 elif s == "MD5":
231 return hashlib.md5
232 else:
233 raise ValueError("unknown algorithm: {}".format(s))
234
235
133 def out_bsd(dest, digest, filename, digestname, binary): 236 def out_bsd(dest, digest, filename, digestname, binary):
134 """BSD format output, also :command:`openssl dgst` and 237 """BSD format output, also :command:`openssl dgst` and
135 :command:`b2sum --tag" format output 238 :command:`b2sum --tag" format output
136 239
137 """ 240 """