From 4a7644c11e09b40596b568306d01b4293841c359 Mon Sep 17 00:00:00 2001 From: Jon Langevin Date: Sat, 3 Oct 2026 11:24:17 -0400 Subject: [PATCH] feat: expose package artifact verification Preserve the existing ordered tuple hash and trusted key aliases. Keep bootstrap and directive signing domains separate. --- protocol/package_artifact.go | 51 ++++ protocol/package_artifact_test.go | 233 ++++++++++++++++++ protocol/recovery_vectors_test.go | 89 ++++++- .../testdata/recovery_contract_vectors.json | 164 ++++++++++++ 4 files changed, 524 insertions(+), 13 deletions(-) create mode 100644 protocol/package_artifact.go create mode 100644 protocol/package_artifact_test.go diff --git a/protocol/package_artifact.go b/protocol/package_artifact.go new file mode 100644 index 0000000..a79e16e --- /dev/null +++ b/protocol/package_artifact.go @@ -0,0 +1,51 @@ +package protocol + +import ( + "crypto/ed25519" + "encoding/base64" + "errors" + "fmt" +) + +// PackageArtifactSignaturePayload is the reusable workflow-compute artifact +// tuple. Field order and plugin_id omission preserve its existing JSON hash. +// Callers supply the expected tuple and trusted keys from their own authority; +// this verifier does not validate download policy or authorize installation. +type PackageArtifactSignaturePayload struct { + Component string `json:"component"` + PluginID string `json:"plugin_id,omitempty"` + Version string `json:"version"` + URL string `json:"url"` + SHA256 string `json:"sha256"` +} + +// SigningBytes returns the UTF-8 sha256-prefixed CanonicalHash of the tuple, +// not raw JSON bytes. Bootstrap and directive-bound signatures are separate +// signing domains and are not accepted by Verify. +func (p PackageArtifactSignaturePayload) SigningBytes() []byte { + return []byte(CanonicalHash(p)) +} + +// Verify checks only the reusable tuple signature. KeyID selects an exact +// trusted map entry, including legacy aliases; it need not be a fingerprint. +// The envelope's Verified flag is never evidence of a valid signature. +func (p PackageArtifactSignaturePayload) Verify(signature SignatureEnvelope, keys map[string]ed25519.PublicKey) error { + if signature.Algorithm != "ed25519" { + return errors.New("artifact signature algorithm is unsupported") + } + key, ok := keys[signature.KeyID] + if !ok { + return errors.New("artifact signature key is not trusted") + } + if len(key) != ed25519.PublicKeySize { + return errors.New("artifact signature public key has invalid size") + } + value, err := base64.StdEncoding.DecodeString(signature.Value) + if err != nil { + return fmt.Errorf("artifact signature is not base64: %w", err) + } + if !ed25519.Verify(key, p.SigningBytes(), value) { + return errors.New("artifact signature verification failed") + } + return nil +} diff --git a/protocol/package_artifact_test.go b/protocol/package_artifact_test.go new file mode 100644 index 0000000..6f26343 --- /dev/null +++ b/protocol/package_artifact_test.go @@ -0,0 +1,233 @@ +package protocol_test + +import ( + "bytes" + "crypto/ed25519" + "encoding/base64" + "encoding/json" + "os" + "os/exec" + "path/filepath" + "strings" + "testing" + + "github.com/GoCodeAlone/workflow-plugin-compute-core/protocol" +) + +func packageArtifactFixture() protocol.PackageArtifactSignaturePayload { + return protocol.PackageArtifactSignaturePayload{ + Component: "agent-core", Version: "v1.0.0", + URL: "/v1/agent-artifacts/agent-core/agent-linux-amd64.tar.gz", + SHA256: "sha256:" + strings.Repeat("a", 64), + } +} + +// This is the existing workflow-compute reusable tuple, independent of the +// public payload implementation. It deliberately has no directive ID. +func existingPackageTuple(p protocol.PackageArtifactSignaturePayload) any { + return struct { + Component string `json:"component"` + PluginID string `json:"plugin_id,omitempty"` + Version string `json:"version"` + URL string `json:"url"` + SHA256 string `json:"sha256"` + }{p.Component, p.PluginID, p.Version, p.URL, p.SHA256} +} + +func existingPackageSignature(p protocol.PackageArtifactSignaturePayload) protocol.SignatureEnvelope { + return protocol.SignatureEnvelope{ + Algorithm: "ed25519", KeyID: "artifact-key", + Value: base64.StdEncoding.EncodeToString(ed25519.Sign(recoveryKey(), []byte(protocol.CanonicalHash(existingPackageTuple(p))))), + } +} + +func TestPackageArtifact_ExistingTupleBytesAndKeySemantics(t *testing.T) { + public := recoveryKey().Public().(ed25519.PublicKey) + for _, p := range []protocol.PackageArtifactSignaturePayload{ + packageArtifactFixture(), + {Component: "provider", PluginID: "workflow-plugin-example", Version: "v1.2.3", URL: "https://packages.example.test/immutable/a%2Fb?platform=linux&arch=amd64", SHA256: recoveryDigest()}, + } { + got, err := json.Marshal(p) + if err != nil { + t.Fatal(err) + } + want, err := json.Marshal(existingPackageTuple(p)) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(got, want) || string(p.SigningBytes()) != protocol.CanonicalHash(existingPackageTuple(p)) { + t.Error("existing tuple JSON/hash bytes changed") + } + sig := existingPackageSignature(p) + for _, keyID := range []string{"artifact-key", protocol.Ed25519KeyID(public), ""} { + sig.KeyID = keyID + if err := p.Verify(sig, map[string]ed25519.PublicKey{keyID: public}); err != nil { + t.Fatalf("explicitly trusted wire key rejected: %v", err) + } + } + // StdEncoding.DecodeString historically accepts line breaks; do not + // silently replace it with the bootstrap canonical-base64 policy. + sig = existingPackageSignature(p) + sig.Value = sig.Value[:40] + "\n" + sig.Value[40:] + if err := p.Verify(sig, map[string]ed25519.PublicKey{"artifact-key": public}); err != nil { + t.Fatalf("existing signature encoding rejected: %v", err) + } + } +} + +func TestPackageArtifact_RejectsAlteredTuple(t *testing.T) { + p := packageArtifactFixture() + sig := existingPackageSignature(p) + keys := map[string]ed25519.PublicKey{"artifact-key": recoveryKey().Public().(ed25519.PublicKey)} + for _, mutation := range []struct { + name string + apply func(*protocol.PackageArtifactSignaturePayload) + }{ + {"component", func(p *protocol.PackageArtifactSignaturePayload) { p.Component = "provider" }}, + {"plugin", func(p *protocol.PackageArtifactSignaturePayload) { p.PluginID = "workflow-plugin-other" }}, + {"version", func(p *protocol.PackageArtifactSignaturePayload) { p.Version = "v1.0.1" }}, + {"url", func(p *protocol.PackageArtifactSignaturePayload) { p.URL += "?different" }}, + {"sha256", func(p *protocol.PackageArtifactSignaturePayload) { p.SHA256 = "sha256:" + strings.Repeat("b", 64) }}, + } { + t.Run(mutation.name, func(t *testing.T) { + changed := p + mutation.apply(&changed) + if err := changed.Verify(sig, keys); err == nil { + t.Fatal("altered tuple accepted") + } + }) + } +} + +func TestPackageArtifact_RejectsInvalidSignatureAndTrust(t *testing.T) { + p := packageArtifactFixture() + public := recoveryKey().Public().(ed25519.PublicKey) + wrong := ed25519.NewKeyFromSeed(bytes.Repeat([]byte{8}, ed25519.SeedSize)).Public().(ed25519.PublicKey) + const marker = "known-private-package-value" + for _, tc := range []struct { + name string + mutate func(*protocol.SignatureEnvelope, map[string]ed25519.PublicKey) + }{ + {"algorithm", func(s *protocol.SignatureEnvelope, _ map[string]ed25519.PublicKey) { s.Algorithm = marker }}, + {"unknown-key", func(s *protocol.SignatureEnvelope, _ map[string]ed25519.PublicKey) { s.KeyID = marker }}, + {"removed-key", func(_ *protocol.SignatureEnvelope, keys map[string]ed25519.PublicKey) { delete(keys, "artifact-key") }}, + {"wrong-key", func(_ *protocol.SignatureEnvelope, keys map[string]ed25519.PublicKey) { keys["artifact-key"] = wrong }}, + {"nil-key", func(_ *protocol.SignatureEnvelope, keys map[string]ed25519.PublicKey) { keys["artifact-key"] = nil }}, + {"short-key", func(_ *protocol.SignatureEnvelope, keys map[string]ed25519.PublicKey) { + keys["artifact-key"] = public[:31] + }}, + {"long-key", func(_ *protocol.SignatureEnvelope, keys map[string]ed25519.PublicKey) { + keys["artifact-key"] = append(bytes.Clone(public), 0) + }}, + {"malformed-base64", func(s *protocol.SignatureEnvelope, _ map[string]ed25519.PublicKey) { s.Value = marker }}, + {"empty-signature", func(s *protocol.SignatureEnvelope, _ map[string]ed25519.PublicKey) { s.Value = "" }}, + {"short-signature", func(s *protocol.SignatureEnvelope, _ map[string]ed25519.PublicKey) { + s.Value = base64.StdEncoding.EncodeToString(make([]byte, 63)) + }}, + {"long-signature", func(s *protocol.SignatureEnvelope, _ map[string]ed25519.PublicKey) { + s.Value = base64.StdEncoding.EncodeToString(make([]byte, 65)) + }}, + {"tampered-signature", func(s *protocol.SignatureEnvelope, _ map[string]ed25519.PublicKey) { + s.Value = base64.StdEncoding.EncodeToString(make([]byte, 64)) + }}, + {"verified-is-not-proof", func(s *protocol.SignatureEnvelope, _ map[string]ed25519.PublicKey) { s.Verified = true; s.Value = "" }}, + } { + t.Run(tc.name, func(t *testing.T) { + sig := existingPackageSignature(p) + keys := map[string]ed25519.PublicKey{"artifact-key": public} + tc.mutate(&sig, keys) + if err := p.Verify(sig, keys); err == nil || strings.Contains(err.Error(), marker) { + t.Fatalf("invalid signature accepted or diagnostic disclosed source: %v", err) + } + }) + } + if err := p.Verify(existingPackageSignature(p), nil); err == nil { + t.Fatal("absent trust accepted") + } +} + +func TestPackageArtifact_RejectsOtherSigningDomains(t *testing.T) { + p := packageArtifactFixture() + public := recoveryKey().Public().(ed25519.PublicKey) + raw, err := json.Marshal(existingPackageTuple(p)) + if err != nil { + t.Fatal(err) + } + bootstrap := bootstrapFixture() + bootstrapBytes, err := bootstrap.CanonicalBytes() + if err != nil { + t.Fatal(err) + } + legacy := struct { + DirectiveID string `json:"directive_id"` + Component string `json:"component"` + PluginID string `json:"plugin_id,omitempty"` + Version string `json:"version"` + URL string `json:"url"` + SHA256 string `json:"sha256"` + }{"directive-1", p.Component, p.PluginID, p.Version, p.URL, p.SHA256} + for name, data := range map[string][]byte{ + "raw-tuple-json": raw, "raw-bootstrap-json": bootstrapBytes, + "legacy-directive-tuple-hash": []byte(protocol.CanonicalHash(legacy)), + } { + t.Run(name, func(t *testing.T) { + sig := protocol.SignatureEnvelope{Algorithm: "ed25519", KeyID: "artifact-key", Value: base64.StdEncoding.EncodeToString(ed25519.Sign(recoveryKey(), data))} + if err := p.Verify(sig, map[string]ed25519.PublicKey{"artifact-key": public}); err == nil { + t.Fatal("signature from another signing domain accepted") + } + }) + } +} + +func TestPackageArtifact_PublicConsumerVectors(t *testing.T) { + consumer := `package main +import ( + "crypto/ed25519" + "encoding/base64" + "encoding/json" + "fmt" + "os" + "github.com/GoCodeAlone/workflow-plugin-compute-core/protocol" +) +func main() { + var v struct { + PublicKey string ` + "`json:\"public_key\"`" + ` + PackageArtifacts []struct { + Payload protocol.PackageArtifactSignaturePayload ` + "`json:\"payload\"`" + ` + TupleJSON string ` + "`json:\"tuple_json\"`" + ` + SigningHash string ` + "`json:\"signing_hash\"`" + ` + Signature protocol.SignatureEnvelope ` + "`json:\"signature\"`" + ` + } ` + "`json:\"package_artifacts\"`" + ` + } + data, err := os.ReadFile(os.Args[1]); if err != nil { panic(err) } + if err := json.Unmarshal(data, &v); err != nil { panic(err) } + public, err := base64.StdEncoding.DecodeString(v.PublicKey); if err != nil { panic(err) } + if len(v.PackageArtifacts) != 2 { panic("missing external vectors") } + for _, a := range v.PackageArtifacts { + data, err := json.Marshal(a.Payload); if err != nil { panic(err) } + if string(data) != a.TupleJSON || string(a.Payload.SigningBytes()) != a.SigningHash { panic("tuple mismatch") } + keys := map[string]ed25519.PublicKey{a.Signature.KeyID: public} + if err := a.Payload.Verify(a.Signature, keys); err != nil { panic(err) } + a.Payload.URL += "?substituted" + if err := a.Payload.Verify(a.Signature, keys); err == nil { panic("substitution accepted") } + } + fmt.Println("public package-artifact vectors: 2 accepted, 2 substitutions denied") +} +` + name := filepath.Join(t.TempDir(), "main.go") + if err := os.WriteFile(name, []byte(consumer), 0o600); err != nil { + t.Fatal(err) + } + vectors, err := filepath.Abs("testdata/recovery_contract_vectors.json") + if err != nil { + t.Fatal(err) + } + cmd := exec.Command("go", "run", name, vectors) + data, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("actual public consumer failed: %v\n%s", err, data) + } + if string(data) != "public package-artifact vectors: 2 accepted, 2 substitutions denied\n" { + t.Fatalf("unexpected public consumer result: %s", data) + } +} diff --git a/protocol/recovery_vectors_test.go b/protocol/recovery_vectors_test.go index dbe69f5..95204f5 100644 --- a/protocol/recovery_vectors_test.go +++ b/protocol/recovery_vectors_test.go @@ -29,20 +29,30 @@ type recoveryRejection[T any] struct { Message T `json:"message"` } +type packageArtifactVector struct { + Name string `json:"name,omitempty"` + Payload protocol.PackageArtifactSignaturePayload `json:"payload"` + TupleJSON string `json:"tuple_json"` + SigningHash string `json:"signing_hash"` + Signature protocol.SignatureEnvelope `json:"signature"` +} + type recoveryContractVectors struct { - ValidationTime time.Time `json:"validation_time"` - PublicKey string `json:"public_key"` - WrongPublicKey string `json:"wrong_public_key"` - Manifest recoveryVector[protocol.ComputeBootstrapManifest] `json:"manifest"` - ManifestSignature protocol.SignatureEnvelope `json:"manifest_signature"` - Receipt recoveryVector[protocol.AgentSetupInstallReceipt] `json:"receipt"` - ReceiptBinding protocol.AgentSetupInstallReceiptBinding `json:"receipt_binding"` - Artifact recoveryVector[protocol.ManagedLifecycleArtifactRef] `json:"artifact"` - Request recoveryVector[protocol.ManagedProviderLifecycleRequest] `json:"request"` - Result recoveryVector[protocol.ManagedProviderLifecycleResult] `json:"result"` - Preflight protocol.DedicatedProviderHostPreflightResult `json:"preflight"` - ReceiptRejections []recoveryRejection[protocol.AgentSetupInstallReceipt] `json:"receipt_rejections"` - ChangedRequests []recoveryRejection[protocol.ManagedProviderLifecycleRequest] `json:"changed_requests_for_result"` + ValidationTime time.Time `json:"validation_time"` + PublicKey string `json:"public_key"` + WrongPublicKey string `json:"wrong_public_key"` + Manifest recoveryVector[protocol.ComputeBootstrapManifest] `json:"manifest"` + ManifestSignature protocol.SignatureEnvelope `json:"manifest_signature"` + Receipt recoveryVector[protocol.AgentSetupInstallReceipt] `json:"receipt"` + ReceiptBinding protocol.AgentSetupInstallReceiptBinding `json:"receipt_binding"` + Artifact recoveryVector[protocol.ManagedLifecycleArtifactRef] `json:"artifact"` + Request recoveryVector[protocol.ManagedProviderLifecycleRequest] `json:"request"` + Result recoveryVector[protocol.ManagedProviderLifecycleResult] `json:"result"` + Preflight protocol.DedicatedProviderHostPreflightResult `json:"preflight"` + ReceiptRejections []recoveryRejection[protocol.AgentSetupInstallReceipt] `json:"receipt_rejections"` + ChangedRequests []recoveryRejection[protocol.ManagedProviderLifecycleRequest] `json:"changed_requests_for_result"` + PackageArtifacts []packageArtifactVector `json:"package_artifacts"` + PackageArtifactRejections []packageArtifactVector `json:"package_artifact_rejections"` } func makeRecoveryVector[T any](message T, signing []byte) recoveryVector[T] { @@ -153,6 +163,38 @@ func exportedRecoveryVectors(t *testing.T) recoveryContractVectors { } vectors.ChangedRequests = append(vectors.ChangedRequests, recoveryRejection[protocol.ManagedProviderLifecycleRequest]{Name: mutation.name, Message: changed}) } + for _, payload := range []protocol.PackageArtifactSignaturePayload{ + packageArtifactFixture(), + {Component: "provider", PluginID: "workflow-plugin-example", Version: "v1.2.3", URL: "https://packages.example.test/immutable/a%2Fb?platform=linux&arch=amd64", SHA256: recoveryDigest()}, + } { + data, err := json.Marshal(existingPackageTuple(payload)) + if err != nil { + t.Fatal(err) + } + vectors.PackageArtifacts = append(vectors.PackageArtifacts, packageArtifactVector{ + Payload: payload, TupleJSON: string(data), SigningHash: protocol.CanonicalHash(existingPackageTuple(payload)), Signature: existingPackageSignature(payload), + }) + } + for _, mutation := range []struct { + name string + apply func(*packageArtifactVector) + }{ + {"component", func(v *packageArtifactVector) { v.Payload.Component = "provider" }}, + {"plugin", func(v *packageArtifactVector) { v.Payload.PluginID = "workflow-plugin-other" }}, + {"version", func(v *packageArtifactVector) { v.Payload.Version = "v1.0.1" }}, + {"url", func(v *packageArtifactVector) { v.Payload.URL += "?substituted" }}, + {"sha256", func(v *packageArtifactVector) { v.Payload.SHA256 = "sha256:" + strings.Repeat("b", 64) }}, + {"algorithm", func(v *packageArtifactVector) { v.Signature.Algorithm = "rsa" }}, + {"untrusted-key", func(v *packageArtifactVector) { v.Signature.KeyID = "other-key" }}, + {"raw-json-domain", func(v *packageArtifactVector) { + v.Signature.Value = base64.StdEncoding.EncodeToString(ed25519.Sign(key, []byte(v.TupleJSON))) + }}, + } { + changed := vectors.PackageArtifacts[0] + changed.Name = mutation.name + mutation.apply(&changed) + vectors.PackageArtifactRejections = append(vectors.PackageArtifactRejections, changed) + } return vectors } @@ -226,4 +268,25 @@ func TestRecoveryContractVectors_SharedConsumerBytes(t *testing.T) { t.Fatalf("shared changed-request result replay accepted: %s", vector.Name) } } + keys := map[string]ed25519.PublicKey{"artifact-key": public} + for _, vector := range consumer.PackageArtifacts { + data, err := json.Marshal(vector.Payload) + if err != nil { + t.Fatal(err) + } + if string(data) != vector.TupleJSON || string(vector.Payload.SigningBytes()) != vector.SigningHash { + t.Fatal("shared package tuple JSON/hash changed") + } + if err := vector.Payload.Verify(vector.Signature, keys); err != nil { + t.Fatal(err) + } + if err := vector.Payload.Verify(vector.Signature, map[string]ed25519.PublicKey{"artifact-key": wrongPublic}); err == nil { + t.Fatal("shared package wrong-key vector accepted") + } + } + for _, vector := range consumer.PackageArtifactRejections { + if err := vector.Payload.Verify(vector.Signature, keys); err == nil { + t.Fatalf("shared package rejection accepted: %s", vector.Name) + } + } } diff --git a/protocol/testdata/recovery_contract_vectors.json b/protocol/testdata/recovery_contract_vectors.json index d7cbd22..bdc1756 100644 --- a/protocol/testdata/recovery_contract_vectors.json +++ b/protocol/testdata/recovery_contract_vectors.json @@ -529,5 +529,169 @@ } } } + ], + "package_artifacts": [ + { + "payload": { + "component": "agent-core", + "version": "v1.0.0", + "url": "/v1/agent-artifacts/agent-core/agent-linux-amd64.tar.gz", + "sha256": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + }, + "tuple_json": "{\"component\":\"agent-core\",\"version\":\"v1.0.0\",\"url\":\"/v1/agent-artifacts/agent-core/agent-linux-amd64.tar.gz\",\"sha256\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}", + "signing_hash": "sha256:017b032c0836bba65a1c23e89630203ec47ebb9af4e930deeda02aa9b9d3ad9c", + "signature": { + "algorithm": "ed25519", + "key_id": "artifact-key", + "value": "kkr5+GLTkEDbTK8e2b608qIrRKLg+4U1fdyx7YLQ1ZWkzSTcgQDEMGgCOESWtylqt+YpTNSL7jH3h4Ozvj2zAg==" + } + }, + { + "payload": { + "component": "provider", + "plugin_id": "workflow-plugin-example", + "version": "v1.2.3", + "url": "https://packages.example.test/immutable/a%2Fb?platform=linux\u0026arch=amd64", + "sha256": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + }, + "tuple_json": "{\"component\":\"provider\",\"plugin_id\":\"workflow-plugin-example\",\"version\":\"v1.2.3\",\"url\":\"https://packages.example.test/immutable/a%2Fb?platform=linux\\u0026arch=amd64\",\"sha256\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}", + "signing_hash": "sha256:3e5eccb7eb149d41e4fd631e23af1b2dfacf41c08bfeeed5ed527190c1080513", + "signature": { + "algorithm": "ed25519", + "key_id": "artifact-key", + "value": "0LJrA/KbSpUrWf9LFQIbG/ovr9XVU2ezrJdZN8ps6wOVUS5Zbmyhbi5qDATmueQ9qYtEJeux+N1KRD+Y2YHnAQ==" + } + } + ], + "package_artifact_rejections": [ + { + "name": "component", + "payload": { + "component": "provider", + "version": "v1.0.0", + "url": "/v1/agent-artifacts/agent-core/agent-linux-amd64.tar.gz", + "sha256": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + }, + "tuple_json": "{\"component\":\"agent-core\",\"version\":\"v1.0.0\",\"url\":\"/v1/agent-artifacts/agent-core/agent-linux-amd64.tar.gz\",\"sha256\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}", + "signing_hash": "sha256:017b032c0836bba65a1c23e89630203ec47ebb9af4e930deeda02aa9b9d3ad9c", + "signature": { + "algorithm": "ed25519", + "key_id": "artifact-key", + "value": "kkr5+GLTkEDbTK8e2b608qIrRKLg+4U1fdyx7YLQ1ZWkzSTcgQDEMGgCOESWtylqt+YpTNSL7jH3h4Ozvj2zAg==" + } + }, + { + "name": "plugin", + "payload": { + "component": "agent-core", + "plugin_id": "workflow-plugin-other", + "version": "v1.0.0", + "url": "/v1/agent-artifacts/agent-core/agent-linux-amd64.tar.gz", + "sha256": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + }, + "tuple_json": "{\"component\":\"agent-core\",\"version\":\"v1.0.0\",\"url\":\"/v1/agent-artifacts/agent-core/agent-linux-amd64.tar.gz\",\"sha256\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}", + "signing_hash": "sha256:017b032c0836bba65a1c23e89630203ec47ebb9af4e930deeda02aa9b9d3ad9c", + "signature": { + "algorithm": "ed25519", + "key_id": "artifact-key", + "value": "kkr5+GLTkEDbTK8e2b608qIrRKLg+4U1fdyx7YLQ1ZWkzSTcgQDEMGgCOESWtylqt+YpTNSL7jH3h4Ozvj2zAg==" + } + }, + { + "name": "version", + "payload": { + "component": "agent-core", + "version": "v1.0.1", + "url": "/v1/agent-artifacts/agent-core/agent-linux-amd64.tar.gz", + "sha256": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + }, + "tuple_json": "{\"component\":\"agent-core\",\"version\":\"v1.0.0\",\"url\":\"/v1/agent-artifacts/agent-core/agent-linux-amd64.tar.gz\",\"sha256\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}", + "signing_hash": "sha256:017b032c0836bba65a1c23e89630203ec47ebb9af4e930deeda02aa9b9d3ad9c", + "signature": { + "algorithm": "ed25519", + "key_id": "artifact-key", + "value": "kkr5+GLTkEDbTK8e2b608qIrRKLg+4U1fdyx7YLQ1ZWkzSTcgQDEMGgCOESWtylqt+YpTNSL7jH3h4Ozvj2zAg==" + } + }, + { + "name": "url", + "payload": { + "component": "agent-core", + "version": "v1.0.0", + "url": "/v1/agent-artifacts/agent-core/agent-linux-amd64.tar.gz?substituted", + "sha256": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + }, + "tuple_json": "{\"component\":\"agent-core\",\"version\":\"v1.0.0\",\"url\":\"/v1/agent-artifacts/agent-core/agent-linux-amd64.tar.gz\",\"sha256\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}", + "signing_hash": "sha256:017b032c0836bba65a1c23e89630203ec47ebb9af4e930deeda02aa9b9d3ad9c", + "signature": { + "algorithm": "ed25519", + "key_id": "artifact-key", + "value": "kkr5+GLTkEDbTK8e2b608qIrRKLg+4U1fdyx7YLQ1ZWkzSTcgQDEMGgCOESWtylqt+YpTNSL7jH3h4Ozvj2zAg==" + } + }, + { + "name": "sha256", + "payload": { + "component": "agent-core", + "version": "v1.0.0", + "url": "/v1/agent-artifacts/agent-core/agent-linux-amd64.tar.gz", + "sha256": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + }, + "tuple_json": "{\"component\":\"agent-core\",\"version\":\"v1.0.0\",\"url\":\"/v1/agent-artifacts/agent-core/agent-linux-amd64.tar.gz\",\"sha256\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}", + "signing_hash": "sha256:017b032c0836bba65a1c23e89630203ec47ebb9af4e930deeda02aa9b9d3ad9c", + "signature": { + "algorithm": "ed25519", + "key_id": "artifact-key", + "value": "kkr5+GLTkEDbTK8e2b608qIrRKLg+4U1fdyx7YLQ1ZWkzSTcgQDEMGgCOESWtylqt+YpTNSL7jH3h4Ozvj2zAg==" + } + }, + { + "name": "algorithm", + "payload": { + "component": "agent-core", + "version": "v1.0.0", + "url": "/v1/agent-artifacts/agent-core/agent-linux-amd64.tar.gz", + "sha256": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + }, + "tuple_json": "{\"component\":\"agent-core\",\"version\":\"v1.0.0\",\"url\":\"/v1/agent-artifacts/agent-core/agent-linux-amd64.tar.gz\",\"sha256\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}", + "signing_hash": "sha256:017b032c0836bba65a1c23e89630203ec47ebb9af4e930deeda02aa9b9d3ad9c", + "signature": { + "algorithm": "rsa", + "key_id": "artifact-key", + "value": "kkr5+GLTkEDbTK8e2b608qIrRKLg+4U1fdyx7YLQ1ZWkzSTcgQDEMGgCOESWtylqt+YpTNSL7jH3h4Ozvj2zAg==" + } + }, + { + "name": "untrusted-key", + "payload": { + "component": "agent-core", + "version": "v1.0.0", + "url": "/v1/agent-artifacts/agent-core/agent-linux-amd64.tar.gz", + "sha256": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + }, + "tuple_json": "{\"component\":\"agent-core\",\"version\":\"v1.0.0\",\"url\":\"/v1/agent-artifacts/agent-core/agent-linux-amd64.tar.gz\",\"sha256\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}", + "signing_hash": "sha256:017b032c0836bba65a1c23e89630203ec47ebb9af4e930deeda02aa9b9d3ad9c", + "signature": { + "algorithm": "ed25519", + "key_id": "other-key", + "value": "kkr5+GLTkEDbTK8e2b608qIrRKLg+4U1fdyx7YLQ1ZWkzSTcgQDEMGgCOESWtylqt+YpTNSL7jH3h4Ozvj2zAg==" + } + }, + { + "name": "raw-json-domain", + "payload": { + "component": "agent-core", + "version": "v1.0.0", + "url": "/v1/agent-artifacts/agent-core/agent-linux-amd64.tar.gz", + "sha256": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + }, + "tuple_json": "{\"component\":\"agent-core\",\"version\":\"v1.0.0\",\"url\":\"/v1/agent-artifacts/agent-core/agent-linux-amd64.tar.gz\",\"sha256\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}", + "signing_hash": "sha256:017b032c0836bba65a1c23e89630203ec47ebb9af4e930deeda02aa9b9d3ad9c", + "signature": { + "algorithm": "ed25519", + "key_id": "artifact-key", + "value": "TS/W3qxTlN4gaj8h9L5yu+C3z5EYFd20tuKa6EvPbQFxq01wUXbZiShTdstm4DoT4Nk53v5FBTqapfPcvxYCCQ==" + } + } ] }