github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/externals/proof_service_web.go (about) 1 // Copyright 2015 Keybase, Inc. All rights reserved. Use of 2 // this source code is governed by the included BSD license. 3 4 package externals 5 6 import ( 7 "fmt" 8 "net/url" 9 "regexp" 10 "strings" 11 12 libkb "github.com/keybase/client/go/libkb" 13 keybase1 "github.com/keybase/client/go/protocol/keybase1" 14 jsonw "github.com/keybase/go-jsonw" 15 ) 16 17 // ============================================================================= 18 // Web 19 // 20 21 type WebChecker struct { 22 proof libkb.RemoteProofChainLink 23 } 24 25 var _ libkb.ProofChecker = (*WebChecker)(nil) 26 27 var webKeybaseFiles = []string{".well-known/keybase.txt", "keybase.txt"} 28 29 func NewWebChecker(p libkb.RemoteProofChainLink) (*WebChecker, libkb.ProofError) { 30 return &WebChecker{p}, nil 31 } 32 33 func (rc *WebChecker) GetTorError() libkb.ProofError { 34 urlBase := rc.proof.ToDisplayString() 35 36 u, err := url.Parse(urlBase) 37 if err != nil || u.Scheme != "https" { 38 return libkb.ProofErrorHTTPOverTor 39 } 40 41 return nil 42 } 43 44 func (rc *WebChecker) CheckStatus(mctx libkb.MetaContext, h libkb.SigHint, pcm libkb.ProofCheckerMode, 45 pvlU keybase1.MerkleStoreEntry) (*libkb.SigHint, libkb.ProofError) { 46 if pcm != libkb.ProofCheckerModeActive { 47 mctx.Debug("Web check skipped since proof checking was not in active mode (%s)", h.GetAPIURL()) 48 return nil, libkb.ProofErrorUnchecked 49 } 50 // TODO CORE-8951 see if we can populate verifiedHint with anything useful. 51 return nil, CheckProofPvl(mctx, keybase1.ProofType_GENERIC_WEB_SITE, rc.proof, h, pvlU) 52 } 53 54 // 55 // ============================================================================= 56 57 type WebServiceType struct { 58 libkb.BaseServiceType 59 scheme string 60 } 61 62 func (t *WebServiceType) Key() string { 63 if t.scheme == "" { 64 return "web" 65 } 66 return t.scheme 67 } 68 69 func (t *WebServiceType) NormalizeUsername(s string) (ret string, err error) { 70 // The username is just the (lowercased) hostname. 71 if !libkb.IsValidHostname(s) { 72 return "", libkb.NewInvalidHostnameError(s) 73 } 74 return strings.ToLower(s), nil 75 } 76 77 func ParseWeb(s string) (hostname string, prot string, err error) { 78 rxx := regexp.MustCompile("^(http(s?))://(.*)$") 79 if v := rxx.FindStringSubmatch(s); v != nil { 80 s = v[3] 81 prot = v[1] 82 } 83 if !libkb.IsValidHostname(s) { 84 err = libkb.NewInvalidHostnameError(s) 85 } else { 86 hostname = s 87 } 88 return 89 } 90 91 func (t *WebServiceType) NormalizeRemoteName(mctx libkb.MetaContext, s string) (ret string, err error) { 92 // The remote name is a full (case-preserved) URL. 93 var prot, host string 94 if host, prot, err = ParseWeb(s); err != nil { 95 return 96 } 97 var res *libkb.APIRes 98 res, err = mctx.G().GetAPI().Get(mctx, libkb.APIArg{ 99 Endpoint: "remotes/check", 100 SessionType: libkb.APISessionTypeREQUIRED, 101 Args: libkb.HTTPArgs{ 102 "hostname": libkb.S{Val: host}, 103 }, 104 }) 105 if err != nil { 106 return 107 } 108 var found string 109 found, err = res.Body.AtPath("results.first").GetString() 110 if err != nil { 111 err = libkb.NewWebUnreachableError(host) 112 return 113 } 114 if len(t.scheme) > 0 && len(prot) > 0 && prot != t.scheme { 115 msg := fmt.Sprintf("You tried to prove ownership of %s over %s but gave a %s link.", host, t.scheme, prot) 116 err = libkb.NewProtocolSchemeMismatch(msg) 117 return 118 } 119 protocolAssertsHTTPS := prot == "https" 120 proofTypeAssertsHTTPS := t.scheme == "https" 121 if (protocolAssertsHTTPS || proofTypeAssertsHTTPS) && found != "https:" { 122 msg := fmt.Sprintf("You specified HTTPS for %s but only HTTP is available", host) 123 err = libkb.NewProtocolDowngradeError(msg) 124 return 125 } 126 ret = found + "//" + host 127 128 return 129 } 130 131 func (t *WebServiceType) GetPrompt() string { 132 return "Web site to check" 133 } 134 135 func (t *WebServiceType) ToServiceJSON(un string) *jsonw.Wrapper { 136 h, p, _ := ParseWeb(un) 137 ret := jsonw.NewDictionary() 138 _ = ret.SetKey("protocol", jsonw.NewString(p+":")) 139 _ = ret.SetKey("hostname", jsonw.NewString(h)) 140 return ret 141 } 142 143 func (t *WebServiceType) MarkupFilenames(un string, mkp *libkb.Markup) { 144 mkp.Append(`<ul>`) 145 first := true 146 for _, f := range webKeybaseFiles { 147 var bullet string 148 if first { 149 bullet = " " 150 first = false 151 } else { 152 bullet = "OR " 153 } 154 mkp.Append(`<li bullet="` + bullet + `"><url>` + un + "/" + f + `</url></li>`) 155 } 156 mkp.Append(`</ul>`) 157 } 158 159 func (t *WebServiceType) PreProofWarning(un string) *libkb.Markup { 160 mkp := libkb.FmtMarkup(`<p>You will be asked to post a file to:</p>`) 161 t.MarkupFilenames(un, mkp) 162 return mkp 163 } 164 165 func (t *WebServiceType) PostInstructions(un string) *libkb.Markup { 166 mkp := libkb.FmtMarkup(`<p>Make the following file available at:</p>`) 167 t.MarkupFilenames(un, mkp) 168 return mkp 169 } 170 171 func (t *WebServiceType) DisplayName() string { return "Web" } 172 func (t *WebServiceType) GetTypeName() string { return "web" } 173 func (t *WebServiceType) PickerSubtext() string { return t.GetTypeName() } 174 175 func (t *WebServiceType) RecheckProofPosting(tryNumber int, status keybase1.ProofStatus, _ string) (warning *libkb.Markup, err error) { 176 if status == keybase1.ProofStatus_PERMISSION_DENIED { 177 warning = libkb.FmtMarkup("Permission denied! Make sure your proof page is <strong>public</strong>.") 178 } else { 179 warning, err = t.BaseRecheckProofPosting(tryNumber, status) 180 } 181 return 182 } 183 func (t *WebServiceType) GetProofType() string { return "web_service_binding.generic" } 184 185 func (t *WebServiceType) CheckProofText(text string, id keybase1.SigID, sig string) (err error) { 186 return t.BaseCheckProofTextFull(text, id, sig) 187 } 188 189 func (t *WebServiceType) GetAPIArgKey() string { return "remote_host" } 190 func (t *WebServiceType) LastWriterWins() bool { return false } 191 192 func (t *WebServiceType) MakeProofChecker(l libkb.RemoteProofChainLink) libkb.ProofChecker { 193 return &WebChecker{l} 194 } 195 196 // =============================================================================