oficina / home Public

Clone
git clone https://amadan.net/oficina/home

Plain git — no account needed to clone.

Download

Download this file

1package operator
2
3import (
4 "bytes"
5 "encoding/base64"
6 "os"
7 "strings"
8 "testing"
9 "time"
10
11 family "amadan.net/rastrillo/rastrillo/crypto"
12)
13
14func schemaWireFixture(t *testing.T) (*family.Keypair, *Verifier, *schemaVerifierV2, schemaAuthorizationV2) {
15 t.Helper()
16 key, old, a := fixture(t)
17 a.Version = 2
18 p := old.pins
19 p.Actions = append(p.Actions, "directory.inspect")
20 v, err := newSchemaVerifierV2(p, "directory-historical-empty-v1", strings.Repeat("a", 64))
21 if err != nil {
22 t.Fatal(err)
23 }
24 return key, old, v, schemaAuthorizationV2{a, v.profile, v.manifestDigest}
25}
26func TestSchemaWireVersionsAndSignedPins(t *testing.T) {
27 key, old, v, a := schemaWireFixture(t)
28 raw, err := signSchemaRequestV2(key, a, struct {
29 Generation string `json:"generation"`
30 }{token(8)})
31 if err != nil {
32 t.Fatal(err)
33 }
34 r, err := verifySchemaRequestV2(v, raw)
35 if err != nil || r.authorization.Version != 2 || r.head == "" {
36 t.Fatal(err)
37 }
38 if _, err := old.Verify(raw); err == nil {
39 t.Fatal("public v1 verifier accepted v2")
40 }
41 oldAuth := a.Authorization
42 oldAuth.Version = 1
43 if _, err := verifySchemaRequestV2(v, signed(t, key, oldAuth)); err == nil {
44 t.Fatal("v2 accepted v1")
45 }
46 if _, err := SignRequest(key, a.Authorization, map[string]string{}); err == nil {
47 t.Fatal("public signer accepted v2")
48 }
49 for _, change := range []func(*schemaAuthorizationV2){
50 func(a *schemaAuthorizationV2) { a.SchemaProfile = "directory-current-v1" },
51 func(a *schemaAuthorizationV2) { a.SchemaManifestDigest = strings.Repeat("b", 64) },
52 func(a *schemaAuthorizationV2) { a.Origin = "https://other.example.test" },
53 func(a *schemaAuthorizationV2) { a.Installation = token(19) },
54 func(a *schemaAuthorizationV2) { a.Registry = token(19) },
55 func(a *schemaAuthorizationV2) { a.Device = token(19) },
56 func(a *schemaAuthorizationV2) { a.Action = "directory.activate-consumer" },
57 } {
58 changed := a
59 change(&changed)
60 wire, err := signSchemaRequestV2(key, changed, map[string]string{})
61 if err != nil {
62 t.Fatal(err)
63 }
64 if _, err := verifySchemaRequestV2(v, wire); err == nil {
65 t.Fatal("trusted key authorized another target/profile")
66 }
67 }
68 for _, bad := range []schemaAuthorizationV2{
69 {a.Authorization, "", a.SchemaManifestDigest},
70 {a.Authorization, "unknown", a.SchemaManifestDigest},
71 {a.Authorization, a.SchemaProfile, ""},
72 {a.Authorization, a.SchemaProfile, strings.Repeat("A", 64)},
73 } {
74 if _, err := signSchemaRequestV2(key, bad, map[string]string{}); err == nil {
75 t.Fatal("invalid commitment signed")
76 }
77 }
78 var w wrapper
79 if strictCanonical(raw, &w) != nil {
80 t.Fatal("wrapper")
81 }
82 for _, version := range []int{0, 1, 3} {
83 changed := w
84 changed.Version = version
85 wire, _ := encode(changed)
86 if _, err := verifySchemaRequestV2(v, wire); err == nil {
87 t.Fatal("mixed wrapper accepted")
88 }
89 }
90 var env envelope
91 if strictCanonical(w.Authorization, &env) != nil {
92 t.Fatal("envelope")
93 }
94 env.Version = 2
95 changed := w
96 changed.Authorization, _ = encode(env)
97 wire, _ := encode(changed)
98 if _, err := verifySchemaRequestV2(v, wire); err == nil {
99 t.Fatal("envelope v2 accepted")
100 }
101 changed = w
102 changed.Input = []byte(`{"generation":"changed"}`)
103 wire, _ = encode(changed)
104 if _, err := verifySchemaRequestV2(v, wire); err == nil {
105 t.Fatal("changed input accepted")
106 }
107 if _, err := verifySchemaRequestV2(v, append([]byte{' '}, raw...)); err == nil {
108 t.Fatal("noncanonical wire")
109 }
110 if _, err := verifySchemaRequestV2(v, bytes.Replace(raw, []byte(`"version":2`), []byte(`"version":2,"version":2`), 1)); err == nil {
111 t.Fatal("duplicate member")
112 }
113}
114func TestSchemaInspectUsesOwnContextAndFreshPins(t *testing.T) {
115 key, old, v, a := schemaWireFixture(t)
116 c := schemaInspectV2{InspectChallenge{2, "operator-inspect", a.Role, a.Origin, a.Installation, a.Registry, a.Device, token(6), a.Issued, a.Expires}, a.SchemaProfile, a.SchemaManifestDigest}
117 raw, err := signSchemaInspectV2(key, c)
118 if err != nil {
119 t.Fatal(err)
120 }
121 now := time.Unix(a.Issued, 0)
122 if _, err := verifySchemaInspectV2(v, raw, now); err != nil {
123 t.Fatal(err)
124 }
125 if _, err := old.verifyInspect(raw, now); err == nil {
126 t.Fatal("v1 inspector accepted v2")
127 }
128 if _, err := SignInspect(key, c.InspectChallenge); err == nil {
129 t.Fatal("public inspection signer accepted v2")
130 }
131 for _, at := range []int64{a.Issued - 1, a.Expires, a.Expires + 1} {
132 if _, err := verifySchemaInspectV2(v, raw, time.Unix(at, 0)); err == nil {
133 t.Fatal("stale inspection accepted")
134 }
135 }
136 changed := c
137 changed.SchemaProfile = "directory-current-v1"
138 wire, _ := signSchemaInspectV2(key, changed)
139 if _, err := verifySchemaInspectV2(v, wire, now); err == nil {
140 t.Fatal("other signed profile accepted")
141 }
142 var env envelope
143 if strictCanonical(raw, &env) != nil {
144 t.Fatal("envelope")
145 }
146 payload, _ := base64.RawURLEncoding.DecodeString(env.Payload)
147 sig, err := family.Sign(key, InputContext, payload)
148 if err != nil {
149 t.Fatal(err)
150 }
151 env.Signature = base64.RawURLEncoding.EncodeToString(sig)
152 wire, _ = encode(env)
153 if _, err := verifySchemaInspectV2(v, wire, now); err == nil {
154 t.Fatal("inspection signed for mutation context accepted")
155 }
156 oldChallenge := c.InspectChallenge
157 oldChallenge.Version = 1
158 wire, err = SignInspect(key, oldChallenge)
159 if err != nil {
160 t.Fatal(err)
161 }
162 if _, err := verifySchemaInspectV2(v, wire, now); err == nil {
163 t.Fatal("v2 inspector accepted v1")
164 }
165}
166
167func TestSchemaWireKeepsV1PayloadBytes(t *testing.T) {
168 _, _, a := fixture(t)
169 a.InputDigest = token(7)
170 c := InspectChallenge{1, "operator-inspect", a.Role, a.Origin, a.Installation, a.Registry, a.Device, token(6), a.Issued, a.Expires}
171 for name, value := range map[string]any{"authorization": a, "inspection": c} {
172 raw, err := encode(value)
173 if err != nil {
174 t.Fatal(err)
175 }
176 path := "testdata/schema-v1-" + name + ".json"
177 if os.Getenv("OFICINA_CAPTURE_V1_SCHEMA_WIRE") == "1" {
178 if err := os.WriteFile(path, raw, 0600); err != nil {
179 t.Fatal(err)
180 }
181 continue
182 }
183 frozen, err := os.ReadFile(path)
184 if err != nil || !bytes.Equal(frozen, raw) {
185 t.Fatal("v1 signature input changed", name, err)
186 }
187 }
188}
189