carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package host
2
3import (
4 "context"
5 "encoding/json"
6 "errors"
7 "fmt"
8 "os"
9 "path/filepath"
10 "strconv"
11 "strings"
12
13 "github.com/carlosframework/platform/internal/moves"
14 "github.com/carlosframework/platform/internal/rootparticipation"
15 "github.com/carlosframework/platform/internal/rootstate"
16)
17
18const (
19 rootFloorAccepted = "accepted.json"
20 rootFloorDeny = "deny.json"
21 rootFloorSelector = "floor.line"
22 rootFloorProjectionFile = "accepted.json"
23)
24
25type rootFloorIdentity struct{ Deployment, Box, Enrollment string }
26type rootFloorReconstruction struct {
27 Identity rootFloorIdentity
28 Floor *moves.Floor
29 NeverArmed bool
30 Source, Digest string
31 Authorization *rootFloorAuthorization `json:"-"`
32 LegacyAuthorization string `json:"Authorization,omitempty"`
33}
34type rootFloorRecord struct {
35 Schema int
36 Identity rootFloorIdentity
37 Generation uint64
38 Floor *moves.Floor
39 NeverArmed bool
40 Provenance rootFloorReconstruction
41}
42type rootFloorProjection struct {
43 rootFloorRecord
44 Accepted bool
45}
46type rootMovesFloors struct {
47 private, public rootstate.Store
48 identity rootFloorIdentity
49}
50
51func rootFloorDir(opt string) string { return filepath.Join(opt, "system", "guard", "moves") }
52func openRootMovesFloors(opt string, id rootFloorIdentity, create bool) (rootMovesFloors, error) {
53 return openRootProtocolFloors(rootFloorDir(opt), id, create)
54}
55func openRootProtocolFloors(dir string, id rootFloorIdentity, create bool) (rootMovesFloors, error) {
56 f := rootMovesFloors{identity: id}
57 var err error
58 f.public, err = rootstate.Open(dir, create, true)
59 if err != nil {
60 return f, err
61 }
62 f.private, err = rootstate.Open(filepath.Join(dir, "private"), create, false)
63 if err != nil {
64 f.public.Close()
65 return rootMovesFloors{}, err
66 }
67 return f, nil
68}
69func (f rootMovesFloors) close() { f.private.Close(); f.public.Close() }
70func validRootFloor(fl *moves.Floor) bool {
71 return fl != nil && fl.Min > 0 && validFloorRelease(fl.MinRelease)
72}
73func (r rootFloorRecord) valid(id rootFloorIdentity) bool {
74 return r.Schema == 1 && r.Identity == id && id.Deployment != "" && id.Box != "" && id.Enrollment != "" && r.Generation > 0 && ((validRootFloor(r.Floor) && !r.NeverArmed) || (r.Floor == nil && r.NeverArmed))
75}
76func (f rootMovesFloors) record(name string) (rootFloorRecord, error) {
77 var r rootFloorRecord
78 raw, err := f.private.Read(name)
79 if err != nil {
80 return r, err
81 }
82 if len(raw) > movesFloorMaxBytes || rootparticipation.Decode(raw, &r) != nil || !r.valid(f.identity) {
83 return r, errRootFloorHistory
84 }
85 return r, nil
86}
87func floorMaximum(a, b *moves.Floor) *moves.Floor {
88 if a == nil {
89 if b == nil {
90 return nil
91 }
92 c := *b
93 return &c
94 }
95 c := *a
96 if b != nil {
97 if b.Min > c.Min {
98 c.Min = b.Min
99 }
100 if at, _ := moves.ReleaseAtOrAbove(b.MinRelease, c.MinRelease); at {
101 c.MinRelease = b.MinRelease
102 }
103 }
104 return &c
105}
106func rootFloorLine(fl *moves.Floor) []byte {
107 if fl == nil {
108 return []byte("0 none\n")
109 }
110 return []byte(fmt.Sprintf("%d %s\n", fl.Min, fl.MinRelease))
111}
112func (f rootMovesFloors) selector() (*moves.Floor, error) {
113 raw, err := f.public.Read(rootFloorSelector)
114 if err != nil {
115 return nil, err
116 }
117 fields := strings.Fields(string(raw))
118 if len(raw) > 256 || len(fields) != 2 {
119 return nil, errRootFloorHistory
120 }
121 if string(raw) == "0 none\n" {
122 return nil, nil
123 }
124 min, err := strconv.Atoi(fields[0])
125 fl := &moves.Floor{Min: min, MinRelease: fields[1]}
126 if err != nil || !validRootFloor(fl) || string(raw) != string(rootFloorLine(fl)) {
127 return nil, errRootFloorHistory
128 }
129 return fl, nil
130}
131
132// A selector visible after an interrupted write remains a deny, even when the
133// accepted JSON still names the preceding generation.
134func (f rootMovesFloors) bound() (*moves.Floor, error) {
135 var bound *moves.Floor
136 var errs []error
137 for _, name := range []string{rootFloorAccepted, rootFloorDeny} {
138 r, err := f.record(name)
139 if err == nil {
140 bound = floorMaximum(bound, r.Floor)
141 } else if !errors.Is(err, os.ErrNotExist) {
142 errs = append(errs, err)
143 }
144 }
145 fl, err := f.selector()
146 if err == nil {
147 bound = floorMaximum(bound, fl)
148 } else if !errors.Is(err, os.ErrNotExist) {
149 errs = append(errs, err)
150 }
151 return bound, errors.Join(errs...)
152}
153
154// Missing or malformed history contributes no permission and no invented deny.
155// Every independently readable retained bound still applies.
156func (f rootMovesFloors) localBound() (*moves.Floor, error) {
157 var bound *moves.Floor
158 if f.private != nil {
159 for _, name := range []string{rootFloorAccepted, rootFloorDeny} {
160 r, err := f.record(name)
161 if err == nil {
162 bound = floorMaximum(bound, r.Floor)
163 } else if !errors.Is(err, os.ErrNotExist) && !errors.Is(err, errRootFloorHistory) {
164 return bound, err
165 }
166 }
167 }
168 fl, err := f.selector()
169 if err == nil {
170 bound = floorMaximum(bound, fl)
171 } else if !errors.Is(err, os.ErrNotExist) && !errors.Is(err, errRootFloorHistory) {
172 return bound, err
173 }
174 raw, err := f.public.Read(rootFloorProjectionFile)
175 if err == nil {
176 var p rootFloorProjection
177 if rootparticipation.Decode(raw, &p) == nil && p.rootFloorRecord.valid(f.identity) {
178 bound = floorMaximum(bound, p.Floor)
179 }
180 } else if !errors.Is(err, os.ErrNotExist) {
181 return bound, err
182 }
183 return bound, nil
184}
185
186func (f rootMovesFloors) quarantine(cause error) error {
187 raw, _ := json.Marshal(rootFloorProjection{Accepted: false})
188 return errors.Join(cause, f.public.Write(rootFloorProjectionFile, raw))
189}
190func (f rootMovesFloors) projection() (rootFloorProjection, error) {
191 var p rootFloorProjection
192 raw, err := f.public.Read(rootFloorProjectionFile)
193 if err != nil {
194 return p, err
195 }
196 if len(raw) > movesFloorMaxBytes || rootparticipation.Decode(raw, &p) != nil || !p.Accepted || !p.rootFloorRecord.valid(f.identity) {
197 return p, errRootFloorHistory
198 }
199 bound, err := f.selector()
200 if err != nil || string(rootFloorLine(bound)) != string(rootFloorLine(p.Floor)) {
201 return p, errRootFloorHistory
202 }
203 if f.private != nil {
204 accepted, err := f.record(rootFloorAccepted)
205 if err != nil || accepted.Generation != p.Generation || accepted.Identity != p.Identity || string(rootFloorLine(accepted.Floor)) != string(rootFloorLine(p.Floor)) {
206 return p, errRootFloorHistory
207 }
208 bound, err := f.bound()
209 if err != nil || string(rootFloorLine(bound)) != string(rootFloorLine(p.Floor)) {
210 return p, errRootFloorHistory
211 }
212 }
213 return p, nil
214}
215func (f rootMovesFloors) writeRecord(name string, r rootFloorRecord) error {
216 raw, err := json.Marshal(r)
217 if err != nil {
218 return err
219 }
220 return f.private.Write(name, raw)
221}
222
223// Caller holds the stable private floor lock. Every retry rewrites and syncs
224// all prerequisites; visible bytes are never taken as proof of an earlier sync.
225func (f rootMovesFloors) persist(r rootFloorRecord, bound *moves.Floor, accept bool) error {
226 if err := f.quarantine(nil); err != nil {
227 return err
228 }
229 if err := f.public.Write(rootFloorSelector, rootFloorLine(bound)); err != nil {
230 return err
231 }
232 deny := r
233 deny.Floor = bound
234 deny.NeverArmed = bound == nil
235 if err := f.writeRecord(rootFloorDeny, deny); err != nil {
236 return err
237 }
238 if !accept {
239 return errRootFloorHistory
240 }
241 if err := f.writeRecord(rootFloorAccepted, r); err != nil {
242 return err
243 }
244 raw, _ := json.Marshal(rootFloorProjection{rootFloorRecord: r, Accepted: true})
245 if err := f.public.Write(rootFloorProjectionFile, raw); err != nil {
246 return f.quarantine(err)
247 }
248 return nil
249}
250
251// This entry is only for a root-authorized complete reconstruction, including
252// an explicit never-armed attestation. Normal refresh cannot manufacture it.
253func (f rootMovesFloors) reconstruct(ctx context.Context, r rootFloorReconstruction) error {
254 unlock, err := f.private.Lock(ctx, "floor.lock")
255 if err != nil {
256 return err
257 }
258 defer unlock()
259 if r.Identity != f.identity || r.Source == "" || len(r.Digest) != 64 || (r.NeverArmed != (r.Floor == nil)) || (r.Floor != nil && !validRootFloor(r.Floor)) {
260 return f.quarantine(errRootFloorHistory)
261 }
262 bound, err := f.bound()
263 if err != nil {
264 return f.quarantine(err)
265 }
266 if bound != nil && (r.Floor == nil || floorRegression(bound, r.Floor) != nil) {
267 return f.quarantine(errRootFloorHistory)
268 }
269 generation := uint64(1)
270 old, err := f.record(rootFloorAccepted)
271 if err == nil {
272 generation = old.Generation + 1
273 } else if !errors.Is(err, os.ErrNotExist) {
274 return f.quarantine(err)
275 }
276 if !f.authorizedReconstruction(r.Authorization, generation) {
277 return f.quarantine(errRootFloorHistory)
278 }
279 next := rootFloorRecord{Schema: 1, Identity: f.identity, Generation: generation, Floor: r.Floor, NeverArmed: r.NeverArmed, Provenance: r}
280 if !next.valid(f.identity) {
281 return f.quarantine(errRootFloorHistory)
282 }
283 return f.persist(next, floorMaximum(bound, r.Floor), true)
284}
285
286func (f rootMovesFloors) refresh(ctx context.Context, remote *moves.Floor, legacy []*moves.Floor, readErr error) error {
287 unlock, err := f.private.Lock(ctx, "floor.lock")
288 if err != nil {
289 return err
290 }
291 defer unlock()
292 legacy, err = f.capLegacy(legacy)
293 if err != nil {
294 return err
295 }
296 accepted, aerr := f.record(rootFloorAccepted)
297 if errors.Is(aerr, os.ErrNotExist) || errors.Is(aerr, errRootFloorHistory) {
298 bound, err := f.localBound()
299 if err != nil {
300 return err
301 }
302 for _, fl := range append(legacy, remote) {
303 if validRootFloor(fl) {
304 bound = floorMaximum(bound, fl)
305 }
306 }
307 if err := f.quarantine(nil); err != nil {
308 return err
309 }
310 if err := f.public.Write(rootFloorSelector, rootFloorLine(bound)); err != nil {
311 return err
312 }
313 deny := rootFloorRecord{Schema: 1, Identity: f.identity, Generation: 1, Floor: bound, NeverArmed: bound == nil}
314 return f.writeRecord(rootFloorDeny, deny)
315 }
316 bound, berr := f.bound()
317 for _, fl := range legacy {
318 if validRootFloor(fl) {
319 bound = floorMaximum(bound, fl)
320 } else {
321 readErr = errors.Join(readErr, errRootFloorHistory)
322 }
323 }
324 if remote != nil && !validRootFloor(remote) {
325 readErr = errors.Join(readErr, errRootFloorHistory)
326 remote = nil
327 }
328 // Even uninitialized or corrupt histories retain every usable observed deny.
329 next := accepted
330 if aerr != nil {
331 next = rootFloorRecord{Schema: 1, Identity: f.identity, Generation: 1}
332 }
333 next.Generation++
334 refusal := errors.Join(readErr, berr, aerr)
335 if remote != nil {
336 if bound != nil {
337 refusal = errors.Join(refusal, floorRegression(bound, remote))
338 }
339 bound = floorMaximum(bound, remote)
340 }
341 if refusal == nil && remote == nil && string(rootFloorLine(bound)) != string(rootFloorLine(accepted.Floor)) {
342 refusal = errRootFloorHistory
343 }
344 if refusal == nil {
345 next.Floor = bound
346 next.NeverArmed = bound == nil
347 }
348 // A corrupt selector cannot be safely overwritten from incomplete evidence.
349 if berr != nil {
350 return f.quarantine(refusal)
351 }
352 if refusal == nil && !next.valid(f.identity) {
353 return f.quarantine(errRootFloorHistory)
354 }
355 err = f.persist(next, bound, refusal == nil)
356 return errors.Join(refusal, err)
357}
358