verifying blogposts

to author or not to author ✍🏻


I think the first question one might ask is: why?

Verification, as the name implies, verifies that the content you see is actually what the owner wanted you to see. According to popular belief we currently are in an age of ✨ DiSiNfoRmAtIoN ✨ and 🎀 CybErWaRfArE 🎀, if we believe in that assesment verifying the source of information is actually a relevant problem statement.

Thanks to the theoretical anonoymity of the internet identity has been a relevant topic since its early beginning. this is why pgp (pretty good privacy) got introduced in 1991. With pgp a user can generate private public keypair. You can encrypt data with your private key and it can only be decrypted using your public key and viceversa. this means, if somebody published their public key, you donwloaded it and later get a encrypted message, if you can decrypt it you know it was encrypted by the private key (meaning hopefully also by the private keys owner). Another thing pgp is capable of is creating signatures, this means taking in some piece of data and creating a key specific hash, if another person then has this hash, and your public key they can verify if all components belong together. this therefore results in the ability to verify authorship.

gpg is an implementation of pgp, using that we can simply create those signatures by:

gpg --detach-sign /path/to/file --output /path/to/signature

which saves the signature of a file in an output file. signatures are usually saved as .sig or .asc files.

signing data seems pretty simple and straightforward, so why not use it? thats what i thought as well. the most obvious place where i publish things on the internet is this blog so it seemed logical to start here. I am quite into CI/CD so why not even automate it to make it happen without me even having to think about it. Sounds great, right? for this blog i use hugo to build the static pages from markdown, it is great and i love it so maybe i can add this signature generation as a build step? sadly that is not possible as hugo is very protective of its build system and does not let you easily expand it, the only option would be to fork hugo and i might even do that in the future, but today i am more interested to create some sort of prove of concept.

Maybe lets start with the basics and create a bash script that verifies all generated pages, that means:

well some bash`ing later i got this:

# build
hugo
# find all pages (index.html)
find ./build -type f -name "index.html" | while read -r filepath; do
    # create folders for signatures in hugo static section
    relative_path="${filepath#public/}"
    output_directory="./static/${relative_path%/index.html}"
    mkdir -p "$output_directory"
    output_file="${output_directory}/index.asc"
    # save signatures at precalculated filepath
    gpg --output "$output_file" --detach-sign "$filepath"
done

in this setup the signatures get stored in the static folder with a file structure that copies what is generated by hugo, this results in a setup where for each page the index.html is stored in the same folder as its index.asc signature. great, lets generate those signatures and test it:

gpg: Signature made Mon Feb  3 11:28:43 2025 CET
gpg:                using RSA key 3A19E7AE165B5E49D064A0C49F533BD51A4E27FA
gpg:                issuer "mail[at]erik.gdn"
gpg: Good signature from "erik.gdn <mail[at]erik.gdn>" [ultimate]

Thats a success in my books, lets push it to github then and see the ci/cd pipeline handle it.

gpg: Signature made Mon Feb  3 11:32:37 2025 CET
gpg:                using RSA key 3A19E7AE165B5E49D064A0C49F533BD51A4E27FA
gpg:                issuer "mail[at]erik.gdn"
gpg: Bad signature from "erik.gdn <mail[at]erik.gdn>" [ultimate]

well thats disapointing, time to start debugging. after some further bash`ing (this time it might have been my head) i noticed that the builds created by the github ci/cd pipeline were different than the ones i was getting locally. now its obvious that the signatures are ‘bad’ as the generated html files are simply different from the ones i verified locally. after looking at the diffs some things got obvious. to make this easier for you (in the unlikely case you as well want to verify your blogposts) i will just go through the 3 gotchas i encountered.

lets start with the easy stuff, [1] my remote gets compiled with a different ‘baseURL’, this being my blogs domain (instead of localhost, obviously duh) and the resulting html then gets minified, so thats an easy fix:

