arblant
firefox-vpn-client / core / internal / tunservice / update_test.go
update_test.go87 lines · 2.8 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
package tunservice

import (
	"crypto/ed25519"
	"encoding/base64"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"github.com/armanlife/firefox-vpn-client/core/internal/signing"
)

// signedFile writes an executable and its signature by priv.
func signedFile(t *testing.T, data []byte, priv ed25519.PrivateKey) string {
	t.Helper()
	path := filepath.Join(t.TempDir(), "firefox-vpn-tun.exe")
	if err := os.WriteFile(path, data, 0o600); err != nil {
		t.Fatal(err)
	}
	sig := base64.StdEncoding.EncodeToString(ed25519.Sign(priv, data))
	if err := os.WriteFile(SignatureFile(path), []byte(sig+"\n"), 0o600); err != nil {
		t.Fatal(err)
	}
	return path
}

func withKey(t *testing.T, pub ed25519.PublicKey) {
	t.Helper()
	old := signing.Key
	signing.Key = base64.StdEncoding.EncodeToString(pub)
	t.Cleanup(func() { signing.Key = old })
}

func TestASignedUpdateIsAccepted(t *testing.T) {
	pub, priv, _ := ed25519.GenerateKey(nil)
	withKey(t, pub)
	data := []byte("MZ new service")
	got, err := readSigned(signedFile(t, data, priv))
	if err != nil || string(got) != string(data) {
		t.Fatalf("got %q, %v", got, err)
	}
}

func TestUpdatesNotSignedByTheKeyAreRefused(t *testing.T) {
	pub, priv, _ := ed25519.GenerateKey(nil)
	_, other, _ := ed25519.GenerateKey(nil)
	withKey(t, pub)

	if _, err := readSigned(signedFile(t, []byte("MZ"), other)); err == nil || !strings.Contains(err.Error(), "does not match") {
		t.Fatalf("another key's signature: %v", err)
	}
	path := signedFile(t, []byte("MZ original"), priv)
	if err := os.WriteFile(path, []byte("MZ changed"), 0o600); err != nil {
		t.Fatal(err)
	}
	if _, err := readSigned(path); err == nil {
		t.Fatal("a changed file was accepted")
	}
	os.Remove(SignatureFile(path))
	if _, err := readSigned(path); err == nil || !strings.Contains(err.Error(), "not signed") {
		t.Fatalf("no signature: %v", err)
	}
}

func TestABuildWithoutAKeyTakesNoUpdates(t *testing.T) {
	_, priv, _ := ed25519.GenerateKey(nil)
	withKey(t, nil)
	signing.Key = ""
	if _, err := readSigned(signedFile(t, []byte("MZ"), priv)); err == nil || !strings.Contains(err.Error(), "takes no updates") {
		t.Fatalf("got %v", err)
	}
}

func TestTheSignatureOfAMacAppsExecutableIsInItsResources(t *testing.T) {
	app := filepath.Join("Applications", "Firefox VPN.app", "Contents")
	for exe, want := range map[string]string{
		filepath.Join(app, "MacOS", "firefox-vpn-tun"): filepath.Join(app, "Resources", "firefox-vpn-tun.sig"),
		filepath.Join("opt", "firefox-vpn-tun", "x"):   filepath.Join("opt", "firefox-vpn-tun", "x.sig"),
		filepath.Join("core", "firefox-vpn-tun.exe"):   filepath.Join("core", "firefox-vpn-tun.exe.sig"),
		filepath.Join("MacOS", "firefox-vpn-tun"):      filepath.Join("MacOS", "firefox-vpn-tun.sig"),
	} {
		if got := SignatureFile(exe); got != want {
			t.Errorf("SignatureFile(%s) = %s, want %s", exe, got, want)
		}
	}
}