carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package hostactors
2
3import (
4 "bytes"
5 "context"
6 "crypto/ed25519"
7 "encoding/json"
8 "math"
9 "time"
10
11 "github.com/carlosframework/platform/internal/hostfloor"
12 "github.com/carlosframework/platform/internal/release"
13)
14
15// CensusKeyBinding is approved by the root enrollment coordinator after its
16// authenticated box session supplies a proof of possession. It is not an
17// unauthenticated enrollment request or a console-generated box report.
18type CensusKeyBinding struct {
19 Bucket string `json:"bucket"`
20 Box string `json:"box"`
21 Enrollment string `json:"enrollment"`
22 PublicKey ed25519.PublicKey `json:"public_key"`
23}
24
25func (d Directory) BindCensusKey(ctx context.Context, signer release.Signer, before Observed, binding CensusKeyBinding, proof []byte) (Observed, error) {
26 // This write bypasses Replace's membership gate, so it decides against
27 // the signed roster at the caller's ETag, never a caller-supplied copy.
28 fresh, err := d.Read(ctx)
29 if err != nil {
30 return Observed{}, err
31 }
32 if before.ETag == "" || fresh.ETag != before.ETag {
33 return Observed{}, ErrRoster
34 }
35 before = fresh
36 b, ok := before.Roster.Boxes[binding.Box]
37 if !ok || binding.Bucket != d.Bucket || binding.Enrollment != b.Enrollment || len(binding.PublicKey) != ed25519.PublicKeySize || before.Roster.Revision == math.MaxUint64 {
38 return Observed{}, ErrRoster
39 }
40 raw, err := json.Marshal(binding)
41 if err != nil {
42 return Observed{}, err
43 }
44 if !ed25519.Verify(binding.PublicKey, raw, proof) {
45 return Observed{}, ErrRoster
46 }
47 // I2: binaries older than census_public_key refuse the strict-decoded
48 // roster once a key is bound, so binding waits for the published floor
49 // that excludes them. Only an active, unfenced enrollment is bound.
50 if before.Roster.Publication != "published" || b.State != "active" {
51 return Observed{}, ErrRoster
52 }
53 floor, err := hostfloor.Read(ctx, d.Store, d.PublicKey, d.Bucket)
54 if err != nil {
55 return Observed{}, err
56 }
57 if floor.Digest != before.Roster.FloorSHA256 {
58 return Observed{}, ErrRoster
59 }
60 fenced, err := d.TerminationFence(ctx, before, binding.Box, time.Now())
61 if err != nil {
62 return Observed{}, err
63 }
64 if fenced {
65 return Observed{}, ErrRoster
66 }
67 if len(b.CensusPublicKey) != 0 {
68 if bytes.Equal(b.CensusPublicKey, binding.PublicKey) {
69 return before, nil
70 }
71 return Observed{}, ErrRoster
72 }
73 // One write-once extension to an existing enrollment. Ordinary Replace
74 // continues to reject changes to this field, including deletion/rotation.
75 next := Clone(before.Roster)
76 b.CensusPublicKey = append(ed25519.PublicKey(nil), binding.PublicKey...)
77 next.Boxes[binding.Box] = b
78 next.Revision++
79 if err = next.Validate(d.Bucket); err != nil {
80 return Observed{}, err
81 }
82 wire, err := release.SignRootEnvelope(signer, next)
83 if err != nil {
84 return Observed{}, err
85 }
86 tag, err := d.Store.Put(ctx, Key, wire, before.ETag)
87 if err != nil {
88 return Observed{}, err
89 }
90 return Observed{Roster: next, ETag: tag}, nil
91}
92