As this site is all static HTML, I wanted to find a way to automatically add PGP signatures to each page, so that anybody could verify a page was actually published by me.
While it’s possible to have inline signing for HTML documents, I didn’t like any of the systems I found, and decided it would be cleaner to use detached signatures in separate
.asc files, as often done with binary files and software releases.
I found a way to do this that I really like that just uses GNU Make, some Git hooks, and a little Bash.
The below assumes that you already have a private key set up with GnuPG, and know the basics of signing and verifying files. I’ve written a separate article about basic GnuPG key setup.
Creating the detached ASCII signature for each HTML file in the web root is straightforward:
$ gpg --armor --sign-detach public_html/document.html
I’m prompted by the GnuPG agent for my passphrase, and the document is signed.
This creates a new file
Anyone with my key can then use this to verify the document:
$ gpg --verify public_html/document.html.asc public_html/document.html gpg: Signature made Sat 01 Mar 2014 13:32:50 NZDT gpg: using RSA key 0xB5AF5F8925926609 gpg: Good signature from "Thomas Ryder (tyrmored, tejr) <firstname.lastname@example.org>" [ultimate]
Of course, I don't want to have to do all that manually each time I change a file, so I use a
Makefile to generate new and changed signatures automatically each time I run
The file looks like this:
HTMLS := $(shell find public_html -type f -name \*.html) SIGS := $(patsubst %.html,%.html.asc,$(HTMLS)) all : $(SIGS) %.html.asc : %.html gpg --armor --detach-sign $<
.html.ascin every filename matching the glob
allis defined as comprised of all the
SIGS. This means I can just type
makewithout specifying a build target.
.html.ascfile from a
.htmlfile with the same name before the extension.
gpg --armor --detach-signon the HTML file, just as I did manually above, and the signature is produced.
The result is that whenever I add or edit an HTML file, I need only type
make, and any new or updated files have their signatures rebuilt automatically.
To syntax-check HTML files with
tidy(1) and to enforce valid signatures for all HTML files, I automated it with a Git
pre-commit hook, in
.git/hooks/pre-commit in my repository root:
#!/usr/bin/env bash # Need extglob for the +(0) bit shopt -s extglob # Start counting errors declare -i errors # Read records supplied by git diff-index, null-terminated; we only want the # sha1 object name of the staged file while read -r -d '' _ _ _ sha1 _ ; do # git diff-files has a NULL both before and after the filename it prints, # so we need to run read again to get the filename out to move on to the # next record (this is a bit weird, but it does seem to work consistently) read -r -d '' filename _ # Skip the file if its digest is empty or is all zeroes, the latter being # how diff-index shows deleted files or moves [[ $sha1 ]] || continue [[ $sha1 != +(0) ]] || continue # Skip the file if it's an ignored path [[ $filename != public_html/blinkenlights/* ]] || continue # Skip the file if it's not HTML [[ $filename == *.html ]] || continue # Show that we're checking it printf 'Checking modified %s ... \n' "$filename" # Check HTML formatting git cat-file -p "$sha1" | tidy -eq -utf8 || ((errors++)) # Check ASCII signature up to date git cat-file -p "$sha1" | gpg --verify -- "$filename".asc "$filename" >/dev/null || ((errors++)) # Standard input for the while loop is here done < <(git diff-index -z --cached HEAD) # Exit 0 if there were no errors, 1 if there were exit "$((errors > 0))"
I make this executable with
chmod +x .git/hooks/pre-commit, and it all works.
The result is that just as I’m running
git iterates through all of the HTML files to make sure they all have a valid signature.
If any of the checks fail, errors are accrued in the
errors count, and the non-zero exit value means the commit won’t go through until I fix it by running
make(1) and entering my passphrase for the signatures.