func removeMeta(ps *store.SecretStore, cfg *config) error { label := cfg.Args[0] if !ps.Has(label) { return errors.New("entry not found") } rec := ps.Store[label] for { var keys = make([]string, 0, len(rec.Metadata)) for k := range rec.Metadata { keys = append(keys, k) } sort.Strings(keys) fmt.Println("Keys:") for i := range keys { fmt.Printf("\t%s\n", keys[i]) } key, err := util.ReadLine("Remove key: ") if err != nil { util.Errorf("Failed to read key: %v", err) continue } else if key == "" { break } delete(rec.Metadata, key) fmt.Println("Deleted key", key) } rec.Timestamp = time.Now().Unix() ps.Store[label] = rec return nil }
func showSecret(ps *store.SecretStore, cfg *config) error { label := cfg.Args[0] if !ps.Has(label) { return errors.New("no token found under label") } rec := ps.Store[label] var otpType int otp, label, err := twofactor.FromURL(string(rec.Secret)) if err != nil { return err } if _, ok := rec.Metadata["type"]; !ok { otpType = parseTwofactorType(otp.Type()) } else { otpType = parseOTPKind(string(rec.Metadata["type"])) } switch otpType { case TOTP: printTOTP(label, otp) case GoogleTOTP: printGTOTP(label, otp) case HOTP: printHOTP(label, otp) cfg.Updated = true hotp := otp.(*twofactor.HOTP) rec.Secret = []byte(hotp.URL(label)) rec.Timestamp = time.Now().Unix() ps.Store[label] = rec default: return errors.New("unknown OTP type") } return nil }
func addSecret(ps *store.SecretStore, cfg *config, m secret.ScryptMode) error { if cfg.WithMeta { return addMeta(ps, cfg, m) } label := cfg.Args[0] var rec *store.SecretRecord if ps.Has(label) { if !cfg.Overwrite { return errors.New("entry exists, not forcing overwrite") } util.Errorf("*** WARNING: overwriting password") rec = ps.Store[label] } else { rec = &store.SecretRecord{Label: label} } password, err := readpass.PasswordPromptBytes("New password: "******"no password entered") } rec.Secret = password rec.Timestamp = time.Now().Unix() ps.Store[label] = rec return nil }
func showQR(ps *store.SecretStore, cfg *config) error { label := cfg.Args[0] if !ps.Has(label) { return errors.New("no token found under label") } filename := cfg.Args[1] rec := ps.Store[label] otp, label, err := twofactor.FromURL(string(rec.Secret)) if err != nil { return err } var qr []byte switch otp.Type() { case twofactor.OATH_HOTP: hotp := otp.(*twofactor.HOTP) qr, err = hotp.QR(label) case twofactor.OATH_TOTP: totp := otp.(*twofactor.TOTP) qr, err = totp.QR(label) default: err = errors.New("QR codes can only be generated for OATH OTPs") } if err != nil { return err } return ioutil.WriteFile(filename, qr, 0600) }
func showEntry(ps *store.SecretStore, cfg *config) error { title := cfg.Args[0] if !ps.Has(title) { return errors.New("entry not found") } fmt.Printf("%s\n", ps.Store[title].Secret) return nil }
func editEntry(ps *store.SecretStore, cfg *config) error { title := cfg.Args[0] if !ps.Has(title) { return errors.New("entry not found") } tmp, err := ioutil.TempFile("", "cu_journal") if err != nil { return err } fileName := tmp.Name() tmp.Close() defer os.Remove(fileName) err = ioutil.WriteFile(fileName, ps.Store[title].Secret, 0600) defer func() { err := os.Remove(fileName) if err != nil { fmt.Println("*** WARNING ***") fmt.Println("FAILED TO REMOVE TEMPORARY FILE", fileName) fmt.Println("You should remove this yourself.") fmt.Printf("\nThe reason: %v\n", err) } }() editor := cfg.Editor if editor == "" { editor = defaultEditor } args := strings.Split(editor, " ") args = append(args, fileName) cmd := exec.Command(args[0], args[1:]...) cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr err = cmd.Run() if err != nil { return err } fileData, err := ioutil.ReadFile(fileName) if err != nil { return err } util.Zero(ps.Store[title].Secret) ps.Store[title].Secret = fileData return nil }
func remove(ps *store.SecretStore, cfg *config, m secret.ScryptMode) error { if cfg.WithMeta { return removeMeta(ps, cfg) } label := cfg.Args[0] if !ps.Has(label) { return errors.New("entry not found") } fmt.Println("Removed ", label) delete(ps.Store, label) return nil }
func showSecret(ps *store.SecretStore, cfg *config, m secret.ScryptMode) error { label := cfg.Args[0] if !ps.Has(label) { return errors.New("entry not found") } r := ps.Store[label] if !cfg.Clip { fmt.Printf("Secret: %s\n", r.Secret) } else { fmt.Printf("%s", r.Secret) return nil } if cfg.WithMeta { fmt.Printf("Timestamp: %d (%s)\n", r.Timestamp, time.Unix(r.Timestamp, 0).Format(timeFormat)) for k, v := range r.Metadata { fmt.Printf("\t%s: %s\n", k, v) } } return nil }
func writeNew(ps *store.SecretStore, cfg *config) error { title := strings.TrimSpace(cfg.Args[0]) if ps.Has(title) { fmt.Printf("There is already an entry with the title '%s'.\n", title) yesOrNo, err := util.ReadLine("Do you want to edit the entry (y/n)? ") if err != nil { return err } if yesOrNo = strings.ToLower(yesOrNo); yesOrNo == "y" || yesOrNo == "yes" { return editEntry(ps, cfg) } fmt.Println("Please enter a new title (or an empty string to abort).") newTitle, err := util.ReadLine("Title: ") if err != nil { return err } else if newTitle == "" { return errors.New("user aborted entry") } title = newTitle } entry, err := newEntry(title, cfg.Editor) if err != nil { fmt.Println("[!] Failed to write a new entry:") fmt.Printf("\t%v\n", err) return err } ps.Store[title] = &store.SecretRecord{ Label: title, Timestamp: time.Now().Unix(), Secret: entry, } return nil }
func multi(ps *store.SecretStore, cfg *config, m secret.ScryptMode) error { fmt.Println("Use an empty name to indicate that you are done.") for { name, err := util.ReadLine("Name: ") if err != nil { return err } else if name == "" { break } var rec *store.SecretRecord if ps.Has(name) { if !cfg.Overwrite { util.Errorf("Entry exists, not forcing overwrite.") continue } else { util.Errorf("*** WARNING: overwriting password") } rec = ps.Store[name] } else { rec = &store.SecretRecord{ Label: name, } } password, err := util.PassPrompt("Password: "******"No password entered.") continue } rec.Secret = password rec.Timestamp = time.Now().Unix() ps.Store[name] = rec } return nil }
func addMeta(ps *store.SecretStore, cfg *config, m secret.ScryptMode) error { label := cfg.Args[0] if !ps.Has(label) { tempConfig := *cfg tempConfig.WithMeta = false err := addSecret(ps, &tempConfig, m) if err != nil { return err } } rec := ps.Store[label] if rec.Metadata == nil { rec.Metadata = map[string][]byte{} } fmt.Println("Enter metadata; use an empty line to indicate that you are done.") for { line, err := util.ReadLine("key = value: ") if err != nil { return err } else if line == "" { break } meta := strings.SplitN(line, "=", 2) if len(meta) < 2 { util.Errorf("Metadata should be in the form 'key=value'") continue } key := strings.TrimSpace(meta[0]) val := strings.TrimSpace(meta[1]) rec.Metadata[key] = []byte(val) } rec.Timestamp = time.Now().Unix() ps.Store[label] = rec return nil }
func addSecret(ps *store.SecretStore, cfg *config) error { label := cfg.Args[0] if ps.Has(label) { if cfg.Overwrite { util.Errorf("WARNING: a token already exists under this label!") } else { return errors.New("token already exists under label") } } // Default prompt is echoing, which we want here. secret, err := util.PassPrompt("Secret: ") if err != nil { return err } var rec *store.SecretRecord secret = sanitiseSecret(secret) switch cfg.OTPType { case HOTP: in, err := util.ReadLine("Initial counter (0): ") if err != nil { return err } if in == "" { in = "0" } d, err := strconv.Atoi(in) if err != nil { return err } in, err = util.ReadLine("Digits (6 or 8): ") if err != nil { return err } else if in == "" { in = "6" } digits, err := strconv.Atoi(in) if err != nil { return err } key, err := base32.StdEncoding.DecodeString(string(secret)) if err != nil { fmt.Printf("%s", secret) return err } var hotp *twofactor.HOTP hotp = twofactor.NewHOTP(key, uint64(d), digits) confirmation := hotp.OTP() fmt.Printf("Confirmation: %s\n", confirmation) rec = &store.SecretRecord{ Label: label, Secret: []byte(hotp.URL(label)), Timestamp: time.Now().Unix(), Metadata: map[string][]byte{ "key": secret, "type": []byte("HOTP"), "confirmation": []byte(confirmation), }, } case TOTP: in, err := util.ReadLine("Time step (30s): ") if err != nil { return err } if in == "" { in = "30s" } d, err := time.ParseDuration(in) if err != nil { return err } in, err = util.ReadLine("Digits (6 or 8): ") if err != nil { return err } else if in == "" { in = "6" } digits, err := strconv.Atoi(in) if err != nil { return err } key, err := base32.StdEncoding.DecodeString(string(secret)) if err != nil { return err } var totp *twofactor.TOTP totp = twofactor.NewTOTPSHA1(key, 0, uint64(d.Seconds()), digits) confirmation := totp.OTP() fmt.Printf("Confirmation: %s\n", confirmation) rec = &store.SecretRecord{ Label: label, Secret: []byte(totp.URL(label)), Timestamp: time.Now().Unix(), Metadata: map[string][]byte{ "key": secret, "type": []byte("TOTP-SHA1"), "step": []byte(d.String()), "confirmation": []byte(confirmation), }, } case GoogleTOTP: var totp *twofactor.TOTP totp, err = twofactor.NewGoogleTOTP(string(secret)) if err != nil { return err } confirmation := totp.OTP() fmt.Printf("Confirmation: %s\n", confirmation) rec = &store.SecretRecord{ Label: label, Secret: []byte(totp.URL(label)), Timestamp: time.Now().Unix(), Metadata: map[string][]byte{ "key": secret, "type": []byte("TOTP-GOOGLE"), "step": []byte("30s"), "confirmation": []byte(confirmation), }, } default: return errors.New("unrecognised OTP type") } ps.Store[label] = rec return nil }