package operator import ( "encoding/base64" "strings" "time" family "amadan.net/rastrillo/rastrillo/crypto" "github.com/sqids/sqids-go" ) const OwnerLimit = 8 << 10 var teamCodec, teamCodecError = sqids.New() func TeamSQID(id int64) (string, error) { if id <= 0 || teamCodecError != nil { return "", ErrRefused } s, e := teamCodec.Encode([]uint64{uint64(id)}) if e != nil || len(s) == 0 || len(s) > 64 { return "", ErrRefused } return s, nil } func validTeam(id int64, s string) bool { expected, e := TeamSQID(id) return e == nil && s == expected } func PersonRef(s string) bool { if len(s) != 29 || !strings.HasPrefix(s, "pr_") { return false } for _, c := range s[3:] { if !(c >= 'a' && c <= 'z' || c >= '2' && c <= '7') { return false } } return true } type OwnerReceipt struct { Version int `json:"version"` Purpose string `json:"purpose"` OperationID string `json:"operation_id"` Issuer string `json:"issuer"` Directory string `json:"directory"` TeamID int64 `json:"team_id"` TeamSQID string `json:"team_sqid"` PersonRef string `json:"person_ref"` Role string `json:"role"` Issued int64 `json:"issued_at"` Expires int64 `json:"expires_at"` } func (p OwnerReceipt) valid() bool { 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 } func SignOwner(k *family.Keypair, p OwnerReceipt) ([]byte, error) { if !signingKey(k) || !p.valid() { return nil, ErrRefused } payload, e := encode(p) if e != nil { return nil, e } sig, e := family.Sign(k, OwnerContext, payload) if e != nil { return nil, ErrUnavailable } out, e := encode(envelope{1, keyID(k.SignPub()), base64.RawURLEncoding.EncodeToString(payload), base64.RawURLEncoding.EncodeToString(sig)}) if e != nil || len(out) > OwnerLimit { return nil, ErrRefused } return out, nil } type OwnerVerifier struct { key []byte kid, issuer, directory string } func NewOwnerVerifier(publicKey, issuer, directory string) (*OwnerVerifier, error) { k, e := public(publicKey) if e != nil || !Origin(issuer) || !Origin(directory) || issuer == directory { return nil, ErrRefused } return &OwnerVerifier{k, keyID(k), issuer, directory}, nil } func (v *OwnerVerifier) Verify(raw []byte, now time.Time) (OwnerReceipt, error) { if v == nil || len(raw) == 0 || len(raw) > OwnerLimit { return OwnerReceipt{}, ErrRefused } var env envelope if strictCanonical(raw, &env) != nil || env.Version != 1 || env.KeyID != v.kid { return OwnerReceipt{}, ErrRefused } p, ok := canonical(env.Payload, -1) sig, ok2 := canonical(env.Signature, 64) if !ok || !ok2 || len(p) > OwnerLimit || !family.Verify(v.key, OwnerContext, p, sig) { return OwnerReceipt{}, ErrRefused } var receipt OwnerReceipt 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 { return OwnerReceipt{}, ErrRefused } return receipt, nil }