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 "context"
5 "encoding/base64"
6 "time"
7
8 family "amadan.net/rastrillo/rastrillo/crypto"
9)
10
11const InspectContext = "oficina/home/operator-inspect/v1"
12
13type InspectChallenge struct {
14 Version int `json:"version"`
15 Purpose string `json:"purpose"`
16 Role string `json:"role"`
17 Origin string `json:"origin"`
18 Installation string `json:"installation"`
19 Registry string `json:"registry"`
20 Device string `json:"device"`
21 Challenge string `json:"challenge"`
22 Issued int64 `json:"issued_at"`
23 Expires int64 `json:"expires_at"`
24}
25
26func (c InspectChallenge) valid() bool {
27 return c.Version == 1 && c.Purpose == "operator-inspect" && (c.Role == "directory" || c.Role == "credential") && Origin(c.Origin) && CanonicalToken(c.Installation) && CanonicalToken(c.Registry) && CanonicalToken(c.Device) && CanonicalToken(c.Challenge) && c.Issued > 0 && c.Expires > c.Issued && c.Expires-c.Issued <= 300
28}
29func SignInspect(k *family.Keypair, c InspectChallenge) ([]byte, error) {
30 if !signingKey(k) || !c.valid() {
31 return nil, ErrRefused
32 }
33 payload, err := encode(c)
34 if err != nil {
35 return nil, ErrRefused
36 }
37 signature, err := family.Sign(k, InspectContext, payload)
38 if err != nil {
39 return nil, ErrUnavailable
40 }
41 return encode(envelope{1, keyID(k.SignPub()), base64.RawURLEncoding.EncodeToString(payload), base64.RawURLEncoding.EncodeToString(signature)})
42}
43func (v *Verifier) verifyInspect(raw []byte, now time.Time) (InspectChallenge, error) {
44 var env envelope
45 var c InspectChallenge
46 if v == nil || len(raw) == 0 || len(raw) > AuthorizationLimit || strictCanonical(raw, &env) != nil || env.Version != 1 || env.KeyID != v.kid {
47 return c, ErrRefused
48 }
49 payload, ok := canonical(env.Payload, -1)
50 sig, ok2 := canonical(env.Signature, 64)
51 if !ok || !ok2 || !family.Verify(v.key, InspectContext, payload, sig) || strictCanonical(payload, &c) != nil || !c.valid() {
52 return InspectChallenge{}, ErrRefused
53 }
54 p := v.pins
55 if c.Role != p.Role || c.Origin != p.Origin || c.Installation != p.Installation || c.Registry != p.Registry || c.Device != p.Device || c.Issued > now.Unix() || c.Expires <= now.Unix() {
56 return InspectChallenge{}, ErrRefused
57 }
58 return c, nil
59}
60
61type Inspection struct {
62 Version int `json:"version"`
63 Challenge string `json:"challenge"`
64 Observed int64 `json:"observed_at"`
65 Generation *string `json:"generation"`
66 TeamID int64 `json:"proposed_team_id"`
67 TeamSQID string `json:"proposed_team_sqid"`
68}
69
70// Inspect observes the next proposal without reserving it or advancing authority.
71// Its private result still requires the platform's attempt-bound result channel.
72func (d *Directory) Inspect(ctx context.Context, raw []byte) ([]byte, error) {
73 if d == nil || d.engine == nil {
74 return nil, ErrRefused
75 }
76 e := d.engine
77 challenge, err := e.verifier.verifyInspect(raw, e.now())
78 if err != nil {
79 return nil, err
80 }
81 ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
82 defer cancel()
83 c, err := e.db.Conn(ctx)
84 if err != nil {
85 return nil, ErrUnavailable
86 }
87 defer c.Close()
88 if _, err = c.ExecContext(ctx, "BEGIN"); err != nil {
89 return nil, ErrUnavailable
90 }
91 defer c.ExecContext(context.Background(), "ROLLBACK")
92 if e.checkSchema != nil {
93 if _, err := c.ExecContext(ctx, "PRAGMA query_only=ON"); err != nil {
94 return nil, ErrUnavailable
95 }
96 var readonly int
97 if c.QueryRowContext(ctx, "PRAGMA query_only").Scan(&readonly) != nil || readonly != 1 {
98 return nil, ErrUnavailable
99 }
100 if err := e.checkSchema(ctx, c); err != nil {
101 return nil, err
102 }
103 }
104 var count int
105 if err = c.QueryRowContext(ctx, "SELECT count(*) FROM directory_operator_state WHERE singleton=1 AND (origin!=? OR installation!=? OR registry!=? OR device!=? OR key_id!=? OR completed_at>?)", e.verifier.pins.Origin, e.verifier.pins.Installation, e.verifier.pins.Registry, e.verifier.pins.Device, e.verifier.kid, e.now().Unix()).Scan(&count); err != nil {
106 return nil, ErrUnavailable
107 }
108 if count != 0 {
109 return nil, ErrRefused
110 }
111 var generation *string
112 rows, err := c.QueryContext(ctx, "SELECT generation FROM roster_generation WHERE singleton=1")
113 if err != nil {
114 return nil, ErrUnavailable
115 }
116 if rows.Next() {
117 var g string
118 if rows.Scan(&g) != nil || !CanonicalToken(g) {
119 rows.Close()
120 return nil, ErrUnavailable
121 }
122 generation = &g
123 }
124 err = rows.Err()
125 closeErr := rows.Close()
126 if err != nil || closeErr != nil {
127 return nil, ErrUnavailable
128 }
129 id, sqid, err := nextTeam(ctx, c)
130 if err != nil {
131 return nil, ErrUnavailable
132 }
133 if _, err = e.verifier.verifyInspect(raw, e.now()); err != nil {
134 return nil, ErrRefused
135 }
136 out, err := encode(Inspection{1, challenge.Challenge, e.now().Unix(), generation, id, sqid})
137 if err != nil {
138 return nil, ErrUnavailable
139 }
140 if _, err = c.ExecContext(ctx, "COMMIT"); err != nil {
141 return nil, ErrUnavailable
142 }
143 return out, nil
144}
145