github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/externals/proof_service_generic_social.go (about)

     1  package externals
     2  
     3  import (
     4  	"fmt"
     5  	"net/url"
     6  	"regexp"
     7  	"strings"
     8  
     9  	"github.com/keybase/client/go/jsonhelpers"
    10  	libkb "github.com/keybase/client/go/libkb"
    11  	keybase1 "github.com/keybase/client/go/protocol/keybase1"
    12  	jsonw "github.com/keybase/go-jsonw"
    13  )
    14  
    15  const kbUsernameKey = "%{kb_username}"
    16  const remoteUsernameKey = "%{username}"
    17  const sigHashKey = "%{sig_hash}"
    18  const kbUaKey = "%{kb_ua}"
    19  
    20  // =============================================================================
    21  
    22  // Validated configuration from the server
    23  type GenericSocialProofConfig struct {
    24  	keybase1.ParamProofServiceConfig
    25  	usernameRe *regexp.Regexp
    26  }
    27  
    28  func NewGenericSocialProofConfig(g *libkb.GlobalContext, config keybase1.ParamProofServiceConfig) (*GenericSocialProofConfig, error) {
    29  	gsConfig := &GenericSocialProofConfig{
    30  		ParamProofServiceConfig: config,
    31  	}
    32  	if err := gsConfig.parseAndValidate(g); err != nil {
    33  		return nil, err
    34  	}
    35  	return gsConfig, nil
    36  }
    37  
    38  func (c *GenericSocialProofConfig) parseAndValidate(g *libkb.GlobalContext) (err error) {
    39  	if c.usernameRe, err = regexp.Compile(c.UsernameConfig.Re); err != nil {
    40  		return err
    41  	}
    42  	if err = c.validatePrefillURL(); err != nil {
    43  		return err
    44  	}
    45  	if err = c.validateCheckURL(); err != nil {
    46  		return err
    47  	}
    48  	if err = c.validateProfileURL(); err != nil {
    49  		return err
    50  	}
    51  
    52  	// In devel, we need to update the config url with the IP for the CI
    53  	// container.
    54  	if g.Env.GetRunMode() == libkb.DevelRunMode {
    55  		serverURI, err := g.Env.GetServerURI()
    56  		if err != nil {
    57  			return err
    58  		}
    59  
    60  		c.ProfileUrl = strings.Replace(c.ProfileUrl, libkb.DevelServerURI, serverURI, 1)
    61  		c.PrefillUrl = strings.Replace(c.PrefillUrl, libkb.DevelServerURI, serverURI, 1)
    62  		c.CheckUrl = strings.Replace(c.CheckUrl, libkb.DevelServerURI, serverURI, 1)
    63  	}
    64  
    65  	return nil
    66  }
    67  
    68  func (c *GenericSocialProofConfig) validateProfileURL() error {
    69  	if !strings.Contains(c.ProfileUrl, remoteUsernameKey) {
    70  		return fmt.Errorf("invalid ProfileUrl: %s, missing: %s", c.ProfileUrl, remoteUsernameKey)
    71  	}
    72  	return nil
    73  }
    74  
    75  func (c *GenericSocialProofConfig) validatePrefillURL() error {
    76  	if !strings.Contains(c.PrefillUrl, kbUsernameKey) {
    77  		return fmt.Errorf("invalid PrefillUrl: %s, missing: %s", c.PrefillUrl, kbUsernameKey)
    78  	}
    79  	if !strings.Contains(c.PrefillUrl, remoteUsernameKey) {
    80  		return fmt.Errorf("invalid PrefillUrl: %s, missing: %s", c.PrefillUrl, remoteUsernameKey)
    81  	}
    82  	if !strings.Contains(c.PrefillUrl, sigHashKey) {
    83  		return fmt.Errorf("invalid PrefillUrl: %s, missing: %s", c.PrefillUrl, sigHashKey)
    84  	}
    85  	if !strings.Contains(c.PrefillUrl, kbUaKey) {
    86  		return fmt.Errorf("invalid PrefillUrl: %s, missing: %s", c.PrefillUrl, kbUaKey)
    87  	}
    88  	return nil
    89  }
    90  
    91  func (c *GenericSocialProofConfig) validateCheckURL() error {
    92  	if !strings.Contains(c.CheckUrl, remoteUsernameKey) {
    93  		return fmt.Errorf("invalid CheckUrl: %s, missing: %s", c.CheckUrl, remoteUsernameKey)
    94  	}
    95  	return nil
    96  }
    97  
    98  func (c *GenericSocialProofConfig) profileURLWithValues(remoteUsername string) (string, error) {
    99  	url := strings.Replace(c.ProfileUrl, remoteUsernameKey, remoteUsername, 1)
   100  	if !strings.Contains(url, remoteUsername) {
   101  		return "", fmt.Errorf("Invalid ProfileUrl: %s, missing remoteUsername: %s", url, remoteUsername)
   102  	}
   103  	return url, nil
   104  }
   105  
   106  func (c *GenericSocialProofConfig) prefillURLWithValues(kbUsername, remoteUsername string, sigID keybase1.SigID) (string, error) {
   107  	remoteUsername = strings.ToLower(remoteUsername)
   108  	url := strings.Replace(c.PrefillUrl, kbUsernameKey, kbUsername, 1)
   109  	if !strings.Contains(url, kbUsername) {
   110  		return "", fmt.Errorf("Invalid PrefillUrl: %s, missing kbUsername: %s", url, kbUsername)
   111  	}
   112  	url = strings.Replace(url, remoteUsernameKey, remoteUsername, 1)
   113  	if !strings.Contains(url, remoteUsername) {
   114  		return "", fmt.Errorf("Invalid PrefillUrl: %s, missing remoteUsername: %s", url, remoteUsername)
   115  	}
   116  	url = strings.Replace(url, sigHashKey, sigID.String(), 1)
   117  	if !strings.Contains(url, sigID.String()) {
   118  		return "", fmt.Errorf("Invalid PrefillUrl: %s, missing sigHash: %s", url, sigID)
   119  	}
   120  	url = strings.Replace(url, kbUaKey, libkb.ProofUserAgent(), 1)
   121  	if !strings.Contains(url, libkb.ProofUserAgent()) {
   122  		return "", fmt.Errorf("Invalid PrefillUrl: %s, missing kbUa: %s", url, libkb.ProofUserAgent())
   123  	}
   124  	return url, nil
   125  }
   126  
   127  func (c *GenericSocialProofConfig) checkURLWithValues(remoteUsername string) (string, error) {
   128  	url := strings.Replace(c.CheckUrl, remoteUsernameKey, remoteUsername, 1)
   129  	if !strings.Contains(strings.ToLower(url), strings.ToLower(remoteUsername)) {
   130  		return "", fmt.Errorf("Invalid CheckUrl: %s, missing remoteUsername: %s", url, remoteUsername)
   131  	}
   132  	return url, nil
   133  }
   134  
   135  func (c *GenericSocialProofConfig) validateRemoteUsername(remoteUsername string) error {
   136  	uc := c.UsernameConfig
   137  	switch {
   138  	case len(remoteUsername) < uc.Min:
   139  		return fmt.Errorf("username must be at least %d characters, was %d", c.UsernameConfig.Min, len(remoteUsername))
   140  	case len(remoteUsername) > uc.Max:
   141  		return fmt.Errorf("username can be at most %d characters, was %d", c.UsernameConfig.Max, len(remoteUsername))
   142  	case !c.usernameRe.MatchString(strings.ToLower(remoteUsername)):
   143  		return libkb.NewBadUsernameError(remoteUsername)
   144  	}
   145  	return nil
   146  }
   147  
   148  // =============================================================================
   149  // GenericSocialProof
   150  //
   151  
   152  type GenericSocialProofChecker struct {
   153  	proof  libkb.RemoteProofChainLink
   154  	config *GenericSocialProofConfig
   155  }
   156  
   157  var _ libkb.ProofChecker = (*GenericSocialProofChecker)(nil)
   158  
   159  func NewGenericSocialProofChecker(proof libkb.RemoteProofChainLink, config *GenericSocialProofConfig) (*GenericSocialProofChecker, libkb.ProofError) {
   160  	return &GenericSocialProofChecker{
   161  		proof:  proof,
   162  		config: config,
   163  	}, nil
   164  }
   165  
   166  func (rc *GenericSocialProofChecker) GetTorError() libkb.ProofError { return nil }
   167  
   168  func (rc *GenericSocialProofChecker) castInternalError(ierr libkb.ProofError) error {
   169  	err, ok := ierr.(error)
   170  	if ok {
   171  		return err
   172  	}
   173  	return nil
   174  }
   175  
   176  func (rc *GenericSocialProofChecker) CheckStatus(mctx libkb.MetaContext, _ libkb.SigHint, _ libkb.ProofCheckerMode,
   177  	pvlU keybase1.MerkleStoreEntry) (_ *libkb.SigHint, retErr libkb.ProofError) {
   178  	mctx = mctx.WithLogTag("PCS")
   179  	var err error
   180  	defer mctx.Trace("GenericSocialProofChecker.CheckStatus", &err)()
   181  	defer func() { err = rc.castInternalError(retErr) }()
   182  
   183  	_, sigIDBase, err := libkb.OpenSig(rc.proof.GetArmoredSig())
   184  	if err != nil {
   185  		return nil, libkb.NewProofError(keybase1.ProofStatus_BAD_SIGNATURE,
   186  			"Bad signature: %v", err)
   187  	}
   188  	sigID := sigIDBase.ToSigIDLegacy()
   189  
   190  	remoteUsername := rc.proof.GetRemoteUsername()
   191  	if err := rc.config.validateRemoteUsername(remoteUsername); err != nil {
   192  		return nil, libkb.NewProofError(keybase1.ProofStatus_BAD_USERNAME,
   193  			"remoteUsername %s was invalid: %v", remoteUsername, err)
   194  	}
   195  
   196  	apiURL, err := rc.config.checkURLWithValues(remoteUsername)
   197  	if err != nil {
   198  		return nil, libkb.NewProofError(keybase1.ProofStatus_BAD_API_URL,
   199  			"Bad api url: %v", err)
   200  	}
   201  
   202  	if _, err = url.Parse(apiURL); err != nil {
   203  		return nil, libkb.NewProofError(keybase1.ProofStatus_FAILED_PARSE,
   204  			"Could not parse url: '%v'", apiURL)
   205  	}
   206  
   207  	res, err := mctx.G().GetExternalAPI().Get(mctx, libkb.APIArg{
   208  		Endpoint: apiURL,
   209  	})
   210  	if err != nil {
   211  		return nil, libkb.XapiError(err, apiURL)
   212  	}
   213  
   214  	// We expect a single result to match which contains an array of proofs.
   215  	results, perr := jsonhelpers.AtSelectorPath(res.Body, rc.config.CheckPath, mctx.Debug, libkb.NewInvalidPVLSelectorError)
   216  	if perrInner, _ := perr.(libkb.ProofError); perrInner != nil {
   217  		return nil, perrInner
   218  	}
   219  
   220  	if len(results) != 1 {
   221  		return nil, libkb.NewProofError(keybase1.ProofStatus_CONTENT_FAILURE,
   222  			"Json selector did not match any values")
   223  	}
   224  	var proofs []keybase1.ParamProofJSON
   225  	if err = results[0].UnmarshalAgain(&proofs); err != nil {
   226  		return nil, libkb.NewProofError(keybase1.ProofStatus_CONTENT_FAILURE,
   227  			"Json could not be deserialized")
   228  	}
   229  
   230  	var foundProof, foundUsername bool
   231  	for _, proof := range proofs {
   232  		if proof.KbUsername == rc.proof.GetUsername() && sigID.Eq(proof.SigHash) {
   233  			foundProof = true
   234  			break
   235  		}
   236  		// Report if we found any matching usernames but the signature didn't match.
   237  		foundUsername = foundUsername || proof.KbUsername == rc.proof.GetUsername()
   238  	}
   239  	if !foundProof {
   240  		if foundUsername {
   241  			return nil, libkb.NewProofError(keybase1.ProofStatus_NOT_FOUND,
   242  				"Unable to find the proof, signature mismatch")
   243  		}
   244  		return nil, libkb.NewProofError(keybase1.ProofStatus_NOT_FOUND,
   245  			"Unable to find the proof")
   246  	}
   247  
   248  	humanURL, err := rc.config.profileURLWithValues(remoteUsername)
   249  	if err != nil {
   250  		mctx.Debug("Unable to generate humanURL for verifiedSigHint: %v", err)
   251  		humanURL = ""
   252  	}
   253  	verifiedSigHint := libkb.NewVerifiedSigHint(sigID, "" /* remoteID */, apiURL, humanURL, "" /* checkText */)
   254  	return verifiedSigHint, nil
   255  }
   256  
   257  // =============================================================================
   258  
   259  type GenericSocialProofServiceType struct {
   260  	libkb.BaseServiceType
   261  	config *GenericSocialProofConfig
   262  }
   263  
   264  func NewGenericSocialProofServiceType(config *GenericSocialProofConfig) *GenericSocialProofServiceType {
   265  	return &GenericSocialProofServiceType{
   266  		config: config,
   267  	}
   268  }
   269  
   270  func (t *GenericSocialProofServiceType) Key() string { return t.GetTypeName() }
   271  
   272  func (t *GenericSocialProofServiceType) NormalizeUsername(s string) (string, error) {
   273  	if err := t.config.validateRemoteUsername(s); err != nil {
   274  		return "", err
   275  	}
   276  	return strings.ToLower(s), nil
   277  }
   278  
   279  func (t *GenericSocialProofServiceType) NormalizeRemoteName(mctx libkb.MetaContext, s string) (ret string, err error) {
   280  	return t.NormalizeUsername(s)
   281  }
   282  
   283  func (t *GenericSocialProofServiceType) GetPrompt() string {
   284  	return fmt.Sprintf("Your username on %s", t.config.DisplayName)
   285  }
   286  
   287  func (t *GenericSocialProofServiceType) ToServiceJSON(username string) *jsonw.Wrapper {
   288  	ret := t.BaseToServiceJSON(t, username)
   289  	if strings.HasPrefix(strings.ToLower(t.DisplayGroup()), "mastodon") {
   290  		_ = ret.SetKey("form", jsonw.NewString("mastodon"))
   291  	}
   292  	return ret
   293  }
   294  
   295  func (t *GenericSocialProofServiceType) PostInstructions(username string) *libkb.Markup {
   296  	return libkb.FmtMarkup(`Please click on the following link to post to %v:`, t.config.DisplayName)
   297  }
   298  
   299  func (t *GenericSocialProofServiceType) DisplayName() string {
   300  	return t.config.DisplayName
   301  }
   302  func (t *GenericSocialProofServiceType) GetTypeName() string   { return t.config.Domain }
   303  func (t *GenericSocialProofServiceType) PickerSubtext() string { return t.config.Domain }
   304  
   305  func (t *GenericSocialProofServiceType) ProfileURL(remoteUsername string) (string, error) {
   306  	return t.config.profileURLWithValues(remoteUsername)
   307  }
   308  
   309  func (t *GenericSocialProofServiceType) RecheckProofPosting(tryNumber int, status keybase1.ProofStatus, _ string) (warning *libkb.Markup, err error) {
   310  	return t.BaseRecheckProofPosting(tryNumber, status)
   311  }
   312  
   313  func (t *GenericSocialProofServiceType) GetProofType() string {
   314  	return libkb.GenericSocialWebServiceBinding
   315  }
   316  
   317  func (t *GenericSocialProofServiceType) CheckProofText(text string, id keybase1.SigID, sig string) (err error) {
   318  	// We don't rely only any server trust in FormatProofText so there is nothing to verify here.
   319  	return nil
   320  }
   321  
   322  func (t *GenericSocialProofServiceType) FormatProofText(m libkb.MetaContext, ppr *libkb.PostProofRes,
   323  	kbUsername, remoteUsername string, sigID keybase1.SigID) (string, error) {
   324  	return t.config.prefillURLWithValues(kbUsername, remoteUsername, sigID)
   325  }
   326  
   327  func (t *GenericSocialProofServiceType) MakeProofChecker(l libkb.RemoteProofChainLink) libkb.ProofChecker {
   328  	return &GenericSocialProofChecker{
   329  		proof:  l,
   330  		config: t.config,
   331  	}
   332  }
   333  
   334  func (t *GenericSocialProofServiceType) IsDevelOnly() bool { return false }
   335  
   336  func (t *GenericSocialProofServiceType) ProveParameters(mctx libkb.MetaContext) keybase1.ProveParameters {
   337  	subtext := t.config.Description
   338  	if len(subtext) == 0 {
   339  		subtext = t.DisplayName()
   340  	}
   341  	return keybase1.ProveParameters{
   342  		LogoFull:    libkb.MakeProofIcons(mctx, t.GetLogoKey(), "logo_full", 64),
   343  		LogoBlack:   libkb.MakeProofIcons(mctx, t.GetLogoKey(), "logo_black", 16),
   344  		LogoWhite:   libkb.MakeProofIcons(mctx, t.GetLogoKey(), "logo_white", 16),
   345  		Title:       t.config.Domain,
   346  		Subtext:     subtext,
   347  		Suffix:      fmt.Sprintf("@%v", t.config.Domain),
   348  		ButtonLabel: fmt.Sprintf("Authorize on %v", t.config.Domain),
   349  	}
   350  }