hugo --minify --baseURL "${BASE_URL}" --destination "${HUGO_BUILD_DIR}"

another thing i noticed is that [2] while on localhost my scripts and stylesheets get loaded using relative paths on remote they are transformed into absolute paths so i changed them up to always be explicitly absolute paths. in hugo this means changing for example <link rel='icon' href='/favicon.ico'> to <link rel='icon' href='{{ printf "%s/favicon.ico" $base_url }}'> and at last [3] some service providers like to just add their own html tags to your html. for example cloudflare has several services that add script tags, for example if you have ‘web analytics’ enabled this will add an script tag that will call home with client statistics or ’email address obfuscation’, there are a lot of such services actually. by the way, if you intend to comply with gdpr getting rid of these is also relevant.

so with all of that done finally my signatures also started working on remote. halleluja. so how can i automate this now? well, as i will not upload my private key anywhere this has to automated locally (meaning making this a simple build step for the github workflow is not an option). the next logical step is looking into git pre-commit and pre-push hooks, i really tried to make it work but all my implementations ended in loops. howsd that? well lets say for example on commit i want to generate these signatures. as the pre-commit hook cant add new files to the commit that is currently being handled (atleast i havent found a way to do so). therefore i would need to add these changes to a new commit but if do this from the pre-commit hook it retriggers the pre-commit hook resulting in a loop, there are similiar problems with the pre-push hook. the only other tool in my current toolstack is hugo, having hugo execute the bash script after its done building would be nice, but hugo does not support this, i would assume this should be an easy change in the source code but i wont bother with it for now as i dont want to go down another rabbit hole 😅.

looks like i wont be able to automate the building process but atleast i want to know when things dont work out because if i provide signatures and they are not correct without me noticing it that would create distrust in the whole system and would arguably be worse than no signatures at all. this is why i put a signature verification steps into my github ci/cd pipeline, this ensures that if the page is published it has been signed correctly meaning that all pages can be verified with the public found on my website (if you read this carefully or thought about it you might already have spotted the glaring issue but more on that later). i simply extended the hugo github-pages workflow example with a signature check before deploying. if you are interested you can find that version here.

lets now address the problem i foreshadowed before. the goal of these signatures is to protect from a bad actor taking over control this blog, as my public key is also hosted on this blog nothing would prevent a bad actor from simply creating a new public key, exchanging it with the one on this blog and regenerating all signatures. if this is being performed correctly a visitor could not tell the difference, how should they know the public key is actually mine. and even if the person knows me and has my actual public key it would be easy for the attacker to claim i lost access to my private key and therefore now operate with this new one. for this to be sort of useful my public key would need to be hosted completely seperate from this. Another consideration is that this version only protects the html. i use very minimal js on this blog but if the attacker changed only the content of that js file the signature of the page would not change while it would be possible to change the content of the page displayed on the clients computer (this change would be done clientside but a non technical person would not notice it and likely even a more technical person would have to look into the pages source code to see it).

well there are solutions for both of these, on the one hand you could simply verify all linked files aswel, this should not be a problem and also creating a little scripts that verifies that shouldnt be too complicated. with the public key problem one should simply store their public key to a key server, then the user should build connections in their community to establish a trust chain. this means that other users verify the authenticity of the public key by signing it. the idea is that if the network of connections gets large enough this is a valid way to establish trust. this imrpoves the situation but there can never be complete safety. if i am overseeing something here i would be delighted to hear from you on how this would be further improved.

also an honourable mention at the end. the only other person i saw doint similiar stuff was this guy but i havent seen him address the issues i discussed here, he also referenced this blogpost that also tackled the matter.

in the future i would like to further investigate this concpet by adding dependency verification which in my opinion would add significant value and actually make this useful in reality as dependency injections are a more realistic attack vector, especially when these dependencys are hosted by third parties. automated verification using a browser extension would then be the logical next step. a script that is not verifiable would than simply not be loaded, this could realisticly improve security while browsing the web.