From 9ae5e409c236aee8c15b2d261bb9eb5c5c66cfb0 Mon Sep 17 00:00:00 2001 From: MickLesk <47820557+MickLesk@users.noreply.github.com> Date: Thu, 12 Nov 2026 17:43:05 +0100 Subject: [PATCH 1/2] Issue self-signed certificates for 825 days and renew expiring ones create_self_signed_cert issued 365-day certificates and never replaced one: an existing certificate was kept even after it had expired. Clients that verify strictly (Engram's sync client, Zitadel's login) stopped working after a year. New certificates now last 825 days, the most macOS and iOS accept even for a certificate the user has trusted. Called again, for example from an update, the helper replaces a certificate with less than 30 days left and keeps any other. --- .github/workflows/verify-split-files.yml | 4 ++ lib/system.func | 9 ++++- tests/self-signed-cert.sh | 49 ++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 tests/self-signed-cert.sh diff --git a/.github/workflows/verify-split-files.yml b/.github/workflows/verify-split-files.yml index 5909a40..80e5dba 100644 --- a/.github/workflows/verify-split-files.yml +++ b/.github/workflows/verify-split-files.yml @@ -72,6 +72,7 @@ on: - "tests/db-helper-context.sh" - "tests/forge-tag-spelling.sh" - "tests/forge-deb-services.sh" + - "tests/self-signed-cert.sh" - "tests/cs-clear.sh" - "tests/forge-appversion-scope.sh" - "tests/forge-singlefile-compressed.sh" @@ -143,6 +144,7 @@ on: - "tests/db-helper-context.sh" - "tests/forge-tag-spelling.sh" - "tests/forge-deb-services.sh" + - "tests/self-signed-cert.sh" - "tests/cs-clear.sh" - "tests/forge-appversion-scope.sh" - "tests/forge-singlefile-compressed.sh" @@ -305,6 +307,8 @@ jobs: run: bash tests/forge-tag-spelling.sh - name: Services after a .deb install tests run: bash tests/forge-deb-services.sh + - name: Self-signed certificate tests + run: bash tests/self-signed-cert.sh - name: Screen clear without a terminal tests run: bash tests/cs-clear.sh - name: App version pin and .deb stamp tests diff --git a/lib/system.func b/lib/system.func index fc56a65..9267b14 100644 --- a/lib/system.func +++ b/lib/system.func @@ -2166,8 +2166,12 @@ create_self_signed_cert() { local CERT_KEY="${CERT_DIR}/${APP_NAME_LC}.key" local CERT_CRT="${CERT_DIR}/${APP_NAME_LC}.crt" + # A certificate that still has 30 days left is kept. Called again (e.g. from an + # update), an expiring one is replaced: it used to stay until clients refused it. if [[ -f "$CERT_CRT" && -f "$CERT_KEY" ]]; then - return 0 + if ! command -v openssl >/dev/null 2>&1 || openssl x509 -checkend 2592000 -noout -in "$CERT_CRT" >/dev/null 2>&1; then + return 0 + fi fi # Use ensure_dependencies for cleaner handling @@ -2180,7 +2184,8 @@ create_self_signed_cert() { [[ -n "$EXTRA_SAN" ]] && san="${san},${EXTRA_SAN}" mkdir -p "$CERT_DIR" - $STD openssl req -new -newkey rsa:2048 -days 365 -nodes -x509 \ + # 825 days is the most macOS and iOS accept, even for a certificate the user trusts. + $STD openssl req -new -newkey rsa:2048 -days 825 -nodes -x509 \ -subj "/CN=${HOSTNAME}" \ -addext "subjectAltName=${san}" \ -keyout "$CERT_KEY" \ diff --git a/tests/self-signed-cert.sh b/tests/self-signed-cert.sh new file mode 100644 index 0000000..a694223 --- /dev/null +++ b/tests/self-signed-cert.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +set -uo pipefail + +CORE_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" +command -v openssl >/dev/null 2>&1 || { + echo "SKIP: no openssl" + exit 0 +} + +TEST_DIR="$(mktemp -d)" +trap 'rm -rf -- "$TEST_DIR"' EXIT +# The helper writes to /etc/ssl/; load it with that pointed at the test dir. +eval "$(awk '/^create_self_signed_cert\(\) \{/,/^}/' "$CORE_DIR/lib/system.func" | sed "s#/etc/ssl#${TEST_DIR}#g")" +passed=0 failed=0 + +check() { + if [[ "$3" == "$2" ]]; then + echo "PASS $1" + passed=$((passed + 1)) + else + echo "FAIL $1: expected <$2>, got <$3>" >&2 + failed=$((failed + 1)) + fi +} + +# shellcheck disable=SC2034 # read by the eval'd helper +STD="" +msg_error() { echo "error: $*" >&2; } +ensure_dependencies() { :; } +crt="$TEST_DIR/demo/demo.crt" +days_left() { local d=$((($(date -d "$(openssl x509 -enddate -noout -in "$crt" | cut -d= -f2)" +%s) - $(date +%s)) / 86400)); ((d >= 824 && d <= 825)) && echo 825 || echo "$d"; } +serial() { openssl x509 -serial -noout -in "$crt"; } + +create_self_signed_cert demo +check "a new certificate is valid for 825 days" "825" "$(days_left)" + +before="$(serial)" +create_self_signed_cert demo +check "a valid certificate is kept" "$before" "$(serial)" + +# Replace it with one that runs out in 10 days. +openssl req -new -newkey rsa:2048 -days 10 -nodes -x509 -subj "/CN=old" \ + -keyout "$TEST_DIR/demo/demo.key" -out "$crt" >/dev/null 2>&1 +create_self_signed_cert demo +check "an expiring certificate is renewed" "825" "$(days_left)" +check "and the key stays private" "600" "$(stat -c %a "$TEST_DIR/demo/demo.key")" + +echo "${passed} passed, ${failed} failed" +[[ "$failed" -eq 0 ]] From 6f2d158166da9e8b1c3b4e7f1ea6e0f7ce5e0104 Mon Sep 17 00:00:00 2001 From: MickLesk <47820557+MickLesk@users.noreply.github.com> Date: Fri, 13 Nov 2026 15:42:25 +0100 Subject: [PATCH 2/2] Trim the comments to what a reader would get wrong --- lib/system.func | 2 -- tests/self-signed-cert.sh | 2 -- 2 files changed, 4 deletions(-) diff --git a/lib/system.func b/lib/system.func index 9267b14..de5ef0a 100644 --- a/lib/system.func +++ b/lib/system.func @@ -2166,8 +2166,6 @@ create_self_signed_cert() { local CERT_KEY="${CERT_DIR}/${APP_NAME_LC}.key" local CERT_CRT="${CERT_DIR}/${APP_NAME_LC}.crt" - # A certificate that still has 30 days left is kept. Called again (e.g. from an - # update), an expiring one is replaced: it used to stay until clients refused it. if [[ -f "$CERT_CRT" && -f "$CERT_KEY" ]]; then if ! command -v openssl >/dev/null 2>&1 || openssl x509 -checkend 2592000 -noout -in "$CERT_CRT" >/dev/null 2>&1; then return 0 diff --git a/tests/self-signed-cert.sh b/tests/self-signed-cert.sh index a694223..8614625 100644 --- a/tests/self-signed-cert.sh +++ b/tests/self-signed-cert.sh @@ -9,7 +9,6 @@ command -v openssl >/dev/null 2>&1 || { TEST_DIR="$(mktemp -d)" trap 'rm -rf -- "$TEST_DIR"' EXIT -# The helper writes to /etc/ssl/; load it with that pointed at the test dir. eval "$(awk '/^create_self_signed_cert\(\) \{/,/^}/' "$CORE_DIR/lib/system.func" | sed "s#/etc/ssl#${TEST_DIR}#g")" passed=0 failed=0 @@ -38,7 +37,6 @@ before="$(serial)" create_self_signed_cert demo check "a valid certificate is kept" "$before" "$(serial)" -# Replace it with one that runs out in 10 days. openssl req -new -newkey rsa:2048 -days 10 -nodes -x509 -subj "/CN=old" \ -keyout "$TEST_DIR/demo/demo.key" -out "$crt" >/dev/null 2>&1 create_self_signed_cert demo