carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package release
2
3import (
4 "context"
5 "crypto/sha256"
6 "encoding/hex"
7 "encoding/json"
8 "errors"
9 "os"
10)
11
12const generationCompletenessKey = "completeness.json"
13
14type GenerationEvidence struct {
15 Box, Enrollment, Boot string
16 Migration uint64
17 Installations, Launches, Kernel string
18}
19
20type GenerationCompleteness struct {
21 Schema int
22 Revision uint64
23 Complete bool
24 Reservations string
25 Evidence GenerationEvidence
26}
27
28func generationDigest(v any) string {
29 raw, _ := json.Marshal(v)
30 sum := sha256.Sum256(raw)
31 return hex.EncodeToString(sum[:])
32}
33func generationHash(s string) bool {
34 b, err := hex.DecodeString(s)
35 return err == nil && len(b) == sha256.Size && hex.EncodeToString(b) == s
36}
37func (e GenerationEvidence) valid() bool {
38 return e.Box != "" && e.Enrollment != "" && e.Boot != "" && e.Migration > 0 && generationHash(e.Installations) && generationHash(e.Launches) && generationHash(e.Kernel)
39}
40
41// Completeness is evidence, not a cached permission. A destructive reader must
42// Certify again under release exclusion and retain that exclusion through use.
43func (g *RootGenerations) Completeness() (GenerationCompleteness, error) {
44 var c GenerationCompleteness
45 if g == nil || g.Store == nil {
46 return c, os.ErrPermission
47 }
48 raw, err := g.Store.Read(generationCompletenessKey)
49 if err != nil {
50 return c, err
51 }
52 if len(raw) > rootGenerationCompletenessLimit || strictCanonicalJSON(raw, &c) != nil || c.Schema != 1 || c.Revision == 0 || !generationHash(c.Reservations) || !c.Evidence.valid() {
53 return GenerationCompleteness{}, os.ErrPermission
54 }
55 if err := g.Store.Write(generationCompletenessKey, raw); err != nil {
56 return GenerationCompleteness{}, err
57 }
58 return c, nil
59}
60
61const rootGenerationCompletenessLimit = 4096
62
63func (g *RootGenerations) writeCompleteness(c GenerationCompleteness) error {
64 if c.Revision == 0 {
65 return os.ErrPermission
66 }
67 raw, err := json.Marshal(c)
68 if err != nil {
69 return err
70 }
71 if len(raw) > rootGenerationCompletenessLimit {
72 return os.ErrPermission
73 }
74 return g.Store.Write(generationCompletenessKey, raw)
75}
76
77// Invalidation precedes each reservation transition. A crash between the two
78// writes leaves the old obligation with an unusable certificate.
79func (g *RootGenerations) invalidateCompleteness() error {
80 c, err := g.Completeness()
81 if errors.Is(err, os.ErrNotExist) {
82 return nil
83 }
84 if err != nil {
85 return err
86 }
87 c.Complete = false
88 c.Revision++
89 return g.writeCompleteness(c)
90}
91
92// Certify accepts only independently reconciled installation, launch and kernel
93// evidence. The verifier must not acquire release exclusion: its caller holds it.
94func (g *RootGenerations) Certify(ctx context.Context, expected []GenerationReservation, verify func(context.Context, []GenerationReservation) (GenerationEvidence, error)) (GenerationCompleteness, error) {
95 var zero GenerationCompleteness
96 if verify == nil {
97 return zero, os.ErrPermission
98 }
99 before, err := g.Reservations(ctx)
100 if err != nil {
101 return zero, err
102 }
103 digest := generationDigest(before)
104 if digest != generationDigest(expected) {
105 return zero, os.ErrPermission
106 }
107 // A failed reconciliation must not leave the previous positive record live.
108 if err := g.invalidateCompleteness(); err != nil {
109 return zero, err
110 }
111 e, err := verify(ctx, before)
112 if err != nil {
113 return zero, err
114 }
115 if !e.valid() {
116 return zero, os.ErrPermission
117 }
118 for _, r := range before {
119 if r.Binding.Target.Box != e.Box || r.Binding.Target.Enrollment != e.Enrollment {
120 return zero, os.ErrPermission
121 }
122 }
123 after, err := g.Reservations(ctx)
124 if err != nil {
125 return zero, err
126 }
127 if generationDigest(after) != digest {
128 return zero, os.ErrPermission
129 }
130 old, err := g.Completeness()
131 if err != nil && !errors.Is(err, os.ErrNotExist) {
132 return zero, err
133 }
134 c := GenerationCompleteness{Schema: 1, Revision: old.Revision + 1, Complete: true, Reservations: digest, Evidence: e}
135 if err := ctx.Err(); err != nil {
136 return zero, err
137 }
138 if err := g.writeCompleteness(c); err != nil {
139 return zero, err
140 }
141 return c, nil
142}
143