carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package publicrelease
2
3import (
4 "context"
5 "crypto/ed25519"
6 "crypto/sha256"
7 "encoding/base64"
8 "encoding/hex"
9 "fmt"
10 "net/http"
11 "net/http/httptest"
12 "strings"
13 "testing"
14)
15
16func testFeed(t *testing.T, sign bool, assets map[string][]byte) (Feed, string) {
17 t.Helper()
18 pub, priv, _ := ed25519.GenerateKey(nil)
19 var sums strings.Builder
20 for name, body := range assets {
21 s := sha256.Sum256(body)
22 fmt.Fprintf(&sums, "%s %s\n", hex.EncodeToString(s[:]), name)
23 }
24 sig := ed25519.Sign(priv, []byte(sums.String()))
25 mux := http.NewServeMux()
26 mux.HandleFunc("/latest.txt", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "v0.18.0") })
27 mux.HandleFunc("/dl/v0.18.0/SHA256SUMS.txt", func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, sums.String()) })
28 mux.HandleFunc("/dl/v0.18.0/SHA256SUMS.txt.sig", func(w http.ResponseWriter, r *http.Request) {
29 if sign {
30 fmt.Fprintln(w, base64.StdEncoding.EncodeToString(sig))
31 } else {
32 fmt.Fprintln(w, base64.StdEncoding.EncodeToString(make([]byte, ed25519.SignatureSize)))
33 }
34 })
35 for name, body := range assets {
36 body := body
37 mux.HandleFunc("/dl/v0.18.0/"+name, func(w http.ResponseWriter, r *http.Request) { w.Write(body) })
38 }
39 ts := httptest.NewServer(mux)
40 t.Cleanup(ts.Close)
41 return Feed{LatestURL: ts.URL + "/latest.txt", DownloadBase: ts.URL + "/dl", PubKey: base64.StdEncoding.EncodeToString(pub)}, ts.URL
42}
43
44func TestFeedLatestAndVerifiedAsset(t *testing.T) {
45 body := []byte("ELF arm64")
46 f, _ := testFeed(t, true, map[string][]byte{"carlos-linux-arm64": body, "carlos-linux-amd64": []byte("ELF amd64")})
47 ctx := context.Background()
48 tag, err := f.Latest(ctx)
49 if err != nil || tag != "v0.18.0" {
50 t.Fatalf("latest = %q %v", tag, err)
51 }
52 data, sha, err := f.Asset(ctx, tag, AssetName("linux-arm64"), 1<<20)
53 if err != nil || string(data) != string(body) {
54 t.Fatalf("asset: %v", err)
55 }
56 s := sha256.Sum256(body)
57 if sha != hex.EncodeToString(s[:]) {
58 t.Fatalf("sha = %s", sha)
59 }
60 if _, _, err := f.Asset(ctx, tag, "carlos-windows-amd64.zip", 1<<20); err == nil {
61 t.Fatal("an asset absent from the sums was accepted")
62 }
63}
64
65func TestFeedRefusesABadSignatureAndATamperedAsset(t *testing.T) {
66 body := []byte("ELF")
67 f, _ := testFeed(t, false, map[string][]byte{"carlos-linux-arm64": body})
68 if _, err := f.Sums(context.Background(), "v0.18.0"); err == nil || !strings.Contains(err.Error(), "signature") {
69 t.Fatalf("bad signature: %v", err)
70 }
71 // Signed sums, but the served asset differs from the sum.
72 f2, _ := testFeed(t, true, map[string][]byte{"carlos-linux-arm64": body})
73 // Simulate tampering by asking for a limit smaller than the asset: the
74 // truncated download cannot match its sum.
75 if _, _, err := f2.Asset(context.Background(), "v0.18.0", "carlos-linux-arm64", 1); err == nil {
76 t.Fatal("a truncated asset matched its sum")
77 }
78}
79
80func TestFeedLatestRequiresATag(t *testing.T) {
81 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "main") }))
82 defer ts.Close()
83 f := Feed{LatestURL: ts.URL, DownloadBase: ts.URL, PubKey: ""}
84 if _, err := f.Latest(context.Background()); err == nil {
85 t.Fatal("accepted a non-tag latest")
86 }
87}
88