Files
mrambossekandClaude Opus 5 a49bef5821 server: refuse unsigned releases and polluted reserved addresses
Self-update now verifies SHA256SUMS.sig (ed25519, relsign package) against
a public key baked into the binary; the private key exists only in the CI
secret store, so a compromised release host can withhold updates but not
inject one. CI signs on every server-v* tag and hard-fails without the
secret. Operators with their own pipeline override the key via
ECHOLOT_SELF_UPDATE_PUBKEY (mint a pair with release-sign -gen).

Startup also now proves 80/443 are actually free on the reserved
measurement addresses by asking the OS (throwaway bind), not the config -
CheckReserved could never see a stray process, and the adb-beacon receiver
on 0.0.0.0:443 was exactly that.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 12:32:02 +02:00

75 lines
2.2 KiB
Go

// SPDX-FileCopyrightText: 2026 Echolot contributors
// SPDX-License-Identifier: GPL-3.0-or-later
package relsign
import (
"strings"
"testing"
)
func TestRoundTrip(t *testing.T) {
pub, seed, err := GenerateKey()
if err != nil {
t.Fatal(err)
}
data := []byte("abc123 echolot-server_linux_amd64\n")
sig, err := Sign(seed, data)
if err != nil {
t.Fatal(err)
}
if err := Verify(pub, data, sig); err != nil {
t.Fatalf("a signature this package just made must verify: %v", err)
}
}
func TestAlteredContentIsRefused(t *testing.T) {
// The attack this exists for: same length, one checksum swapped for another.
pub, seed, _ := GenerateKey()
sig, _ := Sign(seed, []byte("aaa echolot-server_linux_amd64\n"))
if err := Verify(pub, []byte("bbb echolot-server_linux_amd64\n"), sig); err == nil {
t.Fatal("altered content must not verify")
}
}
func TestWrongKeyIsRefused(t *testing.T) {
// A compromised release host can re-sign with its own key; only ours may pass.
pub1, _, _ := GenerateKey()
_, seed2, _ := GenerateKey()
data := []byte("payload")
sig, _ := Sign(seed2, data)
if err := Verify(pub1, data, sig); err == nil {
t.Fatal("a signature from a different key must not verify")
}
}
func TestSurroundingWhitespaceIsTolerated(t *testing.T) {
// Keys travel through env vars and files; a trailing newline must not break verification.
pub, seed, _ := GenerateKey()
data := []byte("data")
sig, err := Sign(" "+seed+"\n", data)
if err != nil {
t.Fatal(err)
}
if err := Verify(pub+"\n", data, "\t"+sig+"\n"); err != nil {
t.Fatalf("whitespace around base64 must be tolerated: %v", err)
}
}
func TestGarbageInputsFailCleanly(t *testing.T) {
pub, seed, _ := GenerateKey()
if _, err := Sign("not base64!!", []byte("x")); err == nil || !strings.Contains(err.Error(), "base64") {
t.Fatalf("bad seed must name the problem, got %v", err)
}
if _, err := Sign("c2hvcnQ=", []byte("x")); err == nil {
t.Fatal("short seed must be refused")
}
if err := Verify("c2hvcnQ=", []byte("x"), "AAAA"); err == nil {
t.Fatal("short public key must be refused")
}
if err := Verify(pub, []byte("x"), "not base64!!"); err == nil {
t.Fatal("bad signature encoding must be refused")
}
_ = seed
}