package host import ( "context" "encoding/json" "errors" "fmt" "os" "path/filepath" "strconv" "strings" "github.com/carlosframework/platform/internal/moves" "github.com/carlosframework/platform/internal/rootparticipation" "github.com/carlosframework/platform/internal/rootstate" ) const ( rootFloorAccepted = "accepted.json" rootFloorDeny = "deny.json" rootFloorSelector = "floor.line" rootFloorProjectionFile = "accepted.json" ) type rootFloorIdentity struct{ Deployment, Box, Enrollment string } type rootFloorReconstruction struct { Identity rootFloorIdentity Floor *moves.Floor NeverArmed bool Source, Digest string Authorization *rootFloorAuthorization `json:"-"` LegacyAuthorization string `json:"Authorization,omitempty"` } type rootFloorRecord struct { Schema int Identity rootFloorIdentity Generation uint64 Floor *moves.Floor NeverArmed bool Provenance rootFloorReconstruction } type rootFloorProjection struct { rootFloorRecord Accepted bool } type rootMovesFloors struct { private, public rootstate.Store identity rootFloorIdentity } func rootFloorDir(opt string) string { return filepath.Join(opt, "system", "guard", "moves") } func openRootMovesFloors(opt string, id rootFloorIdentity, create bool) (rootMovesFloors, error) { return openRootProtocolFloors(rootFloorDir(opt), id, create) } func openRootProtocolFloors(dir string, id rootFloorIdentity, create bool) (rootMovesFloors, error) { f := rootMovesFloors{identity: id} var err error f.public, err = rootstate.Open(dir, create, true) if err != nil { return f, err } f.private, err = rootstate.Open(filepath.Join(dir, "private"), create, false) if err != nil { f.public.Close() return rootMovesFloors{}, err } return f, nil } func (f rootMovesFloors) close() { f.private.Close(); f.public.Close() } func validRootFloor(fl *moves.Floor) bool { return fl != nil && fl.Min > 0 && validFloorRelease(fl.MinRelease) } func (r rootFloorRecord) valid(id rootFloorIdentity) bool { 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)) } func (f rootMovesFloors) record(name string) (rootFloorRecord, error) { var r rootFloorRecord raw, err := f.private.Read(name) if err != nil { return r, err } if len(raw) > movesFloorMaxBytes || rootparticipation.Decode(raw, &r) != nil || !r.valid(f.identity) { return r, errRootFloorHistory } return r, nil } func floorMaximum(a, b *moves.Floor) *moves.Floor { if a == nil { if b == nil { return nil } c := *b return &c } c := *a if b != nil { if b.Min > c.Min { c.Min = b.Min } if at, _ := moves.ReleaseAtOrAbove(b.MinRelease, c.MinRelease); at { c.MinRelease = b.MinRelease } } return &c } func rootFloorLine(fl *moves.Floor) []byte { if fl == nil { return []byte("0 none\n") } return []byte(fmt.Sprintf("%d %s\n", fl.Min, fl.MinRelease)) } func (f rootMovesFloors) selector() (*moves.Floor, error) { raw, err := f.public.Read(rootFloorSelector) if err != nil { return nil, err } fields := strings.Fields(string(raw)) if len(raw) > 256 || len(fields) != 2 { return nil, errRootFloorHistory } if string(raw) == "0 none\n" { return nil, nil } min, err := strconv.Atoi(fields[0]) fl := &moves.Floor{Min: min, MinRelease: fields[1]} if err != nil || !validRootFloor(fl) || string(raw) != string(rootFloorLine(fl)) { return nil, errRootFloorHistory } return fl, nil } // A selector visible after an interrupted write remains a deny, even when the // accepted JSON still names the preceding generation. func (f rootMovesFloors) bound() (*moves.Floor, error) { var bound *moves.Floor var errs []error for _, name := range []string{rootFloorAccepted, rootFloorDeny} { r, err := f.record(name) if err == nil { bound = floorMaximum(bound, r.Floor) } else if !errors.Is(err, os.ErrNotExist) { errs = append(errs, err) } } fl, err := f.selector() if err == nil { bound = floorMaximum(bound, fl) } else if !errors.Is(err, os.ErrNotExist) { errs = append(errs, err) } return bound, errors.Join(errs...) } // Missing or malformed history contributes no permission and no invented deny. // Every independently readable retained bound still applies. func (f rootMovesFloors) localBound() (*moves.Floor, error) { var bound *moves.Floor if f.private != nil { for _, name := range []string{rootFloorAccepted, rootFloorDeny} { r, err := f.record(name) if err == nil { bound = floorMaximum(bound, r.Floor) } else if !errors.Is(err, os.ErrNotExist) && !errors.Is(err, errRootFloorHistory) { return bound, err } } } fl, err := f.selector() if err == nil { bound = floorMaximum(bound, fl) } else if !errors.Is(err, os.ErrNotExist) && !errors.Is(err, errRootFloorHistory) { return bound, err } raw, err := f.public.Read(rootFloorProjectionFile) if err == nil { var p rootFloorProjection if rootparticipation.Decode(raw, &p) == nil && p.rootFloorRecord.valid(f.identity) { bound = floorMaximum(bound, p.Floor) } } else if !errors.Is(err, os.ErrNotExist) { return bound, err } return bound, nil } func (f rootMovesFloors) quarantine(cause error) error { raw, _ := json.Marshal(rootFloorProjection{Accepted: false}) return errors.Join(cause, f.public.Write(rootFloorProjectionFile, raw)) } func (f rootMovesFloors) projection() (rootFloorProjection, error) { var p rootFloorProjection raw, err := f.public.Read(rootFloorProjectionFile) if err != nil { return p, err } if len(raw) > movesFloorMaxBytes || rootparticipation.Decode(raw, &p) != nil || !p.Accepted || !p.rootFloorRecord.valid(f.identity) { return p, errRootFloorHistory } bound, err := f.selector() if err != nil || string(rootFloorLine(bound)) != string(rootFloorLine(p.Floor)) { return p, errRootFloorHistory } if f.private != nil { accepted, err := f.record(rootFloorAccepted) if err != nil || accepted.Generation != p.Generation || accepted.Identity != p.Identity || string(rootFloorLine(accepted.Floor)) != string(rootFloorLine(p.Floor)) { return p, errRootFloorHistory } bound, err := f.bound() if err != nil || string(rootFloorLine(bound)) != string(rootFloorLine(p.Floor)) { return p, errRootFloorHistory } } return p, nil } func (f rootMovesFloors) writeRecord(name string, r rootFloorRecord) error { raw, err := json.Marshal(r) if err != nil { return err } return f.private.Write(name, raw) } // Caller holds the stable private floor lock. Every retry rewrites and syncs // all prerequisites; visible bytes are never taken as proof of an earlier sync. func (f rootMovesFloors) persist(r rootFloorRecord, bound *moves.Floor, accept bool) error { if err := f.quarantine(nil); err != nil { return err } if err := f.public.Write(rootFloorSelector, rootFloorLine(bound)); err != nil { return err } deny := r deny.Floor = bound deny.NeverArmed = bound == nil if err := f.writeRecord(rootFloorDeny, deny); err != nil { return err } if !accept { return errRootFloorHistory } if err := f.writeRecord(rootFloorAccepted, r); err != nil { return err } raw, _ := json.Marshal(rootFloorProjection{rootFloorRecord: r, Accepted: true}) if err := f.public.Write(rootFloorProjectionFile, raw); err != nil { return f.quarantine(err) } return nil } // This entry is only for a root-authorized complete reconstruction, including // an explicit never-armed attestation. Normal refresh cannot manufacture it. func (f rootMovesFloors) reconstruct(ctx context.Context, r rootFloorReconstruction) error { unlock, err := f.private.Lock(ctx, "floor.lock") if err != nil { return err } defer unlock() if r.Identity != f.identity || r.Source == "" || len(r.Digest) != 64 || (r.NeverArmed != (r.Floor == nil)) || (r.Floor != nil && !validRootFloor(r.Floor)) { return f.quarantine(errRootFloorHistory) } bound, err := f.bound() if err != nil { return f.quarantine(err) } if bound != nil && (r.Floor == nil || floorRegression(bound, r.Floor) != nil) { return f.quarantine(errRootFloorHistory) } generation := uint64(1) old, err := f.record(rootFloorAccepted) if err == nil { generation = old.Generation + 1 } else if !errors.Is(err, os.ErrNotExist) { return f.quarantine(err) } if !f.authorizedReconstruction(r.Authorization, generation) { return f.quarantine(errRootFloorHistory) } next := rootFloorRecord{Schema: 1, Identity: f.identity, Generation: generation, Floor: r.Floor, NeverArmed: r.NeverArmed, Provenance: r} if !next.valid(f.identity) { return f.quarantine(errRootFloorHistory) } return f.persist(next, floorMaximum(bound, r.Floor), true) } func (f rootMovesFloors) refresh(ctx context.Context, remote *moves.Floor, legacy []*moves.Floor, readErr error) error { unlock, err := f.private.Lock(ctx, "floor.lock") if err != nil { return err } defer unlock() legacy, err = f.capLegacy(legacy) if err != nil { return err } accepted, aerr := f.record(rootFloorAccepted) if errors.Is(aerr, os.ErrNotExist) || errors.Is(aerr, errRootFloorHistory) { bound, err := f.localBound() if err != nil { return err } for _, fl := range append(legacy, remote) { if validRootFloor(fl) { bound = floorMaximum(bound, fl) } } if err := f.quarantine(nil); err != nil { return err } if err := f.public.Write(rootFloorSelector, rootFloorLine(bound)); err != nil { return err } deny := rootFloorRecord{Schema: 1, Identity: f.identity, Generation: 1, Floor: bound, NeverArmed: bound == nil} return f.writeRecord(rootFloorDeny, deny) } bound, berr := f.bound() for _, fl := range legacy { if validRootFloor(fl) { bound = floorMaximum(bound, fl) } else { readErr = errors.Join(readErr, errRootFloorHistory) } } if remote != nil && !validRootFloor(remote) { readErr = errors.Join(readErr, errRootFloorHistory) remote = nil } // Even uninitialized or corrupt histories retain every usable observed deny. next := accepted if aerr != nil { next = rootFloorRecord{Schema: 1, Identity: f.identity, Generation: 1} } next.Generation++ refusal := errors.Join(readErr, berr, aerr) if remote != nil { if bound != nil { refusal = errors.Join(refusal, floorRegression(bound, remote)) } bound = floorMaximum(bound, remote) } if refusal == nil && remote == nil && string(rootFloorLine(bound)) != string(rootFloorLine(accepted.Floor)) { refusal = errRootFloorHistory } if refusal == nil { next.Floor = bound next.NeverArmed = bound == nil } // A corrupt selector cannot be safely overwritten from incomplete evidence. if berr != nil { return f.quarantine(refusal) } if refusal == nil && !next.valid(f.identity) { return f.quarantine(errRootFloorHistory) } err = f.persist(next, bound, refusal == nil) return errors.Join(refusal, err) }