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 "encoding/base64"
5 "strings"
6 "time"
7
8 family "amadan.net/rastrillo/rastrillo/crypto"
9 "github.com/sqids/sqids-go"
10)
11
12const OwnerLimit = 8 << 10
13
14var teamCodec, teamCodecError = sqids.New()
15
16func TeamSQID(id int64) (string, error) {
17 if id <= 0 || teamCodecError != nil {
18 return "", ErrRefused
19 }
20 s, e := teamCodec.Encode([]uint64{uint64(id)})
21 if e != nil || len(s) == 0 || len(s) > 64 {
22 return "", ErrRefused
23 }
24 return s, nil
25}
26func validTeam(id int64, s string) bool {
27 expected, e := TeamSQID(id)
28 return e == nil && s == expected
29}
30func PersonRef(s string) bool {
31 if len(s) != 29 || !strings.HasPrefix(s, "pr_") {
32 return false
33 }
34 for _, c := range s[3:] {
35 if !(c >= 'a' && c <= 'z' || c >= '2' && c <= '7') {
36 return false
37 }
38 }
39 return true
40}
41
42type OwnerReceipt struct {
43 Version int `json:"version"`
44 Purpose string `json:"purpose"`
45 OperationID string `json:"operation_id"`
46 Issuer string `json:"issuer"`
47 Directory string `json:"directory"`
48 TeamID int64 `json:"team_id"`
49 TeamSQID string `json:"team_sqid"`
50 PersonRef string `json:"person_ref"`
51 Role string `json:"role"`
52 Issued int64 `json:"issued_at"`
53 Expires int64 `json:"expires_at"`
54}
55
56func (p OwnerReceipt) valid() bool {
57 return p.Version == 1 && p.Purpose == "owner-seed" && CanonicalToken(p.OperationID) && Origin(p.Issuer) && Origin(p.Directory) && validTeam(p.TeamID, p.TeamSQID) && PersonRef(p.PersonRef) && p.Role == "owner" && p.Issued > 0 && p.Expires > p.Issued && p.Expires-p.Issued <= 300
58}
59
60func SignOwner(k *family.Keypair, p OwnerReceipt) ([]byte, error) {
61 if !signingKey(k) || !p.valid() {
62 return nil, ErrRefused
63 }
64 payload, e := encode(p)
65 if e != nil {
66 return nil, e
67 }
68 sig, e := family.Sign(k, OwnerContext, payload)
69 if e != nil {
70 return nil, ErrUnavailable
71 }
72 out, e := encode(envelope{1, keyID(k.SignPub()), base64.RawURLEncoding.EncodeToString(payload), base64.RawURLEncoding.EncodeToString(sig)})
73 if e != nil || len(out) > OwnerLimit {
74 return nil, ErrRefused
75 }
76 return out, nil
77}
78
79type OwnerVerifier struct {
80 key []byte
81 kid, issuer, directory string
82}
83
84func NewOwnerVerifier(publicKey, issuer, directory string) (*OwnerVerifier, error) {
85 k, e := public(publicKey)
86 if e != nil || !Origin(issuer) || !Origin(directory) || issuer == directory {
87 return nil, ErrRefused
88 }
89 return &OwnerVerifier{k, keyID(k), issuer, directory}, nil
90}
91func (v *OwnerVerifier) Verify(raw []byte, now time.Time) (OwnerReceipt, error) {
92 if v == nil || len(raw) == 0 || len(raw) > OwnerLimit {
93 return OwnerReceipt{}, ErrRefused
94 }
95 var env envelope
96 if strictCanonical(raw, &env) != nil || env.Version != 1 || env.KeyID != v.kid {
97 return OwnerReceipt{}, ErrRefused
98 }
99 p, ok := canonical(env.Payload, -1)
100 sig, ok2 := canonical(env.Signature, 64)
101 if !ok || !ok2 || len(p) > OwnerLimit || !family.Verify(v.key, OwnerContext, p, sig) {
102 return OwnerReceipt{}, ErrRefused
103 }
104 var receipt OwnerReceipt
105 if strictCanonical(p, &receipt) != nil || !receipt.valid() || receipt.Issuer != v.issuer || receipt.Directory != v.directory || now.Unix() <= 0 || receipt.Issued > now.Unix() || now.Unix() >= receipt.Expires {
106 return OwnerReceipt{}, ErrRefused
107 }
108 return receipt, nil
109}
110