package profile import ( "encoding/hex" "fmt" datekeys "g.activething.com/go/DateKeys" ) // Registry resolves a profile_id to a locally trusted Provider Profile. The // client MUST NOT accept a profile or key supplied by the endpoint that // delivers the release (spec §13); a Registry is built by the caller from // pinned data only. type Registry interface { // Lookup returns a copy of the pinned profile for id. Lookup(id string) (*Profile, bool) } // Pin is a profile together with the profile_hash the caller expects it to // have (spec §13: the profile hash is known in advance). type Pin struct { Profile *Profile Hash [32]byte } type pinned struct { profile *Profile hash [32]byte } type registry struct { m map[string]pinned } // NewRegistry validates every profile, checks it against its expected // profile_hash and returns an immutable registry holding private copies. func NewRegistry(pins ...Pin) (Registry, error) { r := ®istry{m: make(map[string]pinned, len(pins))} for _, pin := range pins { if pin.Profile == nil { return nil, fmt.Errorf("profile: nil profile in registry: %w", datekeys.ErrUnknownProfile) } p := pin.Profile.Clone() if err := p.Validate(); err != nil { return nil, err } h, err := p.Hash() if err != nil { return nil, err } if h != pin.Hash { return nil, fmt.Errorf("profile %s: profile_hash %x does not match the pinned %x: %w", p.ID, h, pin.Hash, datekeys.ErrProfileMismatch) } if _, dup := r.m[p.ID]; dup { return nil, fmt.Errorf("profile %s: pinned twice", p.ID) } r.m[p.ID] = pinned{profile: p, hash: h} } return r, nil } func (r *registry) Lookup(id string) (*Profile, bool) { e, ok := r.m[id] if !ok { return nil, false } return e.profile.Clone(), true } // Default returns the default registry, which contains only the Quicknet // profile checked against QuicknetProfileHash. func Default() (Registry, error) { var h [32]byte b, err := hex.DecodeString(QuicknetProfileHash) if err != nil || len(b) != len(h) { return nil, fmt.Errorf("profile: invalid pinned Quicknet profile hash: %w", datekeys.ErrProfileMismatch) } copy(h[:], b) return NewRegistry(Pin{Profile: Quicknet(), Hash: h}) }