summaryrefslogtreecommitdiff
path: root/internal/verify
diff options
context:
space:
mode:
authorjwijenbergh <jeroenwijenbergh@protonmail.com>2022-06-17 14:00:40 +0200
committerjwijenbergh <jeroenwijenbergh@protonmail.com>2022-09-20 20:31:23 +0200
commit7af07c596166bf93b79a9d0816b1950dde360fb9 (patch)
tree08b5374c34d6c33b3c596ed981bfb069cca37ade /internal/verify
parent6dc7b64f634f6dcbeedea24c741382366a3c7b8c (diff)
Server: Implement function for checking renewal button visibility
Diffstat (limited to 'internal/verify')
-rw-r--r--internal/verify/test_data/empty0
-rw-r--r--internal/verify/test_data/generate.sh58
-rw-r--r--internal/verify/test_data/generate_forged.py41
-rw-r--r--internal/verify/test_data/organization_list.json1
-rw-r--r--internal/verify/test_data/organization_list.json.minisig4
-rw-r--r--internal/verify/test_data/organization_list.json.tc_servlist.minisig4
-rw-r--r--internal/verify/test_data/other_list.json1
-rw-r--r--internal/verify/test_data/other_list.json.minisig4
-rw-r--r--internal/verify/test_data/public.key2
-rw-r--r--internal/verify/test_data/random.txt1
-rw-r--r--internal/verify/test_data/secret.key2
-rw-r--r--internal/verify/test_data/server_list.json3
-rw-r--r--internal/verify/test_data/server_list.json.blake2bbin0 -> 64 bytes
-rw-r--r--internal/verify/test_data/server_list.json.forged_keyid.minisig4
-rw-r--r--internal/verify/test_data/server_list.json.forged_pure.minisig4
-rw-r--r--internal/verify/test_data/server_list.json.large_time.minisig4
-rw-r--r--internal/verify/test_data/server_list.json.minisig4
-rw-r--r--internal/verify/test_data/server_list.json.pure.minisig4
-rw-r--r--internal/verify/test_data/server_list.json.tc_earliertime.minisig4
-rw-r--r--internal/verify/test_data/server_list.json.tc_emptyfile.minisig4
-rw-r--r--internal/verify/test_data/server_list.json.tc_emptytime.minisig4
-rw-r--r--internal/verify/test_data/server_list.json.tc_latertime.minisig4
-rw-r--r--internal/verify/test_data/server_list.json.tc_nofile.minisig4
-rw-r--r--internal/verify/test_data/server_list.json.tc_nohashed.minisig4
-rw-r--r--internal/verify/test_data/server_list.json.tc_notime.minisig4
-rw-r--r--internal/verify/test_data/server_list.json.tc_orglist.minisig4
-rw-r--r--internal/verify/test_data/server_list.json.tc_otherfile.minisig4
-rw-r--r--internal/verify/test_data/server_list.json.tc_random.minisig4
-rw-r--r--internal/verify/test_data/server_list.json.wrong_key.minisig4
-rw-r--r--internal/verify/test_data/wrong_public.key2
-rw-r--r--internal/verify/test_data/wrong_secret.key2
-rw-r--r--internal/verify/verify.go205
-rw-r--r--internal/verify/verify_test.go140
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
new file mode 100644
index 0000000..5d2ca5a
--- /dev/null
+++ b/internal/verify/test_data/server_list.json.blake2b
Binary files differ
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)
+ }
+}