diff options
| author | jwijenbergh <jeroenwijenbergh@protonmail.com> | 2022-06-17 14:00:40 +0200 |
|---|---|---|
| committer | jwijenbergh <jeroenwijenbergh@protonmail.com> | 2022-09-20 20:31:23 +0200 |
| commit | 7af07c596166bf93b79a9d0816b1950dde360fb9 (patch) | |
| tree | 08b5374c34d6c33b3c596ed981bfb069cca37ade /internal/verify | |
| parent | 6dc7b64f634f6dcbeedea24c741382366a3c7b8c (diff) | |
Server: Implement function for checking renewal button visibility
Diffstat (limited to 'internal/verify')
33 files changed, 534 insertions, 0 deletions
diff --git a/internal/verify/test_data/empty b/internal/verify/test_data/empty new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/internal/verify/test_data/empty diff --git a/internal/verify/test_data/generate.sh b/internal/verify/test_data/generate.sh new file mode 100644 index 0000000..b1b4545 --- /dev/null +++ b/internal/verify/test_data/generate.sh @@ -0,0 +1,58 @@ +#!/bin/bash +# Generate testcases with fake keys + +# Make sure we do not delete *.minisigs etc. in the wrong directory +if [ ${PWD##*/} != "test_data" ] +then + >&2 echo "Wrong directory, should be run in test_data/" + exit 1 +fi + +rm -f *.minisig *.blake2b + +# Uncomment to regenerate keys +#rm -f *.key +#echo -en "\n\n" | minisign -Gf -p public.key -s secret.key & +#echo -en "\n\n" | minisign -Gf -p wrong_public.key -s wrong_secret.key & +#wait + +# Try to create pure signature with default Minisign (works with version < 0.10) +echo | minisign -Sm server_list.json -x server_list.json.pure.minisig -t $'timestamp:10\tfile:server_list.json' -s secret.key +# Check if it is actually a prehashed signature +if echo | minisign -VHm server_list.json -x server_list.json.pure.minisig -p public.key +then + echo "minisign version is >0.9, trying minisign-0.9" + # If it is, try to sign with some minisign-0.9 program + if ! echo | minisign-0.9 -Sm server_list.json -x server_list.json.pure.minisig -t $'timestamp:10\tfile:server_list.json' -s secret.key + then + >&2 echo -e "\n\nTo produce a non-prehashed signature we need Minisign 0.9\n\n" + fi +fi + +# Rest works with Minisign 0.9 and 0.10 (and up, probably) + +echo | minisign -SHm server_list.json -t $'timestamp:10\tfile:server_list.json\thashed' -s secret.key & +echo | minisign -SHm server_list.json -x server_list.json.tc_nohashed.minisig -t $'timestamp:10\tfile:server_list.json' -s secret.key & +echo | minisign -SHm server_list.json -x server_list.json.tc_latertime.minisig -t $'timestamp:20\tfile:server_list.json\t hashed' -s secret.key & +echo | minisign -SHm server_list.json -x server_list.json.tc_orglist.minisig -t $'timestamp:10\tfile:organization_list.json\thashed' -s secret.key & +wait +echo | minisign -SHm server_list.json -x server_list.json.tc_otherfile.minisig -t $'timestamp:10\tfile:otherfile\thashed' -s secret.key & +echo | minisign -SHm server_list.json -x server_list.json.tc_nofile.minisig -t $'timestamp:10\thashed' -s secret.key & +echo | minisign -SHm server_list.json -x server_list.json.tc_notime.minisig -t $'file:server_list.json\thashed' -s secret.key & +echo | minisign -SHm server_list.json -x server_list.json.tc_emptytime.minisig -t $'timestamp:\tfile:server_list.json\thashed' -s secret.key & +wait +echo | minisign -SHm server_list.json -x server_list.json.tc_emptyfile.minisig -t $'timestamp:10\tfile:\thashed' -s secret.key & +echo | minisign -SHm server_list.json -x server_list.json.tc_earliertime.minisig -t $'timestamp:9\tfile:server_list.json\thashed' -s secret.key & +echo | minisign -SHm server_list.json -x server_list.json.tc_random.minisig -t 'random stuff' -s secret.key & +echo | minisign -SHm server_list.json -x server_list.json.large_time.minisig -t $'timestamp:4300000000\tfile:server_list.json' -s secret.key & +wait + +echo | minisign -SHm organization_list.json -t $'timestamp:10\tfile:organization_list.json\thashed' -s secret.key & +echo | minisign -SHm organization_list.json -x organization_list.json.tc_servlist.minisig -t $'timestamp:10\tfile:server_list.json\thashed' -s secret.key & + +echo | minisign -SHm other_list.json -t $'timestamp:10\tfile:other_list.json\thashed' -s secret.key & + +echo | minisign -SHm server_list.json -x server_list.json.wrong_key.minisig -t $'timestamp:10\tfile:server_list.json\thashed' -s wrong_secret.key & +wait + +./generate_forged.py diff --git a/internal/verify/test_data/generate_forged.py b/internal/verify/test_data/generate_forged.py new file mode 100644 index 0000000..9d42adc --- /dev/null +++ b/internal/verify/test_data/generate_forged.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 + +import hashlib +import base64 + +# Hash server_list.json + +with open("server_list.json", "rb") as f: + b = f.read() + +with open("server_list.json.blake2b", "wb") as f: + f.write(hashlib.blake2b(b).digest()) + +# Forge pure signature on hash, see https://github.com/jedisct1/minisign/issues/104 + +with open("server_list.json.minisig", "rb") as f: + siglines = f.readlines() + +siglines[0] = b"untrusted comment: this signature has ED changed to Ed\n" +sig = base64.b64decode(siglines[1]) +siglines[1] = base64.b64encode(b"Ed" + sig[2:]) + b"\n" + +with open("server_list.json.forged_pure.minisig", "wb") as f: + f.writelines(siglines) + # Should now work: minisign -Vm server_list.json.blake2b -x server_list.json.forged_pure.minisig -p public-key + +# Try to forge key ID + +with open("server_list.json.wrong_key.minisig", "rb") as f: + siglines = f.readlines() + +siglines[ + 0 +] = b"untrusted comment: this signature was created with wrong_secret.key but has key ID changed to that of public.key\n" +sig_wrong = base64.b64decode(siglines[1]) +siglines[1] = ( + base64.b64encode(sig_wrong[:2] + sig[2 : 2 + 8] + sig_wrong[2 + 8 :]) + b"\n" +) + +with open("server_list.json.forged_keyid.minisig", "wb") as f: + f.writelines(siglines) diff --git a/internal/verify/test_data/organization_list.json b/internal/verify/test_data/organization_list.json new file mode 100644 index 0000000..8c53044 --- /dev/null +++ b/internal/verify/test_data/organization_list.json @@ -0,0 +1 @@ +{"organization_list": [{}]}
\ No newline at end of file diff --git a/internal/verify/test_data/organization_list.json.minisig b/internal/verify/test_data/organization_list.json.minisig new file mode 100644 index 0000000..1fa546e --- /dev/null +++ b/internal/verify/test_data/organization_list.json.minisig @@ -0,0 +1,4 @@ +untrusted comment: signature from minisign secret key +RURMm6vfaPgH31cHjNvTEh+TCqDVCwUgFVZoRdgWYAaQDxH3L3UIsRi9Qb1O4vLI4V1CYPatKzXZnSodSJM/AZgl9v7l/5bfPQ0= +trusted comment: timestamp:10 file:organization_list.json hashed +21zZv1DviMpLCdv1NgzLBl6d+F1ZllSNyjAquYxhTHGcs2F64bDFpqY0I0xjCHIoXly6HKqJKIBXNgud12ijCQ== diff --git a/internal/verify/test_data/organization_list.json.tc_servlist.minisig b/internal/verify/test_data/organization_list.json.tc_servlist.minisig new file mode 100644 index 0000000..a7fe41f --- /dev/null +++ b/internal/verify/test_data/organization_list.json.tc_servlist.minisig @@ -0,0 +1,4 @@ +untrusted comment: signature from minisign secret key +RURMm6vfaPgH31cHjNvTEh+TCqDVCwUgFVZoRdgWYAaQDxH3L3UIsRi9Qb1O4vLI4V1CYPatKzXZnSodSJM/AZgl9v7l/5bfPQ0= +trusted comment: timestamp:10 file:server_list.json hashed +R6hjM/oMS5LAvpYM4F6E7iUpnlPxqiY0QfuOnpum31CW0sUy/Ypy2PiomSwvZXKVR7keEZS/+lZjyra9TkrLDQ== diff --git a/internal/verify/test_data/other_list.json b/internal/verify/test_data/other_list.json new file mode 100644 index 0000000..25ba1a8 --- /dev/null +++ b/internal/verify/test_data/other_list.json @@ -0,0 +1 @@ +{"other_list": [{}]}
\ No newline at end of file diff --git a/internal/verify/test_data/other_list.json.minisig b/internal/verify/test_data/other_list.json.minisig new file mode 100644 index 0000000..eaa2248 --- /dev/null +++ b/internal/verify/test_data/other_list.json.minisig @@ -0,0 +1,4 @@ +untrusted comment: signature from minisign secret key +RURMm6vfaPgH366C1RnYeUAgEeX/S5A1Z9qmkV2+GJaVj06FWGd4aMLc+HS7iFMhG69u3TVD4YmzMH12rk7hQrnyCC6ex8ypIQA= +trusted comment: timestamp:10 file:other_list.json hashed +26+608n+bjQF9lwNdXbIK6t5bP8dzhjNQ9hACeYJLiB2tr437Aec2GkmJh0jSiRv1QV4RYBcKJeHQBUcV2grCQ== diff --git a/internal/verify/test_data/public.key b/internal/verify/test_data/public.key new file mode 100644 index 0000000..72676d3 --- /dev/null +++ b/internal/verify/test_data/public.key @@ -0,0 +1,2 @@ +untrusted comment: minisign public key DF07F868DFAB9B4C +RWRMm6vfaPgH39iT++NBiUKZim2nDWnalgkNROovPbZdSwVFgUdKU4ac diff --git a/internal/verify/test_data/random.txt b/internal/verify/test_data/random.txt new file mode 100644 index 0000000..b6fc4c6 --- /dev/null +++ b/internal/verify/test_data/random.txt @@ -0,0 +1 @@ +hello
\ No newline at end of file diff --git a/internal/verify/test_data/secret.key b/internal/verify/test_data/secret.key new file mode 100644 index 0000000..6e4af37 --- /dev/null +++ b/internal/verify/test_data/secret.key @@ -0,0 +1,2 @@ +untrusted comment: minisign encrypted secret key +RWRTY0IyobkTOt4ugAHNTPB6zOxHgX8spW6HQWddB5IrdCPDAgsAAAACAAAAAAAAAEAAAAAAvK1S1gsOgozZHuIdLWXq1IwxnWVr+dlySiykTbO6F85HvzPtgxZ7oLcGkT/vPdskAh0SV9H2ylHlt9oarXcWNDKs2r6EcZw/qy5FsD+5uhPfxwWV4qDF+1G456tYDYID63d50CgzdO0= diff --git a/internal/verify/test_data/server_list.json b/internal/verify/test_data/server_list.json new file mode 100644 index 0000000..67c4c8d --- /dev/null +++ b/internal/verify/test_data/server_list.json @@ -0,0 +1,3 @@ +{ +"server_list": [{}] +}
\ No newline at end of file diff --git a/internal/verify/test_data/server_list.json.blake2b b/internal/verify/test_data/server_list.json.blake2b Binary files differnew file mode 100644 index 0000000..5d2ca5a --- /dev/null +++ b/internal/verify/test_data/server_list.json.blake2b diff --git a/internal/verify/test_data/server_list.json.forged_keyid.minisig b/internal/verify/test_data/server_list.json.forged_keyid.minisig new file mode 100644 index 0000000..efa349d --- /dev/null +++ b/internal/verify/test_data/server_list.json.forged_keyid.minisig @@ -0,0 +1,4 @@ +untrusted comment: this signature was created with wrong_secret.key but has key ID changed to that of public.key +RURMm6vfaPgH35aarz3NMq4gbv6JvzOnjG003bDe6USu+HT/JzuxHjQcQGE/KBPdyCF6BDDwwFu+NVmi5jotYCJHWOEqSBU70gE= +trusted comment: timestamp:10 file:server_list.json hashed +3BWYJamM3t6ImuXQufTeO81UMZNyM7TujMu7SCmR+oapsSEBpmkazGOgzlJYR53HP9K9zrEA+4lV8gFFngooBA== diff --git a/internal/verify/test_data/server_list.json.forged_pure.minisig b/internal/verify/test_data/server_list.json.forged_pure.minisig new file mode 100644 index 0000000..a362504 --- /dev/null +++ b/internal/verify/test_data/server_list.json.forged_pure.minisig @@ -0,0 +1,4 @@ +untrusted comment: this signature has ED changed to Ed +RWRMm6vfaPgH3997FX/cHwhXJpcluwbNiznrfYV83WS/Gsd3BeO/g10Mo7Z9N5rMSXcpGrmT2CagiEEm5zSw/MEnTqs4YWICdQs= +trusted comment: timestamp:10 file:server_list.json hashed +oK41aX7rmpbO2ohF3v3+JGgSexQaVlfWvYPzaKEkDlJm8mVZtuK/h26SCRuL6PbTR92DLZU59rw8ckICUH/ADw== diff --git a/internal/verify/test_data/server_list.json.large_time.minisig b/internal/verify/test_data/server_list.json.large_time.minisig new file mode 100644 index 0000000..79a2a52 --- /dev/null +++ b/internal/verify/test_data/server_list.json.large_time.minisig @@ -0,0 +1,4 @@ +untrusted comment: signature from minisign secret key +RURMm6vfaPgH3997FX/cHwhXJpcluwbNiznrfYV83WS/Gsd3BeO/g10Mo7Z9N5rMSXcpGrmT2CagiEEm5zSw/MEnTqs4YWICdQs= +trusted comment: timestamp:4300000000 file:server_list.json +L9C58LIq7bTLf4otqW4Eb+ASL0+FM7nRRjstCBuCPtuUerFIsOqNUpDp2AQJJ4pZJKE7SkgIq2tV8/IaVpzxBQ== diff --git a/internal/verify/test_data/server_list.json.minisig b/internal/verify/test_data/server_list.json.minisig new file mode 100644 index 0000000..143585b --- /dev/null +++ b/internal/verify/test_data/server_list.json.minisig @@ -0,0 +1,4 @@ +untrusted comment: signature from minisign secret key +RURMm6vfaPgH3997FX/cHwhXJpcluwbNiznrfYV83WS/Gsd3BeO/g10Mo7Z9N5rMSXcpGrmT2CagiEEm5zSw/MEnTqs4YWICdQs= +trusted comment: timestamp:10 file:server_list.json hashed +oK41aX7rmpbO2ohF3v3+JGgSexQaVlfWvYPzaKEkDlJm8mVZtuK/h26SCRuL6PbTR92DLZU59rw8ckICUH/ADw== diff --git a/internal/verify/test_data/server_list.json.pure.minisig b/internal/verify/test_data/server_list.json.pure.minisig new file mode 100644 index 0000000..57dccfc --- /dev/null +++ b/internal/verify/test_data/server_list.json.pure.minisig @@ -0,0 +1,4 @@ +untrusted comment: signature from minisign secret key +RWRMm6vfaPgH3zQ/rcq2GMsNz1SYySz+olupm0I+nzNpOkPyUHTBwig3Pep4biOk/bH73bH+0sLNoZPcDk1f2Acn8JINc9MWMw4= +trusted comment: timestamp:10 file:server_list.json +FZ0eA96SlADsMrSOUgStQJpmUnBGpPbRvNI/oaYhKrylu6jUcXOgsRu6571mmDxYdlruSuUSlQbdmG81Qbl4AA== diff --git a/internal/verify/test_data/server_list.json.tc_earliertime.minisig b/internal/verify/test_data/server_list.json.tc_earliertime.minisig new file mode 100644 index 0000000..03da710 --- /dev/null +++ b/internal/verify/test_data/server_list.json.tc_earliertime.minisig @@ -0,0 +1,4 @@ +untrusted comment: signature from minisign secret key +RURMm6vfaPgH3997FX/cHwhXJpcluwbNiznrfYV83WS/Gsd3BeO/g10Mo7Z9N5rMSXcpGrmT2CagiEEm5zSw/MEnTqs4YWICdQs= +trusted comment: timestamp:9 file:server_list.json hashed +vw3wjLDNZWoV98/GnFv38REiaeh+wUPEZgmBUvY35CEq00jDdHiJcYRV/7zBoKv+n9TAYxZ8WKUOGWNOPonTBg== diff --git a/internal/verify/test_data/server_list.json.tc_emptyfile.minisig b/internal/verify/test_data/server_list.json.tc_emptyfile.minisig new file mode 100644 index 0000000..a7aa3ed --- /dev/null +++ b/internal/verify/test_data/server_list.json.tc_emptyfile.minisig @@ -0,0 +1,4 @@ +untrusted comment: signature from minisign secret key +RURMm6vfaPgH3997FX/cHwhXJpcluwbNiznrfYV83WS/Gsd3BeO/g10Mo7Z9N5rMSXcpGrmT2CagiEEm5zSw/MEnTqs4YWICdQs= +trusted comment: timestamp:10 file: hashed +g4drZ91TcYXNLnIGbeH5ZIFzrs2wWB9JTXjV3Jwg9ehSC2D8lCTqw3u2Rg+PvLPRvYmXTHyuJoKNWelsSh64CA== diff --git a/internal/verify/test_data/server_list.json.tc_emptytime.minisig b/internal/verify/test_data/server_list.json.tc_emptytime.minisig new file mode 100644 index 0000000..d3ef01e --- /dev/null +++ b/internal/verify/test_data/server_list.json.tc_emptytime.minisig @@ -0,0 +1,4 @@ +untrusted comment: signature from minisign secret key +RURMm6vfaPgH3997FX/cHwhXJpcluwbNiznrfYV83WS/Gsd3BeO/g10Mo7Z9N5rMSXcpGrmT2CagiEEm5zSw/MEnTqs4YWICdQs= +trusted comment: timestamp: file:server_list.json hashed +lw5rnZsPi+TkZ4lOCy7bjsUgTXxG+jaGOGdHuNL95FSD2mmP9ZzEJPrJ2jnH7iYfkF3zDm0QvEUDxhEirlHBDA== diff --git a/internal/verify/test_data/server_list.json.tc_latertime.minisig b/internal/verify/test_data/server_list.json.tc_latertime.minisig new file mode 100644 index 0000000..8237123 --- /dev/null +++ b/internal/verify/test_data/server_list.json.tc_latertime.minisig @@ -0,0 +1,4 @@ +untrusted comment: signature from minisign secret key +RURMm6vfaPgH3997FX/cHwhXJpcluwbNiznrfYV83WS/Gsd3BeO/g10Mo7Z9N5rMSXcpGrmT2CagiEEm5zSw/MEnTqs4YWICdQs= +trusted comment: timestamp:20 file:server_list.json hashed +rHcsHF2mmcZvDLreeuljVauuFULWiY8luCsxyBxxobcJkCedEDW3/RX5KeT+2NjHSFuQxkmrYOBWTY9+ECuUDQ== diff --git a/internal/verify/test_data/server_list.json.tc_nofile.minisig b/internal/verify/test_data/server_list.json.tc_nofile.minisig new file mode 100644 index 0000000..3c1dcbe --- /dev/null +++ b/internal/verify/test_data/server_list.json.tc_nofile.minisig @@ -0,0 +1,4 @@ +untrusted comment: signature from minisign secret key +RURMm6vfaPgH3997FX/cHwhXJpcluwbNiznrfYV83WS/Gsd3BeO/g10Mo7Z9N5rMSXcpGrmT2CagiEEm5zSw/MEnTqs4YWICdQs= +trusted comment: timestamp:10 hashed +NonaTZH7RDbsHXv85M7sL43YE7CTzs5qDRRoFYjajeqzHa+hdIuMGyemK85rAJ3prLGnMdWHkZhD4hsr3cZoDA== diff --git a/internal/verify/test_data/server_list.json.tc_nohashed.minisig b/internal/verify/test_data/server_list.json.tc_nohashed.minisig new file mode 100644 index 0000000..1d140c1 --- /dev/null +++ b/internal/verify/test_data/server_list.json.tc_nohashed.minisig @@ -0,0 +1,4 @@ +untrusted comment: signature from minisign secret key +RURMm6vfaPgH3997FX/cHwhXJpcluwbNiznrfYV83WS/Gsd3BeO/g10Mo7Z9N5rMSXcpGrmT2CagiEEm5zSw/MEnTqs4YWICdQs= +trusted comment: timestamp:10 file:server_list.json +HaPGKT+Jqxjyw2Nt1GEKaPIZsAmVl/RI6p1mQ+S1LqzYicVgT5GxPs9NR6khdGGIFvo/xhVkXFceAWTRUCVQAg== diff --git a/internal/verify/test_data/server_list.json.tc_notime.minisig b/internal/verify/test_data/server_list.json.tc_notime.minisig new file mode 100644 index 0000000..39625c3 --- /dev/null +++ b/internal/verify/test_data/server_list.json.tc_notime.minisig @@ -0,0 +1,4 @@ +untrusted comment: signature from minisign secret key +RURMm6vfaPgH3997FX/cHwhXJpcluwbNiznrfYV83WS/Gsd3BeO/g10Mo7Z9N5rMSXcpGrmT2CagiEEm5zSw/MEnTqs4YWICdQs= +trusted comment: file:server_list.json hashed +dMhb+0Y0KAO2tzI4g0ukL/VdMiLVopmXa9BS1RQBY8bYwzmebdIM4DAIZrhtO1avkpdy0prZehuhA1No6cOSAw== diff --git a/internal/verify/test_data/server_list.json.tc_orglist.minisig b/internal/verify/test_data/server_list.json.tc_orglist.minisig new file mode 100644 index 0000000..7c2a3a8 --- /dev/null +++ b/internal/verify/test_data/server_list.json.tc_orglist.minisig @@ -0,0 +1,4 @@ +untrusted comment: signature from minisign secret key +RURMm6vfaPgH3997FX/cHwhXJpcluwbNiznrfYV83WS/Gsd3BeO/g10Mo7Z9N5rMSXcpGrmT2CagiEEm5zSw/MEnTqs4YWICdQs= +trusted comment: timestamp:10 file:organization_list.json hashed +NreDM4iGEjMWs5sfaJCGZBZ7D9QLqxBKJ/fVW2lvIDr249DSUNR4ZRca8UL73e3c9eTXgHnY/ojsjDtzxDScDw== diff --git a/internal/verify/test_data/server_list.json.tc_otherfile.minisig b/internal/verify/test_data/server_list.json.tc_otherfile.minisig new file mode 100644 index 0000000..58a29b2 --- /dev/null +++ b/internal/verify/test_data/server_list.json.tc_otherfile.minisig @@ -0,0 +1,4 @@ +untrusted comment: signature from minisign secret key +RURMm6vfaPgH3997FX/cHwhXJpcluwbNiznrfYV83WS/Gsd3BeO/g10Mo7Z9N5rMSXcpGrmT2CagiEEm5zSw/MEnTqs4YWICdQs= +trusted comment: timestamp:10 file:otherfile hashed +PfDEIMlt2aNFyOnqHb45S7xm4fIg0vfUUbqXENPxry9GEZFX14c5BGtgcL/krDg8WFJHcIA5bzYcX58kgBiZCA== diff --git a/internal/verify/test_data/server_list.json.tc_random.minisig b/internal/verify/test_data/server_list.json.tc_random.minisig new file mode 100644 index 0000000..7240980 --- /dev/null +++ b/internal/verify/test_data/server_list.json.tc_random.minisig @@ -0,0 +1,4 @@ +untrusted comment: signature from minisign secret key +RURMm6vfaPgH3997FX/cHwhXJpcluwbNiznrfYV83WS/Gsd3BeO/g10Mo7Z9N5rMSXcpGrmT2CagiEEm5zSw/MEnTqs4YWICdQs= +trusted comment: random stuff +szGsyESH0EizTXH6n0yuQg6sHTKXr+TJW/Er9ZNJYgQV+1hVM+fc5q1EmVsJlA3kW4Rt/d1p9F0ShLIIgW2vAA== diff --git a/internal/verify/test_data/server_list.json.wrong_key.minisig b/internal/verify/test_data/server_list.json.wrong_key.minisig new file mode 100644 index 0000000..5a83c0e --- /dev/null +++ b/internal/verify/test_data/server_list.json.wrong_key.minisig @@ -0,0 +1,4 @@ +untrusted comment: signature from minisign secret key +RUTQvDHvQuYCCJaarz3NMq4gbv6JvzOnjG003bDe6USu+HT/JzuxHjQcQGE/KBPdyCF6BDDwwFu+NVmi5jotYCJHWOEqSBU70gE= +trusted comment: timestamp:10 file:server_list.json hashed +3BWYJamM3t6ImuXQufTeO81UMZNyM7TujMu7SCmR+oapsSEBpmkazGOgzlJYR53HP9K9zrEA+4lV8gFFngooBA== diff --git a/internal/verify/test_data/wrong_public.key b/internal/verify/test_data/wrong_public.key new file mode 100644 index 0000000..aa794d4 --- /dev/null +++ b/internal/verify/test_data/wrong_public.key @@ -0,0 +1,2 @@ +untrusted comment: minisign public key 802E642EF31BCD0 +RWTQvDHvQuYCCPDLi3UCXzj3BbzFM5QxUFfrp174iaqYo8lT0VaAkhOt diff --git a/internal/verify/test_data/wrong_secret.key b/internal/verify/test_data/wrong_secret.key new file mode 100644 index 0000000..68e9092 --- /dev/null +++ b/internal/verify/test_data/wrong_secret.key @@ -0,0 +1,2 @@ +untrusted comment: minisign encrypted secret key +RWRTY0Iyrc2CTG2W1ZqEq9tb94oQWTnYUy4k8boMf13478FwlDYAAAACAAAAAAAAAEAAAAAA2gFhwOtjETu5WN1LpgtJHV1dk/7466LBJ8dgO/pZoQ3LLAYxlswJHVR/N/Q1HmmKvlxWo2jNTJcARXuHHlMTEgg1MERTldE88CqETrVbvq1JaqJlAY/HMkiqNEUR3L6+5VHbYPKXlVQ= diff --git a/internal/verify/verify.go b/internal/verify/verify.go new file mode 100644 index 0000000..2d53b2e --- /dev/null +++ b/internal/verify/verify.go @@ -0,0 +1,205 @@ +package verify + +import ( + "errors" + "fmt" + "os" + + "github.com/jedisct1/go-minisign" +) + +// getKeys returns keys taken from https://git.sr.ht/~eduvpn/disco.eduvpn.org#public-keys. +func getKeys() []string { + return []string{ + "RWRtBSX1alxyGX+Xn3LuZnWUT0w//B6EmTJvgaAxBMYzlQeI+jdrO6KF", // fkooman@tuxed.net, kolla@uninett.no + "RWQKqtqvd0R7rUDp0rWzbtYPA3towPWcLDCl7eY9pBMMI/ohCmrS0WiM", // RoSp + } +} + +// Verify verifies the signature (.minisig file format) on signedJson. +// +// expectedFileName must be set to the file type to be verified, either "server_list.json" or "organization_list.json". +// minSign must be set to the minimum UNIX timestamp (without milliseconds) for the file version. +// This value should not be smaller than the time on the previous document verified. +// forcePrehash indicates whether or not we want to force the use of prehashed signatures +// In the future we want to remove this parameter and only allow prehashed signatures +// +// The return value will either be (true, nil) for a valid signature or (false, VerifyError) otherwise. +// +// Verify is a wrapper around verifyWithKeys where allowedPublicKeys is set to the list from https://git.sr.ht/~eduvpn/disco.eduvpn.org#public-keys. +func Verify(signatureFileContent string, signedJson []byte, expectedFileName string, minSignTime uint64, forcePrehash bool) (bool, error) { + keyStrs := getKeys() + if extraKey != "" { + keyStrs = append(keyStrs, extraKey) + _, err := fmt.Fprintf(os.Stderr, "INSECURE TEST MODE ENABLED WITH KEY %q\n", extraKey) + if err != nil { + panic(err) + } + } + valid, err := verifyWithKeys(signatureFileContent, signedJson, expectedFileName, minSignTime, keyStrs, forcePrehash) + if err != nil { + var verifyCreatePublickeyError *VerifyCreatePublicKeyError + if errors.As(err, &verifyCreatePublickeyError) { + panic(err) // This should not happen unless keyStrs has an invalid key + } + return valid, err + } + return valid, nil +} + +// extraKey is an extra allowed key for testing. +var extraKey = "" + +// InsecureTestingSetExtraKey adds an extra allowed key for verification with Verify. +// ONLY USE FOR TESTING. Applies to all threads. Probably not thread-safe. Do not call in parallel to Verify. +// +// keyString must be a Base64-encoded Minisign key, or empty to reset. +func InsecureTestingSetExtraKey(keyString string) { + extraKey = keyString +} + +// verifyWithKeys verifies the Minisign signature in signatureFileContent (minisig file format) over the server_list/organization_list JSON in signedJson. +// +// Verification is performed using a matching key in allowedPublicKeys. +// The signature is checked to be a Ed25519 Minisign (optionally Ed25519 Blake2b-512 prehashed, see forcePrehash) signature with a valid trusted comment. +// The file type that is verified is indicated by expectedFileName, which must be one of "server_list.json"/"organization_list.json". +// The trusted comment is checked to be of the form "timestamp:<timestamp>\tfile:<expectedFileName>", optionally suffixed by something, e.g. "\thashed". +// The signature is checked to have a timestamp with a value of at least minSignTime, which is a UNIX timestamp without milliseconds. +// +// The return value will either be (true, nil) on success or (false, detailedVerifyError) on failure. +func verifyWithKeys(signatureFileContent string, signedJson []byte, filename string, minSignTime uint64, allowedPublicKeys []string, forcePrehash bool) (bool, error) { + switch filename { + case "server_list.json", "organization_list.json": + break + default: + return false, &VerifyUnknownExpectedFilenameError{Filename: filename, Expected: "server_list.json or organization_list.json"} + } + + sig, err := minisign.DecodeSignature(signatureFileContent) + if err != nil { + return false, &VerifyInvalidSignatureFormatError{Err: err} + } + + // Check if signature is prehashed, see https://jedisct1.github.io/minisign/#signature-format + if forcePrehash && sig.SignatureAlgorithm != [2]byte{'E', 'D'} { + return false, &VerifyInvalidSignatureAlgorithmError{Algorithm: string(sig.SignatureAlgorithm[:]), WantedAlgorithm: "ED (BLAKE2b-prehashed EdDSA)"} + } + + // Find allowed key used for signature + for _, keyStr := range allowedPublicKeys { + key, err := minisign.NewPublicKey(keyStr) + if err != nil { + // Should only happen if Verify is wrong or extraKey is invalid + return false, &VerifyCreatePublicKeyError{PublicKey: keyStr, Err: err} + } + + if sig.KeyId != key.KeyId { + continue // Wrong key + } + + valid, err := key.Verify(signedJson, sig) + if !valid { + return false, &VerifyInvalidSignatureError{Err: err} + } + + // Parse trusted comment + var signTime uint64 + var sigFileName string + // sigFileName cannot have spaces + _, err = fmt.Sscanf(sig.TrustedComment, "trusted comment: timestamp:%d\tfile:%s", &signTime, &sigFileName) + if err != nil { + return false, &VerifyInvalidTrustedCommentError{TrustedComment: sig.TrustedComment, Err: err} + } + + if sigFileName != filename { + return false, &VerifyWrongSigFilenameError{Filename: filename, SigFilename: sigFileName} + } + + if signTime < minSignTime { + return false, &VerifySigTimeEarlierError{SigTime: signTime, MinSigTime: minSignTime} + } + + return true, nil + } + + // No matching allowed key found + return false, &VerifyUnknownKeyError{Filename: filename} +} + +type VerifyUnknownExpectedFilenameError struct { + Filename string + Expected string +} + +func (e *VerifyUnknownExpectedFilenameError) Error() string { + return fmt.Sprintf("invalid filename: %s, expected: %s", e.Filename, e.Expected) +} + +type VerifyInvalidSignatureFormatError struct { + Err error +} + +func (e *VerifyInvalidSignatureFormatError) Error() string { + return fmt.Sprintf("invalid signature format with error: %v", e.Err) +} + +type VerifyInvalidSignatureAlgorithmError struct { + Algorithm string + WantedAlgorithm string +} + +func (e *VerifyInvalidSignatureAlgorithmError) Error() string { + return fmt.Sprintf("invalid signature algorithm: %s, wanted: %s", e.Algorithm, e.WantedAlgorithm) +} + +type VerifyCreatePublicKeyError struct { + PublicKey string + Err error +} + +func (e *VerifyCreatePublicKeyError) Error() string { + return fmt.Sprintf("failed to create public key: %s with error: %v", e.PublicKey, e.Err) +} + +type VerifyInvalidSignatureError struct { + Err error +} + +func (e *VerifyInvalidSignatureError) Error() string { + return fmt.Sprintf("invalid signature with error: %v", e.Err) +} + +type VerifyInvalidTrustedCommentError struct { + TrustedComment string + Err error +} + +func (e *VerifyInvalidTrustedCommentError) Error() string { + return fmt.Sprintf("invalid trusted comment: %s with error: %v", e.TrustedComment, e.Err) +} + +type VerifyWrongSigFilenameError struct { + Filename string + SigFilename string +} + +func (e *VerifyWrongSigFilenameError) Error() string { + return fmt.Sprintf("wrong filename: %s, expected filename: %s for signature", e.Filename, e.SigFilename) +} + +type VerifySigTimeEarlierError struct { + SigTime uint64 + MinSigTime uint64 +} + +func (e *VerifySigTimeEarlierError) Error() string { + return fmt.Sprintf("Sign time: %d is earlier than sign time: %d", e.SigTime, e.MinSigTime) +} + +type VerifyUnknownKeyError struct { + Filename string +} + +func (e *VerifyUnknownKeyError) Error() string { + return fmt.Sprintf("signature for filename: %s was created with an unknown key", e.Filename) +} diff --git a/internal/verify/verify_test.go b/internal/verify/verify_test.go new file mode 100644 index 0000000..7d577dd --- /dev/null +++ b/internal/verify/verify_test.go @@ -0,0 +1,140 @@ +package verify + +import ( + "bufio" + "errors" + "fmt" + "io/ioutil" + "os" + "testing" +) + +func Test_verifyWithKeys(t *testing.T) { + var err error + + var pk []string + { + file, err := os.Open("test_data/public.key") + if err != nil { + panic(err) + } + defer file.Close() + + // Get last line (key string) from file + scanner := bufio.NewScanner(file) + for i := 0; i < 2; i++ { + if !scanner.Scan() { + panic(scanner.Err()) + } + } + pk = []string{scanner.Text()} + } + + var ( + verifyCreatePublicKeyError *VerifyCreatePublicKeyError + verifyInvalidSignatureAlgorithmError *VerifyInvalidSignatureAlgorithmError + verifyWrongSigFilenameError *VerifyWrongSigFilenameError + verifyInvalidTrustedCommentError *VerifyInvalidTrustedCommentError + verifyInvalidSignatureFormatError *VerifyInvalidSignatureFormatError + verifyInvalidSignatureError *VerifyInvalidSignatureError + verifySigTimeEarlierError *VerifySigTimeEarlierError + verifyUnknownExpectedFilenameError *VerifyUnknownExpectedFilenameError + verifyUnknownKeyError *VerifyUnknownKeyError + ) + + tests := []struct { + expectedErr interface{} + testName string + signatureFile string + jsonFile string + expectedFileName string + minSignTime uint64 + allowedPks []string + }{ + {&verifyInvalidSignatureAlgorithmError, "pure", "server_list.json.pure.minisig", "server_list.json", "server_list.json", 10, pk}, + + {nil, "valid server_list", "server_list.json.minisig", "server_list.json", "server_list.json", 10, pk}, + {nil, "TC no hashed", "server_list.json.tc_nohashed.minisig", "server_list.json", "server_list.json", 10, pk}, + {nil, "TC later time", "server_list.json.tc_latertime.minisig", "server_list.json", "server_list.json", 10, pk}, + {&verifyWrongSigFilenameError, "server_list TC file:organization_list", "server_list.json.tc_orglist.minisig", "server_list.json", "server_list.json", 10, pk}, + {&verifyWrongSigFilenameError, "organization_list as server_list", "organization_list.json.minisig", "organization_list.json", "server_list.json", 10, pk}, + {&verifyWrongSigFilenameError, "TC file:otherfile", "server_list.json.tc_otherfile.minisig", "server_list.json", "server_list.json", 10, pk}, + {&verifySigTimeEarlierError, "TC no file", "server_list.json.tc_nofile.minisig", "server_list.json", "server_list.json", 10, pk}, + {&verifySigTimeEarlierError, "TC no time", "server_list.json.tc_notime.minisig", "server_list.json", "server_list.json", 10, pk}, + {&verifySigTimeEarlierError, "TC empty time", "server_list.json.tc_emptytime.minisig", "server_list.json", "server_list.json", 10, pk}, + {&verifyInvalidSignatureFormatError, "TC empty file", "server_list.json.tc_emptyfile.minisig", "server_list.json", "server_list.json", 10, pk}, + {&verifyInvalidTrustedCommentError, "TC random", "server_list.json.tc_random.minisig", "server_list.json", "server_list.json", 10, pk}, + {nil, "large time", "server_list.json.large_time.minisig", "server_list.json", "server_list.json", 43e8, pk}, + {nil, "lower min time", "server_list.json.minisig", "server_list.json", "server_list.json", 5, pk}, + {&verifySigTimeEarlierError, "higher min time", "server_list.json.minisig", "server_list.json", "server_list.json", 11, pk}, + + {nil, "valid organization_list", "organization_list.json.minisig", "organization_list.json", "organization_list.json", 10, pk}, + {&verifyWrongSigFilenameError, "organization_list TC file:server_list", "organization_list.json.tc_servlist.minisig", "organization_list.json", "organization_list.json", 10, pk}, + {&verifyWrongSigFilenameError, "server_list as organization_list", "server_list.json.minisig", "server_list.json", "organization_list.json", 10, pk}, + + {&verifyUnknownExpectedFilenameError, "valid other_list", "other_list.json.minisig", "other_list.json", "other_list.json", 10, pk}, + {&verifyWrongSigFilenameError, "other_list as server_list", "other_list.json.minisig", "other_list.json", "server_list.json", 10, pk}, + + {&verifyInvalidSignatureFormatError, "invalid signature file", "random.txt", "server_list.json", "server_list.json", 10, pk}, + {&verifyInvalidSignatureFormatError, "empty signature file", "empty", "server_list.json", "server_list.json", 10, pk}, + + {&verifyUnknownKeyError, "wrong key", "server_list.json.wrong_key.minisig", "server_list.json", "server_list.json", 10, pk}, + + {&verifyInvalidSignatureAlgorithmError, "forged pure signature", "server_list.json.forged_pure.minisig", "server_list.json.blake2b", "server_list.json", 10, pk}, + {&verifyInvalidSignatureError, "forged key ID", "server_list.json.forged_keyid.minisig", "server_list.json", "server_list.json", 10, pk}, + + {&verifyUnknownKeyError, "no allowed keys", "server_list.json.minisig", "server_list.json", "server_list.json", 10, []string{}}, + {nil, "multiple allowed keys 1", "server_list.json.minisig", "server_list.json", "server_list.json", 10, []string{ + pk[0], "RWSf0PYToIUJmDlsz21YOXvgQzHj9NSdyJUqEY5ZdfS9GepeXt3+JJRZ", + }}, + {nil, "multiple allowed keys 2", "server_list.json.minisig", "server_list.json", "server_list.json", 10, []string{ + "RWSf0PYToIUJmDlsz21YOXvgQzHj9NSdyJUqEY5ZdfS9GepeXt3+JJRZ", pk[0], + }}, + {&verifyCreatePublicKeyError, "invalid allowed key", "server_list.json.minisig", "server_list.json", "server_list.json", 10, []string{"AAA"}}, + } + + // Cache file contents in map, mapping file names to contents + files := map[string][]byte{} + loadFile := func(name string) { + content, loaded := files[name] + if !loaded { + content, err = ioutil.ReadFile("test_data/" + name) + if err != nil { + panic(err) + } + files[name] = content + } + } + for _, test := range tests { + loadFile(test.signatureFile) + loadFile(test.jsonFile) + } + + forcePrehash := true + for _, tt := range tests { + t.Run(tt.testName, func(t *testing.T) { + t.Parallel() + valid, err := verifyWithKeys(string(files[tt.signatureFile]), files[tt.jsonFile], + tt.expectedFileName, tt.minSignTime, tt.allowedPks, forcePrehash) + compareResults(t, valid, err, tt.expectedErr, func() string { + return fmt.Sprintf("verifyWithKeys(%q, %q, %q, %v, %v, %t)", + tt.signatureFile, tt.jsonFile, tt.expectedFileName, tt.minSignTime, tt.allowedPks, forcePrehash) + }) + }) + } +} + +// compareResults compares returned ret, err from a verify function with expected error code expected. +// callStr is called to get the formatted parameters passed to the function. +func compareResults(t *testing.T, ret bool, err error, expectedErr interface{}, callStr func() string) { + // different error returned + if expectedErr != nil && !errors.As(err, expectedErr) { + t.Errorf("%v\nerror %T = %v, wantErr %T", callStr(), err, err, expectedErr) + return + } + // different boolean returned + expectedBool := expectedErr == nil + if ret != expectedBool { + t.Errorf("%v\n= %v, want %v", callStr(), ret, expectedBool) + } +} |
