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 }