package bundle import ( "archive/tar" "bytes" "compress/gzip" "crypto/ed25519" "crypto/sha256" "encoding/base64" "encoding/hex" "encoding/json" "io" "os" "reflect" "sort" "testing" ) // legacyManifestV1 mirrors the decoder fields present in the locally available // v1.0.0 source. It deliberately has no future capability or deployment fields. type legacyManifestV1 struct { RevisionID string `json:"revision_id"` SpeakerID string `json:"speaker_id"` CreatedAt string `json:"created_at"` Files []struct { Path string `json:"path"` SHA256 string `json:"sha256"` } `json:"files"` Algorithm string `json:"signature_algorithm"` PublicKeyBase64 string `json:"public_key_base64"` } func TestLegacyCompatibilityFrozenSignedCurrentBaseline(t *testing.T) { raw, err := os.ReadFile("testdata/current_baseline_signed_bundle.tar.gz") if err != nil { t.Fatal(err) } pub := ed25519.NewKeyFromSeed(bytes.Repeat([]byte{0x42}, ed25519.SeedSize)).Public().(ed25519.PublicKey) verified, err := VerifyGzippedTar(raw, pub) if err != nil { t.Fatalf("frozen signed baseline rejected: %v", err) } if verified.Manifest.RevisionID != "revision-legacy" || verified.Manifest.SpeakerID != "speaker-legacy" || len(verified.Files) != 2 { t.Fatalf("frozen baseline contents: %+v", verified.Manifest) } } func TestLegacyCompatibilityBundleWire(t *testing.T) { seed := bytes.Repeat([]byte{0x42}, ed25519.SeedSize) priv := ed25519.NewKeyFromSeed(seed) pub := priv.Public().(ed25519.PublicKey) fragments := map[string]string{ "bird.conf": "router id 192.0.2.1;\n", "bird.d/evobgp_prefixes_v4.conf": "# revision\n", "_preview": "must not ship", } raw, err := BuildGzippedTar("revision-legacy", "speaker-legacy", fragments, priv) if err != nil { t.Fatal(err) } gr, err := gzip.NewReader(bytes.NewReader(raw)) if err != nil { t.Fatal(err) } defer func() { _ = gr.Close() }() tr := tar.NewReader(gr) entries := map[string][]byte{} var order []string for { h, err := tr.Next() if err == io.EOF { break } if err != nil { t.Fatal(err) } body, err := io.ReadAll(tr) if err != nil { t.Fatal(err) } order = append(order, h.Name) entries[h.Name] = body } wantOrder := []string{"manifest.json", "bundle.sig", "bird.conf", "bird.d/evobgp_prefixes_v4.conf"} if !reflect.DeepEqual(order, wantOrder) { t.Fatalf("legacy tar layout: got %v, want %v", order, wantOrder) } var top map[string]json.RawMessage if err := json.Unmarshal(entries["manifest.json"], &top); err != nil { t.Fatal(err) } wantFields := []string{"created_at", "files", "public_key_base64", "revision_id", "signature_algorithm", "speaker_id"} fields := make([]string, 0, len(top)) for k := range top { fields = append(fields, k) } sort.Strings(fields) if !reflect.DeepEqual(fields, wantFields) { t.Fatalf("manifest fields: got %v, want %v", fields, wantFields) } var m legacyManifestV1 if err := json.Unmarshal(entries["manifest.json"], &m); err != nil { t.Fatal(err) } if m.RevisionID != "revision-legacy" || m.SpeakerID != "speaker-legacy" || m.CreatedAt == "" || m.Algorithm != "ed25519" || m.PublicKeyBase64 != base64.StdEncoding.EncodeToString(pub) { t.Fatalf("legacy manifest fields: %+v", m) } if !ed25519.Verify(pub, entries["manifest.json"], entries["bundle.sig"]) { t.Fatal("legacy Ed25519 verifier rejected current signed manifest") } if len(m.Files) != 2 { t.Fatalf("legacy manifest file count: %d", len(m.Files)) } for _, f := range m.Files { sum := sha256.Sum256(entries[f.Path]) if hex.EncodeToString(sum[:]) != f.SHA256 { t.Fatalf("legacy SHA-256 mismatch for %q", f.Path) } } }