Add LookupID/LookupAll client functions

This commit is contained in:
Elara 2024-03-10 15:25:36 -07:00
parent 249b99f46f
commit 2c3e0b3df6
1 changed files with 52 additions and 38 deletions

View File

@ -62,21 +62,45 @@ type Client struct {
GetPubkey func(serverName string) (ed25519.PublicKey, error) GetPubkey func(serverName string) (ed25519.PublicKey, error)
} }
// Lookup looks up the profile descriptor for the given ID. // Lookup looks up the profile descriptor for the given resource.
func (c Client) Lookup(id string) (*Descriptor, error) { func (c Client) Lookup(resource string) (*Descriptor, error) {
wfdesc, err := webfinger.LookupAcct(id) out := &Descriptor{}
return out, c.lookup(resource, "", out)
}
// LookupID looks up the profile descriptor that matches the given ID
// for the given resource.
func (c Client) LookupID(resource, id string) (*Descriptor, error) {
out := &Descriptor{}
return out, c.lookup(resource, id, out)
}
// Lookup looks up all the available profile descriptors for the given resource.
func (c Client) LookupAll(resource string) (map[string]*Descriptor, error) {
out := map[string]*Descriptor{}
return out, c.lookup(resource, "", &out)
}
func (c Client) lookup(resource, id string, dest any) error {
wfdesc, err := webfinger.LookupAcct(resource)
if err != nil { if err != nil {
return nil, err return err
} }
pfdLink, ok := wfdesc.LinkByType("application/x-pfd+json") pfdLink, ok := wfdesc.LinkByType("application/x-pfd+json")
if !ok { if !ok {
return nil, errors.New("server does not support the profilefed protocol") return errors.New("server does not support the profilefed protocol")
} }
pfdURL, err := url.Parse(pfdLink.Href) pfdURL, err := url.Parse(pfdLink.Href)
if err != nil { if err != nil {
return nil, err return err
}
if id != "" {
q := pfdURL.Query()
q.Set("id", id)
pfdURL.RawQuery = q.Encode()
} }
pubkeySaved := false pubkeySaved := false
@ -84,77 +108,77 @@ func (c Client) Lookup(id string) (*Descriptor, error) {
if errors.Is(err, ErrPubkeyNotFound) { if errors.Is(err, ErrPubkeyNotFound) {
info, _, err := getServerInfo(pfdURL.Scheme, pfdURL.Host) info, _, err := getServerInfo(pfdURL.Scheme, pfdURL.Host)
if err != nil { if err != nil {
return nil, err return err
} }
pubkey, err = base64.StdEncoding.DecodeString(info.PublicKey) pubkey, err = base64.StdEncoding.DecodeString(info.PublicKey)
if err != nil { if err != nil {
return nil, err return err
} }
err = c.SavePubkey(pfdURL.Host, info.PreviousNames, pubkey) err = c.SavePubkey(pfdURL.Host, info.PreviousNames, pubkey)
if err != nil { if err != nil {
return nil, err return err
} }
pubkeySaved = true pubkeySaved = true
} else if err != nil { } else if err != nil {
return nil, err return err
} }
res, err := http.Get(pfdLink.Href) res, err := http.Get(pfdURL.String())
if err != nil { if err != nil {
return nil, err return err
} }
defer res.Body.Close() defer res.Body.Close()
if err := checkResp(res, "getProfileDescriptor"); err != nil { if err := checkResp(res, "getProfileDescriptor"); err != nil {
return nil, err return err
} }
data, err := io.ReadAll(io.LimitReader(res.Body, responseSizeLimit)) data, err := io.ReadAll(io.LimitReader(res.Body, responseSizeLimit))
if err != nil { if err != nil {
return nil, err return err
} }
if err := res.Body.Close(); err != nil { if err := res.Body.Close(); err != nil {
return nil, err return err
} }
sig, err := getSignature(res) sig, err := getSignature(res)
if err != nil { if err != nil {
return nil, err return err
} }
if !ed25519.Verify(pubkey, data, sig) { if !ed25519.Verify(pubkey, data, sig) {
// If the pubkey was just saved in the current request, we probably // If the pubkey was just saved in the current request, we probably
// already have the newest one, so just return a mismatch error. // already have the newest one, so just return a mismatch error.
if pubkeySaved { if pubkeySaved {
return nil, ErrSignatureMismatch return ErrSignatureMismatch
} }
res, err := serverInfoReq(pfdURL.Scheme, pfdURL.Host) res, err := serverInfoReq(pfdURL.Scheme, pfdURL.Host)
if err != nil { if err != nil {
return nil, err return err
} }
serverData, err := io.ReadAll(io.LimitReader(res.Body, responseSizeLimit)) serverData, err := io.ReadAll(io.LimitReader(res.Body, responseSizeLimit))
if err != nil { if err != nil {
return nil, err return err
} }
var info serverInfoData var info serverInfoData
err = json.Unmarshal(serverData, &info) err = json.Unmarshal(serverData, &info)
if err != nil { if err != nil {
return nil, err return err
} }
newPubkey, err := base64.StdEncoding.DecodeString(info.PublicKey) newPubkey, err := base64.StdEncoding.DecodeString(info.PublicKey)
if err != nil { if err != nil {
return nil, err return err
} }
if bytes.Equal(pubkey, newPubkey) { if bytes.Equal(pubkey, newPubkey) {
return nil, ErrSignatureMismatch return ErrSignatureMismatch
} }
verified := false verified := false
@ -167,39 +191,29 @@ func (c Client) Lookup(id string) (*Descriptor, error) {
} }
if !verified { if !verified {
return nil, ErrSignatureMismatch return ErrSignatureMismatch
} }
infoSig, err := getSignature(res) infoSig, err := getSignature(res)
if err != nil { if err != nil {
return nil, err return err
} }
if !ed25519.Verify(newPubkey, infoSig, serverData) { if !ed25519.Verify(newPubkey, infoSig, serverData) {
return nil, ErrSignatureMismatch return ErrSignatureMismatch
} }
err = c.SavePubkey(pfdURL.Host, info.PreviousNames, newPubkey) err = c.SavePubkey(pfdURL.Host, info.PreviousNames, newPubkey)
if err != nil { if err != nil {
return nil, err return err
} }
if !ed25519.Verify(newPubkey, data, sig) { if !ed25519.Verify(newPubkey, data, sig) {
return nil, ErrSignatureMismatch return ErrSignatureMismatch
} }
} }
desc := &Descriptor{} return json.Unmarshal(data, dest)
err = json.Unmarshal(data, desc)
if err != nil {
return nil, err
}
if desc.Role == "" {
desc.Role = RoleUser
}
return desc, nil
} }
// serverInfoReq performs an HTTP request to retrieve server information. // serverInfoReq performs an HTTP request to retrieve server information.