wget script, but pipe it to bash only if SHA256 of script matches one-liner

wget http://example.com/install.sh -O - | bash runs the script automatically but due to possible (TLS) MITM attacks etc, this is not safe. Is is possible to construct a one-liner that downloads the script, but executes it only if it's hash matches the one specified in the one-liner? It would be nice if the one-liner would print something like Warning! Hash mismatch if hash check fails.


Solution 1:

So you want to download and run http://example.com/install.sh.

For now I assume that you have its SHA256 hash stored locally in a file called my-sha256.txt. The file contains only the hash itself and a Unix-style linebreak character, so its size must be exactly 65 bytes. You can create it by simply running this:

sha256sum ORIGINAL_FILE.SH | grep -Eo '^\w+' > my-sha256.txt

How you distribute this hash file from your development machine to the client is not part of this answer (yet, you might clarify your question and ask me to update this part based on your detailed specifications).


The actual command your client has to run to download, verify, and on success execute the script could look like this:

t=$(mktemp) && wget 'http://example.com/install.sh' -qO "$t" && if sha256sum "$t" | grep -Eo '^\w+' | cmp -s my-sha256.txt ; then bash "$t" ; else echo FAIL ; fi ; rm "$t"

Slightly shortened and ugly version without whitespace:

t=$(mktemp)&&wget 'http://example.com/install.sh' -qO"$t"&&if sha256sum "$t"|grep -Eo '^\w+'|cmp -s my-sha256.txt;then bash "$t";else echo FAIL;fi;rm "$t"

Placed on multiple lines for readability:

t=$(mktemp) && 
wget 'http://example.com/install.sh' -qO "$t" && 
if sha256sum "$t" | grep -Eo '^\w+' | cmp -s my-sha256.txt 
    then bash "$t" 
    else echo FAIL 
fi 
rm "$t"

If you want to directly provide the hash inside the command as string instead of reading from a file, simply use one of my original command versions above and replace the occurrence of my-sha256.txt with <(echo YOUR_HASH), inserting your real hash instead of the "YOUR_HASH" placeholder of course.

Explanation:

The script/one-liner first creates a temporary file using mktemp (uses the system's temp folder /tmp).
Then it uses wget to download your installation script from the specified URL and save it in the temp file.
Now we calculate its hash sum, filter only the hash value from the output of sha256sum and compare that to what we have stored in out my-sha256.txt.
If both hashes equal, we will invoke bash with our temporary script file as argument, else we echo FAIL or you could output a custom error message.
In the end we clean up by deleting our temporary file in both cases.


However, coming back to the problem of securely distributing the hash to verify the original script, this solution above will not help you much, as it solves one problem by creating another one of the same kind.

What you actually should do is to create a GPG key pair (and publish your public key to a keyserver), sign your script with it and offer the compressed signed binary for download. Then let the client verify and decrypt the script using gpg again and run it on success.