diff options
author | Mike Crute <mike@crute.us> | 2017-12-28 05:13:19 +0000 |
---|---|---|
committer | Mike Crute <mike@crute.us> | 2017-12-28 05:13:19 +0000 |
commit | 6f7a7647c7716377ee4d4e5afee36dd85fc01031 (patch) | |
tree | 59b7dfa813313e4f4b041e336d8f45f8841e6e8c | |
parent | b96e751c8944c1b63bf78033c76892c72abad432 (diff) | |
download | go-1password-master.tar.bz2 go-1password-master.tar.xz go-1password-master.zip |
-rw-r--r-- | main.go | 167 |
1 files changed, 141 insertions, 26 deletions
@@ -13,8 +13,11 @@ import ( | |||
13 | "errors" | 13 | "errors" |
14 | "fmt" | 14 | "fmt" |
15 | "io/ioutil" | 15 | "io/ioutil" |
16 | "net/url" | ||
17 | "os" | ||
16 | "os/user" | 18 | "os/user" |
17 | "path" | 19 | "path" |
20 | "regexp" | ||
18 | "strings" | 21 | "strings" |
19 | "syscall" | 22 | "syscall" |
20 | "time" | 23 | "time" |
@@ -82,6 +85,26 @@ func NewKeyPBKDF2(pass []byte, p *OPProfile) *Key { | |||
82 | return NewKey(pbkdf2.Key(pass, p.Salt, p.Iterations, 64, sha512.New)) | 85 | return NewKey(pbkdf2.Key(pass, p.Salt, p.Iterations, 64, sha512.New)) |
83 | } | 86 | } |
84 | 87 | ||
88 | type ODataOverview struct { | ||
89 | Title string `json:"title"` | ||
90 | URL string `json:"url"` | ||
91 | } | ||
92 | |||
93 | type OPDataSection struct { | ||
94 | Name string `json:"name"` | ||
95 | Title string `json:"title"` | ||
96 | Fields []OPDataField `json:"fields"` | ||
97 | } | ||
98 | |||
99 | type OPDataField struct { | ||
100 | ID string // id | ||
101 | Name string // name, n | ||
102 | Type string // type, t | ||
103 | Kind string // k | ||
104 | Value string // value, v | ||
105 | Designation string // designation | ||
106 | } | ||
107 | |||
85 | type OPBandItem struct { | 108 | type OPBandItem struct { |
86 | Profile *OPProfile `json:"-"` | 109 | Profile *OPProfile `json:"-"` |
87 | UUID string `json:"uuid"` | 110 | UUID string `json:"uuid"` |
@@ -398,49 +421,141 @@ func DeriveKey(data []byte, dkey *Key) (*Key, error) { | |||
398 | return NewKey(h.Sum(nil)), nil | 421 | return NewKey(h.Sum(nil)), nil |
399 | } | 422 | } |
400 | 423 | ||
401 | func main() { | 424 | type SearchResult struct { |
402 | datapath := "~/Dropbox/1Password/1Password.opvault" | 425 | Title string `json:"title"` |
403 | if strings.HasPrefix(datapath, "~/") { | 426 | URL string `json:"url"` |
404 | u, _ := user.Current() | 427 | BandItem *OPBandItem |
405 | datapath = u.HomeDir + datapath[1:len(datapath)] | 428 | } |
429 | |||
430 | func SearchOverviews(needle string, haystack map[string]*OPBandItem) (map[string]SearchResult, error) { | ||
431 | test, err := regexp.Compile(fmt.Sprintf("(?iU).*%s.*", needle)) | ||
432 | if err != nil { | ||
433 | return nil, err | ||
406 | } | 434 | } |
407 | 435 | ||
408 | fmt.Print("Enter Password: ") | 436 | m := make(map[string]SearchResult) |
409 | password, err := terminal.ReadPassword(int(syscall.Stdin)) | 437 | |
438 | // Simple linear search because the data set size is small | ||
439 | for k, v := range haystack { | ||
440 | if v.Trashed { | ||
441 | continue | ||
442 | } | ||
443 | |||
444 | var o SearchResult | ||
445 | if err = json.Unmarshal(v.Overview, &o); err != nil { | ||
446 | fmt.Printf("[ERROR] Decoding overview for %s\n", k) | ||
447 | continue | ||
448 | } | ||
449 | |||
450 | if test.MatchString(o.Title) || test.MatchString(o.URL) { | ||
451 | m[k] = SearchResult{o.Title, o.URL, v} | ||
452 | } | ||
453 | } | ||
454 | |||
455 | return m, nil | ||
456 | } | ||
457 | |||
458 | // TODO: a more sophisticated approach, perhaps | ||
459 | func TruncateURL(u string) string { | ||
460 | if u == "" { | ||
461 | return u | ||
462 | } | ||
463 | |||
464 | p, err := url.Parse(u) | ||
410 | if err != nil { | 465 | if err != nil { |
411 | fmt.Println("Unable to read password") | 466 | if len(u) > 20 { |
412 | return | 467 | return fmt.Sprintf("%s...", u[:17]) |
468 | } else { | ||
469 | return u | ||
470 | } | ||
471 | } else { | ||
472 | return fmt.Sprintf("%s://%s", p.Scheme, p.Host) | ||
413 | } | 473 | } |
414 | fmt.Println("") | 474 | } |
415 | 475 | ||
416 | p, err := LoadProfile(datapath, password) | 476 | // TODO: testing |
477 | func doSearch(p *OPProfile) { | ||
478 | candidates, err := SearchOverviews(os.Args[1], p.Items) | ||
417 | if err != nil { | 479 | if err != nil { |
418 | fmt.Println(err) | 480 | fmt.Println("Error searching") |
419 | return | 481 | return |
420 | } | 482 | } else { |
483 | fmt.Printf("Found %d candidates\n", len(candidates)) | ||
484 | |||
485 | outs := make([][]string, 0, len(candidates)+1) | ||
486 | c1len := 0 | ||
487 | |||
488 | for k, v := range candidates { | ||
489 | c1l := len(v.BandItem.CategoryName) | ||
490 | if c1l > c1len { | ||
491 | c1len = c1l | ||
492 | } | ||
493 | outs = append(outs, []string{v.BandItem.CategoryName, k, v.Title, TruncateURL(v.URL)}) | ||
494 | } | ||
495 | |||
496 | for _, i := range outs { | ||
497 | pad := "" | ||
498 | if len(i[0]) < (c1len + 5) { | ||
499 | pad = strings.Join(make([]string, c1len-(len(i[0])-1)), " ") | ||
500 | } | ||
501 | fmt.Printf("%s %s %s %s (%s)\n", i[0], pad, i[1], i[2], i[3]) | ||
502 | } | ||
421 | 503 | ||
422 | // Login - CD05161569D347ADB401DE06D30A0A89 | 504 | return |
423 | // Note - 7FF3565B434B47CF8906869BDCAD28C3 | 505 | } |
424 | // Password - 7059A882C5F84DDCBD2EAD9EFFAA2B58 | 506 | } |
425 | // Router - 6009533D5A3B483A93FC7A843C39EDED | ||
426 | // Server - 7BDDE92045834A5386D56576DAEDDE54 | ||
427 | // Credit - 6B9AFF656D264EEF8A887F79D243AE0D | ||
428 | // SW License - 7529DADA9453426BAF8B23A931834B2A | ||
429 | // Database - 102 | ||
430 | // Email - 111 | ||
431 | 507 | ||
432 | item, ok := p.Items[""] | 508 | // TODO: testing |
509 | func printItem(p *OPProfile, idx string) { | ||
510 | item, ok := p.Items[idx] | ||
433 | if !ok { | 511 | if !ok { |
434 | fmt.Println("UUID not found in profile") | 512 | fmt.Println("Error: No such item") |
435 | return | 513 | return |
436 | } | 514 | } |
437 | 515 | ||
438 | itemD, err := item.DecryptData() | 516 | itemDetails, err := item.DecryptData() |
439 | if err != nil { | 517 | if err != nil { |
440 | fmt.Println("Error decoding item data") | 518 | fmt.Println("Error decoding item data") |
441 | fmt.Println(err) | 519 | fmt.Println(err) |
442 | return | 520 | return |
443 | } | 521 | } |
444 | 522 | ||
445 | fmt.Printf("[\"%s\", %s, %s]", item.CategoryName, item.Overview, itemD) | 523 | fmt.Printf("[\"%s\", %s, %s]", item.CategoryName, item.Overview, itemDetails) |
524 | } | ||
525 | |||
526 | func ExpandUser(path string) (string, error) { | ||
527 | if strings.HasPrefix(datapath, "~/") { | ||
528 | if u, err := user.Current(); err != nil { | ||
529 | return nil, err | ||
530 | } else { | ||
531 | return fmt.Sprintf("%s%s", u.HomeDir, datapath[1:len(datapath)]), nil | ||
532 | } | ||
533 | } | ||
534 | return path, nil | ||
535 | } | ||
536 | |||
537 | func main() { | ||
538 | datapath, err := ExpandUser("~/Dropbox/1Password/1Password.opvault") | ||
539 | if err != nil { | ||
540 | fmt.Printf(err) | ||
541 | return | ||
542 | } | ||
543 | |||
544 | fmt.Print("Enter Password: ") | ||
545 | password, err := terminal.ReadPassword(int(syscall.Stdin)) | ||
546 | if err != nil { | ||
547 | fmt.Println("Unable to read password") | ||
548 | return | ||
549 | } | ||
550 | fmt.Println("") | ||
551 | |||
552 | p, err := LoadProfile(datapath, password) | ||
553 | if err != nil { | ||
554 | fmt.Printf("ERROR: Unable to load keychain: %s", err) | ||
555 | return | ||
556 | } | ||
557 | |||
558 | // TODO: actually handle commands, maybe cobra? | ||
559 | doSearch(p) | ||
560 | //printItem(p, "") | ||
446 | } | 561 | } |