| 1 | package update |
| 2 | |
| 3 | import ( |
| 4 | "crypto/rand" |
| 5 | "fmt" |
| 6 | "testing" |
| 7 | |
| 8 | "aead.dev/minisign" |
| 9 | ) |
| 10 | |
| 11 | // TestEmbeddedPublicKeyParses guards the hard-coded publicKey constant: it must |
| 12 | // parse and carry the expected key ID, so a copy-paste slip is caught here rather |
| 13 | // than silently failing every signature check in the field. |
| 14 | func TestEmbeddedPublicKeyParses(t *testing.T) { |
| 15 | var key minisign.PublicKey |
| 16 | if err := key.UnmarshalText([]byte(publicKey)); err != nil { |
| 17 | t.Fatalf("embedded public key does not parse: %v", err) |
| 18 | } |
| 19 | if got := fmt.Sprintf("%016X", key.ID()); got != "AF12CA46F4A9EBB0" { |
| 20 | t.Fatalf("embedded public key ID = %s, want AF12CA46F4A9EBB0", got) |
| 21 | } |
| 22 | } |
| 23 | |
| 24 | // TestVerifyWith exercises the verify path end-to-end with a throwaway key pair: |
| 25 | // a genuine signature passes, tampered data fails, and a wrong-key signature fails. |
| 26 | func TestVerifyWith(t *testing.T) { |
| 27 | pub, priv, err := minisign.GenerateKey(rand.Reader) |
| 28 | if err != nil { |
| 29 | t.Fatal(err) |
| 30 | } |
| 31 | pubText, err := pub.MarshalText() |
| 32 | if err != nil { |
| 33 | t.Fatal(err) |
| 34 | } |
| 35 | data := []byte("the quick brown fox") |
| 36 | sig := minisign.Sign(priv, data) |
| 37 | |
| 38 | if err := verifyWith(string(pubText), data, sig); err != nil { |
| 39 | t.Fatalf("genuine signature should verify, got: %v", err) |
| 40 | } |
| 41 | if err := verifyWith(string(pubText), []byte("tampered payload"), sig); err == nil { |
| 42 | t.Fatal("tampered data should fail verification") |
| 43 | } |
| 44 | |
| 45 | otherPub, _, err := minisign.GenerateKey(rand.Reader) |
| 46 | if err != nil { |
| 47 | t.Fatal(err) |
| 48 | } |
| 49 | otherText, _ := otherPub.MarshalText() |
| 50 | if err := verifyWith(string(otherText), data, sig); err == nil { |
| 51 | t.Fatal("signature under a different key should fail verification") |
| 52 | } |
| 53 | } |
| 54 | |
| 55 | // TestPlatformKey pins the key format the manifest generator and the updater both |
| 56 | // rely on; if these drift, lookups silently miss. |
| 57 | func TestPlatformKey(t *testing.T) { |
| 58 | if got := PlatformKey("darwin", "arm64"); got != "darwin-arm64" { |
| 59 | t.Fatalf("PlatformKey = %q, want darwin-arm64", got) |
| 60 | } |
| 61 | } |
| 62 | |
| 63 | // TestManifestAsset checks the running-platform lookup returns the listed asset |
| 64 | // and reports absence cleanly. |
| 65 | func TestManifestAsset(t *testing.T) { |
| 66 | want := Asset{URL: "https://example/app", SHA256: "abc", Size: 42} |
| 67 | m := Manifest{Platforms: map[string]Asset{CurrentPlatform(): want}} |
| 68 | got, ok := m.Asset() |
| 69 | if !ok || got != want { |
| 70 | t.Fatalf("Asset() = %+v, %v; want %+v, true", got, ok, want) |
| 71 | } |
| 72 | if _, ok := (Manifest{Platforms: map[string]Asset{}}).Asset(); ok { |
| 73 | t.Fatal("Asset() should report absence for an empty manifest") |
| 74 | } |
| 75 | } |
| 76 | |
| 77 | // TestManifestNativePackage covers the optional native_packages field: present |
| 78 | // assets resolve, and older manifests without the field report absence cleanly. |
| 79 | func TestManifestNativePackage(t *testing.T) { |
| 80 | want := Asset{URL: "https://example/app.deb", SHA256: "def", Size: 99} |
| 81 | m := Manifest{NativePackages: map[string]Asset{CurrentPlatform(): want}} |
| 82 | got, ok := m.NativePackage() |
| 83 | if !ok || got != want { |
| 84 | t.Fatalf("NativePackage() = %+v, %v; want %+v, true", got, ok, want) |
| 85 | } |
| 86 | if _, ok := (Manifest{}).NativePackage(); ok { |
| 87 | t.Fatal("NativePackage() should report absence when native_packages is nil") |
| 88 | } |
| 89 | // Old clients ignore unknown fields; new clients must still read platforms. |
| 90 | legacy := Manifest{Platforms: map[string]Asset{CurrentPlatform(): {URL: "tar"}}} |
| 91 | if _, ok := legacy.NativePackage(); ok { |
| 92 | t.Fatal("legacy manifest without native_packages must not invent one") |
| 93 | } |
| 94 | if a, ok := legacy.Asset(); !ok || a.URL != "tar" { |
| 95 | t.Fatalf("legacy platforms still resolve: %+v %v", a, ok) |
| 96 | } |
| 97 | } |
| 98 |