Commit | Line | Data |
---|---|---|
fdf917e1 SBS |
1 | #!/bin/bash |
2 | # | |
3 | # Date: 2020-02-18T20:06Z | |
4 | # | |
5 | # Author: Steven Baltakatei Sandoval (baltakatei.com) | |
6 | # | |
7 | # License: This bash script, 'bkpoe', is licensed under GPLv3 or | |
8 | # later by Steven Baltakatei Sandoval: | |
9 | # | |
10 | # 'bkpoe', a file system proof of existence generator | |
11 | # Copyright (C) 2020 Steven Baltakatei Sandoval (baltakatei.com) | |
12 | # | |
13 | # This program is free software: you can redistribute it and/or modify | |
14 | # it under the terms of the GNU General Public License as published by | |
15 | # the Free Software Foundation, either version 3 of the License, or | |
16 | # any later version. | |
17 | # | |
18 | # This program is distributed in the hope that it will be useful, | |
19 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
21 | # GNU General Public License for more details. | |
22 | # | |
23 | # A copy of the GNU General Public License may be found at | |
24 | # <https://www.gnu.org/licenses/>. | |
25 | # | |
26 | # Description: This is a simple script that uses GNU Coreutils | |
27 | # OpenTimestamps to generate a proof that permits a user to prove the | |
28 | # existence of any individual file within a directory. A proof | |
29 | # consists of: | |
30 | # | |
31 | # - An OpenTimestamps 'ots' file (if '-t' option provided) | |
32 | # | |
33 | # - Public proof: text file(s) containing a list of salted file | |
34 | # hashes. | |
35 | # | |
36 | # - Private proof: text file(s) containing: | |
37 | # | |
38 | # - A list of unsalted file hashes | |
39 | # | |
40 | # - A list of nonces used to generate the list of salted file hashes. | |
41 | # | |
42 | # - The list of salted file hashes | |
43 | # | |
44 | # - File attributes (size, relative path) | |
45 | # | |
46 | # The proof is produced by performing the following steps: | |
47 | # | |
48 | # - Use 'find' and GNU Coreutils digest algorithms to create | |
49 | # numbered lists of file digests at specified directories. | |
50 | # | |
51 | # - Save these numbered lists of digests into text files within a | |
52 | # proof directory. | |
53 | # | |
54 | # - If more than one specified directory is provided also process | |
55 | # the files within the proof directory. | |
56 | # | |
57 | # - Use OpenTimestamps to create an 'ots' timestamp file | |
58 | # for the final digest list file. (if '-t' option provided). | |
59 | # | |
60 | # - Use OpenTimestamps to upgrade each 'ots' timestamp file after a | |
61 | # delay. (if '-w' option provided). | |
62 | # | |
63 | # Dependencies: coreutils, ots. See end of file | |
64 | # | |
65 | # Tested on: | |
66 | # | |
67 | # - GNU/Linux Debian 10 | |
68 | ||
69 | ||
70 | # === VARIABLE INITIALIZATION AND FUNCTION DEFINITIONS === | |
71 | ||
72 | PATH="/usr/local/bin/:$PATH" # Add default OpenTimestamps path to PATH (necessary if cron used to call this script) | |
73 | scriptHostname=$(hostname) # Save hostname of system running this script. | |
74 | OTS_TIMEOUT="6h" # maximum time to run `ots --wait` (used via `timeout` command) | |
75 | ||
76 | # Declare array for storing directories whose contents will be hashed. | |
77 | declare -a directoriesToProcess | |
78 | ||
79 | # Declare arrays for storing file paths, file index, file hashes, file | |
80 | # hash nonces, the salted hashes, file sizes, file paths, file | |
81 | # modification times, and proof file paths. | |
82 | declare -a filePathsFull # array | |
83 | declare -a filePathsIndex # array | |
84 | declare -a fileHashes # array | |
85 | declare -a fileHashNonces # array | |
86 | declare -a fileHashesSalted # array | |
87 | declare -a fileSizes # array | |
88 | declare -a fileModTime # array | |
89 | declare -g PROOF_DIRECTORY # global | |
90 | declare -ag prvProofPaths # array, global | |
91 | declare -ag pubProofPaths # array, global | |
92 | ||
93 | # Define 'echoerr' function which outputs text to stderr | |
94 | # Note: function copied from https://stackoverflow.com/a/2990533 | |
95 | echoerr() { | |
96 | echo "$@" 1>&2; | |
97 | } | |
98 | ||
99 | # Define script version. | |
100 | SCRIPT_VERSION="bkpoe 0.1.0" | |
101 | ||
102 | # Save current date. | |
103 | runDate=$(date +%Y%m%dT%H%M%S%z) | |
104 | sleep 1 # Space out program run times by at least one second. | |
105 | ###echoerr "DEBUG: Current date is:"$runDate | |
106 | ||
107 | # Save working directory | |
108 | runPath=$(pwd) | |
109 | ###echoerr "DEBUG: Current working directory is:"$runPath | |
110 | ||
111 | # Define proof directory basename | |
112 | PROOF_DIRECTORY_BASE="$runDate""..""$scriptHostname""_bkpoe_proofs" | |
113 | ||
114 | # Define default proof directory | |
115 | PROOF_DIRECTORY="$runPath"/"$PROOF_DIRECTORY_BASE" | |
116 | ||
117 | # Print information on how to use this bash script. | |
118 | usage() { | |
119 | ||
120 | echo "USAGE:" | |
121 | echo " bkpoe [ options ] DIRECTORIES" | |
122 | echo | |
123 | echo "OPTIONS:" | |
124 | echo " -h, --help" | |
125 | echo " Display help information." | |
126 | echo | |
127 | echo " -r, --recursive" | |
128 | echo " Perform recursive hashing of directories." | |
129 | echo | |
130 | echo " -v, --verbose" | |
131 | echo " Display debugging info." | |
132 | echo | |
133 | echo " -a, --algorithm [ algo ]" | |
134 | echo " Specify GNU Coreutils hash command. Options include: " | |
135 | echo " b2sum md5sum sha1sum sha224sum sha256sum sha384sum " | |
136 | echo " sha512sum" | |
137 | echo | |
138 | echo " -m, --message [ string ]" | |
139 | echo " Specify message to be included in filehash files." | |
140 | echo | |
141 | echo " -d, --delimiter [ string ]" | |
142 | echo " Specify character to serve as delimiter in proof files. " | |
143 | echo " (Default: ,) " | |
144 | echo " -t, --timestamp" | |
145 | echo " Create openTimestmaps proof file." | |
146 | echo " -w, --wait" | |
147 | echo " Pass '--wait' option to OpenTimestamps to 'wait " | |
148 | echo " until a complete timestamp committed in the " | |
149 | echo " Bitcoin blockchain is available instead of " | |
150 | echo " returning immediately. " | |
151 | echo " -o, --output" | |
152 | echo " Specify output directory to save proof. Default is working" | |
153 | echo " directory where script was run." | |
154 | } | |
155 | ||
156 | ||
157 | # Check that a string matches one of GNU Coreutils hash commands. | |
158 | isValidDigestAlgo() { | |
159 | # Syntax: isValidDigestAlgo [string] | |
160 | # Description: Returns 1 if [string] is a valid GNU Coreutils hash function command. Returns 0 otherwise. | |
161 | ||
162 | # Exit if more than one argument provided. | |
163 | ###echoerr "DEBUG: Arguments provided to isValidDigestAlgo() are:$@" | |
164 | if [[ $# -ne 1 ]]; then | |
165 | echoerr "ERROR: Illegal number of digest arguments provided." | |
166 | exit 1 | |
167 | fi | |
168 | ||
169 | # Exit if argument contains non-alphanumeric characters | |
170 | if [[ "$@" =~ [^a-zA-Z0-9$] ]]; then | |
171 | echoerr "ERROR: Illegal characters for digest argument provided." | |
172 | exit 1 | |
173 | fi | |
174 | ||
175 | local input=$1 #Save first argument provided to function as input for further processing. | |
176 | ||
177 | ###echoerr "DEBUG: Starting function to check validity of string as valid digest command." | |
178 | ||
179 | # Define array of valid hash algorithm commands present in by GNU coreutils package. List is current as of Debian Buster. List taken from https://packages.debian.org/buster/amd64/coreutils/filelist ). | |
180 | local gnuCoreutilsAlgos=(b2sum md5sum sha1sum sha224sum sha256sum sha384sum sha512sum) | |
181 | ###echoerr "DEBUG:Contents of \$gnuCoreutilsAlgos array is:${gnuCoreutilsAlgos[@]}" | |
182 | ||
183 | # Make input string lowercase | |
184 | #input=${input,,} # Make all letters in $1 lowercase (see https://stackoverflow.com/questions/2264428/how-to-convert-a-string-to-lower-case-in-bash ) and save as $input | |
185 | ###echoerr "DEBUG: User-specified hash algorithm is:$input" | |
186 | ||
187 | # Return true (0) if $gnuCoreutilsAlgos array contains string $input (see https://unix.stackexchange.com/a/177139 ) | |
188 | for item in "${gnuCoreutilsAlgos[@]}"; do | |
189 | ###echoerr "DEBUG: \$item is:$item" | |
190 | ###echoerr "DEBUG: \$input is:$input" | |
191 | if [ "$input" == "$item" ]; then | |
192 | ###echoerr "DEBUG: $input present in \$gnuCoreutilsAlgos array." | |
193 | return 0 | |
194 | fi | |
195 | done | |
196 | ||
197 | # Return false (1) otherwise. | |
198 | echoerr "DEBUG: $input not recognized as valid hash algorithm name." | |
199 | return 1 | |
200 | } | |
201 | ||
202 | writeProofs() { | |
203 | # Syntax: writeProofs [ loopIndex ] [ dirPath ] | |
204 | ||
205 | # Description: Construct private and public proofs for files | |
206 | # contained within [ dirPath ]. Save file with date, [ loopIndex ], | |
207 | # and base directory name within filename. | |
208 | ||
209 | # Required variables: | |
210 | # - $runDate: used in creating proof directory | |
211 | # - $PROOF_DIRECTORY: used as directory where proof files are saved | |
212 | # - $OPTION_ALGORITHM: used to decide which hash algorithm to use | |
213 | # - $OPTION_DELIMITER: used to determine which char to use as a delimiter in proof files | |
214 | # - $OPTION_MESSAGE: used to determine message included in proof files | |
215 | # - $OPTION_RECURSIVE: used to determine if files within subdirectories under [ dir Path ] directory should be included | |
216 | ||
217 | [[ $OPTION_VERBOSE == "true" ]] && echoerr "DEBUG:This is the writeProofs() function. Provided arguments are:$@" | |
218 | ||
219 | # Determine scope of 'find' file search (recursive search or not) | |
220 | if [[ $OPTION_RECURSIVE == "true" ]]; then | |
221 | [[ $OPTION_VERBOSE == "true" ]] && echoerr "DEBUG: Performing recursive file search on directory $directoryToHash" | |
222 | findMaxDepthOption="" # Do not specify -maxdepth option for find (causes 'find' to include all files recursively within [ dirPath ] | |
223 | else | |
224 | findMaxDepthOption="-maxdepth 1" # Specify "-maxdepth 1 " as option for 'find' so only files immediately within [ dirPath ] directory are included (no subdirectories). | |
225 | fi | |
226 | ||
227 | # Save current date of loop | |
228 | proofLoopRunDate=$(date +%Y%m%dT%H%M%S%z) | |
229 | ###echoerr "DEBUG: Date and time of current loop is:"$proofLoopRunDate | |
230 | ||
231 | # Reset arrays. | |
232 | filePathsFull=() | |
233 | filePathsIndex=() | |
234 | fileHashes=() | |
235 | fileHashNonces=() | |
236 | fileHashesSalted=() | |
237 | fileSizes=() | |
238 | fileModTime=() | |
239 | ||
240 | # Pass first argument provided to function as directory index. | |
241 | local loopIndex=$1 | |
242 | ||
243 | # Pass second argument as directory to process | |
244 | if [[ -d $2 ]]; then | |
245 | [[ $OPTION_VERBOSE == "true" ]] && echoerr "DEBUG: Valid directory argument provided to writeProofs() function." | |
246 | local directoryToHash=$2 | |
247 | ## local directoryToHash=$(readlink -f $2) # Use absolutely full path (not used for privacy). | |
248 | else | |
249 | echoerr "ERROR: Invalid directory argument provided to writeProofs() function." | |
250 | exit 1 | |
251 | fi | |
252 | [[ $OPTION_VERBOSE == "true" ]] && echoerr "DEBUG:Processing directory:"$directoryToHash | |
253 | local directoryToHashShort="$(basename "$directoryToHash")" | |
254 | [[ $OPTION_VERBOSE == "true" ]] && echoerr "DEBUG:Directory basename:"$directoryToHashShort | |
255 | ||
256 | # Populate filePathsFull array with file paths from 'find' results (see https://stackoverflow.com/a/54561526 ). | |
257 | # Note: For creating array from output of find via readarray, see https://unix.stackexchange.com/a/263885 | |
258 | # Note: For sorting output of find, see https://unix.stackexchange.com/a/34328 | |
259 | readarray -d '' filePathsFull < <(find $directoryToHash $findMaxDepthOption -readable -type f -print0 | sort -z) # Populate filePathsFull array with sorted list of files present in $directoryToHash (and subdirectories if $OPTION_RECURSIVE set to 'true'. | |
260 | [[ $OPTION_VERBOSE == "true" ]] && echoerr "DEBUG: The ${#filePathsFull[@]} elements of filePathsFull array are:${filePathsFull[@]}" # Show array length (see https://www.cyberciti.biz/faq/finding-bash-shell-array-length-elements/ ) | |
261 | ||
262 | # Construct index array from filePathsFull array for the for loops used to write proofs. | |
263 | filePathsIndex=(${!filePathsFull[@]}) | |
264 | [[ $OPTION_VERBOSE == "true" ]] && echoerr "DEBUG: The ${#filePathsIndex[@]} elements of filePathsIndex array are:${filePathsIndex[@]}" | |
265 | ||
266 | # Populate fileHashes with hashes of files listed in filePathsFull array | |
267 | # Iterate through all elements of filePathsFull array using an integer index. (see https://stackoverflow.com/a/6723516 ) | |
268 | for i in "${!filePathsIndex[@]}"; do | |
269 | ###[[ $OPTION_VERBOSE == "true" ]] && echoerr "DEBUG:The $i th element of the filePathsFull array is:${filePathsFull[i]}" | |
270 | fileHashes[i]=$($OPTION_ALGORITHM "${filePathsFull[i]}" | awk '{ print $1 }') # Get hash value from GNU Coreutils hash command (see https://stackoverflow.com/a/3679803 ) | |
271 | done | |
272 | [[ $OPTION_VERBOSE == "true" ]] && echoerr "DEBUG: The ${#fileHashes[@]} elements of fileHashes array are:${fileHashes[@]}" | |
273 | ||
274 | # Populate fileHashNonces array with same number of elements as filePathsFull array. Each element is a random hexadecimal nonce of length matching OPTION_ALGORITHM. Hex nonce generated from 64-byte (512-bits) block from /dev/urandom. | |
275 | for i in "${!filePathsIndex[@]}"; do | |
276 | fileHashNonces[i]=$(dd bs=64 count=1 if=/dev/urandom 2>/dev/null | $OPTION_ALGORITHM | awk '{ print $1 }') | |
277 | done | |
278 | [[ $OPTION_VERBOSE == "true" ]] && echoerr "DEBUG: The ${#fileHashNonces[@]} elements of fileHashNonces array are:${fileHashNonces[@]}" | |
279 | ||
280 | # Populate fileHashesSalted array with salted digest for each file hash corresponding nonce. | |
281 | for i in "${!filePathsIndex[@]}"; do | |
282 | fileHashesSalted[i]=$(echo -n "${fileHashes[i]}${fileHashNonces[i]}" | $OPTION_ALGORITHM | awk '{ print $1 }') | |
283 | done | |
284 | [[ $OPTION_VERBOSE == "true" ]] && echoerr "DEBUG: The ${#fileHashesSalted[@]} elements of fileHashesSalted array are:${fileHashesSalted[@]}" | |
285 | ||
286 | # Populate fileSizes array with file sizes (in bytes). | |
287 | for i in "${!filePathsIndex[@]}"; do | |
288 | fileSizes[i]=$(wc -c "${filePathsFull[i]}" | awk '{ print $1 }') | |
289 | done | |
290 | [[ $OPTION_VERBOSE == "true" ]] && echoerr "DEBUG: The ${#fileSizes[@]} elements of fileSizes array are:${fileSizes[@]}" | |
291 | ||
292 | # Populate fileModTime array with file modification times (in ISO-8601 format) | |
293 | for i in "${!filePathsIndex[@]}"; do | |
294 | fileModTime[i]=$(date --iso-8601=seconds -r "${filePathsFull[i]}" | awk '{ print $1 }') | |
295 | done | |
296 | [[ $OPTION_VERBOSE == "true" ]] && echoerr "DEBUG: The ${#fileModTime[@]} elements of fileModTime array are:${fileModTime[@]}" | |
297 | ||
298 | # ==== GENERATE PROOF FILES FROM HASH LIST(S) ==== | |
299 | ||
300 | # Define output file names and paths. | |
301 | local PROOF_PRIVATE_FILENAME=$proofLoopRunDate"_"$loopIndex"_prv.."$scriptHostname"_"$directoryToHashShort"_"$OPTION_ALGORITHM"_proof_private.txt" | |
302 | local PROOF_PUBLIC_FILENAME=$proofLoopRunDate"_"$loopIndex"_pub.."$scriptHostname"_"$directoryToHashShort"_"$OPTION_ALGORITHM"_proof_public.txt" | |
303 | [[ $OPTION_VERBOSE == "true" ]] && echoerr "DEBUG: \$PROOF_PRIVATE_FILENAME is $PROOF_PRIVATE_FILENAME" | |
304 | [[ $OPTION_VERBOSE == "true" ]] && echoerr "DEBUG: \$PROOF_PUBLIC_FILENAME is $PROOF_PUBLIC_FILENAME" | |
305 | PROOF_PRIVATE_FILEPATH="$PROOF_DIRECTORY"/"$PROOF_PRIVATE_FILENAME" | |
306 | PROOF_PUBLIC_FILEPATH="$PROOF_DIRECTORY"/"$PROOF_PUBLIC_FILENAME" | |
307 | ||
308 | # Create private proof and public proof files. | |
309 | touch "$PROOF_PRIVATE_FILEPATH" | |
310 | touch "$PROOF_PUBLIC_FILEPATH" | |
311 | ||
312 | # Abbreviate OPTION_DELIMITER to local variable DELIM | |
313 | local DELIM=$OPTION_DELIMITER | |
314 | ||
315 | # Define private proof first-line headers (date, version, algorithm, message) | |
316 | local PROOF_PRIVATE_HEADER1="$proofLoopRunDate$DELIM$SCRIPT_VERSION$DELIM$OPTION_ALGORITHM$DELIM$OPTION_MESSAGE" | |
317 | [[ $OPTION_VERBOSE == "true" ]] && echoerr "DEBUG: \$PROOF_PRIVATE_HEADER1 is $PROOF_PRIVATE_HEADER1" | |
318 | # Define public proof first-line headers (date, version, algorithm, message) | |
319 | local PROOF_PUBLIC_HEADER1="$proofLoopRunDate$DELIM$SCRIPT_VERSION$DELIM$OPTION_ALGORITHM$DELIM$OPTION_MESSAGE" | |
320 | [[ $OPTION_VERBOSE == "true" ]] && echoerr "DEBUG: \$PROOF_PUBLIC_HEADER1 is $PROOF_PUBLIC_HEADER1" | |
321 | ||
322 | # Define private proof second-line column labels (index, digest, nonce, salted digest, mdate, size, filepath) | |
323 | local PROOF_PRIVATE_HEADER2="index"$DELIM"digest"$DELIM"nonce"$DELIM"digest_salted"$DELIM"mdate"$DELIM"size_bytes"$DELIM"file_path" | |
324 | [[ $OPTION_VERBOSE == "true" ]] && echoerr "DEBUG: \$PROOF_PRIVATE_HEADER2 is $PROOF_PRIVATE_HEADER2" | |
325 | # Define public proof second-line column labels (index, salted digest) | |
326 | local PROOF_PUBLIC_HEADER2="index"$DELIM"digest_salted" | |
327 | [[ $OPTION_VERBOSE == "true" ]] && echoerr "DEBUG: \$PROOF_PUBLIC_HEADER2 is $PROOF_PUBLIC_HEADER2" | |
328 | ||
329 | # Write headers to proof files | |
330 | echo $PROOF_PRIVATE_HEADER1 >> "$PROOF_PRIVATE_FILEPATH" | |
331 | echo $PROOF_PRIVATE_HEADER2 >> "$PROOF_PRIVATE_FILEPATH" | |
332 | echo $PROOF_PUBLIC_HEADER1 >> "$PROOF_PUBLIC_FILEPATH" | |
333 | echo $PROOF_PUBLIC_HEADER2 >> "$PROOF_PUBLIC_FILEPATH" | |
334 | ||
335 | # Loop to append array contents. | |
336 | for i in "${!filePathsIndex[@]}"; do | |
337 | # Append to private proof: filePathsIndex[i],fileHashes[i],fileHashNonces[i],fileHashesSalted[i],fileModTime[i],fileSizes[i],filePathsFull[i] | |
338 | ###[[ $OPTION_VERBOSE == "true" ]] && echoerr "DEBUG: Writing line to $PROOF_PRIVATE_FILEPATH" | |
339 | echo ${filePathsIndex[i]}$DELIM${fileHashes[i]}$DELIM${fileHashNonces[i]}$DELIM${fileHashesSalted[i]}$DELIM${fileModTime[i]}$DELIM${fileSizes[i]}$DELIM${filePathsFull[i]} >> "$PROOF_PRIVATE_FILEPATH" | |
340 | # Append to public proof: filePathsIndex[i],fileHashesSalted[i] | |
341 | [[ $OPTION_VERBOSE == "true" ]] && echoerr "DEBUG: Writing line to $PROOF_PUBLIC_FILEPATH" | |
342 | echo ${filePathsIndex[i]}$DELIM${fileHashesSalted[i]} >> $PROOF_PUBLIC_FILEPATH | |
343 | done | |
344 | ||
345 | # Save file paths to global variables prvProofPaths and pubProofPaths to allow other functions to manipulate files. | |
346 | prvProofPaths+=($PROOF_PRIVATE_FILEPATH) | |
347 | pubProofPaths+=($PROOF_PUBLIC_FILEPATH) | |
348 | [[ $OPTION_VERBOSE == "true" ]] && echoerr "DEBUG: The ${#prvProofPaths[@]} elements of prvProofPaths array are:${prvProofPaths[@]}" | |
349 | [[ $OPTION_VERBOSE == "true" ]] && echoerr "DEBUG: The ${#pubProofPaths[@]} elements of pubProofPaths array are:${pubProofPaths[@]}" | |
350 | ||
351 | # Reset arrays. | |
352 | filePathsFull=() | |
353 | filePathsIndex=() | |
354 | fileHashes=() | |
355 | fileHashNonces=() | |
356 | fileHashesSalted=() | |
357 | fileSizes=() | |
358 | fileModTime=() | |
359 | } | |
360 | ||
361 | ||
362 | # === INPUT PROCESSING === | |
363 | # Check for presence of options. | |
364 | ###echoerr "DEBUG: === INPUT PROCESSING START ===" | |
365 | ||
366 | # Process initial option arguments (see https://jonalmeida.com/posts/2013/05/26/different-ways-to-implement-flags-in-bash/ and https://likegeeks.com/linux-bash-scripting-awesome-guide-part3/ ) | |
367 | while [ ! $# -eq 0 ] # While number of arguments ($#) is not (!) equal to (-eq) zero (0). | |
368 | do | |
369 | case "$1" in # Check first of remaining arguments to see if it matches one of strings below. | |
370 | --help | -h) | |
371 | # Code to run if $1 matched "--help" or "-h": | |
372 | ###echoerr "DEBUG: This is the help information." | |
373 | usage # Run usage function to display helpful info to user. | |
374 | exit 1 | |
375 | ;; | |
376 | --recursive | -r) | |
377 | # Code to run if $1 matched "--recursive" or "-r": | |
378 | ###echoerr "DEBUG: Recursive option activated." | |
379 | OPTION_RECURSIVE="true" | |
380 | ;; | |
381 | --verbose | -v) | |
382 | # Code to run if $1 matched "--verbose" or "-v": | |
383 | ###echoerr "DEBUG: Verbose option activated." | |
384 | OPTION_VERBOSE="true" | |
385 | ;; | |
386 | --algorithm | -a) | |
387 | # Code to run if $1 matched "--algorithm" or "-a": | |
388 | ||
389 | ###echoerr "DEBUG: Selecting hash algorithm." | |
390 | # Check that OPTION_ALGORITHM variable hasn't already been set. | |
391 | # Check that OPTION_ALGORITHM contains only alphanumeric characters. | |
392 | # Check that argument following '-a' or '--algorithm' ($2) is valid algorithm. | |
393 | if [[ ! -v OPTION_ALGORITHM ]] && [[ ! "$2" =~ [^a-zA-Z0-9$] ]] && isValidDigestAlgo $2; then | |
394 | ###echoerr "DEBUG: Valid hash algorithm provided." | |
395 | OPTION_ALGORITHM=$2 # Save argument following '-a' or '--algorithm' to $OPTION_ALGORITHM | |
396 | shift # Remove an additional argument so the additional algorithm argument $2 is properly removed at the end of the while loop. | |
397 | else | |
398 | echoerr "ERROR: Invalid hash algorithm argument provided:""$2" | |
399 | exit 1 | |
400 | fi | |
401 | ;; | |
402 | --message | -m) | |
403 | # Code to run if $1 matched "--message" or "-m": | |
404 | ###echoerr "DEBUG: Specifying header message." | |
405 | # Check that OPTION_MESSAGE variable hasn't already been set. | |
406 | if [[ ! -v OPTION_MESSAGE ]]; then | |
407 | OPTION_MESSAGE=$2 # Save argument following '-m' or '--message' to $OPTION_MESSAGE | |
408 | shift # Remove an additional argument so the additional algorithm argument $2 is properly removed at the end of the while loop. | |
409 | fi | |
410 | ;; | |
411 | --delimiter | -d) | |
412 | # Code to run if $1 matched "--delimiter" or "-d": | |
413 | ###echoerr "DEBUG: Specifying delimiter." | |
414 | # Check that OPTION_DELIMITER variable hasn't already been set. | |
415 | if [[ ! -v OPTION_DELIMITER ]]; then | |
416 | OPTION_DELIMITER=$2 # Save argument following '-d' or '--delimiter' to $OPTION_DELIMITER | |
417 | shift # Remove an additional argument so the additional delimiter argument $2 is properly removed at the end of the while loop. | |
418 | fi | |
419 | ;; | |
420 | --timestamp | -t) | |
421 | # Code to run if $1 matched "--timestamp" or "-t": | |
422 | echoerr "DEBUG: OpenTimestamps option selected." | |
423 | # Check that ots command exists. (see https://stackoverflow.com/a/677212 ) | |
424 | if command -v ots >/dev/null 2>&1 ; then | |
425 | echoerr "DEBUG: OpenTimestampscommand ('ots') detected." | |
426 | OPTION_OPENTIMESTAMPS="true" | |
427 | else | |
428 | echo >&2 "I require the ots (OpenTimestamps) command but it's not installed. Aborting." | |
429 | exit 1 | |
430 | fi | |
431 | ;; | |
432 | --wait | -w) | |
433 | # Code to run if $1 matched "--wait" or "-w": | |
434 | echoerr "DEBUG: OpenTimestamps '--wait' option selected." | |
435 | OPTION_OPENTIMESTAMPS_WAIT="true" | |
436 | ;; | |
437 | --output | -o) | |
438 | # Code to run if $1 matched "--output" or "-o": | |
439 | # Check that argument $2 is a directory. | |
440 | if [[ -d $2 ]]; then | |
441 | echoerr "DEBUG: Specified ""$PROOF_DIRECTORY_BASE"" in ""$2"" as directory for saving proof files." | |
442 | PROOF_DIRECTORY="$2"/"$PROOF_DIRECTORY_BASE" | |
443 | echoerr "DEBUG: Proof Directory is:""$PROOF_DIRECTORY" | |
444 | shift # Remove an additional argument so the additional delimiter argument $2 is properly removed at the end of the while loop. | |
445 | else | |
446 | echoerr "ERROR: Invalid argument provided as output directory for proof files:""$2" | |
447 | exit 1 | |
448 | fi | |
449 | ;; | |
450 | *) | |
451 | # Code to run if $1 isn't any of the above cases: | |
452 | # Check that remaining argument is a directory. | |
453 | if [[ -d $1 ]]; then | |
454 | ###echoerr "DEBUG: Remaining argument is directory. Adding to \$directoriesToProcess array." | |
455 | # Add directory argument $1 to directory array | |
456 | directoriesToProcess+=($1) # (see https://stackoverflow.com/a/1951523 ) | |
457 | else | |
458 | echoerr "ERROR: Remaining argument is not a directory or valid option. Exiting." | |
459 | exit 1 | |
460 | fi | |
461 | ;; | |
462 | esac | |
463 | shift # Remove first argument ($1) so remaining arguments can be processed on next loop. | |
464 | done | |
465 | ||
466 | # If OPTION_ALGORITHM is not set then set it to 'sha256sum' by default. | |
467 | if [[ ! -v OPTION_ALGORITHM ]]; then | |
468 | echoerr "DEBUG: No hash algorithm set. Defaulting to sha256sum." | |
469 | OPTION_ALGORITHM="sha256sum" | |
470 | fi | |
471 | ||
472 | # If OPTION_DELIMITER is not set then set it to ',' by default. | |
473 | if [[ ! -v OPTION_DELIMITER ]]; then | |
474 | ###echoerr "DEBUG: No delimiter set. Defaulting to ','." | |
475 | OPTION_DELIMITER="," | |
476 | fi | |
477 | ||
478 | # Exit program if directoriesToProcess array is empty. | |
479 | if [[ ${#directoriesToProcess[@]} -eq 0 ]]; then | |
480 | echoerr "ERROR: No valid directory specified." | |
481 | exit 1 | |
482 | fi | |
483 | ||
484 | ###echoerr "DEBUG: === INPUT PROCESSING END ===" | |
485 | ||
486 | # === MAIN PROGRAM === | |
487 | [[ $OPTION_VERBOSE == "true" ]] && echoerr "DEBUG: === MAIN PROGRAM START ===" | |
488 | [[ $OPTION_VERBOSE == "true" ]] && echoerr "DEBUG: Verbose option active." | |
489 | [[ $OPTION_VERBOSE == "true" ]] && [[ $OPTION_RECURSIVE == "true" ]] && echoerr "DEBUG: Recursive option active." | |
490 | [[ $OPTION_VERBOSE == "true" ]] && [[ -v OPTION_ALGORITHM ]] && echoerr "DEBUG: Selected Hash algorithm is:""$OPTION_ALGORITHM" | |
491 | [[ $OPTION_VERBOSE == "true" ]] && echoerr "DEBUG: Directories to process are: ${directoriesToProcess[@]}" | |
492 | [[ $OPTION_VERBOSE == "true" ]] && echoerr "DEBUG: Message to add to proof is: $OPTION_MESSAGE" | |
493 | [[ $OPTION_VERBOSE == "true" ]] && echoerr "DEBUG: Proof directory is:""$PROOF_DIRECTORY" | |
494 | [[ $OPTION_VERBOSE == "true" ]] && [[ $OPTION_OPENTIMESTAMPS == "true" ]] && echoerr "DEBUG: OpenTimestamps option active." | |
495 | [[ $OPTION_VERBOSE == "true" ]] && [[ $OPTION_OPENTIMESTAMPS_WAIT == "true" ]] && echoerr "DEBUG: OpenTimestamps '--wait' option selected." | |
496 | # Create proof directory | |
497 | mkdir "$PROOF_DIRECTORY" | |
498 | echoerr "Proof directory created at:""$PROOF_DIRECTORY" | |
499 | ||
500 | ## TEMP REF: | |
501 | # declare -a filePathsFull # array | |
502 | # declare -a filePathsIndex # array | |
503 | # declare -a fileHashes # array | |
504 | # declare -a fileHashNonces # array | |
505 | # declare -a fileHashesSalted # array | |
506 | # declare -a fileSizes # array | |
507 | # declare -a fileModTime # array | |
508 | # declare -ag prvProofPaths # array, global | |
509 | # declare -ag pubProofPaths # array, global | |
510 | ||
511 | # ==== GENERATE HASH LIST(S) ==== | |
512 | [[ $OPTION_VERBOSE == "true" ]] && echoerr "DEBUG: ==== GENERATING HASH LIST(S) ====" | |
513 | ||
514 | # Assemble and write proofs to disk for all specified directories. | |
515 | ||
516 | # Generate proofs from all provided directories. | |
517 | for j in "${!directoriesToProcess[@]}"; do | |
518 | writeProofs $j "${directoriesToProcess[j]}" | |
519 | done | |
520 | ||
521 | # Decide if superproof is required. | |
522 | if [[ ${#directoriesToProcess[@]} -eq 1 ]]; then | |
523 | # If only one directory provided then no further proofs required. | |
524 | [[ $OPTION_VERBOSE == "true" ]] && echoerr "DEBUG: Only 1 directory processed. No superproof required." | |
525 | elif [[ ${#directoriesToProcess[@]} -gt 1 ]]; then | |
526 | # If multiple directories provided then create superproof from files in PROOF_DIRECTORY. | |
527 | [[ $OPTION_VERBOSE == "true" ]] && echoerr "DEBUG: More than 1 directory processed. Proceeding to generate superproof from PROOF_DIRECTORY." | |
528 | writeProofs "S" $PROOF_DIRECTORY # Create proof using proof files which are stored in PROOF_DIRECTORY | |
529 | fi | |
530 | ||
531 | # ==== GENERATE HASH LIST(S) REPORT ===== | |
532 | echoerr "Proof files written to $PROOF_DIRECTORY directory:" | |
533 | for i in ${!prvProofPaths[@]}; do | |
534 | echo $(basename "${prvProofPaths[i]}") | |
535 | echo $(basename "${pubProofPaths[i]}") | |
536 | done | |
537 | ||
538 | # ==== GENERATE OPENTIMESTAMPS TIMESTAMP FILE(S) ==== | |
539 | ||
540 | # Determine if OpenTimestamp actions to be performed. | |
541 | if [[ $OPTION_OPENTIMESTAMPS == "true" ]]; then | |
542 | # Identify target for OpenTimeStamps script. | |
543 | otsTarget=${pubProofPaths[-1]} # Select final element in pubProofPaths array as target for 'ots' command. | |
544 | [[ $OPTION_VERBOSE == "true" ]] && echoerr "DEBUG: otsTarget is:""$otsTarget" | |
545 | ||
546 | # Set wait option as determined by OPTION_OPENTIMESTAMPS_WAIT variable. Set timeout length. | |
547 | if [[ $OPTION_OPENTIMESTAMPS_WAIT == "true" ]]; then | |
548 | otsWaitOption="--wait" # Specify '--wait' option for ots command. | |
549 | otsWaitTimeCmd="timeout ""$OTS_TIMEOUT" # Specify timeout length (6 hours) for ots command. | |
550 | [[ $OPTION_VERBOSE == "true" ]] && echoerr "DEBUG: OpenTimestamps '--wait' option active." | |
551 | else | |
552 | otsWaitOption="" | |
553 | otsWaitTimeCmd="" | |
554 | [[ $OPTION_VERBOSE == "true" ]] && echoerr "DEBUG: OpenTimestamps '--wait' option not active." | |
555 | fi | |
556 | ||
557 | # Generate OpenTimeStamps proof file. | |
558 | pushd "$PROOF_DIRECTORY" 1>/dev/null 2>/dev/null # temporarily change working dir to PROOF_DIRECTORY, suppress stdout and stderr | |
559 | $otsWaitTimeCmd ots $otsWaitOption stamp $otsTarget | |
560 | echoerr "OpenTimestamps operation successful! $otsTarget created." | |
561 | echoerr "Use 'ots info [ filename ]' for more info." | |
562 | echoerr "Use 'ots upgrade' if '--wait' option not used to make .ots file independently-verifiable." | |
563 | echoerr "See https://opentimestamps.org/ for more information." | |
564 | popd 1>/dev/null 2>/dev/null # reverse temporary change to working dir, suppress stdout and stderr | |
565 | ||
566 | fi | |
567 | ||
568 | ||
569 | [[ $OPTION_VERBOSE == "true" ]] && echoerr "DEBUG: === MAIN PROGRAM END ===" | |
570 | # Exit program successfully. | |
571 | exit 0 | |
572 | ||
573 | ||
574 | # == Dependencies == | |
575 | # | |
576 | # - bash, find, sort, echo, mkdir, basename, awk, wc, date, dd, | |
577 | # readlink, touch, ots, | |
578 | ||
579 | # - GNU bash, version 5.0.3(1)-release (x86_64-pc-linux-gnu) | |
580 | # Copyright (C) 2019 Free Software Foundation, Inc. | |
581 | # License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> | |
582 | # This is free software; you are free to change and redistribute it. | |
583 | # There is NO WARRANTY, to the extent permitted by law. | |
584 | ||
585 | # - find (GNU findutils) 4.6.0.225-235f | |
586 | # Copyright (C) 2019 Free Software Foundation, Inc. | |
587 | # License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>. | |
588 | # This is free software: you are free to change and redistribute it. | |
589 | # There is NO WARRANTY, to the extent permitted by law. | |
590 | # Written by Eric B. Decker, James Youngman, and Kevin Dalley. | |
591 | # Features enabled: D_TYPE O_NOFOLLOW(enabled) LEAF_OPTIMISATION FTS(FTS_CWDFD) CBO(level=2) | |
592 | ||
593 | # - sort (GNU coreutils) 8.30 | |
594 | # Copyright (C) 2018 Free Software Foundation, Inc. | |
595 | # License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>. | |
596 | # This is free software: you are free to change and redistribute it. | |
597 | # There is NO WARRANTY, to the extent permitted by law. | |
598 | # Written by Mike Haertel and Paul Eggert. | |
599 | ||
600 | # - echo (GNU coreutils) 8.30 | |
601 | # Copyright (C) 2018 Free Software Foundation, Inc. | |
602 | # License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>. | |
603 | # This is free software: you are free to change and redistribute it. | |
604 | # There is NO WARRANTY, to the extent permitted by law. | |
605 | # Written by Brian Fox and Chet Ramey. | |
606 | ||
607 | # - mkdir (GNU coreutils) 8.30 | |
608 | # Copyright (C) 2018 Free Software Foundation, Inc. | |
609 | # License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>. | |
610 | # This is free software: you are free to change and redistribute it. | |
611 | # There is NO WARRANTY, to the extent permitted by law. | |
612 | # Written by David MacKenzie. | |
613 | ||
614 | # - basename (GNU coreutils) 8.30 | |
615 | # Copyright (C) 2018 Free Software Foundation, Inc. | |
616 | # License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>. | |
617 | # This is free software: you are free to change and redistribute it. | |
618 | # There is NO WARRANTY, to the extent permitted by law. | |
619 | # Written by David MacKenzie. | |
620 | ||
621 | # - GNU Awk 4.2.1, API: 2.0 (GNU MPFR 4.0.2, GNU MP 6.1.2) | |
622 | # Copyright (C) 1989, 1991-2018 Free Software Foundation. | |
623 | # This program is free software; you can redistribute it and/or modify | |
624 | # it under the terms of the GNU General Public License as published by | |
625 | # the Free Software Foundation; either version 3 of the License, or | |
626 | # (at your option) any later version. | |
627 | # This program is distributed in the hope that it will be useful, | |
628 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
629 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
630 | # GNU General Public License for more details. | |
631 | # You should have received a copy of the GNU General Public License | |
632 | # along with this program. If not, see http://www.gnu.org/licenses/. | |
633 | ||
634 | # - wc (GNU coreutils) 8.30 | |
635 | # Copyright (C) 2018 Free Software Foundation, Inc. | |
636 | # License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>. | |
637 | # This is free software: you are free to change and redistribute it. | |
638 | # There is NO WARRANTY, to the extent permitted by law. | |
639 | # Written by Paul Rubin and David MacKenzie. | |
640 | ||
641 | # - date (GNU coreutils) 8.30 | |
642 | # Copyright (C) 2018 Free Software Foundation, Inc. | |
643 | # License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>. | |
644 | # This is free software: you are free to change and redistribute it. | |
645 | # There is NO WARRANTY, to the extent permitted by law. | |
646 | # Written by David MacKenzie. | |
647 | ||
648 | # - dd (coreutils) 8.30 | |
649 | # Copyright (C) 2018 Free Software Foundation, Inc. | |
650 | # License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>. | |
651 | # This is free software: you are free to change and redistribute it. | |
652 | # There is NO WARRANTY, to the extent permitted by law. | |
653 | # Written by Paul Rubin, David MacKenzie, and Stuart Kemp. | |
654 | ||
655 | # - readlink (GNU coreutils) 8.30 | |
656 | # Copyright (C) 2018 Free Software Foundation, Inc. | |
657 | # License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>. | |
658 | # This is free software: you are free to change and redistribute it. | |
659 | # There is NO WARRANTY, to the extent permitted by law. | |
660 | # Written by Dmitry V. Levin. | |
661 | ||
662 | # - touch (GNU coreutils) 8.30 | |
663 | # Copyright (C) 2018 Free Software Foundation, Inc. | |
664 | # License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>. | |
665 | # This is free software: you are free to change and redistribute it. | |
666 | # There is NO WARRANTY, to the extent permitted by law. | |
667 | # Written by Paul Rubin, Arnold Robbins, Jim Kingdon, | |
668 | # David MacKenzie, and Randy Smith. | |
669 | ||
670 | # - ots v0.7.0 ( https://github.com/opentimestamps/opentimestamps-client ) | |
671 | # The OpenTimestamps Client is free software: you can redistribute it and/or | |
672 | # modify it under the terms of the GNU Lesser General Public License as published | |
673 | # by the Free Software Foundation, either version 3 of the License, or (at your | |
674 | # option) any later version. | |
675 | # The OpenTimestamps Client is distributed in the hope that it will be useful, | |
676 | # but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY | |
677 | # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License | |
678 | # below for more details. |