@@ -12,6 +12,102 @@ YELLOW='\033[0;33m'; RED='\033[0;31m'
1212MAGENTA=' \033[0;35m' ; CYAN=' \033[0;36m'
1313NC=' \033[0m'
1414
15+ # ============================================================================
16+ # CRYPTOGRAPHIC SIGNATURE VERIFICATION
17+ # ============================================================================
18+ # Ed25519 Public Key for verifying update signatures
19+ # This key is embedded in the script to prevent tampering
20+ # Generate new keypair: Run API with SIGNING_PRIVATE_KEY not set, then generate
21+ # PUBLIC_KEY_BASE64="YOUR_PUBLIC_KEY_HERE"
22+ PUBLIC_KEY_BASE64=" 5MpxNNMkROJLixsSTk/PDBAFRF3bP+zr3U0lzzK/py4="
23+
24+ # Verify signature of downloaded update
25+ # Usage: verify_signature <file_path> <signature_base64> <expected_hash>
26+ # Returns: 0 if valid, 1 if invalid or verification unavailable
27+ verify_signature () {
28+ local file_path=" $1 "
29+ local signature_b64=" $2 "
30+ local expected_hash=" $3 "
31+
32+ # Skip verification if no public key is configured
33+ if [[ -z " $PUBLIC_KEY_BASE64 " ]]; then
34+ echo -e " ${YELLOW} [AutoUpdate] ⚠ Signature verification skipped - no public key configured${NC} "
35+ return 0
36+ fi
37+
38+ # Check if openssl is available
39+ if ! command -v openssl > /dev/null 2>&1 ; then
40+ echo -e " ${YELLOW} [AutoUpdate] ⚠ Signature verification skipped - openssl not available${NC} "
41+ return 0
42+ fi
43+
44+ echo -e " ${CYAN} [AutoUpdate] 🔐 Verifying cryptographic signature...${NC} "
45+
46+ # Verify file hash first
47+ local actual_hash
48+ actual_hash=$( sha256sum " $file_path " | cut -d' ' -f1)
49+
50+ if [[ " $actual_hash " != " $expected_hash " ]]; then
51+ echo -e " ${RED} [AutoUpdate] ✗ Hash mismatch!${NC} "
52+ echo -e " ${RED} [AutoUpdate] Expected: $expected_hash ${NC} "
53+ echo -e " ${RED} [AutoUpdate] Actual: $actual_hash ${NC} "
54+ return 1
55+ fi
56+
57+ echo -e " ${GREEN} [AutoUpdate] ✓ Hash verified: ${actual_hash: 0: 16} ...${NC} "
58+
59+ # Decode public key and signature to temp files
60+ local temp_dir
61+ temp_dir=$( mktemp -d)
62+ local pub_key_file=" $temp_dir /public.pem"
63+ local sig_file=" $temp_dir /signature.bin"
64+ local hash_file=" $temp_dir /hash.bin"
65+
66+ # Convert base64 public key to PEM format for Ed25519
67+ # Ed25519 public key is 32 bytes, we need to wrap it in proper ASN.1 structure
68+ {
69+ echo " -----BEGIN PUBLIC KEY-----"
70+ # Add Ed25519 OID prefix (MCowBQYDK2VwAyEA) + base64 public key
71+ echo " MCowBQYDK2VwAyEA$( echo " $PUBLIC_KEY_BASE64 " ) " | fold -w 64
72+ echo " -----END PUBLIC KEY-----"
73+ } > " $pub_key_file "
74+
75+ # Decode signature from base64
76+ echo " $signature_b64 " | base64 -d > " $sig_file " 2> /dev/null || {
77+ echo -e " ${RED} [AutoUpdate] ✗ Failed to decode signature${NC} "
78+ rm -rf " $temp_dir "
79+ return 1
80+ }
81+
82+ # Create hash file (the signature is over the hash, not the file directly)
83+ # Convert hex to binary - use xxd if available, otherwise use printf
84+ if command -v xxd > /dev/null 2>&1 ; then
85+ echo -n " $expected_hash " | xxd -r -p > " $hash_file "
86+ else
87+ # Fallback: convert hex to binary using printf
88+ local hex=" $expected_hash "
89+ local i
90+ > " $hash_file " # Create empty file
91+ for (( i= 0 ; i< ${# hex} ; i+= 2 )) ; do
92+ printf " \x${hex: $i : 2} " >> " $hash_file "
93+ done
94+ fi
95+
96+ # Verify signature using openssl (Ed25519)
97+ if openssl pkeyutl -verify -pubin -inkey " $pub_key_file " \
98+ -sigfile " $sig_file " -in " $hash_file " -rawin 2> /dev/null; then
99+ echo -e " ${GREEN} [AutoUpdate] ✓ Signature verified - update is authentic${NC} "
100+ rm -rf " $temp_dir "
101+ return 0
102+ else
103+ echo -e " ${RED} [AutoUpdate] ✗ Signature verification FAILED!${NC} "
104+ echo -e " ${RED} [AutoUpdate] The update may have been tampered with.${NC} "
105+ echo -e " ${RED} [AutoUpdate] Update will NOT be applied for security reasons.${NC} "
106+ rm -rf " $temp_dir "
107+ return 1
108+ fi
109+ }
110+
15111# Header function
16112header () {
17113 echo -e " ${BLUE} ───────────────────────────────────────────────${NC} "
@@ -270,13 +366,24 @@ apply_update() {
270366 # Extract download URL from response
271367 local download_url
272368 download_url=$( echo " $diff_response " | grep -o ' "download_url":"[^"]*"' | cut -d' "' -f4)
273-
369+
274370 if [[ -z " $download_url " ]]; then
275371 echo -e " ${RED} [AutoUpdate] No download URL found in diff response${NC} "
276372 rm -f " $temp_diff_file "
277373 return 1
278374 fi
279-
375+
376+ # Extract signature information for verification
377+ local signature checksum
378+ signature=$( echo " $diff_response " | grep -o ' "signature":"[^"]*"' | cut -d' "' -f4 || echo " " )
379+ checksum=$( echo " $diff_response " | grep -o ' "checksum":"[^"]*"' | cut -d' "' -f4 || echo " " )
380+
381+ if [[ -n " $signature " && -n " $checksum " ]]; then
382+ echo -e " ${CYAN} [AutoUpdate] 🔐 Signed update detected${NC} "
383+ elif [[ -n " $PUBLIC_KEY_BASE64 " ]]; then
384+ echo -e " ${YELLOW} [AutoUpdate] ⚠ Update is unsigned but signature verification is enabled${NC} "
385+ fi
386+
280387 # Extract file change information for logging
281388 local total_changes files_added files_modified files_removed
282389 total_changes=$( echo " $diff_response " | grep -o ' "total_changes":[0-9]*' | cut -d' :' -f2)
@@ -321,7 +428,23 @@ apply_update() {
321428 rm -f " $zip_file "
322429 return 1
323430 fi
324-
431+
432+ # Verify signature if available
433+ if [[ -n " $signature " && -n " $checksum " ]]; then
434+ if ! verify_signature " $zip_file " " $signature " " $checksum " ; then
435+ echo -e " ${RED} [AutoUpdate] ✗ Signature verification failed - aborting update${NC} "
436+ rm -f " $zip_file " " $temp_diff_file "
437+ return 1
438+ fi
439+ elif [[ -n " $PUBLIC_KEY_BASE64 " && " $PUBLIC_KEY_BASE64 " != " YOUR_PUBLIC_KEY_HERE" ]]; then
440+ # Public key is configured but update is unsigned - reject for security
441+ echo -e " ${RED} [AutoUpdate] ✗ Update is unsigned but signature verification is required${NC} "
442+ echo -e " ${RED} [AutoUpdate] ✗ Aborting update for security reasons${NC} "
443+ echo -e " ${YELLOW} [AutoUpdate] To allow unsigned updates, remove or comment out PUBLIC_KEY_BASE64${NC} "
444+ rm -f " $zip_file " " $temp_diff_file "
445+ return 1
446+ fi
447+
325448 echo -e " ${WHITE} [AutoUpdate] Extracting and applying updates...${NC} "
326449
327450 # Extract to temporary directory
0 commit comments