4 Construct a signature of the installed software state or check the integrity of the installation
5 using a previously made signature.
7 Usage: signduterre.py [options] FILE1 FILE2 ...
10 -h, --help show this help message and exit
11 -s HEX, --salt=HEX Enter salt in cleartext. If not given, a hexadecimal
12 salt will be suggested. The SUGGESTED[=N] keyword will
13 cause the selection of the suggested string. N is the
14 number of salts generated (default N=1)
15 -p TEXT, --passphrase=TEXT
16 Enter passphrase in cleartext. If not given, a cleartext
17 passphrase will be suggested. The keyword SUGGESTED
18 will cause the suggested passphrase to be used.
19 Entering the name of an existing file will cause it to
20 be read and a random passphrase found in the file will
21 be used (creating a signature), or they will all be used
22 in sequence (--check-file).
23 -c FILE, --check-file=FILE
24 Check contents of file (output of previous run)
25 -i FILE, --input-file=FILE
26 Use names from input-file (one filename per line)
27 -u USER, --user=USER Execute $(cmd) as USER, default 'nobody' (root/sudo
29 -S, --Status For each file, add a line with unvarying file status
30 information: st_mode, st_ino, st_dev, st_uid, st_gid,
31 and st_size (like the '?' prefix, default False)
32 -t, --total-only Only print the total hash, must be checked BEFORE
33 running --detail (default True)
34 -d, --detailed-view Print hashes of individual files, must be checked
35 AFTER running --total-only check (default False)
36 -e, --execute Interpret $(cmd) (default False)
37 -n, --no-execute Explicitely do NOT Interpret ${ENV} and $(cmd)
38 -m, --manual Print the manual and exit
39 -r, --release-notes Print the release notes and exit
40 -l, --license Print license text and exit
41 -v, --verbose Print more information
42 -q, --quiet Print minimal information (hide filenames)
45 Names and paths of one or more files to be checked. Any name starting with a '$', eg, $PATH, will be
46 interpreted as an environmental variable or command according to the bash conventions:
47 '$ENV' and '${ENV}' as variables, '$(cmd;cmd...)' as system commands (bash --restricted -c 'cmd;cmd...' PID).
48 Where PID the current Process ID is (available as positional parameter $0). Do not forget to enclose the
49 arguments in single ''-quotes! The commands are scanned for unwanted characters (eg, ') and these are removed.
50 The use of '$(cmd;cmd...)' requires explicit use of the -e or --execute option.
52 If executed as root or sudo, $(cmd;cmd...) will be executed as 'sudo -H -u <user>' which defaults to
53 --user nobody ('--user root' is at your own risk). This will obviously not work when invoked as non-root/sudo.
54 --user root is necessary when you need to check privileged information, eg,
55 you want to check the MBR with '$(dd if=/dev/hda bs=512 count=1 | od -X)'
56 However, as you might use --check-file with files you did not create yourself, it is important to
57 be warned if commands are to be executed.
59 Interpretation of $() ONLY works if the -e or --execute options are entered. signduterre.py can easily
60 be adapted to automatically use the setting in the check-file. However, this is deemed insecure and
61 commented out in the distribution version.
63 The -n or --no-execute option explicitely supress the interpretation of $(cmd) arguments.
65 Meta information from lstat on files is signed when the filename is preceded by a '?'. '?./signduterre.py' will
66 extract (st_mode, st_ino, st_dev, st_nlinks, st_uid, st_gid, st_size) and hash a line of these data (visible with --verbose).
67 The --Status option will automatically add such a line in front of every file. Note that '?' is implied for directories.
68 both '/' and '?/' produce a hash of, eg,:
69 lstat(/) = [st_mode=041775, st_ino=2, st_dev=234881026, st_uid=0, st_gid=80, st_size=1360]
70 Note that nlinks of a directory include every file in the directory, so this will check whether files have been added
75 A very simple security application to test for the integrity of files and "states" in a computer installation.
76 signduterre.py constructs a signature of the current system state and checks installation state with a previously made signature.
77 The files are hashed with a passphrase to allow detection of compromised systems while running on the same system.
78 The signature checking can be subverted, but the flexibillity of signduterre.py and the fact that the output of any command
79 can be tested should hamper automated root-kit attacks.
81 signduterre.py writes a total SHA-256 hash to STDOUT of all the files and commands entered as arguments. It can also write a
82 hash for each individual file (insecure). The output of a signature can be send to a file and later used to
83 check with --check-file. Hashes are calculated with a hashed salt + passphrase sequence pre-pended to create
84 unpredictable hashes. This procedure ensures that an attacker does not know whether or not the correct passphrase
85 has been entered. An attacker can only know when to supply the requested hash values if she knows
86 the passphrase or has copies available of all the tested files and output of commands to calculate the hashes
91 When run on a compromised system, signduterre.py can be subverted if the attacker keeps a copy of all the files and
92 reroutes the open() and lstat() functions, or simply delegating signduterre.py to a chroot jail with the original system.
93 In principle, signduterre.py only checks whether the computer responds identically to when the sinature file was made.
94 There is no theoretic barrier against a compromised computer perfectly simulating the original system when tested,
95 but behaving adversely at other times. Except for running from clean boot media (USB?), I know of no theoretical
96 sound solution to this problem ;-)
98 However, this scenario assumes the use of unlimited resources and time. Inside a limited, real computer system, the
99 attacker must make compromises on what can and what cannot be simulated with the available time and hardware. The idea
100 behind signduterre.py is to "ask difficult questions" that increase the cost of simulating the original system
101 high enough to make detection of successful attacks likely. But signduterre.py simply intended to raise the bar.
102 One point is to store the times needed to create the original hashes. This timing can later be used to see whether
103 the new timings are reasonable. If the same hardware takes considerably longer to perform the same calculations,
104 or needs a much longer delay before it starts, the tester might want to see where this time is spent.
106 Signature-du-Terroir works on the assumption that any attacker in control of a compromised system cannot
107 predict whether the passphrase entered is correct or not. An attacker can always intercept the in- and output of
108 signduterre. When running a --check-file, this means the program can be made to print out OK irrespective of the
109 tests. A safe use of signduterre.py is to start with a random number of incorrect passphrases and see whether they fail.
111 THE CORRECT USE OF signduterre.py IS TO ENTER A RANDOM NUMBER OF INCORRECT PASSPHRASES FOR EACH TEST
112 AND SEE WHETHER IT FAILS EVERY TIME!
114 On a compromised system, signduterre.py's detailed file testing (--detailed-view) is easily subverted. With a
115 matched file hash, the attacker will know that the correct passphrase has been entered and can print out the
116 stored hashes or 'ok's for the rest of the checks. So if the attacker keeps any entry in the signature file
117 uncompromised, she can intercept the output, test the password on the unchanged entry and substitute the
118 requested hashes for the output if the hash of that entry matches.
120 When checking for root-kits and other malware, it is safest to compare the signature files from a different,
121 clean, system. But then you would not need signduterre.py anyway.
122 If you have to work on the system itself, only use the -t or --total-only options to create
123 signatures with a total hash and without individual file hashes. Such a signature can be used to check
124 whether the system is unchanged. Another signature file WITH A DIFFERENT PASSPHRASE can then be used to
125 identify the individual files that have changed. If a detailed signature file has the same passphrase,
126 an attacker could use that other file to read the individual file hashes to check whether the correct
127 passphrase was entered.
129 Using the --check-file option in itself is perfectly UNsafe. An attacker simply has to print out 'OK' and
130 to defeat the check. This attack can be foiled by making it unpredictable when signduterre.py should return
131 'OK'. This can be done by using a list of salts or passphrases where only one of them (or none!) is correct.
132 Any attacker will have to guess when to return 'OK'.
134 As generating and entering wrong passphrases and salts is tedious, it is to be expected that users will
135 take shortcuts. To assist users, the '--salt SUGGESTED=<N>' option will generate a number N of salts. When
136 checking, each of these salts is tried in turn. An attacker that is unable to simulate the uncompromised
137 system will have to guess which one of the salts is the correct one, and whether or not the passphrase
138 is correct. This increases the chances of detecting compromised systems.
140 The '--passphrase SUGGESTED=N' option will generate and print N passphrases. One of these is chosen at
141 random for the signature. The number of the chosen passphrase is printed on STDERR with the passwords.
142 When checking a file, the stored passphrases can be read in again, either by entering the passphrase
143 file after the --passphrase option ('--passphrase <passphrase file>'), or directly from the --check-file.
144 signduterre.py will print out the result for each of the passphrases.
146 Note, that storing passphrases in a file and feeding it to signduterre.py is MUCH less secure than just
147 typing them in. Moreover, it might completely defeat the purpose of signduterre.py. If future experiences
148 cast any more doubt on the security ofthis option, it will be removed.
150 For those who want to know more about what an "ideal attacker" can do, see:
151 Ken Thompson "Reflections on Trusting Trust"
152 http://www.acm.org/classics/sep95/
154 David A Wheeler "Countering Trusting Trust through Diverse Double-Compiling"
155 http://www.acsa-admin.org/2005/abstracts/47.html
157 and the discussion of these at Bruce Schneier's 'Countering "Trusting Trust"'
158 http://www.schneier.com/blog/archives/2006/01/countering_trus.html
162 The intent of signduterre.py is to ensure that the signature cannot be subverted even if the system has been compromised
163 by an attacker that has obtained root control over the computer and any existing signature files.
165 signduterre.py asks for a passphrase which is PRE-pended to every file before the hash is constructed (unless the
166 passphrase is entered with an option). As long as the passphrase is not compromised, the hashes cannot
167 be reconstructed. A randomly generated, unpadded base-64 encoded 16 Byte password (ie, ~22 characters) is suggested in
168 interactive use. If '--passphrase SUGGESTED' is entered on the command line as the salt, the suggested
169 value will be used. This value is printed to STDERR (the screen or 2) for safe keeping. Please, make sure
170 you store the printed passphrase. For instance:
171 python signduterre.py -p SUGGESTED -s SUGGESTED /boot/* /sbin/* /bin/* \\
172 2> Signature_`date "+%Y%m%d_%H-%M-%S"`.pwd > Signature_`date "+%Y%m%d_%H-%M-%S"`.txt
173 will store the passphrase (and all error messages) in a file like 'Signature_20090630_11-14-03.pwd'
174 and the checik-file in 'Signature_20090630_11-14-03.txt'.
176 It is not secure to store files with the passphrase on the system you want to check. However, you could
177 pipe STDERR to some safe site.
179 Good passphrases are difficult to remember, so their plaintext form should be protected. To protect the
180 passphrase against rainbow and brute force attacks, the passphrase is concatenated to a salt phrase and
181 hashed before use (SHA-256).
183 The salt phrase is requested when constructing a signature. In interactive use, an 8 byte hexadecimal
184 (= 16 character) salt from /dev/urandom is suggested. If '--salt SUGGESTED' is entered on the command line
185 as the salt, the suggested value will be used. The salt is printed in plaintext to the output.
186 The salt will make it more difficult to determine whether the same passphrase has been used to create
187 different signatures.
189 At the bottom, a 'TOTAL HASH' line will be printed that hashes all the lines printed for the files. This includes
190 the file names as printed on the hash lines. It is not inconceivable that existing signature files could have been
191 compromised in ways that might be missed when checking the signature. The total hash will point out such changes.
194 > python signduterre.py --execute --detail --salt 436a73e3 --passphrase liauwefa3251EWC signduterre.py /sbin/* /bin/* \\
195 /usr/bin/find /usr/bin/file /usr/bin/python* '${PATH}' > Signature_`date "+%Y%m%d_%H-%M-%S"`.txt
197 Prints a signature to the file Signature_20090625_14-31-54.txt (with your date). The signature contains the
198 SHA-256 hashes of the files, signduterre.py, /sbin/*, /bin/*, /usr/bin/find, /usr/bin/file, /usr/bin/python*,
199 and a hash of the PATH environment variable.
201 > python signduterre.py --execute --detail --salt SUGGESTED --passphrase SUGGESTED --Status --detailed-view \\
202 signduterre.py /sbin/* /bin/* /usr/bin/find /usr/bin/file /usr/bin/python* '${PATH}' \\
203 2> Signature_`date "+%Y%m%d_%H-%M-%S"`.pwd > Signature_`date "+%Y%m%d_%H-%M-%S"`.txt
205 Prints a signature to the system Signature_20090625_14-31-54.txt (with your date) and the automatically generated
206 password to Signature_20090625_14-31-54.pwd (with your date). The salt will be automatically determined.
207 The signature contains the SHA-256 hashes of the file status and file contents of signduterre.py, /sbin/*,
208 /bin/*, /usr/bin/find, /usr/bin/file, /usr/bin/python* on separate lines, and a hash of the PATH environment variable.
210 > python signduterre.py --execute --passphrase liauwefa3251EWC -c Signature_20090625_14-31-54.txt
212 Will check the files and PATH variable from the signature file Signature_20090625_14-31-54.txt.
214 > python signduterre.py --passphrase liauwefa3251EWC -c Signature_20090625_14-31-54.txt
215 > python signduterre.py --passphrase liauwefa3251EWC -c Signature_20090625_14-31-54.txt --no-execute
217 Will both fail if Signature_20090625_14-31-54.txt contains a $(cmd) entry. The --no-execute
218 option is default and prevents the execute option (if reading the execute optionfrom the signature file
221 > python signduterre.py signduterre.py --salt SUGGESTED -passphrase SUGGESTED=20 signduterre.py \\
222 &> Signature_`date "+%Y%m%d_%H-%M-%S"`.txt
224 Will generate and print 20 passphrases and print a signature using one randomly chosen passphrase
225 from these 20. Everything is written to a single file 'Signature_20090630_16-44-34.txt'.
226 On some systems, this means that the passphrases are written in between the hashes messing up the
227 file names. This will obviously make the output file useless.
229 > python signduterre.py signduterre.py -c Signature_20090630_16-44-34.txt
231 Will check all 20 passphrases generated before from the Signature file and print the results.
233 > sudo python signduterre.py -u root -s SUGGESTED -p SUGGESTED -v -e -t \\
234 '$(ls -LCid /proc/$0/root /proc/$0/exe|sed "s|/$0/|/PID/|g")' '$(dd if=/dev/hda bs=512 count=1 | od -X)' \\
235 >Signature_`date "+%Y%m%d_%H-%M-%S"`.txt
237 Will hash the inode numbers of the effective root directory (eg, chroot) and the executable (python)
238 together with the contents of the MBR (Master Boot Record) in Hex. It uses suggested salt and passphrase.
239 Accessing /dev/hda is only possible when root, so the command is entered with sudo and --user root.
245 Construct a signature of the installed software state or check a previously made signature.
247 copyright 2009, R.J.J.H. van Son
249 This program is free software: you can redistribute it and/or modify
250 it under the terms of the GNU General Public License as published by
251 the Free Software Foundation, either version 3 of the License, or
252 (at your option) any later version.
254 This program is distributed in the hope that it will be useful,
255 but WITHOUT ANY WARRANTY; without even the implied warranty of
256 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
257 GNU General Public License for more details.
259 You should have received a copy of the GNU General Public License
260 along with this program. If not, see <http://www.gnu.org/licenses/>.
263 # Note that only release notes are put here
264 # See git repository for detailed change comments:
265 # git clone git://repo.or.cz/signduterre.git
267 20090713 - Added --quiet option
268 20090712 - moved from /dev/random to /dev/urandom
269 20090702 - Replaced -g with -p SUGGESTED[=N]
270 20090702 - Generating and testing lists of random salts
271 20090701 - Release v0.2
272 20090630 - Generating and testing random passphrases
273 20090630 - --execute works on $(cmd) only, nlinks in ?path and ? implied for directories
274 20090630 - Ported to Python 3.0
276 20090628 - Release v0.1b
277 20090628 - Added release-notes
279 20090626 - Release v0.1a
280 20090626 - Initial commit to Git
287 # if sys.stdout.isatty(): import readline;
292 from optparse
import OptionParser
;
297 # Limit the characters that can be used in $(cmd) commands
298 not_allowed_chars
= re
.compile('[^\w\ \.\/\"\|\;\,\-\$\[\]\{\}\(\)\@\`\!\*]');
300 programname
= "Signature-du-Terroir";
303 print("# Program: "+programname
+ " version " + version
+ "\n");
305 parser
= OptionParser()
306 parser
.add_option("-s", "--salt", metavar
="HEX",
307 dest
="salt", default
=False,
308 help="Enter salt in cleartext. If not given, a hexadecimal salt will be suggested. The SUGGESTED[=N] keyword will cause the selection of the suggested string. N is the number of salts generated (default N=1)")
309 parser
.add_option("-p", "--passphrase", metavar
="TEXT",
310 dest
="passphrase", default
=False,
311 help="Enter passphrase in cleartext, the keyword SUGGESTED will cause the suggested passphrase to be used. Entering the name of an existing file will cause it to be read and a random passphrase found in the file will be used (creating a signature), or they will all be used in sequence (--check-file).")
312 parser
.add_option("-c", "--check-file",
313 dest
="check", default
=False, metavar
="FILE",
314 help="Check contents of file (output of previous run)")
315 parser
.add_option("-i", "--input-file",
316 dest
="input", default
=False, metavar
="FILE",
317 help="Use names from input-file (one filename per line)")
318 parser
.add_option("-u", "--user",
319 dest
="user", default
="nobody", metavar
="USER",
320 help="Execute $(cmd) as USER, default 'nobody' (root/sudo only)")
321 parser
.add_option("-S", "--Status",
322 dest
="status", default
=False, action
="store_true",
323 help="For each file, add a line with unvarying file status information: st_mode, st_ino, st_dev, st_uid, st_gid, and st_size (like the '?' prefix, default False)")
324 parser
.add_option("-t", "--total-only",
325 dest
="total", default
=False, action
="store_true",
326 help="Only print the total hash, must be checked BEFORE running --detail (default True)")
327 parser
.add_option("-d", "--detailed-view",
328 dest
="detail", default
=False, action
="store_true",
329 help="Print hashes of individual files, must be checked AFTER running --total-only check (default False)")
330 parser
.add_option("-e", "--execute",
331 dest
="execute", default
=False, action
="store_true",
332 help="Interpret $(cmd) (default False)")
333 parser
.add_option("-n", "--no-execute",
334 dest
="noexecute", default
=False, action
="store_true",
335 help="Explicitely do NOT Interpret ${ENV} and $(cmd)")
336 parser
.add_option("-m", "--manual",
337 dest
="manual", default
=False, action
="store_true",
338 help="Print the manual and exit")
339 parser
.add_option("-r", "--release-notes",
340 dest
="releasenotes", default
=False, action
="store_true",
341 help="Print the release notes and exit")
342 parser
.add_option("-l", "--license",
343 dest
="license", default
=False, action
="store_true",
344 help="Print license text and exit")
345 parser
.add_option("-v", "--verbose",
346 dest
="verbose", default
=False, action
="store_true",
347 help="Print more information")
348 parser
.add_option("-q", "--quiet",
349 dest
="quiet", default
=False, action
="store_true",
350 help="Print minimal information (hide filenames)")
352 (options
, check_filenames
) = parser
.parse_args();
355 print (license
, file=sys
.stderr
);
359 print (manual
, file=sys
.stderr
);
362 if options
.releasenotes
:
363 print ("Version: "+version
, file=sys
.stderr
);
364 print (releasenotes
, file=sys
.stderr
);
367 my_salt
= options
.salt
;
368 my_passphrase
= options
.passphrase
;
369 my_check
= options
.check
;
370 my_status
= options
.status
;
371 my_verbose
= options
.verbose
and not options
.quiet
;
372 my_quiet
= options
.quiet
;
373 execute
= options
.execute
;
374 noexecute
= options
.noexecute
;
375 input_file
= options
.input;
377 # Set total-only with the correct default
379 total_only
= not options
.detail
;
380 if options
.total
: total_only
= options
.total
;
381 if my_check
: total_only
= False;
383 my_user
= options
.user
;
384 # Things might be executed as another user
387 user_change
= 'sudo -H -u '+my_user
+' ';
388 print("User: "+my_user
);
392 text_execute
= "True";
394 text_execute
= "False";
396 if execute
: print("Execute system commands: "+text_execute
+"\n");
399 if my_quiet
: print("Quiet: True\n");
401 # Measure time intervals
402 start_time
= time
.time();
404 dev_random
= open("/dev/urandom", 'rb');
408 with
open(input_file
, 'r') as i
:
411 current_filename
= re
.sub('[^\w\-\.\/\$\{\(\)\}]', '', line
);
412 check_filenames
.append(current_filename
);
415 for x
in check_filenames
:
418 if my_status
and not x
.startswith(('?', '$')):
419 stat_list
.append('?'+x
);
421 check_filenames
= stat_list
;
423 # Read the check file
424 passphrase_list
= [];
429 print("# Checking: "+my_check
+"\n");
430 arg_list
= check_filenames
;
431 check_filenames
= [];
432 with
open(my_check
, 'r') as c
:
434 match
= re
.search("Execute system commands:\s+(True|False)", line
);
436 # Uncomment the next line if you want automatic --execute from the check-file (DANGEROUS)
437 # execute = match.group(1).upper() == 'TRUE';
440 match
= re
.search("Quiet:\s+(True|False)", line
);
442 my_quiet
= match
.group(1).upper() == 'TRUE';
443 if my_quiet
: my_verbose
= False;
446 match
= re
.search("Salt\:\s+\'([\w]*)\'", line
);
448 salt_list
.append(match
.group(1));
451 match
= re
.search("User\:\s+\'([\w]*)\'", line
);
453 # Uncomment the next line if you want automatic --user from the check-file (DANGEROUS)
454 # my_user = match.group(1);
457 match
= re
.search("Passphrase\:\s+\'([^\']*)\'", line
);
459 passphrase_list
.append(match
.group(1));
462 match
= re
.search("^\s*([a-f0-9]+)\s+\*(TOTAL HASH)\s*$", line
)
464 total_hash
= match
.group(1);
467 match
= re
.search("^\s*([a-f0-9\-]+)\s+\*\[([0-9]+)\]\s*$", line
)
469 filenumber
= int(match
.group(2));
470 # Watch out, arguments count from 0
471 check_filenames
.append(arg_list
[filenumber
- 1]);
472 check_hashes
['['+match
.group(2)+']'] = match
.group(1);
475 match
= re
.search("^\s*([a-f0-9\-]+)\s+\*(.*)\s*$", line
)
477 check_filenames
.append(match
.group(2));
478 check_hashes
[match
.group(2)] = match
.group(1);
481 # Seed Pseudo Random Number Generator
482 seed
= dev_random
.read(16);
485 # Read suggested salts from /dev/(u)random if needed
487 if my_salt
.startswith('SUGGESTED'):
489 match
= re
.search("([0-9][0-9]*)$", my_salt
);
491 N
= int(match
.group(1));
493 salt
= dev_random
.read(8);
494 salt_list
.append(str(binascii
.hexlify(salt
), 'ascii'));
496 salt_list
.append(my_salt
);
497 elif len(salt_list
) == 0:
498 salt
= dev_random
.read(8);
499 sys
.stderr
.write("Enter salt (suggest \'"+str(binascii
.hexlify(salt
), 'ascii')+"\'): ");
501 if not new_salt
: new_salt
= str(binascii
.hexlify(salt
), 'ascii');
502 salt_list
.append(new_salt
);
504 for my_salt
in salt_list
:
505 print("Salt: \'"+my_salt
+"\'");
508 if my_passphrase
and os
.path
.isfile(my_passphrase
):
509 with
open(my_passphrase
, 'r') as file:
511 match
= re
.search("Passphrase\:\s+\'([^\']*)\'", line
);
513 passphrase_list
.append(match
.group(1));
514 elif not my_passphrase
and len(passphrase_list
) == 0:
515 suggest_passphrase
= dev_random
.read(16);
516 sys
.stderr
.write("Enter passphrase (suggest \'"+str(base64
.b64encode(suggest_passphrase
), 'ascii').rstrip('=')+"\'): ");
517 # How kan we make this unreadable on input?
518 current_passphrase
= input();
519 if not current_passphrase
:
520 current_passphrase
= str(base64
.b64encode(suggest_passphrase
), 'ascii').rstrip('=');
521 print("Passphrase: \'"+current_passphrase
+"\'", file=sys
.stderr
);
522 passphrase_list
.append(current_passphrase
);
523 elif my_passphrase
.startswith('SUGGESTED'):
525 match
= re
.search("([0-9][0-9]*)$", my_passphrase
);
527 N
= int(match
.group(1));
528 j
= int(random
.random()*N
);
529 for i
in range(0, N
):
530 suggest_passphrase
= dev_random
.read(16);
531 current_passphrase
= str(base64
.b64encode(suggest_passphrase
), 'ascii').rstrip('=');
532 print("Passphrase: \'"+current_passphrase
+"\'", file=sys
.stderr
);
533 passphrase_list
.append(current_passphrase
);
535 passphrase_list
.append(my_passphrase
);
538 j
= int(random
.random()*len(passphrase_list
));
539 passphrase_list
= [passphrase_list
[j
]];
540 print("# Selected passphrase:", j
+1, file=sys
.stderr
);
541 j
= int(random
.random()*len(salt_list
));
542 salt_list
= [salt_list
[j
]];
543 print("# Selected salt:", j
+1, file=sys
.stderr
);
545 # Close /dev/(u)random
548 end_time
= time
.time();
549 print("# Preparation time:", end_time
- start_time
, "seconds\n\n# Start signature");
555 for my_passphrase
in passphrase_list
:
557 for my_salt
in salt_list
:
559 start_time
= time
.time();
560 # Construct the passphrase hash
561 passphrase
= hashlib
.sha256();
563 passphrase
.update(bytes(my_salt
, encoding
='ascii'));
564 passphrase
.update(bytes(my_passphrase
, encoding
='ascii'));
566 # Create prefix which is a hash of the salt+passphrase
567 prefix
= passphrase
.hexdigest();
569 # Create signature and write output
570 if noexecute
: execute
= False; # Doubly make sure that NOTHING is executed if required
571 totalhash
= hashlib
.sha256();
572 totalhash
.update(bytes(prefix
, encoding
='ascii'));
573 for org_filename
in check_filenames
:
574 # Create file hash object
575 filehash
= hashlib
.sha256();
576 filehash
.update(bytes(prefix
, encoding
='ascii'));
578 filename
= org_filename
.strip('[');
579 filename
= filename
.rstrip(']');
580 print(filename
, file=sys
.stderr
);
581 # Use system variables and commands
582 if filename
.startswith('$'):
583 # Commands $(command)
584 match
= re
.search('^\$([\(\{]?)([^\)\}]+)[\)\}]?$', filename
);
586 if match
.group(1) == '(':
588 error_message
= "Executable argument \'"+filename
+"\' only allowed with the --execute flag";
589 print (error_message
, file=sys
.stderr
);
590 if not sys
.stdout
.isatty(): print(error_message
);
592 current_command
= not_allowed_chars
.sub(" ", match
.group(2));
593 current_command_line
= user_change
+"bash --restricted -c \'"+current_command
+"\' "+str(os
.getpid());
595 print ("#", current_command_line
);
596 (status
, b
) = subprocess
.getstatusoutput(current_command_line
);
598 print ('$('+current_command
+')'+"\n"+b
, file=sys
.stderr
);
601 current_var
= not_allowed_chars
.sub(" ", match
.group(2));
603 print ("# echo $"+ current_var
);
604 b
= os
.environ
[current_var
];
605 filehash
.update(bytes(bytes(b
, encoding
='utf8')));
606 # lstat() meta information
607 elif filename
.startswith('?'):
608 filestat
= os
.lstat(filename
.lstrip('?'));
609 b
= 'lstat('+filename
.lstrip('?')+') = [st_mode='+str(oct(filestat
.st_mode
))+', st_ino='+str(filestat
.st_ino
)+', st_dev='+str(filestat
.st_dev
)+', st_nlink='+str(filestat
.st_nlink
)+', st_uid='+str(filestat
.st_uid
)+', st_gid='+str(filestat
.st_gid
)+', st_size='+str(filestat
.st_size
)+']';
610 filehash
.update(bytes(b
, encoding
='utf8'));
615 # open and read the file
616 with
open(filename
, 'rb') as file:
620 current_digest
= filehash
.hexdigest();
621 print_name
= filename
;
622 if my_quiet
or org_filename
.startswith('['):
624 print_name
= '['+str(file_argnum
)+']';
625 current_hash_line
= current_digest
+" *"+print_name
626 totalhash
.update(bytes(current_hash_line
, encoding
='ascii'));
628 # Be careful to use this ONLY after totalhash has been updated!
629 if total_only
: current_hash_line
= (len(current_digest
)*'-')+" *"+print_name
;
633 print(current_hash_line
);
634 elif check_hashes
[print_name
] == (len(current_digest
)*'-'):
635 print(check_hashes
[print_name
]+" *"+print_name
);
636 elif current_digest
!= check_hashes
[filename
]:
637 print("DIFFERENT: "+current_hash_line
);
639 print("ok"+" *"+filename
);
642 current_total_digest
= totalhash
.hexdigest();
643 current_total_digest_line
= current_total_digest
+" *"+"TOTAL HASH";
644 end_time
= time
.time();
645 print("# \n# Total hash - Time to completion:", end_time
- start_time
, "seconds");
647 print(current_total_digest_line
+"\n");
648 elif current_total_digest
!= total_hash
:
649 print("DIFFERENT: "+current_total_digest_line
+"\n");
652 if len(passphrase_list
) > 1 or len(salt_list
): match_number
= " #"
653 if len(passphrase_list
) > 1: match_number
+= " passphrase no: "+str(pnum
);
654 if len(salt_list
) > 1: match_number
+= " salt no: "+str(snum
);
655 print("OK"+" *"+"TOTAL HASH"+match_number
+"\n");
661 if len(passphrase_list
) > 1:
663 print("Passphrase entry:",corrpnum
,"matched");
665 print("No passphrase entry matched!");
666 if len(salt_list
) > 1:
669 print("Salt entry:",corrsnum
,"matched");
671 print("No salt entry matched!");
673 print("No entry matched");