github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/pvl/interp.go (about) 1 // Copyright 2016 Keybase, Inc. All rights reserved. Use of 2 // this source code is governed by the included BSD license. 3 4 package pvl 5 6 import ( 7 "bytes" 8 b64 "encoding/base64" 9 "fmt" 10 "net/url" 11 "regexp" 12 "strings" 13 14 "net" 15 16 "github.com/PuerkitoBio/goquery" 17 "github.com/keybase/client/go/jsonhelpers" 18 libkb "github.com/keybase/client/go/libkb" 19 keybase1 "github.com/keybase/client/go/protocol/keybase1" 20 jsonw "github.com/keybase/go-jsonw" 21 "github.com/miekg/dns" 22 ) 23 24 // SupportedVersion is which version of PVL is supported by this client. 25 const SupportedVersion int = 1 26 27 // state of execution in a script 28 // copies of a scriptState will point to the same internal mutable data, so be careful 29 type scriptState struct { 30 WhichScript int 31 PC int 32 Service keybase1.ProofType 33 Regs namedRegsStore 34 Sig []byte 35 HasFetched bool 36 // nil until fetched 37 FetchResult *fetchResult 38 } 39 40 type fetchResult struct { 41 fetchMode fetchMode 42 // One of these 3 must be filled. 43 String string 44 HTML *goquery.Document 45 JSON *jsonw.Wrapper 46 } 47 48 type regexDescriptor struct { 49 Template string 50 CaseInsensitive bool 51 MultiLine bool 52 } 53 54 type fetchMode string 55 56 const ( 57 fetchModeJSON fetchMode = "json" 58 fetchModeHTML fetchMode = "html" 59 fetchModeString fetchMode = "string" 60 fetchModeDNS fetchMode = "dns" 61 ) 62 63 type commandName string 64 65 const ( 66 cmdAssertRegexMatch commandName = "assert_regex_match" 67 cmdAssertFindBase64 commandName = "assert_find_base64" 68 cmdAssertCompare commandName = "assert_compare" 69 cmdWhitespaceNormalize commandName = "whitespace_normalize" 70 cmdRegexCapture commandName = "regex_capture" 71 cmdReplaceAll commandName = "replace_all" 72 cmdParseURL commandName = "parse_url" 73 cmdFetch commandName = "fetch" 74 cmdParseHTML commandName = "parse_html" 75 cmdSelectorJSON commandName = "selector_json" 76 cmdSelectorCSS commandName = "selector_css" 77 cmdFill commandName = "fill" 78 ) 79 80 type stateMaker func(int) (scriptState, libkb.ProofError) 81 82 // ProofInfo contains all the data about a proof PVL needs to check it. 83 // It can be derived from a RemoteProofChainLink and SigHint. 84 type ProofInfo struct { 85 ArmoredSig string 86 Username string 87 RemoteUsername string 88 Hostname string 89 Protocol string 90 APIURL string 91 stubDNS *stubDNSEngine 92 } 93 94 // NewProofInfo creates a new ProofInfo 95 func NewProofInfo(link libkb.RemoteProofChainLink, h libkb.SigHint) ProofInfo { 96 return ProofInfo{ 97 ArmoredSig: link.GetArmoredSig(), 98 RemoteUsername: link.GetRemoteUsername(), 99 Username: link.GetUsername(), 100 Hostname: link.GetHostname(), 101 Protocol: link.GetProtocol(), 102 APIURL: h.GetAPIURL(), 103 } 104 } 105 106 // CheckProof verifies one proof by running the pvl on the provided proof information. 107 func CheckProof(m libkb.MetaContext, pvlS string, service keybase1.ProofType, info ProofInfo) libkb.ProofError { 108 m1 := newMetaContext(m, info.stubDNS) 109 perr := checkProofInner(m1, pvlS, service, info) 110 if perr != nil { 111 debug(m1, "CheckProof failed: %v", perr) 112 } 113 if perr != nil && perr.GetProofStatus() == keybase1.ProofStatus_INVALID_PVL { 114 return libkb.NewProofError(keybase1.ProofStatus_INVALID_PVL, 115 "Invalid proof verification instructions! Let us know at https://github.com/keybase/keybase-issues/new") 116 } 117 return perr 118 } 119 120 func checkProofInner(m metaContext, pvlS string, service keybase1.ProofType, info ProofInfo) libkb.ProofError { 121 pvl, err := parse(pvlS) 122 if err != nil { 123 if strings.Contains(err.Error(), "cannot unmarshal string into Go struct") && strings.Contains(pvlS, "from iced tests") { 124 return libkb.NewProofError(keybase1.ProofStatus_INVALID_PVL, 125 "Corrupted pvl in merkle tree from iced tests. To fix see test/merkle_pvl.iced. : %v", err) 126 } 127 return libkb.NewProofError(keybase1.ProofStatus_INVALID_PVL, 128 "Could not parse pvl: %v", err) 129 } 130 131 if perr := validateChunk(m, &pvl, service); perr != nil { 132 return perr 133 } 134 135 sigBody, sigIDBase, err := libkb.OpenSig(info.ArmoredSig) 136 if err != nil { 137 return libkb.NewProofError(keybase1.ProofStatus_BAD_SIGNATURE, 138 "Bad signature: %v", err) 139 } 140 141 scripts, perr := chunkGetScripts(&pvl, service) 142 if perr != nil { 143 return perr 144 } 145 146 // validate hostname 147 webish := (service == keybase1.ProofType_DNS || service == keybase1.ProofType_GENERIC_WEB_SITE) 148 if webish { 149 if !validateDomain(info.Hostname) { 150 return libkb.NewProofError(keybase1.ProofStatus_BAD_SIGNATURE, 151 "Bad hostname in sig: %s", info.Hostname) 152 } 153 } 154 155 // validate protocol 156 if service == keybase1.ProofType_GENERIC_WEB_SITE { 157 _, ok := validateProtocol(info.Protocol, []string{"http", "https"}) 158 if !ok { 159 return libkb.NewProofError(keybase1.ProofStatus_BAD_SIGNATURE, 160 "Bad protocol in sig: %s", info.Protocol) 161 } 162 } 163 164 sigID := sigIDBase.ToSigIDLegacy() 165 166 mknewstate := func(i int) (scriptState, libkb.ProofError) { 167 state := scriptState{ 168 WhichScript: i, 169 PC: 0, 170 Service: service, 171 Regs: *newNamedRegsStore(), 172 Sig: sigBody, 173 HasFetched: false, 174 FetchResult: nil, 175 } 176 177 err := setupRegs(m, &state.Regs, info, sigBody, sigID, service) 178 return state, err 179 } 180 181 var errs []libkb.ProofError 182 if service == keybase1.ProofType_DNS { 183 if perr = runDNS(m, info.Hostname, scripts, mknewstate, sigID.ToMediumID()); perr != nil { 184 errs = append(errs, perr) 185 } 186 } else { 187 // Run the scripts in order. 188 // If any succeed, the proof succeeds. 189 // If one fails, the next takes over. 190 // If all fail, log and report errors. 191 for i, script := range scripts { 192 state, perr := mknewstate(i) 193 if perr != nil { 194 return perr 195 } 196 perr = runScript(m, &script, state) 197 if perr == nil { 198 return nil 199 } 200 errs = append(errs, perr) 201 } 202 } 203 204 switch len(errs) { 205 case 0: 206 return nil 207 case 1: 208 return errs[0] 209 default: 210 for _, err := range errs { 211 debug(m, "multiple failures include: %v", err) 212 } 213 // Arbitrarily use the error code of the first error 214 return libkb.NewProofError(errs[0].GetProofStatus(), "Multiple errors while verifying proof") 215 } 216 } 217 218 func setupRegs(m metaContext, regs *namedRegsStore, info ProofInfo, sigBody []byte, sigID keybase1.SigID, service keybase1.ProofType) libkb.ProofError { 219 webish := (service == keybase1.ProofType_DNS || service == keybase1.ProofType_GENERIC_WEB_SITE) 220 221 // hint_url 222 if err := regs.Set("hint_url", info.APIURL); err != nil { 223 return err 224 } 225 226 // username_service 227 if webish { 228 if err := regs.Ban("username_service"); err != nil { 229 return err 230 } 231 } else { 232 if err := regs.Set("username_service", info.RemoteUsername); err != nil { 233 return err 234 } 235 } 236 237 // username_keybase 238 if err := regs.Set("username_keybase", info.Username); err != nil { 239 return err 240 } 241 242 // sig 243 // Store it b64 encoded. This is rarely used, assert_find_base64 is better. 244 if err := regs.Set("sig", b64.StdEncoding.EncodeToString(sigBody)); err != nil { 245 return err 246 } 247 248 // sig_id_medium 249 if err := regs.Set("sig_id_medium", sigID.ToMediumID()); err != nil { 250 return err 251 } 252 253 // sig_id_short 254 if err := regs.Set("sig_id_short", sigID.ToShortID()); err != nil { 255 return err 256 } 257 258 // hostname 259 if webish { 260 if err := regs.Set("hostname", info.Hostname); err != nil { 261 return err 262 } 263 } else { 264 if err := regs.Ban("hostname"); err != nil { 265 return err 266 } 267 } 268 269 // protocol 270 if service == keybase1.ProofType_GENERIC_WEB_SITE { 271 canonicalProtocol, ok := validateProtocol(info.Protocol, []string{"http", "https"}) 272 if !ok { 273 return libkb.NewProofError(keybase1.ProofStatus_BAD_SIGNATURE, 274 "Bad protocol in sig: %s", info.Protocol) 275 } 276 if err := regs.Set("protocol", canonicalProtocol); err != nil { 277 return err 278 } 279 } else if err := regs.Ban("protocol"); err != nil { 280 return err 281 } 282 283 return nil 284 } 285 286 // Get the list of scripts for a given service. 287 func chunkGetScripts(pvl *pvlT, service keybase1.ProofType) ([]scriptT, libkb.ProofError) { 288 scripts, ok := pvl.Services.Map[service] 289 if !ok { 290 return nil, libkb.NewProofError(keybase1.ProofStatus_INVALID_PVL, 291 "No entry for service: %v", service) 292 } 293 return scripts, nil 294 } 295 296 // Check that a chunk of PVL is valid code. 297 // Will always accept valid code, but may not always notice invalidities. 298 func validateChunk(m metaContext, pvl *pvlT, service keybase1.ProofType) libkb.ProofError { 299 if pvl.PvlVersion != SupportedVersion { 300 return libkb.NewProofError(keybase1.ProofStatus_INVALID_PVL, 301 "PVL is for the wrong version %v != %v", pvl.PvlVersion, SupportedVersion) 302 } 303 304 debug(m, "valid version:%v revision:%v", pvl.PvlVersion, pvl.Revision) 305 306 scripts, perr := chunkGetScripts(pvl, service) 307 if perr != nil { 308 return perr 309 } 310 if len(scripts) == 0 { 311 return libkb.NewProofError(keybase1.ProofStatus_INVALID_PVL, 312 "Empty scripts list for service: %v", service) 313 } 314 315 // Scan all the scripts (for this service) for errors. Report the first error. 316 var errs []libkb.ProofError 317 for whichscript, script := range scripts { 318 perr = validateScript(m, &script, service, whichscript) 319 errs = append(errs, perr) 320 } 321 return errs[0] 322 } 323 324 func validateScript(m metaContext, script *scriptT, service keybase1.ProofType, whichscript int) libkb.ProofError { 325 // Scan the script. 326 // Does not validate each instruction's format. (That is done when running it) 327 // Validate each instruction's "error" field. 328 329 logerr := func(m metaContext, service keybase1.ProofType, whichscript int, pc int, format string, arg ...interface{}) libkb.ProofError { 330 debugWithPosition(m, service, whichscript, pc, format, arg...) 331 return libkb.NewProofError(keybase1.ProofStatus_INVALID_PVL, format, arg...) 332 } 333 334 var modeknown = false 335 var mode fetchMode 336 if service == keybase1.ProofType_DNS { 337 modeknown = true 338 mode = fetchModeDNS 339 } 340 if len(script.Instructions) < 1 { 341 return logerr(m, service, whichscript, 0, "Empty script") 342 } 343 344 for i, ins := range script.Instructions { 345 if ins.variantsFilled() != 1 { 346 return logerr(m, service, whichscript, i, "exactly 1 variant must appear in instruction") 347 } 348 349 switch { 350 351 // These can always run, but must be cases so that the default case works. 352 case ins.AssertRegexMatch != nil: 353 case ins.AssertFindBase64 != nil: 354 case ins.AssertCompare != nil: 355 case ins.WhitespaceNormalize != nil: 356 case ins.RegexCapture != nil: 357 case ins.ReplaceAll != nil: 358 case ins.ParseURL != nil: 359 case ins.Fill != nil: 360 361 case ins.Fetch != nil: 362 // A script can contain only <=1 fetches. 363 // A DNS script cannot contain fetches. 364 365 fetchType := ins.Fetch.Kind 366 367 if service == keybase1.ProofType_DNS { 368 return logerr(m, service, whichscript, i, 369 "DNS script cannot contain fetch instruction") 370 } 371 if modeknown { 372 return logerr(m, service, whichscript, i, 373 "Script cannot contain multiple fetch instructions") 374 } 375 switch fetchMode(fetchType) { 376 case fetchModeString: 377 modeknown = true 378 mode = fetchModeString 379 case fetchModeHTML: 380 modeknown = true 381 mode = fetchModeHTML 382 case fetchModeJSON: 383 modeknown = true 384 mode = fetchModeJSON 385 default: 386 return logerr(m, service, whichscript, i, 387 "Unsupported fetch type: %v", fetchType) 388 } 389 case ins.ParseHTML != nil: 390 case ins.SelectorJSON != nil: 391 // Can only select after fetching. 392 switch { 393 case service == keybase1.ProofType_DNS: 394 return logerr(m, service, whichscript, i, 395 "DNS script cannot use json selector") 396 case !modeknown: 397 return logerr(m, service, whichscript, i, 398 "Script cannot select before fetch") 399 case mode != fetchModeJSON: 400 return logerr(m, service, whichscript, i, 401 "Script contains json selector in non-html mode") 402 } 403 case ins.SelectorCSS != nil: 404 // Can only select one of text, attr, or data. 405 if ins.SelectorCSS.Attr != "" && ins.SelectorCSS.Data { 406 return logerr(m, service, whichscript, i, 407 "Script contains css selector with both 'attr' and 'data' set") 408 } 409 default: 410 return logerr(m, service, whichscript, i, 411 "Unsupported PVL instruction: %v", ins) 412 } 413 } 414 415 return nil 416 } 417 418 // Run each script on each TXT record of each domain. 419 // Succeed if any succeed. 420 func runDNS(m metaContext, userdomain string, scripts []scriptT, mknewstate stateMaker, sigIDMedium string) libkb.ProofError { 421 domains := []string{userdomain, "_keybase." + userdomain} 422 var errs []libkb.ProofError 423 for _, d := range domains { 424 debug(m, "Trying DNS for domain: %v", d) 425 426 err := runDNSOne(m, d, scripts, mknewstate, sigIDMedium) 427 if err != nil { 428 errs = append(errs, err) 429 } else { 430 return nil 431 } 432 } 433 434 // Return only the error for the first domain error 435 if len(errs) == 0 { 436 return nil 437 } 438 var descs []string 439 for _, err := range errs { 440 descs = append(descs, err.GetDesc()) 441 } 442 // Use the code from the first error 443 return libkb.NewProofError(errs[0].GetProofStatus(), strings.Join(descs, "; ")) 444 } 445 446 func formatDNSServer(srv string) string { 447 if strings.Contains(srv, ":") { 448 return fmt.Sprintf("[%s]:53", srv) 449 } 450 return srv + ":53" 451 } 452 453 func runDNSTXTQuery(m metaContext, domain string) (res []string, err error) { 454 455 // Attempt to use the built-in resolver first, but this might fail on mobile. 456 // The reason for that is currently (as of Go 1.8), LookupTXT does not properly 457 // use the cgo DNS routines if they are configured to be used (like they are for mobile). 458 // As for now, we can use a different library to specify our own name servers, since the 459 // Go resolver will attempt to use /etc/resolv.conf, which is not a thing on mobile. 460 if res, err = net.LookupTXT(domain); err != nil { 461 debug(m, "DNS LookupTXT failed: %s", err.Error()) 462 } else { 463 return res, nil 464 } 465 466 // Google IPv4 and IPV6 addresses 467 publicServers := []string{ 468 formatDNSServer("8.8.8.8"), 469 formatDNSServer("2001:4860:4860::8888"), 470 } 471 var fetchedSrvs []string 472 if m.G().GetDNSNameServerFetcher() != nil { 473 fetchedSrvs = m.G().GetDNSNameServerFetcher().GetServers() 474 for i := 0; i < len(fetchedSrvs); i++ { 475 fetchedSrvs[i] = formatDNSServer(fetchedSrvs[i]) 476 } 477 } 478 servers := fetchedSrvs 479 servers = append(servers, publicServers...) 480 481 var r *dns.Msg 482 c := dns.Client{} 483 msg := dns.Msg{} 484 found := false 485 for _, srv := range servers { 486 debug(m, "DNS trying backup server: %s", srv) 487 msg.SetQuestion(domain+".", dns.TypeTXT) 488 r, _, err = c.Exchange(&msg, srv) 489 if err != nil { 490 debug(m, "DNS backup server failed; %s", err.Error()) 491 } else { 492 found = true 493 break 494 } 495 } 496 if !found { 497 return res, fmt.Errorf("failed to lookup DNS: %s", domain) 498 } 499 500 for _, ans := range r.Answer { 501 if record, ok := ans.(*dns.TXT); ok { 502 if len(record.Txt) > 0 { 503 res = append(res, record.Txt[len(record.Txt)-1]) 504 } 505 } 506 } 507 return res, err 508 } 509 510 // Run each script on each TXT record of the domain. 511 func runDNSOne(m metaContext, domain string, scripts []scriptT, mknewstate stateMaker, sigIDMedium string) libkb.ProofError { 512 // Fetch TXT records 513 var txts []string 514 var err error 515 if m.getStubDNS() == nil { 516 txts, err = runDNSTXTQuery(m, domain) 517 } else { 518 txts, err = m.getStubDNS().LookupTXT(domain) 519 } 520 521 if err != nil { 522 return libkb.NewProofError(keybase1.ProofStatus_DNS_ERROR, 523 "DNS failure for %s: %s", domain, err) 524 } 525 526 for _, record := range txts { 527 debug(m, "For DNS domain '%s' got TXT record: '%s'", domain, record) 528 529 // Try all scripts. 530 for i, script := range scripts { 531 state, err := mknewstate(i) 532 if err != nil { 533 return err 534 } 535 536 if err = state.Regs.Set("txt", record); err != nil { 537 return err 538 } 539 540 if err = runScript(m, &script, state); err == nil { 541 return nil 542 } 543 544 // Discard error, it has already been reported by stepInstruction. 545 } 546 } 547 548 return libkb.NewProofError(keybase1.ProofStatus_NOT_FOUND, 549 "Checked %d TXT entries of %s, but didn't find signature keybase-site-verification=%s", 550 len(txts), domain, sigIDMedium) 551 } 552 553 func runScript(m metaContext, script *scriptT, startstate scriptState) libkb.ProofError { 554 var state = startstate 555 if len(script.Instructions) < 1 { 556 perr := libkb.NewProofError(keybase1.ProofStatus_INVALID_PVL, 557 "Empty scripts are not allowed.") 558 debugWithStateError(m, state, perr) 559 return perr 560 } 561 for i, ins := range script.Instructions { 562 // Sanity check. 563 if state.PC != i { 564 perr := libkb.NewProofError(keybase1.ProofStatus_INVALID_PVL, 565 "Execution failure, PC mismatch %v %v", state.PC, i) 566 debugWithStateError(m, state, perr) 567 return perr 568 } 569 570 newstate, perr := stepInstruction(m, ins, state) 571 if perr != nil { 572 return perr 573 } 574 state = newstate 575 state.PC++ 576 } 577 578 // Script executed successfully with no errors. 579 return nil 580 } 581 582 // stepInstruction decides which instruction to run. 583 func stepInstruction(m metaContext, ins instructionT, state scriptState) (scriptState, libkb.ProofError) { 584 debugWithState(m, state, "Running instruction %v: %v", ins.Name(), ins) 585 586 var newState scriptState 587 var stepErr libkb.ProofError 588 var customErrSpec *errorT 589 switch { 590 case ins.AssertRegexMatch != nil: 591 newState, stepErr = stepAssertRegexMatch(m, *ins.AssertRegexMatch, state) 592 customErrSpec = ins.AssertRegexMatch.Error 593 case ins.AssertFindBase64 != nil: 594 newState, stepErr = stepAssertFindBase64(m, *ins.AssertFindBase64, state) 595 customErrSpec = ins.AssertFindBase64.Error 596 case ins.AssertCompare != nil: 597 newState, stepErr = stepAssertCompare(m, *ins.AssertCompare, state) 598 customErrSpec = ins.AssertCompare.Error 599 case ins.WhitespaceNormalize != nil: 600 newState, stepErr = stepWhitespaceNormalize(m, *ins.WhitespaceNormalize, state) 601 customErrSpec = ins.WhitespaceNormalize.Error 602 case ins.RegexCapture != nil: 603 newState, stepErr = stepRegexCapture(m, *ins.RegexCapture, state) 604 customErrSpec = ins.RegexCapture.Error 605 case ins.ReplaceAll != nil: 606 newState, stepErr = stepReplaceAll(m, *ins.ReplaceAll, state) 607 customErrSpec = ins.ReplaceAll.Error 608 case ins.ParseURL != nil: 609 newState, stepErr = stepParseURL(m, *ins.ParseURL, state) 610 customErrSpec = ins.ParseURL.Error 611 case ins.Fetch != nil: 612 newState, stepErr = stepFetch(m, *ins.Fetch, state) 613 customErrSpec = ins.Fetch.Error 614 case ins.ParseHTML != nil: 615 newState, stepErr = stepParseHTML(m, *ins.ParseHTML, state) 616 customErrSpec = ins.ParseHTML.Error 617 case ins.SelectorJSON != nil: 618 newState, stepErr = stepSelectorJSON(m, *ins.SelectorJSON, state) 619 customErrSpec = ins.SelectorJSON.Error 620 case ins.SelectorCSS != nil: 621 newState, stepErr = stepSelectorCSS(m, *ins.SelectorCSS, state) 622 customErrSpec = ins.SelectorCSS.Error 623 case ins.Fill != nil: 624 newState, stepErr = stepFill(m, *ins.Fill, state) 625 customErrSpec = ins.Fill.Error 626 default: 627 newState = state 628 stepErr = libkb.NewProofError(keybase1.ProofStatus_INVALID_PVL, 629 "Invalid instruction: %v", ins) 630 } 631 632 if stepErr != nil { 633 debugWithStateError(m, state, stepErr) 634 stepErr = replaceCustomError(m, state, customErrSpec, stepErr) 635 } 636 return newState, stepErr 637 638 } 639 640 func stepAssertRegexMatch(m metaContext, ins assertRegexMatchT, state scriptState) (scriptState, libkb.ProofError) { 641 rdesc := regexDescriptor{ 642 Template: ins.Pattern, 643 CaseInsensitive: ins.CaseInsensitive, 644 MultiLine: ins.MultiLine, 645 } 646 from, err := state.Regs.Get(ins.From) 647 if err != nil { 648 return state, err 649 } 650 re, err := interpretRegex(m, state, rdesc) 651 if err != nil { 652 return state, err 653 } 654 if re.MatchString(from) == ins.Negate { 655 negate := "not " 656 if ins.Negate { 657 negate = "" 658 } 659 debugWithState(m, state, "Regex did %smatch:\n %v\n %v\n %q", 660 negate, rdesc.Template, re, from) 661 return state, libkb.NewProofError(keybase1.ProofStatus_CONTENT_FAILURE, 662 "Regex did %smatch (%v)", negate, rdesc.Template) 663 } 664 665 return state, nil 666 } 667 668 func stepAssertFindBase64(m metaContext, ins assertFindBase64T, state scriptState) (scriptState, libkb.ProofError) { 669 if ins.Needle != "sig" { 670 return state, libkb.NewProofError(keybase1.ProofStatus_INVALID_PVL, 671 "Can only assert_find_base64 for sig") 672 } 673 haystack, err := state.Regs.Get(ins.Haystack) 674 if err != nil { 675 return state, err 676 } 677 if libkb.FindBase64Block(haystack, state.Sig, false) { 678 return state, nil 679 } 680 return state, libkb.NewProofError(keybase1.ProofStatus_TEXT_NOT_FOUND, 681 "Signature not found") 682 } 683 684 func stepAssertCompare(m metaContext, ins assertCompareT, state scriptState) (scriptState, libkb.ProofError) { 685 a, err := state.Regs.Get(ins.A) 686 if err != nil { 687 return state, err 688 } 689 b, err := state.Regs.Get(ins.B) 690 if err != nil { 691 return state, err 692 } 693 694 var same bool 695 switch ins.Cmp { 696 case "exact": 697 same = (a == b) 698 case "cicmp": 699 same = libkb.Cicmp(a, b) 700 case "stripdots-then-cicmp": 701 norm := func(s string) string { 702 return strings.ToLower(strings.ReplaceAll(s, ".", "")) 703 } 704 same = libkb.Cicmp(norm(a), norm(b)) 705 default: 706 return state, libkb.NewProofError(keybase1.ProofStatus_INVALID_PVL, 707 "Unsupported comparison method: '%v'", ins.Cmp) 708 } 709 710 if !same { 711 debugWithState(m, state, "Comparison (%v) failed\n %v != %v\n '%v' != '%v'", 712 ins.Cmp, ins.A, ins.B, a, b) 713 return state, libkb.NewProofError(keybase1.ProofStatus_CONTENT_FAILURE, 714 "Comparison (%v) failed '%v' != '%v'", ins.Cmp, a, b) 715 } 716 717 return state, nil 718 } 719 720 func stepWhitespaceNormalize(m metaContext, ins whitespaceNormalizeT, state scriptState) (scriptState, libkb.ProofError) { 721 from, err := state.Regs.Get(ins.From) 722 if err != nil { 723 return state, err 724 } 725 normed := libkb.WhitespaceNormalize(from) 726 err = state.Regs.Set(ins.Into, normed) 727 return state, err 728 } 729 730 func stepRegexCapture(m metaContext, ins regexCaptureT, state scriptState) (scriptState, libkb.ProofError) { 731 rdesc := regexDescriptor{ 732 Template: ins.Pattern, 733 CaseInsensitive: ins.CaseInsensitive, 734 MultiLine: ins.MultiLine, 735 } 736 737 from, err := state.Regs.Get(ins.From) 738 if err != nil { 739 return state, err 740 } 741 742 re, err := interpretRegex(m, state, rdesc) 743 if err != nil { 744 return state, err 745 } 746 747 // There must be some registers to write results to. 748 if len(ins.Into) == 0 { 749 return state, libkb.NewProofError(keybase1.ProofStatus_INVALID_PVL, 750 "Into list cannot be empty") 751 } 752 753 match := re.FindStringSubmatch(from) 754 // Assert that the match matched and has at least one capture group. 755 // -1 for the ignored first element of match 756 if len(match)-1 < len(ins.Into) { 757 debugWithState(m, state, "Regex capture did not match enough groups:\n %v\n %v\n %q\n %v", 758 rdesc.Template, re, from, match) 759 return state, libkb.NewProofError(keybase1.ProofStatus_CONTENT_FAILURE, 760 "Regex capture did not match enough groups (%v)", rdesc.Template) 761 } 762 for i := 0; i < len(ins.Into); i++ { 763 err := state.Regs.Set(ins.Into[i], match[i+1]) 764 if err != nil { 765 return state, err 766 } 767 } 768 return state, nil 769 } 770 771 func stepReplaceAll(m metaContext, ins replaceAllT, state scriptState) (scriptState, libkb.ProofError) { 772 from, err := state.Regs.Get(ins.From) 773 if err != nil { 774 return state, err 775 } 776 777 replaced := strings.ReplaceAll(from, ins.Old, ins.New) 778 if err = state.Regs.Set(ins.Into, replaced); err != nil { 779 return state, err 780 } 781 782 return state, nil 783 } 784 785 func stepParseURL(m metaContext, ins parseURLT, state scriptState) (scriptState, libkb.ProofError) { 786 s, err := state.Regs.Get(ins.From) 787 if err != nil { 788 return state, err 789 } 790 791 u, err2 := url.Parse(s) 792 if err2 != nil { 793 return state, libkb.NewProofError(keybase1.ProofStatus_FAILED_PARSE, 794 "Could not parse url: '%v'", s) 795 } 796 797 if ins.Path != "" { 798 err := state.Regs.Set(ins.Path, u.Path) 799 if err != nil { 800 return state, err 801 } 802 } 803 804 if ins.Host != "" { 805 err := state.Regs.Set(ins.Host, u.Host) 806 if err != nil { 807 return state, err 808 } 809 } 810 811 if ins.Scheme != "" { 812 err := state.Regs.Set(ins.Scheme, u.Scheme) 813 if err != nil { 814 return state, err 815 } 816 } 817 818 return state, nil 819 } 820 821 func stepFetch(m metaContext, ins fetchT, state scriptState) (scriptState, libkb.ProofError) { 822 if state.FetchResult != nil { 823 return state, libkb.NewProofError(keybase1.ProofStatus_INVALID_PVL, 824 "Script cannot contain more than one fetch") 825 } 826 if state.Service == keybase1.ProofType_DNS { 827 return state, libkb.NewProofError(keybase1.ProofStatus_INVALID_PVL, 828 "Script cannot fetch in DNS mode") 829 } 830 831 from, err := state.Regs.Get(ins.From) 832 if err != nil { 833 return state, err 834 } 835 if state.Service == keybase1.ProofType_ROOTER { 836 from2, err := rooterRewriteURL(m, from) 837 if err != nil { 838 return state, libkb.NewProofError(keybase1.ProofStatus_FAILED_PARSE, 839 "Could not rewrite rooter URL: %v", err) 840 } 841 from = from2 842 } 843 844 switch fetchMode(ins.Kind) { 845 case fetchModeString: 846 debugWithState(m, state, "fetchurl: %v", from) 847 res, err1 := m.G().GetExternalAPI().GetText(m.MetaContext, libkb.APIArg{Endpoint: from}) 848 if err1 != nil { 849 return state, libkb.XapiError(err1, from) 850 } 851 state.FetchResult = &fetchResult{ 852 fetchMode: fetchModeString, 853 String: res.Body, 854 } 855 err := state.Regs.Set(ins.Into, state.FetchResult.String) 856 if err != nil { 857 return state, err 858 } 859 return state, nil 860 case fetchModeJSON: 861 if ins.Into != "" { 862 return state, libkb.NewProofError(keybase1.ProofStatus_INVALID_PVL, 863 "JSON fetch must not specify 'into' register") 864 } 865 res, err1 := m.G().GetExternalAPI().Get(m.MetaContext, libkb.APIArg{Endpoint: from}) 866 if err1 != nil { 867 return state, libkb.XapiError(err1, from) 868 } 869 state.FetchResult = &fetchResult{ 870 fetchMode: fetchModeJSON, 871 JSON: res.Body, 872 } 873 return state, nil 874 case fetchModeHTML: 875 if ins.Into != "" { 876 return state, libkb.NewProofError(keybase1.ProofStatus_INVALID_PVL, 877 "HTML fetch must not specify 'into' register") 878 } 879 res, err1 := m.G().GetExternalAPI().GetHTML(m.MetaContext, libkb.APIArg{Endpoint: from}) 880 if err1 != nil { 881 return state, libkb.XapiError(err1, from) 882 } 883 state.FetchResult = &fetchResult{ 884 fetchMode: fetchModeHTML, 885 HTML: res.GoQuery, 886 } 887 return state, nil 888 default: 889 return state, libkb.NewProofError(keybase1.ProofStatus_INVALID_PVL, 890 "Unsupported fetch kind %v", ins.Kind) 891 } 892 } 893 894 func stepParseHTML(m metaContext, ins parseHTMLT, state scriptState) (scriptState, libkb.ProofError) { 895 from, err := state.Regs.Get(ins.From) 896 if err != nil { 897 return state, err 898 } 899 900 gq, err2 := goquery.NewDocumentFromReader(bytes.NewBuffer([]byte(from))) 901 if err2 != nil { 902 return state, libkb.NewProofError(keybase1.ProofStatus_FAILED_PARSE, "Failed to parse html from '%v': %v", ins.From, err2) 903 } 904 905 state.FetchResult = &fetchResult{ 906 fetchMode: fetchModeHTML, 907 HTML: gq, 908 } 909 return state, nil 910 } 911 912 func stepSelectorJSON(m metaContext, ins selectorJSONT, state scriptState) (scriptState, libkb.ProofError) { 913 if state.FetchResult == nil || state.FetchResult.fetchMode != fetchModeJSON { 914 return state, libkb.NewProofError(keybase1.ProofStatus_INVALID_PVL, 915 "Cannot use json selector with non-json fetch result") 916 } 917 918 if len(ins.Selectors) < 1 { 919 return state, libkb.NewProofError(keybase1.ProofStatus_INVALID_PVL, 920 "Json selector list must contain at least 1 element") 921 } 922 923 results, perr := runSelectorJSONInner(m, state, state.FetchResult.JSON, ins.Selectors) 924 if perr != nil { 925 return state, perr 926 } 927 if len(results) < 1 { 928 return state, libkb.NewProofError(keybase1.ProofStatus_CONTENT_FAILURE, 929 "Json selector did not match any values") 930 } 931 s := strings.Join(results, " ") 932 933 err := state.Regs.Set(ins.Into, s) 934 return state, err 935 } 936 937 func stepSelectorCSS(m metaContext, ins selectorCSST, state scriptState) (scriptState, libkb.ProofError) { 938 if state.FetchResult == nil || state.FetchResult.fetchMode != fetchModeHTML { 939 return state, libkb.NewProofError(keybase1.ProofStatus_INVALID_PVL, 940 "Cannot use css selector with non-html fetch result") 941 } 942 943 selection, perr := runCSSSelectorInner(m, state.FetchResult.HTML.Selection, ins.Selectors) 944 if perr != nil { 945 return state, perr 946 } 947 948 if selection.Size() < 1 { 949 return state, libkb.NewProofError(keybase1.ProofStatus_CONTENT_FAILURE, 950 "No elements matched by selector") 951 } 952 953 if selection.Size() > 1 && !ins.Multi { 954 return state, libkb.NewProofError(keybase1.ProofStatus_CONTENT_FAILURE, 955 "CSS selector matched too many elements") 956 } 957 958 // Get the text, attribute, or data. 959 var res string 960 switch { 961 case ins.Attr != "": 962 res = selectionAttr(selection, ins.Attr) 963 case ins.Data: 964 res = selectionData(selection) 965 default: 966 res = selectionText(selection) 967 } 968 969 err := state.Regs.Set(ins.Into, res) 970 return state, err 971 } 972 973 func stepFill(m metaContext, ins fillT, state scriptState) (scriptState, libkb.ProofError) { 974 s, err := substituteExact(ins.With, state) 975 if err != nil { 976 debugWithState(m, state, "Fill did not succeed:\n %v\n %v\n %v", 977 ins.With, err, ins.Into) 978 return state, libkb.NewProofError(keybase1.ProofStatus_INVALID_PVL, 979 "Could not fill variable (%v): %v", ins.Into, err) 980 } 981 982 err = state.Regs.Set(ins.Into, s) 983 return state, err 984 } 985 986 // Run a PVL CSS selector. 987 // selectors is a list like [ "div .foo", 0, ".bar"] ]. 988 // Each string runs a selector, each integer runs a Eq. 989 func runCSSSelectorInner(m metaContext, html *goquery.Selection, 990 selectors []keybase1.SelectorEntry) (*goquery.Selection, libkb.ProofError) { 991 if len(selectors) < 1 { 992 return nil, libkb.NewProofError(keybase1.ProofStatus_INVALID_PVL, 993 "CSS selectors array must not be empty") 994 } 995 996 selection := html 997 for _, selector := range selectors { 998 switch { 999 case selector.IsIndex: 1000 selection = selection.Eq(selector.Index) 1001 case selector.IsKey: 1002 selection = selection.Find(selector.Key) 1003 case selector.IsContents: 1004 selection = selection.Contents() 1005 default: 1006 return nil, libkb.NewProofError(keybase1.ProofStatus_INVALID_PVL, 1007 "CSS selector entry must be a string, int, or 'contents' %v", selector) 1008 } 1009 } 1010 1011 return selection, nil 1012 } 1013 1014 func runSelectorJSONInner(m metaContext, state scriptState, selectedObject *jsonw.Wrapper, 1015 selectors []keybase1.SelectorEntry) ([]string, libkb.ProofError) { 1016 logger := func(format string, args ...interface{}) { 1017 debugWithState(m, state, format, args) 1018 } 1019 jsonResults, perr := jsonhelpers.AtSelectorPath(selectedObject, selectors, logger, libkb.NewInvalidPVLSelectorError) 1020 if perrInner, _ := perr.(libkb.ProofError); perrInner != nil { 1021 return nil, perrInner 1022 } 1023 if perr != nil { 1024 return nil, libkb.NewProofError(keybase1.ProofStatus_INVALID_PVL, "json select error in pvl interp") 1025 } 1026 results := []string{} 1027 for _, object := range jsonResults { 1028 s, err := jsonhelpers.JSONStringSimple(object) 1029 if err != nil { 1030 logger("JSON could not read object: %v (%v)", err, object) 1031 continue 1032 } 1033 results = append(results, s) 1034 } 1035 return results, nil 1036 } 1037 1038 // Take a regex descriptor, do variable substitution, and build a regex. 1039 func interpretRegex(m metaContext, state scriptState, rdesc regexDescriptor) (*regexp.Regexp, libkb.ProofError) { 1040 // Check that regex is bounded by "^" and "$". 1041 if !strings.HasPrefix(rdesc.Template, "^") { 1042 return nil, libkb.NewProofError(keybase1.ProofStatus_INVALID_PVL, 1043 "Could not build regex: %v (%v)", "must start with '^'", rdesc.Template) 1044 } 1045 if !strings.HasSuffix(rdesc.Template, "$") { 1046 return nil, libkb.NewProofError(keybase1.ProofStatus_INVALID_PVL, 1047 "Could not build regex: %v (%v)", "must end with '$'", rdesc.Template) 1048 } 1049 1050 var optstring = "" 1051 if rdesc.CaseInsensitive { 1052 optstring += "i" 1053 } 1054 if rdesc.MultiLine { 1055 optstring += "m" 1056 } 1057 1058 var prefix = "" 1059 if len(optstring) > 0 { 1060 prefix = "(?" + optstring + ")" 1061 } 1062 1063 // Do variable interpolation. 1064 prepattern, perr := substituteReEscape(rdesc.Template, state) 1065 if perr != nil { 1066 return nil, perr 1067 } 1068 pattern := prefix + prepattern 1069 1070 // Build the regex. 1071 re, err := regexp.Compile(pattern) 1072 if err != nil { 1073 debugWithState(m, state, "Could not compile regex: %v\n %v\n %v", err, rdesc.Template, pattern) 1074 return nil, libkb.NewProofError(keybase1.ProofStatus_INVALID_PVL, 1075 "Could not compile regex: %v (%v)", err, rdesc.Template) 1076 } 1077 return re, nil 1078 } 1079 1080 // Use a custom error spec to derive an error. 1081 // spec can be none 1082 // Copies over the error code if none is specified. 1083 func replaceCustomError(m metaContext, state scriptState, spec *errorT, err1 libkb.ProofError) libkb.ProofError { 1084 if err1 == nil { 1085 return err1 1086 } 1087 1088 // Don't rewrite invalid_pvl errors 1089 if err1.GetProofStatus() == keybase1.ProofStatus_INVALID_PVL { 1090 return err1 1091 } 1092 1093 if spec == nil { 1094 return err1 1095 } 1096 1097 if (spec.Status != err1.GetProofStatus()) || (spec.Description != err1.GetDesc()) { 1098 newDesc := spec.Description 1099 subbedDesc, subErr := substituteExact(spec.Description, state) 1100 if subErr == nil { 1101 newDesc = subbedDesc 1102 } 1103 err2 := libkb.NewProofError(spec.Status, newDesc) 1104 debugWithState(m, state, "Replacing error with custom error") 1105 debugWithStateError(m, state, err2) 1106 1107 return err2 1108 } 1109 return err1 1110 }