The response has been limited to 50k tokens of the smallest files in the repo. You can remove this limitation by removing the max tokens filter.
├── .capabilities.json
├── .codeclimate.yml
├── .codecov.yml
├── .errcheck.excl
├── .gemini
    └── settings.json
├── .gitattributes
├── .github
    ├── FUNDING.yml
    ├── ISSUE_TEMPLATE.md
    ├── copilot-instructions.md
    ├── dependabot.yml
    ├── stale.yml
    └── workflows
    │   ├── autorelease.yml
    │   ├── build.yml
    │   ├── codeql-analysis.yml
    │   ├── container.yml
    │   ├── golangci-lint.yml
    │   ├── grype.yml
    │   └── scorecard.yml
├── .gitignore
├── .golangci.yml
├── .goreleaser.yml
├── .license-lint.yml
├── .revive.toml
├── AGENTS.md
├── ARCHITECTURE.md
├── CHANGELOG.md
├── CONTRIBUTING.md
├── Dockerfile
├── GOVERNANCE.md
├── LICENSE
├── Makefile
├── README.md
├── VERSION
├── bash.completion
├── docs
    ├── backends.md
    ├── backends
    │   ├── age.md
    │   ├── cryptfs.md
    │   ├── fossilfs.md
    │   ├── fs.md
    │   ├── gitfs.md
    │   ├── gpg.md
    │   └── jjfs.md
    ├── commands
    │   ├── audit.md
    │   ├── cat.md
    │   ├── clone.md
    │   ├── config.md
    │   ├── convert.md
    │   ├── create.md
    │   ├── delete.md
    │   ├── edit.md
    │   ├── env.md
    │   ├── find.md
    │   ├── fsck.md
    │   ├── generate.md
    │   ├── gopass.md
    │   ├── grep.md
    │   ├── history.md
    │   ├── init.md
    │   ├── insert.md
    │   ├── link.md
    │   ├── list.md
    │   ├── mounts.md
    │   ├── move.md
    │   ├── otp.md
    │   ├── process.md
    │   ├── pwgen.md
    │   ├── recipients.md
    │   ├── show.md
    │   ├── sync.md
    │   ├── templates.md
    │   └── update.md
    ├── components.dot
    ├── components.png
    ├── config.md
    ├── entropy.md
    ├── faq.md
    ├── features.md
    ├── hacking.md
    ├── hooks.md
    ├── logo-small.png
    ├── logo.ico
    ├── logo.png
    ├── logo.svg
    ├── releases.md
    ├── secrets.md
    ├── security.md
    ├── setup.md
    ├── showcase.png
    └── usecases
    │   ├── gpaste.md
    │   ├── multi-store.md
    │   ├── readonly-store.md
    │   ├── secure-otp.md
    │   └── secure-otp
    │       ├── Sign-In.png
    │       ├── Sign-Up.png
    │       ├── sign-in.puml
    │       └── sign-up.puml
├── fish.completion
├── go.mod
├── go.sum
├── gopass.1
├── helpers
    ├── changelog
    │   ├── main.go
    │   └── main_test.go
    ├── gitutils
    │   └── gitutils.go
    ├── man
    │   ├── main.go
    │   └── main_test.go
    ├── modinfo
    │   └── main.go
    ├── msipkg
    │   └── main.go
    ├── postrel
    │   ├── main.go
    │   └── main_test.go
    ├── proxy
    │   ├── Dockerfile.debian
    │   ├── README-3111.md
    │   ├── apt.debughttp
    │   ├── gopass.sources
    │   └── main.go
    └── release
    │   ├── main.go
    │   └── main_test.go
├── internal
    ├── action
    │   ├── action.go
    │   ├── action_test.go
    │   ├── aliases.go
    │   ├── aliases_test.go
    │   ├── audit.go
    │   ├── audit_test.go
    │   ├── binary.go
    │   ├── binary_test.go
    │   ├── clihelper.go
    │   ├── clihelper_test.go
    │   ├── clone.go
    │   ├── clone_test.go
    │   ├── commands.go
    │   ├── commands_test.go
    │   ├── completion.go
    │   ├── completion_test.go
    │   ├── config.go
    │   ├── config_test.go
    │   ├── context.go
    │   ├── context_test.go
    │   ├── convert.go
    │   ├── convert_test.go
    │   ├── copy.go
    │   ├── copy_test.go
    │   ├── create.go
    │   ├── create_test.go
    │   ├── delete.go
    │   ├── delete_test.go
    │   ├── doc.go
    │   ├── edit.go
    │   ├── edit_test.go
    │   ├── env.go
    │   ├── env_test.go
    │   ├── exit
    │   │   ├── errors.go
    │   │   └── errors_test.go
    │   ├── find.go
    │   ├── find_test.go
    │   ├── fsck.go
    │   ├── fsck_test.go
    │   ├── generate.go
    │   ├── generate_test.go
    │   ├── git.go
    │   ├── grep.go
    │   ├── grep_test.go
    │   ├── history.go
    │   ├── history_test.go
    │   ├── init.go
    │   ├── init_test.go
    │   ├── insert.go
    │   ├── insert_test.go
    │   ├── link.go
    │   ├── link_test.go
    │   ├── list.go
    │   ├── list_test.go
    │   ├── merge.go
    │   ├── merge_test.go
    │   ├── mount.go
    │   ├── mount_test.go
    │   ├── move.go
    │   ├── move_test.go
    │   ├── otp.go
    │   ├── otp_test.go
    │   ├── process.go
    │   ├── process_test.go
    │   ├── pwgen
    │   │   ├── commands.go
    │   │   ├── commands_test.go
    │   │   ├── pwgen.go
    │   │   └── pwgen_test.go
    │   ├── rcs.go
    │   ├── rcs_test.go
    │   ├── recipients.go
    │   ├── recipients_test.go
    │   ├── reminder.go
    │   ├── reorg.go
    │   ├── reorg_test.go
    │   ├── repl.go
    │   ├── repl_test.go
    │   ├── setup.go
    │   ├── setup_test.go
    │   ├── show.go
    │   ├── show_test.go
    │   ├── sync.go
    │   ├── sync_test.go
    │   ├── templates.go
    │   ├── templates_test.go
    │   ├── unclip.go
    │   ├── unclip_test.go
    │   ├── update.go
    │   ├── update_test.go
    │   ├── version.go
    │   └── version_test.go
    ├── audit
    │   ├── audit.go
    │   ├── audit_test.go
    │   ├── excludes.go
    │   ├── excludes_test.go
    │   ├── output.go
    │   ├── output_test.go
    │   ├── report.go
    │   ├── report_test.go
    │   └── single.go
    ├── backend
    │   ├── context.go
    │   ├── context_test.go
    │   ├── crypto.go
    │   ├── crypto
    │   │   ├── age.go
    │   │   ├── age
    │   │   │   ├── age.go
    │   │   │   ├── age_test.go
    │   │   │   ├── agent
    │   │   │   │   ├── agent.go
    │   │   │   │   ├── agent_test.go
    │   │   │   │   ├── client.go
    │   │   │   │   ├── client_unix.go
    │   │   │   │   └── client_windows.go
    │   │   │   ├── agent_starter_unix.go
    │   │   │   ├── agent_starter_windows.go
    │   │   │   ├── askpass.go
    │   │   │   ├── clientUI.go
    │   │   │   ├── commands.go
    │   │   │   ├── context.go
    │   │   │   ├── context_test.go
    │   │   │   ├── decrypt.go
    │   │   │   ├── encrypt.go
    │   │   │   ├── encrypt_test.go
    │   │   │   ├── identities.go
    │   │   │   ├── identities_test.go
    │   │   │   ├── keyring.go
    │   │   │   ├── loader.go
    │   │   │   ├── loader_test.go
    │   │   │   ├── recipients.go
    │   │   │   ├── recipients_test.go
    │   │   │   ├── ssh.go
    │   │   │   └── unsupported.go
    │   │   ├── doc.go
    │   │   ├── gpg
    │   │   │   ├── cli
    │   │   │   │   ├── decrypt.go
    │   │   │   │   ├── encrypt.go
    │   │   │   │   ├── encrypt_test.go
    │   │   │   │   ├── generate.go
    │   │   │   │   ├── gpg.go
    │   │   │   │   ├── gpg_others_test.go
    │   │   │   │   ├── gpg_test.go
    │   │   │   │   ├── gpg_windows_test.go
    │   │   │   │   ├── identities.go
    │   │   │   │   ├── keyring.go
    │   │   │   │   ├── keyring_test.go
    │   │   │   │   ├── loader.go
    │   │   │   │   ├── recipients.go
    │   │   │   │   ├── recipients_test.go
    │   │   │   │   └── version.go
    │   │   │   ├── colons
    │   │   │   │   ├── parse_colons.go
    │   │   │   │   ├── parse_colons_test.go
    │   │   │   │   ├── parse_fuzz.go
    │   │   │   │   └── utils.go
    │   │   │   ├── context.go
    │   │   │   ├── context_test.go
    │   │   │   ├── doc.go
    │   │   │   ├── gpgconf
    │   │   │   │   ├── binary.go
    │   │   │   │   ├── binary_others.go
    │   │   │   │   ├── binary_windows.go
    │   │   │   │   ├── binary_windows_test.go
    │   │   │   │   ├── gpgconf.go
    │   │   │   │   ├── utils.go
    │   │   │   │   ├── utils_linux.go
    │   │   │   │   ├── utils_linux_test.go
    │   │   │   │   ├── utils_others.go
    │   │   │   │   ├── utils_test.go
    │   │   │   │   ├── utils_windows.go
    │   │   │   │   ├── version.go
    │   │   │   │   └── version_test.go
    │   │   │   ├── identity.go
    │   │   │   ├── identity_test.go
    │   │   │   ├── key.go
    │   │   │   ├── key_list.go
    │   │   │   ├── key_list_test.go
    │   │   │   └── key_test.go
    │   │   ├── gpgcli.go
    │   │   ├── plain.go
    │   │   └── plain
    │   │   │   ├── backend.go
    │   │   │   ├── backend_test.go
    │   │   │   └── loader.go
    │   ├── crypto_test.go
    │   ├── doc.go
    │   ├── rcs.go
    │   ├── rcs_test.go
    │   ├── registry.go
    │   ├── registry_test.go
    │   ├── storage.go
    │   ├── storage
    │   │   ├── cryptfs.go
    │   │   ├── cryptfs
    │   │   │   ├── crypt.go
    │   │   │   ├── crypt_test.go
    │   │   │   └── loader.go
    │   │   ├── doc.go
    │   │   ├── fossilfs.go
    │   │   ├── fossilfs
    │   │   │   ├── context.go
    │   │   │   ├── context_test.go
    │   │   │   ├── fossil.go
    │   │   │   ├── fossil_test.go
    │   │   │   ├── loader.go
    │   │   │   ├── loader_test.go
    │   │   │   ├── settings.go
    │   │   │   ├── status.go
    │   │   │   ├── storage.go
    │   │   │   └── storage_test.go
    │   │   ├── fs.go
    │   │   ├── fs
    │   │   │   ├── fsck.go
    │   │   │   ├── fsck_test.go
    │   │   │   ├── link.go
    │   │   │   ├── link_test.go
    │   │   │   ├── loader.go
    │   │   │   ├── rcs.go
    │   │   │   ├── rcs_test.go
    │   │   │   ├── store.go
    │   │   │   ├── store_others.go
    │   │   │   ├── store_test.go
    │   │   │   ├── store_windows.go
    │   │   │   ├── walk.go
    │   │   │   └── walk_test.go
    │   │   ├── gitfs.go
    │   │   ├── gitfs
    │   │   │   ├── commands.go
    │   │   │   ├── config.go
    │   │   │   ├── config_test.go
    │   │   │   ├── git.go
    │   │   │   ├── git_test.go
    │   │   │   ├── loader.go
    │   │   │   ├── ssh_darwin.go
    │   │   │   ├── ssh_others.go
    │   │   │   ├── ssh_windows.go
    │   │   │   └── storage.go
    │   │   ├── jjfs.go
    │   │   └── jjfs
    │   │   │   ├── jj.go
    │   │   │   └── loader.go
    │   └── storage_test.go
    ├── cache
    │   ├── disk.go
    │   ├── disk_test.go
    │   ├── ghssh
    │   │   ├── cache.go
    │   │   ├── cache_test.go
    │   │   ├── github.go
    │   │   └── github_test.go
    │   ├── inmem.go
    │   └── inmem_test.go
    ├── completion
    │   ├── fish
    │   │   ├── completion.go
    │   │   ├── completion_test.go
    │   │   └── template.go
    │   └── zsh
    │   │   ├── completion.go
    │   │   ├── completion_test.go
    │   │   └── template.go
    ├── config
    │   ├── config.go
    │   ├── config_test.go
    │   ├── config_windows.go
    │   ├── context.go
    │   ├── docs_test.go
    │   ├── legacy.go
    │   ├── legacy
    │   │   ├── config.go
    │   │   ├── config_test.go
    │   │   ├── io.go
    │   │   ├── io_test.go
    │   │   ├── legacy.go
    │   │   ├── location.go
    │   │   └── location_xdg_test.go
    │   ├── location.go
    │   ├── location_test.go
    │   ├── location_xdg_test.go
    │   ├── utils.go
    │   └── utils_test.go
    ├── create
    │   ├── helpers.go
    │   ├── helpers_test.go
    │   ├── templates.go
    │   ├── wizard.go
    │   └── wizard_test.go
    ├── cui
    │   ├── actions.go
    │   ├── actions_test.go
    │   ├── cui.go
    │   ├── cui_test.go
    │   ├── recipients.go
    │   └── recipients_test.go
    ├── diff
    │   ├── diff.go
    │   └── diff_test.go
    ├── editor
    │   ├── edit_linux.go
    │   ├── edit_others.go
    │   ├── edit_others_test.go
    │   ├── edit_test.go
    │   ├── edit_windows.go
    │   ├── edit_windows_test.go
    │   └── editor.go
    ├── env
    │   ├── doc.go
    │   ├── env_darwin.go
    │   └── env_others.go
    ├── hashsum
    │   ├── hashsums.go
    │   └── hashsums_test.go
    ├── hook
    │   └── hook.go
    ├── notify
    │   ├── doc.go
    │   ├── icon.go
    │   ├── notify_darwin.go
    │   ├── notify_darwin_test.go
    │   ├── notify_dbus.go
    │   ├── notify_others.go
    │   ├── notify_test.go
    │   └── notify_windows.go
    ├── out
    │   ├── context.go
    │   ├── context_test.go
    │   ├── print.go
    │   └── print_test.go
    ├── pwschemes
    │   ├── argon2i
    │   │   ├── argon2i.go
    │   │   └── argon2i_test.go
    │   ├── argon2id
    │   │   ├── argon2id.go
    │   │   └── argon2id_test.go
    │   └── bcrypt
    │   │   ├── bcrypt.go
    │   │   └── bcrypt_test.go
    ├── queue
    │   ├── background.go
    │   └── background_test.go
    ├── recipients
    │   ├── recipients.go
    │   └── recipients_test.go
    ├── reminder
    │   ├── reminder.go
    │   └── reminder_test.go
    ├── store
    │   ├── err.go
    │   ├── leaf
    │   │   ├── context.go
    │   │   ├── context_test.go
    │   │   ├── convert.go
    │   │   ├── crypto.go
    │   │   ├── crypto_test.go
    │   │   ├── fsck.go
    │   │   ├── fsck_test.go
    │   │   ├── init.go
    │   │   ├── init_test.go
    │   │   ├── link.go
    │   │   ├── link_test.go
    │   │   ├── list.go
    │   │   ├── list_test.go
    │   │   ├── move.go
    │   │   ├── move_test.go
    │   │   ├── rcs.go
    │   │   ├── rcs_test.go
    │   │   ├── read.go
    │   │   ├── recipients.go
    │   │   ├── recipients_test.go
    │   │   ├── reencrypt.go
    │   │   ├── storage.go
    │   │   ├── store.go
    │   │   ├── store_test.go
    │   │   ├── templates.go
    │   │   ├── templates_test.go
    │   │   ├── write.go
    │   │   └── write_test.go
    │   ├── mockstore
    │   │   ├── inmem
    │   │   │   └── store.go
    │   │   ├── store.go
    │   │   └── store_test.go
    │   ├── root
    │   │   ├── convert.go
    │   │   ├── crypto.go
    │   │   ├── crypto_test.go
    │   │   ├── errors.go
    │   │   ├── fsck.go
    │   │   ├── fsck_test.go
    │   │   ├── init.go
    │   │   ├── init_test.go
    │   │   ├── link.go
    │   │   ├── list.go
    │   │   ├── list_test.go
    │   │   ├── mount.go
    │   │   ├── mount_test.go
    │   │   ├── move.go
    │   │   ├── move_test.go
    │   │   ├── rcs.go
    │   │   ├── rcs_test.go
    │   │   ├── read.go
    │   │   ├── read_test.go
    │   │   ├── recipients.go
    │   │   ├── recipients_test.go
    │   │   ├── store.go
    │   │   ├── store_test.go
    │   │   ├── templates.go
    │   │   ├── templates_test.go
    │   │   ├── write.go
    │   │   └── write_test.go
    │   ├── sort.go
    │   ├── sort_test.go
    │   └── store.go
    ├── tpl
    │   ├── funcs.go
    │   ├── funcs_test.go
    │   ├── template.go
    │   └── template_test.go
    ├── tree
    │   ├── node.go
    │   ├── node_test.go
    │   ├── root.go
    │   ├── root_test.go
    │   ├── tree.go
    │   └── tree_test.go
    └── updater
    │   ├── README.md
    │   ├── access_others.go
    │   ├── access_windows.go
    │   ├── download.go
    │   ├── extract.go
    │   ├── extract_test.go
    │   ├── github.go
    │   ├── github_test.go
    │   ├── update.go
    │   ├── update_test.go
    │   ├── updateable.go
    │   ├── verify.go
    │   └── verify_test.go
├── main.go
├── main_test.go
├── main_unix.go
├── pkg
    ├── appdir
    │   ├── appdir.go
    │   ├── appdir_test.go
    │   ├── appdir_windows.go
    │   ├── appdir_xdg.go
    │   ├── appdir_xdg_test.go
    │   ├── runtime_windows.go
    │   └── runtime_xdg.go
    ├── clipboard
    │   ├── clipboard.go
    │   ├── clipboard_others.go
    │   ├── clipboard_test.go
    │   ├── clipboard_windows.go
    │   ├── kill_others.go
    │   ├── kill_ps.go
    │   ├── unclip.go
    │   ├── unclip_linux.go
    │   ├── unclip_others.go
    │   └── unclip_test.go
    ├── ctxutil
    │   ├── ctxutil.go
    │   ├── ctxutil_test.go
    │   └── helper.go
    ├── debug
    │   ├── debug.go
    │   ├── debug_test.go
    │   ├── doc.go
    │   ├── version.go
    │   └── version_test.go
    ├── fsutil
    │   ├── fsutil.go
    │   ├── fsutil_test.go
    │   ├── umask.go
    │   └── umask_test.go
    ├── gopass
    │   ├── api
    │   │   ├── api.go
    │   │   └── api_test.go
    │   ├── apimock
    │   │   └── mock.go
    │   ├── doc.go
    │   ├── secrets
    │   │   ├── akv.go
    │   │   ├── akv_test.go
    │   │   ├── error.go
    │   │   ├── ident.go
    │   │   ├── new.go
    │   │   ├── secparse
    │   │   │   ├── .gitignore
    │   │   │   ├── mime.go
    │   │   │   ├── parse.go
    │   │   │   └── parse_test.go
    │   │   ├── yaml.go
    │   │   └── yaml_test.go
    │   └── store.go
    ├── otp
    │   ├── otp.go
    │   ├── otp_test.go
    │   ├── screenshot_others.go
    │   └── screenshot_supported.go
    ├── passkey
    │   ├── passkey.go
    │   └── passkey_test.go
    ├── pinentry
    │   └── cli
    │   │   ├── fallback.go
    │   │   └── fallback_test.go
    ├── protect
    │   ├── protect.go
    │   ├── protect_openbsd.go
    │   └── protect_test.go
    ├── pwgen
    │   ├── cryptic.go
    │   ├── cryptic_test.go
    │   ├── external.go
    │   ├── memorable.go
    │   ├── pwgen.go
    │   ├── pwgen_others_test.go
    │   ├── pwgen_test.go
    │   ├── pwgen_windows_test.go
    │   ├── pwrules
    │   │   ├── aliases.go
    │   │   ├── aliases_test.go
    │   │   ├── change.go
    │   │   ├── change_test.go
    │   │   ├── gen.go
    │   │   ├── pwrules.go
    │   │   ├── pwrules_gen.go
    │   │   └── pwrules_test.go
    │   ├── rand.go
    │   ├── validate.go
    │   ├── validate_test.go
    │   ├── wordlist.go
    │   └── xkcdgen
    │   │   ├── pwgen.go
    │   │   └── pwgen_test.go
    ├── qrcon
    │   ├── qrcon.go
    │   └── qrcon_test.go
    ├── set
    │   ├── filter.go
    │   ├── filter_test.go
    │   ├── map.go
    │   ├── map_test.go
    │   ├── set.go
    │   ├── set_test.go
    │   ├── sorted.go
    │   └── sorted_test.go
    ├── tempfile
    │   ├── file.go
    │   ├── file_test.go
    │   ├── mount_darwin.go
    │   ├── mount_linux.go
    │   └── mount_others.go
    └── termio
    │   ├── ask.go
    │   ├── ask_test.go
    │   ├── context.go
    │   ├── context_test.go
    │   ├── identity.go
    │   ├── identity_test.go
    │   ├── progress.go
    │   ├── progress_test.go
    │   ├── promptpass_others.go
    │   ├── promptpass_test.go
    │   ├── promptpass_windows.go
    │   ├── reader.go
    │   └── reader_test.go
├── tests
    ├── age_agent_test.go
    ├── audit_test.go
    ├── binary_test.go
    ├── can
    │   ├── can.go
    │   ├── can_test.go
    │   └── gnupg
    │   │   ├── pubring.gpg
    │   │   ├── random_seed
    │   │   ├── secring.gpg
    │   │   └── trustdb.gpg
    ├── completion_test.go
    ├── config_test.go
    ├── copy_test.go
    ├── delete_test.go
    ├── find_test.go
    ├── generate_test.go
    ├── gptest
    │   ├── gunit.go
    │   ├── unit.go
    │   └── utils.go
    ├── grep_test.go
    ├── init_test.go
    ├── insert_test.go
    ├── list_test.go
    ├── mount_test.go
    ├── move_test.go
    ├── show_test.go
    ├── sync_test.go
    ├── tester.go
    ├── uninitialized_test.go
    └── yaml_test.go
├── version.go
└── zsh.completion


/.codeclimate.yml:
--------------------------------------------------------------------------------
 1 | version: "2"
 2 | 
 3 | checks:
 4 |   argument-count:
 5 |     config:
 6 |       threshold: 4
 7 |   complex-logic:
 8 |     config:
 9 |       threshold: 4
10 |   file-lines:
11 |     config:
12 |       threshold: 250
13 |   method-complexity:
14 |     config:
15 |       threshold: 16
16 |   method-count:
17 |     config:
18 |       threshold: 20
19 |   method-lines:
20 |     config:
21 |       threshold: 100
22 |   nested-control-flow:
23 |     config:
24 |       threshold: 4
25 |   return-statements:
26 |     config:
27 |       threshold: 4
28 | 
29 | plugins:
30 |  gofmt:
31 |    enabled: true
32 |  golint:
33 |    enabled: true
34 |  govet:
35 |    enabled: true
36 |    
37 | ratings:
38 |  paths:
39 |  - "**.go"
40 |  
41 | exclude_patterns:
42 | - "vendor/"
43 | - "utils/notify/icon.go"
44 | - "**/*_test.go"
45 | 


--------------------------------------------------------------------------------
/.codecov.yml:
--------------------------------------------------------------------------------
 1 | coverage:
 2 |   range: 40..90
 3 |   round: nearest
 4 |   precision: 2
 5 |   status:
 6 |     project:
 7 |       default: on
 8 |     patch:
 9 |       default: off
10 |     changes:
11 |       default: off
12 | ignore:
13 |   - "vendor/"
14 | 


--------------------------------------------------------------------------------
/.errcheck.excl:
--------------------------------------------------------------------------------
1 | fmt.Fprintf
2 | fmt.Fprintln
3 | fmt.Fprint
4 | 


--------------------------------------------------------------------------------
/.gemini/settings.json:
--------------------------------------------------------------------------------
1 | {
2 |     "contextFileName": "AGENTS.md"
3 | }
4 | 


--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | CHANGELOG.md merge=union
2 | 
3 | 


--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: dominikschulz
2 | patreon: gopass 
3 | custom: "https://paypal.me/doschulz"
4 | 


--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
 1 | ---
 2 | name: Bug report
 3 | about: Create a report to help us improve gopass
 4 | ---
 5 | 
 6 | ### Summary
 7 | <!--
 8 | Please provide a clear and concise description of what the bug is.
 9 | -->
10 | 
11 | ### Steps To Reproduce
12 | <!--
13 | Steps to reproduce the problem
14 | -->
15 | 
16 | ### Expected behavior
17 | <!--
18 | A clear and concise description of what you expected to happen.
19 | -->
20 | 
21 | ### Environment
22 | <!--
23 | Please complete the following information (see note below)
24 | -->
25 | 
26 | - OS: [e.g. Mac OS X High Sierra, Ubuntu 18.04, Windows 10, ...]
27 | - OS version: [uname -a]
28 | - gopass Version: [gopass version]
29 | - Installation method: [e.g. from source, brew, gopass repo]
30 | 
31 | <!--
32 | **PLEASE NOTE**
33 | 
34 | There is a package named gopass in the official Debian repository.
35 | This package is not related to this project in any way. If you
36 | installed gopass from the Debian archives report any bugs in
37 | the Debian BTS.
38 | -->
39 | 
40 | ### Additional context
41 | <!--
42 | Add any other context about the problem here.
43 | -->
44 | 


--------------------------------------------------------------------------------
/.github/copilot-instructions.md:
--------------------------------------------------------------------------------
1 | Refer to [AGENTS.md](../AGENTS.md) for detailed instructions on how to set up and use agents with gopass.
2 | 


--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | 
4 |   - package-ecosystem: "github-actions"
5 |     directory: "/"
6 |     schedule:
7 |       interval: "monthly"
8 |     open-pull-requests-limit: 15
9 | 


--------------------------------------------------------------------------------
/.github/stale.yml:
--------------------------------------------------------------------------------
 1 | # Number of days of inactivity before an issue becomes stale
 2 | daysUntilStale: 120
 3 | # Number of days of inactivity before a stale issue is closed
 4 | daysUntilClose: 60
 5 | # Issues with these labels will never be considered stale
 6 | exemptLabels:
 7 |   - pinned
 8 |   - security
 9 | # Label to use when marking an issue as stale
10 | staleLabel: wontfix
11 | # Comment to post when marking an issue as stale. Set to `false` to disable
12 | markComment: >
13 |   This issue has been automatically marked as stale because it has not had
14 |   recent activity. It will be closed if no further activity occurs. Thank you
15 |   for your contributions.
16 | # Comment to post when closing a stale issue. Set to `false` to disable
17 | closeComment: false
18 | # Set to true to ignore issues in a milestone (defaults to false)
19 | exemptMilestones: true
20 | 


--------------------------------------------------------------------------------
/.github/workflows/grype.yml:
--------------------------------------------------------------------------------
 1 | name: Scan gopass
 2 | 
 3 | on:
 4 |   push:
 5 |     branches:
 6 |       - master
 7 |   pull_request:
 8 |     branches:
 9 |       - master
10 | 
11 | permissions:
12 |   contents: read
13 | 
14 | concurrency:
15 |   group: ${{ github.workflow }}-${{ github.ref }}
16 |   cancel-in-progress: true
17 | 
18 | jobs:
19 |   linux:
20 |     runs-on: ubuntu-latest
21 |     steps:
22 |     - name: Harden Runner
23 |       uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
24 |       with:
25 |         egress-policy: audit
26 | 
27 |     - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
28 |       with:
29 |         fetch-depth: 0
30 |     - name: Set up Go
31 |       uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
32 |       with:
33 |         go-version: '1.24'
34 |     - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
35 |       with:
36 |         path: ~/go/pkg/mod
37 |         key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
38 |         restore-keys: |
39 |           ${{ runner.os }}-go-
40 |     - name: Scan current project
41 |       uses: anchore/scan-action@f6601287cdb1efc985d6b765bbf99cb4c0ac29d8 # v7.0.0
42 |       with:
43 |         path: "."
44 |         fail-build: true
45 |         severity-cutoff: critical
46 | 


--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
 1 | gopass
 2 | gopass-*-amd64
 3 | gopass-full
 4 | dev.sh
 5 | !pkg/gopass/
 6 | coverage.out
 7 | coverage-all.*
 8 | .vscode/
 9 | 
10 | # Profiling
11 | *.out
12 | 
13 | # Compiled Object files, Static and Dynamic libs (Shared Objects)
14 | *.o
15 | *.a
16 | *.so
17 | 
18 | # Folders
19 | _obj
20 | _test
21 | 
22 | # Architecture specific extensions/prefixes
23 | *.[568vq]
24 | [568vq].out
25 | 
26 | *.cgo1.go
27 | *.cgo2.c
28 | _cgo_defun.c
29 | _cgo_gotypes.go
30 | _cgo_export.*
31 | 
32 | _testmain.go
33 | 
34 | *.exe
35 | *.test
36 | *.prof
37 | 
38 | # gopass specific ignores
39 | *.sublime-*
40 | *.swp
41 | /.env
42 | 
43 | # package files
44 | *.deb
45 | *.pkg.tar.xz
46 | *.rpm
47 | *.tar.bz2
48 | 
49 | releases/
50 | dist/
51 | 
52 | manifest-*.json
53 | 
54 | # go-fuzz
55 | *-fuzz.zip
56 | workdir/
57 | 
58 | .vscode/
59 | NOTICE.new
60 | 
61 | debian/
62 | 


--------------------------------------------------------------------------------
/.license-lint.yml:
--------------------------------------------------------------------------------
 1 | unrestricted_licenses:
 2 |   - Apache-2.0
 3 |   - MIT
 4 |   - BSD-3-Clause
 5 |   - BSD-2-Clause
 6 |   - 0BSD
 7 |   - WTFPL
 8 |   - CC0-1.0
 9 | reciprocal_licenses:
10 |   - MPL-2.0
11 |   - MPL-2.0-no-copyleft-exception
12 | allowlisted_modules:
13 | # Simplified BSD (BSD-2-Clause): https://github.com/russross/blackfriday/blob/master/LICENSE.txt
14 | - github.com/russross/blackfriday
15 | - github.com/russross/blackfriday/v2
16 | # Apache license
17 | - github.com/dgraph-io/ristretto
18 | - github.com/spf13/afero
19 | # Modified BSD-2-Clause with extra no-Google clause: https://github.com/jezek/xgb/blob/master/LICENSE
20 | - github.com/jezek/xgb
21 | # MIT
22 | - github.com/jwalton/go-supportscolor


--------------------------------------------------------------------------------
/.revive.toml:
--------------------------------------------------------------------------------
 1 | # Ignores files with "GENERATED" header, similar to golint
 2 | ignoreGeneratedHeader = false
 3 | 
 4 | # Sets the default severity to "warning"
 5 | severity = "error"
 6 | 
 7 | # Sets the default failure confidence. This means that linting errors
 8 | # with less than 0.8 confidence will be ignored.
 9 | confidence = 0.6
10 | 
11 | # Sets the error code for failures with severity "error"
12 | errorCode = 1
13 | 
14 | # Sets the error code for failures with severity "warning"
15 | warningCode = 1
16 | 
17 | [rule.argument-limit]
18 |   arguments = [10]
19 | [rule.blank-imports]
20 | [rule.context-as-argument]
21 | [rule.context-keys-type]
22 | [rule.cyclomatic]
23 |   arguments = [21]
24 | [rule.dot-imports]
25 | [rule.error-naming]
26 | [rule.error-return]
27 | [rule.error-strings]
28 | [rule.errorf]
29 | [rule.exported]
30 | [rule.if-return]
31 | [rule.increment-decrement]
32 | [rule.indent-error-flow]
33 | [rule.package-comments]
34 | [rule.range]
35 | [rule.receiver-naming]
36 | [rule.time-naming]
37 | [rule.unexported-return]
38 | [rule.var-declaration]
39 | [rule.var-naming]
40 | 


--------------------------------------------------------------------------------
/GOVERNANCE.md:
--------------------------------------------------------------------------------
 1 | # gopass project governance
 2 | 
 3 | ## Overview
 4 | 
 5 | The gopass project uses a governance model commonly described as Benevolent
 6 | Dictator For Life (BDFL). This document outlines our understanding of what this
 7 | means. It is derived from the [i3 window manager project
 8 | governance](https://raw.githubusercontent.com/i3/i3/next/.github/GOVERNANCE.md). 
 9 | 
10 | ## Roles
11 | 
12 | * user: anyone who interacts with the gopass project
13 | * core contributor: a handful of people who have contributed significantly to
14 |   the project by any means (issue triage, support, documentation, code, etc.).
15 |   Core contributors are recognizable via GitHub’s “Member” badge.
16 | * Benevolent Dictator For Life (BDFL): a single individual who makes decisions
17 |   when consensus cannot be reached. gopass’s current BDFL is [@dominikschulz](https://github.com/dominikschulz).
18 | 
19 | ## Decision making process
20 | 
21 | In general, we try to reach consensus in discussions. In case consensus cannot
22 | be reached, the BDFL makes a decision.
23 | 
24 | ## Contribution process
25 | 
26 | Please see [CONTRIBUTING](CONTRIBUTING.md).
27 | 


--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
 1 | The MIT License (MIT)
 2 | 
 3 | Copyright 2017 JustWatch GmbH
 4 | 
 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 6 | 
 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 8 | 
 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 | 


--------------------------------------------------------------------------------
/VERSION:
--------------------------------------------------------------------------------
1 | 1.15.18
2 | 


--------------------------------------------------------------------------------
/bash.completion:
--------------------------------------------------------------------------------
 1 | _gopass_bash_autocomplete() {
 2 |      local cur opts base
 3 |      COMPREPLY=()
 4 |      cur="${COMP_WORDS[COMP_CWORD]}"
 5 |      opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion )
 6 |      local IFS=
#39;\n'
 7 |      COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
 8 |      return 0
 9 |  }
10 | 
11 | complete -F _gopass_bash_autocomplete gopass
12 | 


--------------------------------------------------------------------------------
/docs/backends/cryptfs.md:
--------------------------------------------------------------------------------
1 | # cryptfs storage backend
2 | 
3 | The `cryptfs` backend is an experimental storage backend **PREVIEW**. It hashes secret names and stores the mapping from names to actual file inside an `age` encrypted lookup table. The filesystem backing this storage backend is flexible, but by default uses `gitfs`.
4 | 
5 | **WARNING**: Do not use unless you want to contribute to the development of this backend!
6 | 


--------------------------------------------------------------------------------
/docs/backends/fossilfs.md:
--------------------------------------------------------------------------------
1 | # `fossilfs` storage backend
2 | 
3 | This is an **EXPERIMENTAL** storage backend that uses the Fossil SCM. It isn't well tested and only exists to provide an example how a non-git backend could look like.
4 | 


--------------------------------------------------------------------------------
/docs/backends/fs.md:
--------------------------------------------------------------------------------
1 | # fs storage backend
2 | 
3 | The simplest storage backend, often used for testing.
4 | It stores data directly in the filesystem without any RCS support.
5 | 


--------------------------------------------------------------------------------
/docs/backends/gitfs.md:
--------------------------------------------------------------------------------
1 | # `gitfs` storage backend
2 | 
3 | This is the default storage backend. It stores the encrypted data directly in the filesystem. It uses an external git binary to provide history and remote sync operations.
4 | 
5 | gopass configures git to use persistent ssh connections. If you do not want
6 | this set `GIT_SSH_COMMAND` to an empty string to override the built-in default.
7 | 


--------------------------------------------------------------------------------
/docs/backends/jjfs.md:
--------------------------------------------------------------------------------
1 | # `jjfs` storage backend
2 | 
3 | This is an **EXPERIMENTAL** storage backend that uses the JJ / Git. It isn't well tested and only exists to provide an example how a non-git backend could look like.


--------------------------------------------------------------------------------
/docs/commands/audit.md:
--------------------------------------------------------------------------------
 1 | # `audit` command
 2 | 
 3 | The `audit` command will decrypt all secrets and scan for weak passwords or other common flaws.
 4 | 
 5 | ## Synopsis
 6 | 
 7 | ```
 8 | $ gopass audit
 9 | ```
10 | 
11 | ## Excludes
12 | 
13 | You can exclude certain secrets from the audit by adding a `.gopass-audit-exclude` file to the secret. The file should contain a list of RE2 patters to exclude, one per line. For example:
14 | 
15 | ```
16 | # Lines starting with # are ignored. Trailing comments are not supported.
17 | # Exclude all secrets in the pin folder.
18 | # Note: These are RE2, not Glob patterns!
19 | pin/.*
20 | # Literal matches are also valid RE2 patterns
21 | test_folder/ignore_this
22 | # Gopass internally uses forward slashes as path separators, even on Windows. So no need to escape backslashes.
23 | ```
24 | 
25 | ## Password strength backends
26 | 
27 | | Backend                                         | Description                                                            |
28 | |-------------------------------------------------|------------------------------------------------------------------------|
29 | | [`crunchy`](https://github.com/muesli/crunchy)  | Crunchy password strength checker                                      |
30 | | `name`                                          | Checks if password equals the name of the secret                       |
31 | 


--------------------------------------------------------------------------------
/docs/commands/clone.md:
--------------------------------------------------------------------------------
 1 | # `clone` command
 2 | 
 3 | The `clone` command allows cloning and setting up a new password store
 4 | from a remote location, e.g. a remote git repo.
 5 | 
 6 | ## Synopsis
 7 | 
 8 | ```
 9 | $ gopass clone git@example.com/store.git
10 | $ gopass clone git@example.com/store.git sub/store
11 | ```
12 | 
13 | ## Flags
14 | 
15 | | Flag       | Aliases | Description                                                     |
16 | |------------|---------|-----------------------------------------------------------------|
17 | | `--path`   |         | The path to clone the repo to.                                  |
18 | | `--crypto` |         | Override the crypto backend to use if the auto-detection fails. |
19 | 


--------------------------------------------------------------------------------
/docs/commands/config.md:
--------------------------------------------------------------------------------
 1 | # `config` command
 2 | 
 3 | The config command allows displaying and altering configuration options.
 4 | 
 5 | Note: To manage mounts use `gopass mounts`.
 6 | 
 7 | ## Synopsis
 8 | 
 9 | ```bash
10 | gopass config
11 | gopass config generate.autoclip
12 | gopass config generate.autoclip false
13 | ```
14 | 
15 | ## Flags
16 | 
17 | | Flag      | Description                    |
18 | |-----------|--------------------------------|
19 | | `--store` | Only sync a specific sub store |
20 | 


--------------------------------------------------------------------------------
/docs/commands/convert.md:
--------------------------------------------------------------------------------
 1 | # `convert` command
 2 | 
 3 | The `convert` command exists to migrate stores between different backend
 4 | implementations.
 5 | 
 6 | Note: This command exists to enable a possible migration path. If we agree
 7 | on a single set of backend implementations the multiple backend support
 8 | might go away and this command as well.
 9 | 
10 | Warning: Converting between different RCS backends will loose part of the history. While we try to retain as much information as possible especially the commit timestamps will be set to the convert time.
11 | 
12 | ## Synopsis
13 | 
14 | ```
15 | $ gopass convert --store=foo --move=true --storage=gitfs --crypto=age
16 | $ gopass convert --store=bar --move=false --storage=fs --crypto=plain
17 | ```
18 | 
19 | ## Flags
20 | 
21 | Flag | Description
22 | ---- | -----------
23 | `--store` | Substore to convert.
24 | `--move` | Remove backup after converting? (default: `false`)
25 | `--storage` | Target storage backend.
26 | `--crypto` | Target crypto backend.
27 | 


--------------------------------------------------------------------------------
/docs/commands/delete.md:
--------------------------------------------------------------------------------
 1 | # `delete` command
 2 | 
 3 | The `delete` command is used to remove a single secret or a whole subtree.
 4 | 
 5 | Note: Recursive operations crossing mount points are intentionally not supported.
 6 | 
 7 | ## Synopsis
 8 | 
 9 | ```
10 | $ gopass delete entry
11 | $ gopass rm -r path/to/folder
12 | $ gopass rm -f entry
13 | $ gopass delete entry key
14 | ```
15 | 
16 | ## Modes of operation
17 | 
18 | * Delete a single secret
19 | * Delete a single key from an existing secret
20 | * Delete a directoy of secrets
21 | 
22 | ## Flags
23 | 
24 | | Flag          | Aliases | Description                           |
25 | |---------------|---------|---------------------------------------|
26 | | `--recursive` | `-r`    | Recursively delete files and folders. |
27 | | `--force`     | `-f`    | Do not ask for confirmation.          |
28 | 
29 | ## Details
30 | 
31 | * Removing a single key will need to decrypt the secret
32 | 


--------------------------------------------------------------------------------
/docs/commands/env.md:
--------------------------------------------------------------------------------
 1 | # `env` command
 2 | 
 3 | The `env` command runs a binary as a subprocess with a pre-populated environment.
 4 | The environment of the subprocess is populated with a set of environment variables corresponding
 5 | to the secret subtree specified on the command line.
 6 | 
 7 | ## Synopsis
 8 | 
 9 | ```
10 | $ gopass env entry env
11 | ```
12 | 
13 | 


--------------------------------------------------------------------------------
/docs/commands/find.md:
--------------------------------------------------------------------------------
 1 | # `find` command
 2 | 
 3 | The `find` command will attempt to do a simple substring match on the names of all secrets.
 4 | If there is a single match it will directly invoke `show` and display the result.
 5 | If there are multiple matches a selection will be shown.
 6 | 
 7 | Note: The find command will not fall back to a fuzzy search.
 8 | 
 9 | ## Synopsis
10 | 
11 | ```
12 | $ gopass find entry
13 | $ gopass find -f entry
14 | $ gopass find -c entry
15 | ```
16 | 
17 | ## Flags
18 | 
19 | | Flag       | Aliases | Description                                                   |
20 | |------------|---------|---------------------------------------------------------------|
21 | | `--clip`   | `-c`    | Copy the password into the clipboard.                         |
22 | | `--unsafe` | `-u`    | Display any unsafe content, even if `safecontent` is enabled. |
23 | 
24 | 


--------------------------------------------------------------------------------
/docs/commands/fsck.md:
--------------------------------------------------------------------------------
 1 | # `fsck` command
 2 | 
 3 | `gopass` can check integrity of it's password stores with the `fsck` command.
 4 | It will ensure proper file and directory permissions as well as proper
 5 | recipient coverage (on supported crypto backends, only).
 6 | 
 7 | ## Synopsis
 8 | 
 9 | ```
10 | $ gopass fsck
11 | ```
12 | 
13 | ## Modes of operation
14 | 
15 | * Check the entire password store, incl. all mounts
16 | * Check only the specified mount
17 | 
18 | ## Flags
19 | 
20 | Flag | Aliases | Description
21 | ---- | ------- | -----------
22 | `--decrypt` | | Decrypt and reencrypt all secrets.
23 | 


--------------------------------------------------------------------------------
/docs/commands/grep.md:
--------------------------------------------------------------------------------
 1 | # `grep` command
 2 | 
 3 | The `grep` command works like the Unix `grep` tool. It decrypts all secrets
 4 | and performs a substring or regexp match on the given pattern.
 5 | 
 6 | ## Synopsis
 7 | 
 8 | ```
 9 | $ gopass grep foobar
10 | ```
11 | 
12 | ## Modes of operations
13 | 
14 | * Search for the given pattern in all secrets
15 | 
16 | ## Flags
17 | 
18 | None.
19 | Flag | Aliases | Description
20 | ---- | ------- | -----------
21 | `--regexp` | | Parse the pattern as a RE2 regular expression.
22 | 


--------------------------------------------------------------------------------
/docs/commands/history.md:
--------------------------------------------------------------------------------
 1 | # `history` command
 2 | 
 3 | The `gopass history` command will show all revisions of a given secret.
 4 | 
 5 | ## Synopsis
 6 | 
 7 | ```
 8 | $ gopass history entry
 9 | ```
10 | 
11 | ## Modes of operation
12 | 
13 | * Display all revisions of the given secret.
14 | 
15 | ## Flags
16 | 
17 | None.
18 | 


--------------------------------------------------------------------------------
/docs/commands/link.md:
--------------------------------------------------------------------------------
 1 | # `link` command
 2 | 
 3 | The `link` (or `ln`) command is used to create a symlink from one secret in a
 4 | store to a target in the same store.
 5 | 
 6 | Note: Symlinks across different stores / mounts are currently not supported!
 7 | 
 8 | Note: `audit` and `list` do not recognize symlinks, yet. They will treat
 9 | symlinks as regular (different) entries.
10 | 
11 | ## Synopsis
12 | 
13 | ```
14 | $ gopass ln foo/bar bar/baz
15 | $ gopass show foo/bar
16 | $ gopass show bar/baz
17 | ```
18 | 
19 | ## Modes of operations
20 | 
21 | * Create a symlink from an existing secret to a new name, the target must not exist, yet
22 | 
23 | Note: Use `gopass rm` to remove a symlink.
24 | 
25 | ## Flags
26 | 
27 | None.
28 | 
29 | 


--------------------------------------------------------------------------------
/docs/commands/mounts.md:
--------------------------------------------------------------------------------
 1 | # `mounts` commands
 2 | 
 3 | The `mounts` commands allow managing mounted substores. This is one of the
 4 | distinctive core features of `gopass` and we aim making working with substores
 5 | as seamless as possible.
 6 | 
 7 | Instead of support for encrypting different parts of a store for different
 8 | recipients we instead encourage users to mount different stores - each
 9 | encrypted to a uniform set of recipients - into a semless virtual tree structure.
10 | 
11 | This feature is modeled after standard POSIX mount semantics.
12 | 
13 | ## Synopsis
14 | 
15 | ```
16 | $ gopass mounts
17 | $ gopass mounts add mount/point /path/to/store
18 | $ gopass mounts remove mount/point
19 | ```
20 | 
21 | ## Modes of operation
22 | 
23 | * Add a new mount
24 | * List existing mounts
25 | * Remove an existing mount
26 | 
27 | ## Creating new mounts
28 | 
29 | You can also create new mounts using `init` even if your store is already initialized:
30 | 
31 | ```
32 | gopass init --store mynewsubstore pgpkeyidentitfier
33 | ```
34 | 
35 | (You can also specify a specific local path using `--path`, just make sure to keep your PGP key identifier, e.g. its email or fingerprint, as the last argument.)
36 | 


--------------------------------------------------------------------------------
/docs/commands/otp.md:
--------------------------------------------------------------------------------
 1 | # `otp` command
 2 | 
 3 | The `otp` command generates TOTP tokens from an OTP URL (`otpauth://`).
 4 | The command tries to parse the password and the totp fields as an OTP URL.
 5 | 
 6 | Note: HTOP is currently not supported.
 7 | 
 8 | Note: If `show.safecontent` is enabled, OTP URLs are hidden from the `show` command.
 9 | 
10 | ## Modes of operation
11 | 
12 | * Generate the current TOTP token from a valid OTP URL
13 | * Snip the screen to add a TOTP QR code as an OTP field to an entry.
14 | 
15 | ## Flags
16 | 
17 | | Flag         | Aliases | Description                                                              |
18 | |--------------|---------|--------------------------------------------------------------------------|
19 | | `--clip`     | `-c`    | Copy the time-based token into the clipboard.                            |
20 | | `--alsoclip` | `-C`    | Copy the time-based token into the clipboard and show it.                |
21 | | `--qr`       | `-q`    | Write QR code to file.                                                   |
22 | | `--chained`  | `-p`    | chain the token to the password                                          |
23 | | `--password` | `-o`    | Only display the token. For use in scripts.                              |
24 | | `--snip`     | `-s`    | Try and find a QR code in the screen content to add as OTP to the entry. |
25 | 


--------------------------------------------------------------------------------
/docs/commands/pwgen.md:
--------------------------------------------------------------------------------
 1 | # `pwgen` command
 2 | 
 3 | The `pwgen` command implements a subset of the features of the Unix/Linux
 4 | `pwgen` command line tool. It aims to eventually support most of the `pwgen`
 5 | flags and mirror it's behaviour. It is mainly implemented as a curtosy for
 6 | Windows users.
 7 | 
 8 | ## Modes of operation
 9 | 
10 | * Generate a few dozen random passwords with the chosen length
11 | 
12 | ## Usage
13 | 
14 | ```bash
15 | gopass pwgen [optional length]
16 | ```
17 | 
18 | ## Synopsis
19 | 
20 | ```bash
21 | gopass pwgen
22 | gopass pwgen 24
23 | ```
24 | 
25 | ## Flags
26 | 
27 | Flag | Aliases | Description
28 | ---- | ------- | -----------
29 | `--no-numerals` | `-0` | Do not include numerals in the generated passwords.
30 | `--one-per-line` | `-1` | Print one password per line.
31 | `--xkcd` | `-x` | Use multiple random english words combined to a password.
32 | `--sep` | `--xs` | Word separator for multi-word passwords.
33 | `--lang` | `--xl` | Language to generate password from. Currently only supports english (en, default).
34 | 


--------------------------------------------------------------------------------
/docs/commands/sync.md:
--------------------------------------------------------------------------------
 1 | # `sync` command
 2 | 
 3 | The `sync` command is the preferred way to manually synchronize changes between
 4 | your local stores and any configured remotes.
 5 | 
 6 | You can also `cd` into a git-based store and manually perform git operations,
 7 | or use the `gopass git` command to automatically run a command in the correct
 8 | directory.
 9 | 
10 | Note: `gopass sync` only supports one remote per store.
11 | 
12 | ## Flags
13 | 
14 | | Flag      | Description                    |
15 | |-----------|--------------------------------|
16 | | `--store` | Only sync a specific sub store |
17 | 


--------------------------------------------------------------------------------
/docs/commands/update.md:
--------------------------------------------------------------------------------
 1 | # `update` command
 2 | 
 3 | The `update` command will attempt to auto-update `gopass` by downloading the
 4 | latest release from GitHub. It performs several pre-flight checks in order to
 5 | determine if the binary can be updated or not (e.g. if managed by a package
 6 | manager).
 7 | 
 8 | ## Synopsis
 9 | 
10 | ```
11 | $ gopass update
12 | $ gopass update --pre
13 | ```
14 | 
15 | ## Flags
16 | 
17 | Flag | Description
18 | ---- | -----------
19 | `--pre` | Update to pre-releases / release candidates (default: `false`).
20 | 


--------------------------------------------------------------------------------
/docs/components.dot:
--------------------------------------------------------------------------------
 1 | digraph G {
 2 |   gopass [shape=box,style=filled,color=".2 .2 .6",peripheries=2];
 3 |   gopass -> action;
 4 |   action [label="internal/action"];
 5 |   action -> root;
 6 |   root [label="internal/store/root"];
 7 |   root -> leaf;
 8 |   root -> tree;
 9 |   tree [label="internal/tree"];
10 |   leaf [label="internal/store/leaf"];
11 |   leaf -> gitfs;
12 |   gitfs [label="internal/backend/storage/gitfs"];
13 |   gitfs -> gitcli;
14 |   gitcli [label="git binary",shape=Mdiamond];
15 |   leaf -> gpg;
16 |   gpg [label="internal/backend/crypto/gpg/cli"];
17 |   leaf -> age [style="dotted"];
18 |   age [label="internal/backend/crypto/age"];
19 |   gpg -> gpgcli;
20 |   gpgcli [label="gpg/gpg2 binary",shape=Mdiamond];
21 |   leaf -> secret;
22 |   secret [label="pkg/gopass/secrets"];
23 |   secret -> root;
24 |   jsonapi [label="gopass-jsonapi",shape=box];
25 |   jsonapi -> api;
26 |   api [label="pkg/gopass/api"];
27 |   api -> root;
28 |   api -> config;
29 |   gopass -> config;
30 |   config [label="internal/config"];
31 |   summon -> api;
32 |   summon [label="gopass-summon-provider",shape=box];
33 |   hibp -> api;
34 |   hibp [label="gopass-hibp",shape=box];
35 |   hibp -> pkghibp;
36 |   pkghibp [label="pkg/hibp"];
37 |   gitcreds -> api;
38 |   gitcreds [label="git-credential-gopass",shape=box];
39 | }
40 | 


--------------------------------------------------------------------------------
/docs/components.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gopasspw/gopass/ed5497331809e95f1a6d44eb5a1cce955057b64d/docs/components.png


--------------------------------------------------------------------------------
/docs/logo-small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gopasspw/gopass/ed5497331809e95f1a6d44eb5a1cce955057b64d/docs/logo-small.png


--------------------------------------------------------------------------------
/docs/logo.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gopasspw/gopass/ed5497331809e95f1a6d44eb5a1cce955057b64d/docs/logo.ico


--------------------------------------------------------------------------------
/docs/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gopasspw/gopass/ed5497331809e95f1a6d44eb5a1cce955057b64d/docs/logo.png


--------------------------------------------------------------------------------
/docs/showcase.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gopasspw/gopass/ed5497331809e95f1a6d44eb5a1cce955057b64d/docs/showcase.png


--------------------------------------------------------------------------------
/docs/usecases/multi-store.md:
--------------------------------------------------------------------------------
 1 | # Use case: Multiple Stores
 2 | 
 3 | `gopass` aims to support up to 100 mounted substores without noticeable
 4 | impact on most operations.
 5 | 
 6 | Using multiple stores is the preferred approach to solving different tasks
 7 | like encrypting different sets of secrets for different recipients (as
 8 | opposed to e.g. recipient lists in sub directories).
 9 | 
10 | We understand that being able to use multiple stores is a key features of
11 | `gopass` and we commit to maintaining and improving this feature in the
12 | long term.
13 | 
14 | 


--------------------------------------------------------------------------------
/docs/usecases/secure-otp/Sign-In.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gopasspw/gopass/ed5497331809e95f1a6d44eb5a1cce955057b64d/docs/usecases/secure-otp/Sign-In.png


--------------------------------------------------------------------------------
/docs/usecases/secure-otp/Sign-Up.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gopasspw/gopass/ed5497331809e95f1a6d44eb5a1cce955057b64d/docs/usecases/secure-otp/Sign-Up.png


--------------------------------------------------------------------------------
/docs/usecases/secure-otp/sign-in.puml:
--------------------------------------------------------------------------------
 1 | @startuml
 2 | title Sign-In with local otp
 3 | 
 4 | actor User
 5 | database "Git-Passwordstore"
 6 | database "Local-Passwordstore"
 7 | database "gpg-key"
 8 | 
 9 | activate User
10 | 
11 | activate "Git-Passwordstore"
12 | User -> "Git-Passwordstore": show (login,password)
13 | "Git-Passwordstore" -> "gpg-key": decrypt
14 | "gpg-key" -> User: enter passphrase
15 | deactivate "Git-Passwordstore"
16 | 
17 | activate Website
18 | User -> Website: sign-in (login,password)
19 | Website -> User: request: otp-code
20 | 
21 | User -> "Local-Passwordstore": otp
22 | activate "Local-Passwordstore"
23 | "Local-Passwordstore" -> "gpg-key": decrypt(otp-token)
24 | "Local-Passwordstore" -> "Local-Passwordstore": generate otp-code (otp-token,local-time)
25 | deactivate "Local-Passwordstore"
26 | 
27 | User -> Website: enter (otp-code)
28 | Website -> Website: validate (otp-code,website-time,otp-token)
29 | User <-- Website: success
30 | 
31 | @enduml
32 | 


--------------------------------------------------------------------------------
/docs/usecases/secure-otp/sign-up.puml:
--------------------------------------------------------------------------------
 1 | @startuml
 2 | title Sign-Up with local otp
 3 | 
 4 | actor User
 5 | database "Git-Passwordstore"
 6 | database "Local-Passwordstore"
 7 | database "gpg-key"
 8 | 
 9 | activate User
10 | activate Website
11 | User -> Website: sign-up(login,password)
12 | Website -> Website: create otp-token
13 | User <-- Website: show otp-token
14 | deactivate Website
15 | 
16 | User -> "Git-Passwordstore": store (login,password) for Website
17 | "Git-Passwordstore" -> "gpg-key": encrypt
18 | 
19 | User -> "Local-Passwordstore": store (otp-token) for Website
20 | "Local-Passwordstore" -> "gpg-key": encrypt
21 | 
22 | 
23 | @enduml


--------------------------------------------------------------------------------
/helpers/changelog/main.go:
--------------------------------------------------------------------------------
 1 | // Copyright 2021 The gopass Authors. All rights reserved.
 2 | // Use of this source code is governed by the MIT license,
 3 | // that can be found in the LICENSE file.
 4 | 
 5 | // Changelog implements the changelog extractor that is called by the autorelease GitHub action
 6 | // and used to extract the changelog from the CHANGELOG.md file. It's content is then used to
 7 | // populate the release description on GitHub.
 8 | //
 9 | // This tool will extract every line between the first and the second subheading (##).
10 | // This way the changelog can have a common header under the top most heading (#) and we
11 | // still only get the content of the latest release in the GitHub release notes.
12 | package main
13 | 
14 | import (
15 | 	"bufio"
16 | 	"fmt"
17 | 	"os"
18 | 	"strings"
19 | )
20 | 
21 | var filename = "CHANGELOG.md"
22 | 
23 | func main() {
24 | 	fh, err := os.Open(filename)
25 | 	if err != nil {
26 | 		panic(err)
27 | 	}
28 | 	defer fh.Close()
29 | 
30 | 	s := bufio.NewScanner(fh)
31 | 	var in bool
32 | 	for s.Scan() {
33 | 		line := s.Text()
34 | 		if strings.HasPrefix(line, "## ") {
35 | 			if in {
36 | 				break
37 | 			}
38 | 			in = true
39 | 		}
40 | 
41 | 		if !in {
42 | 			continue
43 | 		}
44 | 
45 | 		fmt.Println(line)
46 | 	}
47 | }
48 | 


--------------------------------------------------------------------------------
/helpers/changelog/main_test.go:
--------------------------------------------------------------------------------
 1 | //go:build linux
 2 | // +build linux
 3 | 
 4 | package main
 5 | 
 6 | import (
 7 | 	"bufio"
 8 | 	"os"
 9 | 	"strings"
10 | 	"testing"
11 | )
12 | 
13 | func TestMain(t *testing.T) {
14 | 	// Create a temporary file
15 | 	tmpfile, err := os.CreateTemp("", "changelog_test_*.md")
16 | 	if err != nil {
17 | 		t.Fatal(err)
18 | 	}
19 | 	defer os.Remove(tmpfile.Name())
20 | 
21 | 	// Write test data to the temporary file
22 | 	content := `# Changelog
23 | ## [1.0.1] - 2021-01-01
24 | ### Added
25 | - New feature
26 | 
27 | ## [1.0.0] - 2020-12-31
28 | ### Added
29 | - Initial release
30 | `
31 | 	if _, err := tmpfile.Write([]byte(content)); err != nil {
32 | 		t.Fatal(err)
33 | 	}
34 | 	if err := tmpfile.Close(); err != nil {
35 | 		t.Fatal(err)
36 | 	}
37 | 
38 | 	// Override the global variable filename
39 | 	filename = tmpfile.Name()
40 | 
41 | 	// Capture the output
42 | 	old := os.Stdout
43 | 	r, w, _ := os.Pipe()
44 | 	os.Stdout = w
45 | 
46 | 	main()
47 | 
48 | 	w.Close()
49 | 	os.Stdout = old
50 | 
51 | 	var output strings.Builder
52 | 	scanner := bufio.NewScanner(r)
53 | 	for scanner.Scan() {
54 | 		output.WriteString(scanner.Text() + "\n")
55 | 	}
56 | 
57 | 	expected := `## [1.0.1] - 2021-01-01
58 | ### Added
59 | - New feature
60 | 
61 | `
62 | 	if output.String() != expected {
63 | 		t.Errorf("expected %q, got %q", expected, output.String())
64 | 	}
65 | }
66 | 


--------------------------------------------------------------------------------
/helpers/modinfo/main.go:
--------------------------------------------------------------------------------
 1 | // modinfo a small helper to print the build info and module versions.
 2 | //
 3 | // Test builds don't have build info, so this will only work in a real build.
 4 | package main
 5 | 
 6 | import (
 7 | 	"fmt"
 8 | 	rd "runtime/debug"
 9 | 
10 | 	_ "github.com/blang/semver/v4"
11 | 	"github.com/gopasspw/gopass/pkg/debug"
12 | )
13 | 
14 | func main() {
15 | 	info, ok := rd.ReadBuildInfo()
16 | 	if !ok {
17 | 		panic("could not read build info")
18 | 	}
19 | 
20 | 	fmt.Printf("Build Info: %+v\n", info)
21 | 
22 | 	for _, v := range []string{
23 | 		"github.com/blang/semver/v4",
24 | 		"github.com/gopasspw/gopass/internal/backend/storage/fs",
25 | 	} {
26 | 		mv := debug.ModuleVersion(v)
27 | 		fmt.Printf("Module Version: %s %s\n", v, mv)
28 | 	}
29 | }
30 | 


--------------------------------------------------------------------------------
/helpers/proxy/Dockerfile.debian:
--------------------------------------------------------------------------------
 1 | FROM debian:bookworm
 2 | 
 3 | RUN apt update && apt install -y \
 4 |     sudo \
 5 |     curl \
 6 |     iproute2 \
 7 |     iputils-ping \
 8 |     vim
 9 | RUN curl -L -q https://packages.gopass.pw/repos/gopass/gopass-archive-keyring.gpg | sudo tee /usr/share/keyrings/gopass-archive-keyring.gpg
10 | ADD helpers/proxy/apt.debughttp /etc/apt/apt.conf.d/99debughttp
11 | ADD helpers/proxy/gopass.sources /etc/apt/sources.list.d/gopass.sources
12 | 
13 | CMD /bin/bash
14 | 


--------------------------------------------------------------------------------
/helpers/proxy/README-3111.md:
--------------------------------------------------------------------------------
 1 | # Debugging Issue 3111
 2 | 
 3 | - Run the proxy on the host: `go run helpers/proxy/main.go`
 4 |   - Turn off the firewall / open the port!
 5 | - Modify apt.debughttp and replace the HOST with the IP of the Docker host
 6 | - `docker build -t debian:gopass -f helpers/proxy/Dockerfile.debian .`
 7 | - `docker run --rm -ti debian:gopass`
 8 | - Inside the container:
 9 | - `apt update`
10 | 


--------------------------------------------------------------------------------
/helpers/proxy/apt.debughttp:
--------------------------------------------------------------------------------
1 | Debug::Acquire::http "true";
2 | Debug::Acquire::https "true";
3 | Debug::pkgAcquire "true";
4 | Acquire::http::Proxy "http://HOST:8080/";
5 | 


--------------------------------------------------------------------------------
/helpers/proxy/gopass.sources:
--------------------------------------------------------------------------------
1 | Types: deb
2 | URIs: https://packages.gopass.pw/repos/gopass
3 | Suites: stable
4 | Architectures: all amd64 arm64 armhf
5 | Components: main
6 | Signed-By: /usr/share/keyrings/gopass-archive-keyring.gpg
7 | 


--------------------------------------------------------------------------------
/internal/action/aliases.go:
--------------------------------------------------------------------------------
 1 | package action
 2 | 
 3 | import (
 4 | 	"sort"
 5 | 	"strings"
 6 | 
 7 | 	"github.com/gopasspw/gopass/internal/out"
 8 | 	"github.com/gopasspw/gopass/pkg/pwgen/pwrules"
 9 | 	"github.com/urfave/cli/v2"
10 | )
11 | 
12 | // AliasesPrint prints all configured aliases for password generation rules.
13 | func (s *Action) AliasesPrint(c *cli.Context) error {
14 | 	out.Printf(c.Context, "Configured aliases:")
15 | 	aliases := pwrules.AllAliases(c.Context)
16 | 	keys := make([]string, 0, len(aliases))
17 | 	for k := range aliases {
18 | 		keys = append(keys, k)
19 | 	}
20 | 
21 | 	sort.Strings(keys)
22 | 	for _, k := range keys {
23 | 		out.Printf(c.Context, "- %s -> %s", k, strings.Join(aliases[k], ", "))
24 | 	}
25 | 
26 | 	return nil
27 | }
28 | 


--------------------------------------------------------------------------------
/internal/action/aliases_test.go:
--------------------------------------------------------------------------------
 1 | package action
 2 | 
 3 | import (
 4 | 	"bytes"
 5 | 	"os"
 6 | 	"testing"
 7 | 
 8 | 	"github.com/gopasspw/gopass/internal/config"
 9 | 	"github.com/gopasspw/gopass/internal/out"
10 | 	"github.com/gopasspw/gopass/pkg/ctxutil"
11 | 	"github.com/gopasspw/gopass/tests/gptest"
12 | 	"github.com/stretchr/testify/require"
13 | )
14 | 
15 | func TestAliases(t *testing.T) {
16 | 	u := gptest.NewUnitTester(t)
17 | 
18 | 	ctx := config.NewContextInMemory()
19 | 	ctx = ctxutil.WithAlwaysYes(ctx, true)
20 | 	ctx = ctxutil.WithHidden(ctx, true)
21 | 	act, err := newMock(ctx, u.StoreDir(""))
22 | 	require.NoError(t, err)
23 | 	require.NotNil(t, act)
24 | 	ctx = act.cfg.WithConfig(ctx)
25 | 
26 | 	buf := &bytes.Buffer{}
27 | 	out.Stdout = buf
28 | 	stdout = buf
29 | 	defer func() {
30 | 		out.Stdout = os.Stdout
31 | 		stdout = os.Stdout
32 | 	}()
33 | 
34 | 	require.NoError(t, act.AliasesPrint(gptest.CliCtx(ctx, t)))
35 | }
36 | 


--------------------------------------------------------------------------------
/internal/action/clihelper.go:
--------------------------------------------------------------------------------
 1 | package action
 2 | 
 3 | import (
 4 | 	"strings"
 5 | 
 6 | 	"github.com/urfave/cli/v2"
 7 | )
 8 | 
 9 | type argList []string
10 | 
11 | func (a argList) Get(n int) string {
12 | 	if len(a) > n {
13 | 		return a[n]
14 | 	}
15 | 
16 | 	return ""
17 | }
18 | 
19 | func parseArgs(c *cli.Context) (argList, map[string]string) {
20 | 	args := make(argList, 0, c.Args().Len())
21 | 	kvps := make(map[string]string, c.Args().Len())
22 | 	if c.Args().Len() == 1 {
23 | 		// If there is only one arg, assume it is
24 | 		// the secret name, so don't attempt to
25 | 		// parse into args and kvps
26 | 		args = append(args, c.Args().Get(0))
27 | 
28 | 		return args, kvps
29 | 	}
30 | OUTER:
31 | 	for _, arg := range c.Args().Slice() {
32 | 		for _, sep := range []string{":", "="} {
33 | 			if !strings.Contains(arg, sep) {
34 | 				continue
35 | 			}
36 | 			p := strings.Split(arg, sep)
37 | 			if len(p) < 2 {
38 | 				args = append(args, arg)
39 | 
40 | 				continue OUTER
41 | 			}
42 | 			key := p[0]
43 | 			kvps[key] = strings.Join(p[1:], ":")
44 | 
45 | 			continue OUTER
46 | 		}
47 | 		args = append(args, arg)
48 | 	}
49 | 
50 | 	return args, kvps
51 | }
52 | 


--------------------------------------------------------------------------------
/internal/action/create_test.go:
--------------------------------------------------------------------------------
 1 | package action
 2 | 
 3 | import (
 4 | 	"bytes"
 5 | 	"os"
 6 | 	"testing"
 7 | 
 8 | 	"github.com/gopasspw/clipboard"
 9 | 	"github.com/gopasspw/gopass/internal/config"
10 | 	"github.com/gopasspw/gopass/internal/out"
11 | 	"github.com/gopasspw/gopass/pkg/ctxutil"
12 | 	"github.com/gopasspw/gopass/tests/gptest"
13 | 	"github.com/stretchr/testify/require"
14 | )
15 | 
16 | func TestCreate(t *testing.T) {
17 | 	u := gptest.NewUnitTester(t)
18 | 
19 | 	clipboard.ForceUnsupported = true
20 | 
21 | 	ctx := config.NewContextInMemory()
22 | 	ctx = ctxutil.WithAlwaysYes(ctx, true)
23 | 
24 | 	act, err := newMock(ctx, u.StoreDir(""))
25 | 	require.NoError(t, err)
26 | 	require.NotNil(t, act)
27 | 	ctx = act.cfg.WithConfig(ctx)
28 | 
29 | 	require.NoError(t, act.cfg.Set("", "core.notifications", "false"))
30 | 	require.NoError(t, act.cfg.Set("", "core.cliptimeout", "1"))
31 | 
32 | 	buf := &bytes.Buffer{}
33 | 	out.Stdout = buf
34 | 	defer func() {
35 | 		out.Stdout = os.Stdout
36 | 	}()
37 | 
38 | 	// create
39 | 	c := gptest.CliCtx(ctx, t)
40 | 
41 | 	require.Error(t, act.Create(c))
42 | 	buf.Reset()
43 | }
44 | 


--------------------------------------------------------------------------------
/internal/action/doc.go:
--------------------------------------------------------------------------------
1 | // Package action implements all the handlers that are available as subcommands
2 | // for gopass.
3 | package action
4 | 


--------------------------------------------------------------------------------
/internal/action/exit/errors_test.go:
--------------------------------------------------------------------------------
 1 | package exit
 2 | 
 3 | import (
 4 | 	"bytes"
 5 | 	"fmt"
 6 | 	"os"
 7 | 	"testing"
 8 | 
 9 | 	"github.com/gopasspw/gopass/internal/out"
10 | 	"github.com/stretchr/testify/assert"
11 | 	"github.com/stretchr/testify/require"
12 | )
13 | 
14 | func TestError(t *testing.T) {
15 | 	buf := &bytes.Buffer{}
16 | 	out.Stdout = buf
17 | 	defer func() {
18 | 		out.Stdout = os.Stdout
19 | 	}()
20 | 
21 | 	require.Error(t, Error(Unknown, fmt.Errorf("test"), "test"))
22 | 	assert.NotContains(t, buf.String(), "Stacktrace")
23 | }
24 | 


--------------------------------------------------------------------------------
/internal/action/git.go:
--------------------------------------------------------------------------------
 1 | package action
 2 | 
 3 | import (
 4 | 	"os"
 5 | 	"os/exec"
 6 | 	"strings"
 7 | 
 8 | 	"github.com/gopasspw/gopass/internal/action/exit"
 9 | 	"github.com/gopasspw/gopass/internal/out"
10 | 	"github.com/gopasspw/gopass/pkg/ctxutil"
11 | 	"github.com/urfave/cli/v2"
12 | )
13 | 
14 | // Git passes the git command to the underlying backend.
15 | func (s *Action) Git(c *cli.Context) error {
16 | 	ctx := ctxutil.WithGlobalFlags(c)
17 | 	store := c.String("store")
18 | 
19 | 	sub, err := s.Store.GetSubStore(store)
20 | 	if err != nil || sub == nil {
21 | 		return exit.Error(exit.Git, err, "failed to get sub store %s: %s", store, err)
22 | 	}
23 | 
24 | 	args := c.Args().Slice()
25 | 	out.Noticef(ctx, "Running 'git %s' in %s...", strings.Join(args, " "), sub.Path())
26 | 	cmd := exec.CommandContext(ctx, "git", args...)
27 | 	cmd.Dir = sub.Path()
28 | 	cmd.Stdout = os.Stdout
29 | 	cmd.Stderr = os.Stderr
30 | 	cmd.Stdin = os.Stdin
31 | 
32 | 	return cmd.Run()
33 | }
34 | 


--------------------------------------------------------------------------------
/internal/action/history.go:
--------------------------------------------------------------------------------
 1 | package action
 2 | 
 3 | import (
 4 | 	"time"
 5 | 
 6 | 	"github.com/gopasspw/gopass/internal/action/exit"
 7 | 	"github.com/gopasspw/gopass/internal/out"
 8 | 	"github.com/gopasspw/gopass/pkg/ctxutil"
 9 | 	"github.com/gopasspw/gopass/pkg/debug"
10 | 	"github.com/urfave/cli/v2"
11 | )
12 | 
13 | // History displays the history of a given secret.
14 | func (s *Action) History(c *cli.Context) error {
15 | 	ctx := ctxutil.WithGlobalFlags(c)
16 | 	name := c.Args().Get(0)
17 | 	showPassword := c.Bool("password")
18 | 
19 | 	if name == "" {
20 | 		return exit.Error(exit.Usage, nil, "Usage: %s history <NAME>", s.Name)
21 | 	}
22 | 
23 | 	if !s.Store.Exists(ctx, name) {
24 | 		return exit.Error(exit.NotFound, nil, "Secret not found")
25 | 	}
26 | 
27 | 	revs, err := s.Store.ListRevisions(ctx, name)
28 | 	if err != nil {
29 | 		return exit.Error(exit.Unknown, err, "Failed to get revisions: %s", err)
30 | 	}
31 | 
32 | 	for _, rev := range revs {
33 | 		pw := ""
34 | 		if showPassword {
35 | 			_, sec, err := s.Store.GetRevision(ctx, name, rev.Hash)
36 | 			if err != nil {
37 | 				debug.Log("Failed to get revision %q of %q: %s", rev.Hash, name, err)
38 | 			}
39 | 			if err == nil {
40 | 				pw = " - " + sec.Password()
41 | 			}
42 | 		}
43 | 		out.Printf(ctx, "%s - %s <%s> - %s - %s%s\n", rev.Hash, rev.AuthorName, rev.AuthorEmail, rev.Date.Format(time.RFC3339), rev.Subject, pw)
44 | 	}
45 | 
46 | 	return nil
47 | }
48 | 


--------------------------------------------------------------------------------
/internal/action/link.go:
--------------------------------------------------------------------------------
 1 | package action
 2 | 
 3 | import (
 4 | 	"github.com/gopasspw/gopass/internal/action/exit"
 5 | 	"github.com/gopasspw/gopass/pkg/ctxutil"
 6 | 	"github.com/urfave/cli/v2"
 7 | )
 8 | 
 9 | // Link creates a symlink.
10 | func (s *Action) Link(c *cli.Context) error {
11 | 	ctx := ctxutil.WithGlobalFlags(c)
12 | 
13 | 	from := c.Args().Get(0)
14 | 	to := c.Args().Get(1)
15 | 
16 | 	if from == "" || to == "" {
17 | 		return exit.Error(exit.Usage, nil, "Usage: link <from> <to>")
18 | 	}
19 | 
20 | 	return s.Store.Link(ctx, from, to)
21 | }
22 | 


--------------------------------------------------------------------------------
/internal/action/move.go:
--------------------------------------------------------------------------------
 1 | package action
 2 | 
 3 | import (
 4 | 	"fmt"
 5 | 
 6 | 	"github.com/gopasspw/gopass/internal/action/exit"
 7 | 	"github.com/gopasspw/gopass/pkg/ctxutil"
 8 | 	"github.com/gopasspw/gopass/pkg/termio"
 9 | 	"github.com/urfave/cli/v2"
10 | )
11 | 
12 | // Move the content from one secret to another.
13 | func (s *Action) Move(c *cli.Context) error {
14 | 	ctx := ctxutil.WithGlobalFlags(c)
15 | 
16 | 	if c.Args().Len() != 2 {
17 | 		return exit.Error(exit.Usage, nil, "Usage: %s mv old-path new-path", s.Name)
18 | 	}
19 | 
20 | 	from := c.Args().Get(0)
21 | 	to := c.Args().Get(1)
22 | 
23 | 	if !c.Bool("force") {
24 | 		if s.Store.Exists(ctx, to) && !termio.AskForConfirmation(ctx, fmt.Sprintf("%s already exists. Overwrite it?", to)) {
25 | 			return exit.Error(exit.Aborted, nil, "not overwriting your current secret")
26 | 		}
27 | 	}
28 | 
29 | 	// Check for custom commit message
30 | 	commitMsg := fmt.Sprintf("Moved %s to %s", from, to)
31 | 	if c.IsSet("commit-message") {
32 | 		commitMsg = c.String("commit-message")
33 | 	}
34 | 	if c.Bool("interactive-commit") {
35 | 		commitMsg = ""
36 | 	}
37 | 	ctx = ctxutil.WithCommitMessage(ctx, commitMsg)
38 | 
39 | 	if err := s.Store.Move(ctx, from, to); err != nil {
40 | 		return exit.Error(exit.Unknown, err, "%s", err)
41 | 	}
42 | 
43 | 	return nil
44 | }
45 | 


--------------------------------------------------------------------------------
/internal/action/move_test.go:
--------------------------------------------------------------------------------
 1 | package action
 2 | 
 3 | import (
 4 | 	"bytes"
 5 | 	"os"
 6 | 	"testing"
 7 | 
 8 | 	"github.com/gopasspw/gopass/internal/config"
 9 | 	"github.com/gopasspw/gopass/internal/out"
10 | 	"github.com/gopasspw/gopass/pkg/ctxutil"
11 | 	"github.com/gopasspw/gopass/tests/gptest"
12 | 	"github.com/stretchr/testify/require"
13 | )
14 | 
15 | func TestMove(t *testing.T) {
16 | 	u := gptest.NewUnitTester(t)
17 | 
18 | 	ctx := config.NewContextInMemory()
19 | 	ctx = ctxutil.WithAlwaysYes(ctx, true)
20 | 	ctx = ctxutil.WithInteractive(ctx, false)
21 | 
22 | 	act, err := newMock(ctx, u.StoreDir(""))
23 | 	require.NoError(t, err)
24 | 	require.NotNil(t, act)
25 | 	ctx = act.cfg.WithConfig(ctx)
26 | 
27 | 	buf := &bytes.Buffer{}
28 | 	out.Stdout = buf
29 | 	out.Stderr = buf
30 | 	defer func() {
31 | 		out.Stdout = os.Stdout
32 | 		out.Stderr = os.Stderr
33 | 	}()
34 | 
35 | 	t.Run("move foo to bar", func(t *testing.T) {
36 | 		defer buf.Reset()
37 | 		require.NoError(t, act.Move(gptest.CliCtx(ctx, t, "foo", "bar")))
38 | 	})
39 | }
40 | 


--------------------------------------------------------------------------------
/internal/action/process.go:
--------------------------------------------------------------------------------
 1 | package action
 2 | 
 3 | import (
 4 | 	"os"
 5 | 
 6 | 	"github.com/gopasspw/gopass/internal/action/exit"
 7 | 	"github.com/gopasspw/gopass/internal/out"
 8 | 	"github.com/gopasspw/gopass/internal/tpl"
 9 | 	"github.com/gopasspw/gopass/pkg/ctxutil"
10 | 	"github.com/urfave/cli/v2"
11 | )
12 | 
13 | // Process is a command to process a template and replace secrets contained in it.
14 | func (s *Action) Process(c *cli.Context) error {
15 | 	ctx := ctxutil.WithGlobalFlags(c)
16 | 	file := c.Args().First()
17 | 	if file == "" {
18 | 		return exit.Error(exit.Usage, nil, "Usage: %s process <FILE>", s.Name)
19 | 	}
20 | 
21 | 	buf, err := os.ReadFile(file)
22 | 	if err != nil {
23 | 		return exit.Error(exit.IO, err, "Failed to read file: %s", file)
24 | 	}
25 | 
26 | 	obuf, err := tpl.Execute(ctx, string(buf), file, nil, s.Store)
27 | 	if err != nil {
28 | 		return exit.Error(exit.IO, err, "Failed to process file: %s", file)
29 | 	}
30 | 
31 | 	out.Print(ctx, string(obuf))
32 | 
33 | 	return nil
34 | }
35 | 


--------------------------------------------------------------------------------
/internal/action/pwgen/commands_test.go:
--------------------------------------------------------------------------------
 1 | package pwgen
 2 | 
 3 | import (
 4 | 	"testing"
 5 | 
 6 | 	"github.com/gopasspw/gopass/tests/gptest"
 7 | 	"github.com/stretchr/testify/assert"
 8 | 	"github.com/urfave/cli/v2"
 9 | )
10 | 
11 | func testCommand(t *testing.T, cmd *cli.Command) {
12 | 	t.Helper()
13 | 
14 | 	if len(cmd.Subcommands) < 1 {
15 | 		assert.NotNil(t, cmd.Action, cmd.Name)
16 | 	}
17 | 
18 | 	assert.NotEmpty(t, cmd.Usage)
19 | 	assert.NotEmpty(t, cmd.Description)
20 | 
21 | 	for _, flag := range cmd.Flags {
22 | 		switch v := flag.(type) {
23 | 		case *cli.StringFlag:
24 | 			assert.NotContains(t, v.Name, ",")
25 | 			assert.NotEmpty(t, v.Usage)
26 | 		case *cli.BoolFlag:
27 | 			assert.NotContains(t, v.Name, ",")
28 | 			assert.NotEmpty(t, v.Usage)
29 | 		}
30 | 	}
31 | 
32 | 	for _, scmd := range cmd.Subcommands {
33 | 		testCommand(t, scmd)
34 | 	}
35 | }
36 | 
37 | func TestCommands(t *testing.T) {
38 | 	// necessary for setting up the env
39 | 	u := gptest.NewGUnitTester(t)
40 | 	assert.NotNil(t, u)
41 | 
42 | 	for _, cmd := range GetCommands() {
43 | 		testCommand(t, cmd)
44 | 	}
45 | }
46 | 


--------------------------------------------------------------------------------
/internal/action/pwgen/pwgen_test.go:
--------------------------------------------------------------------------------
 1 | package pwgen
 2 | 
 3 | import (
 4 | 	"bytes"
 5 | 	"os"
 6 | 	"testing"
 7 | 
 8 | 	"github.com/gopasspw/gopass/internal/config"
 9 | 	"github.com/gopasspw/gopass/internal/out"
10 | 	"github.com/gopasspw/gopass/pkg/ctxutil"
11 | 	"github.com/gopasspw/gopass/tests/gptest"
12 | 	"github.com/stretchr/testify/assert"
13 | 	"github.com/stretchr/testify/require"
14 | )
15 | 
16 | func TestPwgen(t *testing.T) {
17 | 	u := gptest.NewUnitTester(t)
18 | 	assert.NotNil(t, u)
19 | 
20 | 	ctx := config.NewContextInMemory()
21 | 	ctx = ctxutil.WithAlwaysYes(ctx, true)
22 | 
23 | 	buf := &bytes.Buffer{}
24 | 	out.Stdout = buf
25 | 	defer func() {
26 | 		out.Stdout = os.Stdout
27 | 	}()
28 | 
29 | 	require.NoError(t, Pwgen(gptest.CliCtxWithFlags(ctx, t, map[string]string{"one-per-line": "true"}, "24", "1")))
30 | 	assert.GreaterOrEqual(t, len(buf.Bytes()), 24, buf.String())
31 | }
32 | 


--------------------------------------------------------------------------------
/internal/action/sync_test.go:
--------------------------------------------------------------------------------
 1 | package action
 2 | 
 3 | import (
 4 | 	"bytes"
 5 | 	"os"
 6 | 	"testing"
 7 | 
 8 | 	"github.com/gopasspw/gopass/internal/config"
 9 | 	"github.com/gopasspw/gopass/internal/out"
10 | 	"github.com/gopasspw/gopass/pkg/ctxutil"
11 | 	"github.com/gopasspw/gopass/tests/gptest"
12 | 	"github.com/stretchr/testify/require"
13 | )
14 | 
15 | func TestSync(t *testing.T) {
16 | 	u := gptest.NewUnitTester(t)
17 | 
18 | 	buf := &bytes.Buffer{}
19 | 	out.Stdout = buf
20 | 	out.Stderr = buf
21 | 	defer func() {
22 | 		out.Stdout = os.Stdout
23 | 		out.Stderr = os.Stderr
24 | 	}()
25 | 
26 | 	ctx := config.NewContextInMemory()
27 | 	ctx = ctxutil.WithAlwaysYes(ctx, true)
28 | 	act, err := newMock(ctx, u.StoreDir(""))
29 | 	require.NoError(t, err)
30 | 	require.NotNil(t, act)
31 | 	ctx = act.cfg.WithConfig(ctx)
32 | 
33 | 	t.Run("default", func(t *testing.T) {
34 | 		defer buf.Reset()
35 | 		require.NoError(t, act.Sync(gptest.CliCtx(ctx, t)))
36 | 	})
37 | 
38 | 	t.Run("sync --store=root", func(t *testing.T) {
39 | 		defer buf.Reset()
40 | 		require.NoError(t, act.Sync(gptest.CliCtxWithFlags(ctx, t, map[string]string{"store": "root"})))
41 | 	})
42 | }
43 | 


--------------------------------------------------------------------------------
/internal/action/unclip.go:
--------------------------------------------------------------------------------
 1 | package action
 2 | 
 3 | import (
 4 | 	"os"
 5 | 	"time"
 6 | 
 7 | 	"github.com/gopasspw/gopass/internal/action/exit"
 8 | 	"github.com/gopasspw/gopass/internal/config"
 9 | 	"github.com/gopasspw/gopass/pkg/clipboard"
10 | 	"github.com/gopasspw/gopass/pkg/ctxutil"
11 | 	"github.com/urfave/cli/v2"
12 | )
13 | 
14 | // Unclip tries to erase the content of the clipboard.
15 | func (s *Action) Unclip(c *cli.Context) error {
16 | 	ctx := ctxutil.WithGlobalFlags(c)
17 | 	force := c.Bool("force")
18 | 	timeout := c.Int("timeout")
19 | 	name := os.Getenv("GOPASS_UNCLIP_NAME")
20 | 	checksum := os.Getenv("GOPASS_UNCLIP_CHECKSUM")
21 | 
22 | 	time.Sleep(time.Second * time.Duration(timeout))
23 | 
24 | 	mp := s.Store.MountPoint(name)
25 | 	ctx = config.WithMount(ctx, mp)
26 | 
27 | 	if err := clipboard.Clear(ctx, name, checksum, force); err != nil {
28 | 		return exit.Error(exit.IO, err, "Failed to clear clipboard: %s", err)
29 | 	}
30 | 
31 | 	return nil
32 | }
33 | 


--------------------------------------------------------------------------------
/internal/action/unclip_test.go:
--------------------------------------------------------------------------------
 1 | package action
 2 | 
 3 | import (
 4 | 	"bytes"
 5 | 	"os"
 6 | 	"testing"
 7 | 
 8 | 	_ "github.com/gopasspw/gopass/internal/backend/crypto"
 9 | 	_ "github.com/gopasspw/gopass/internal/backend/storage"
10 | 	"github.com/gopasspw/gopass/internal/config"
11 | 	"github.com/gopasspw/gopass/internal/out"
12 | 	"github.com/gopasspw/gopass/tests/gptest"
13 | 	"github.com/stretchr/testify/require"
14 | )
15 | 
16 | func TestUnclip(t *testing.T) {
17 | 	u := gptest.NewUnitTester(t)
18 | 
19 | 	buf := &bytes.Buffer{}
20 | 	out.Stdout = buf
21 | 	stdout = buf
22 | 	defer func() {
23 | 		out.Stdout = os.Stdout
24 | 		stdout = os.Stdout
25 | 	}()
26 | 
27 | 	ctx := config.NewContextInMemory()
28 | 	act, err := newMock(ctx, u.StoreDir(""))
29 | 	require.NoError(t, err)
30 | 	require.NotNil(t, act)
31 | 	ctx = act.cfg.WithConfig(ctx)
32 | 
33 | 	t.Run("unlcip should fail", func(t *testing.T) {
34 | 		require.Error(t, act.Unclip(gptest.CliCtxWithFlags(ctx, t, map[string]string{"timeout": "0"})))
35 | 	})
36 | }
37 | 


--------------------------------------------------------------------------------
/internal/action/update.go:
--------------------------------------------------------------------------------
 1 | package action
 2 | 
 3 | import (
 4 | 	"github.com/gopasspw/gopass/internal/action/exit"
 5 | 	"github.com/gopasspw/gopass/internal/out"
 6 | 	"github.com/gopasspw/gopass/internal/updater"
 7 | 	"github.com/gopasspw/gopass/pkg/ctxutil"
 8 | 	"github.com/urfave/cli/v2"
 9 | )
10 | 
11 | // Update will start the interactive update assistant.
12 | func (s *Action) Update(c *cli.Context) error {
13 | 	_ = s.rem.Reset("update")
14 | 
15 | 	ctx := ctxutil.WithGlobalFlags(c)
16 | 
17 | 	if s.version.String() == "0.0.0+HEAD" {
18 | 		out.Errorf(ctx, "Can not check version against HEAD")
19 | 
20 | 		return nil
21 | 	}
22 | 
23 | 	out.Printf(ctx, "⚒ Checking for available updates ...")
24 | 	if err := updater.Update(ctx, s.version); err != nil {
25 | 		return exit.Error(exit.Unknown, err, "Failed to update gopass: %s", err)
26 | 	}
27 | 
28 | 	out.OKf(ctx, "gopass is up to date")
29 | 
30 | 	return nil
31 | }
32 | 


--------------------------------------------------------------------------------
/internal/action/version_test.go:
--------------------------------------------------------------------------------
 1 | package action
 2 | 
 3 | import (
 4 | 	"bytes"
 5 | 	"os"
 6 | 	"testing"
 7 | 
 8 | 	_ "github.com/gopasspw/gopass/internal/backend/crypto"
 9 | 	_ "github.com/gopasspw/gopass/internal/backend/storage"
10 | 	"github.com/gopasspw/gopass/internal/config"
11 | 	"github.com/gopasspw/gopass/internal/out"
12 | 	"github.com/gopasspw/gopass/pkg/ctxutil"
13 | 	"github.com/gopasspw/gopass/tests/gptest"
14 | 	"github.com/stretchr/testify/require"
15 | 	"github.com/urfave/cli/v2"
16 | )
17 | 
18 | func TestVersion(t *testing.T) {
19 | 	u := gptest.NewUnitTester(t)
20 | 
21 | 	ctx := config.NewContextInMemory()
22 | 	ctx = ctxutil.WithAlwaysYes(ctx, true)
23 | 	ctx = ctxutil.WithInteractive(ctx, false)
24 | 
25 | 	act, err := newMock(ctx, u.StoreDir(""))
26 | 	require.NoError(t, err)
27 | 
28 | 	buf := &bytes.Buffer{}
29 | 	out.Stdout = buf
30 | 	stdout = buf
31 | 	defer func() {
32 | 		out.Stdout = os.Stdout
33 | 		stdout = os.Stdout
34 | 	}()
35 | 
36 | 	cli.VersionPrinter = func(*cli.Context) {
37 | 		out.Printf(ctx, "gopass version 0.0.0-test")
38 | 	}
39 | 
40 | 	t.Run("print fixed version", func(t *testing.T) {
41 | 		require.NoError(t, act.Version(gptest.CliCtx(ctx, t)))
42 | 	})
43 | }
44 | 


--------------------------------------------------------------------------------
/internal/audit/report_test.go:
--------------------------------------------------------------------------------
 1 | package audit
 2 | 
 3 | import (
 4 | 	"testing"
 5 | 
 6 | 	"github.com/stretchr/testify/assert"
 7 | )
 8 | 
 9 | func TestFinalize(t *testing.T) {
10 | 	r := newReport()
11 | 	r.AddPassword("foo", "bar")
12 | 	r.AddPassword("baz", "bar")
13 | 	r.AddPassword("zab", "bar")
14 | 	r.AddPassword("foo", "bar")
15 | 	r.AddFinding("foo", "foo", "bar", "warning")
16 | 	r.AddFinding("bar", "foo", "bar", "warning")
17 | 
18 | 	sr := r.Finalize()
19 | 	assert.NotNil(t, sr)
20 | }
21 | 


--------------------------------------------------------------------------------
/internal/audit/single.go:
--------------------------------------------------------------------------------
 1 | package audit
 2 | 
 3 | import (
 4 | 	"context"
 5 | 	"fmt"
 6 | 
 7 | 	"github.com/gopasspw/gopass/internal/out"
 8 | 	"github.com/muesli/crunchy"
 9 | )
10 | 
11 | // Single runs a password strength audit on a single password.
12 | func Single(ctx context.Context, password string) {
13 | 	validator := crunchy.NewValidator()
14 | 	if err := validator.Check(password); err != nil {
15 | 		out.Printf(ctx, fmt.Sprintf("Warning: %s", err))
16 | 	}
17 | }
18 | 


--------------------------------------------------------------------------------
/internal/backend/context_test.go:
--------------------------------------------------------------------------------
 1 | package backend
 2 | 
 3 | import (
 4 | 	"testing"
 5 | 
 6 | 	"github.com/gopasspw/gopass/internal/config"
 7 | 	"github.com/stretchr/testify/assert"
 8 | )
 9 | 
10 | func TestCryptoBackend(t *testing.T) {
11 | 	t.Parallel()
12 | 
13 | 	ctx := config.NewContextInMemory()
14 | 
15 | 	assert.Equal(t, GPGCLI, GetCryptoBackend(ctx))
16 | 	assert.Equal(t, GPGCLI, GetCryptoBackend(WithCryptoBackendString(ctx, "gpgcli")))
17 | 	assert.Equal(t, GPGCLI, GetCryptoBackend(WithCryptoBackend(ctx, GPGCLI)))
18 | 	assert.True(t, HasCryptoBackend(WithCryptoBackend(ctx, GPGCLI)))
19 | }
20 | 
21 | func TestStorageBackend(t *testing.T) {
22 | 	t.Parallel()
23 | 
24 | 	ctx := config.NewContextInMemory()
25 | 
26 | 	assert.Equal(t, "fs", StorageBackendName(FS))
27 | 	assert.Equal(t, FS, GetStorageBackend(ctx))
28 | 	assert.Equal(t, FS, GetStorageBackend(WithStorageBackendString(ctx, "fs")))
29 | 	assert.Equal(t, FS, GetStorageBackend(WithStorageBackend(ctx, FS)))
30 | 	assert.True(t, HasStorageBackend(WithStorageBackend(ctx, FS)))
31 | }
32 | 
33 | func TestComposite(t *testing.T) {
34 | 	t.Parallel()
35 | 
36 | 	ctx := config.NewContextInMemory()
37 | 	ctx = WithCryptoBackend(ctx, Age)
38 | 	ctx = WithStorageBackend(ctx, FS)
39 | 
40 | 	assert.Equal(t, Age, GetCryptoBackend(ctx))
41 | 	assert.Equal(t, FS, GetStorageBackend(ctx))
42 | }
43 | 


--------------------------------------------------------------------------------
/internal/backend/crypto/age.go:
--------------------------------------------------------------------------------
1 | package crypto
2 | 
3 | import _ "github.com/gopasspw/gopass/internal/backend/crypto/age" // registers age backend
4 | 


--------------------------------------------------------------------------------
/internal/backend/crypto/age/agent/client_unix.go:
--------------------------------------------------------------------------------
 1 | //go:build !windows
 2 | 
 3 | package agent
 4 | 
 5 | import (
 6 | 	"fmt"
 7 | 	"os"
 8 | 	"syscall"
 9 | )
10 | 
11 | func (c *Client) checkSocketSecurity() error {
12 | 	info, err := os.Stat(c.socketPath)
13 | 	if err != nil {
14 | 		return fmt.Errorf("failed to stat socket: %w", err)
15 | 	}
16 | 
17 | 	// Check socket permissions.
18 | 	if info.Mode()&os.ModePerm != 0o600 {
19 | 		return fmt.Errorf("incorrect socket permissions: %v", info.Mode().Perm())
20 | 	}
21 | 
22 | 	// Check socket ownership.
23 | 	stat, ok := info.Sys().(*syscall.Stat_t)
24 | 	if !ok {
25 | 		return fmt.Errorf("failed to get socket system info")
26 | 	}
27 | 
28 | 	if stat.Uid != uint32(os.Getuid()) {
29 | 		return fmt.Errorf("socket owned by wrong user: %d", stat.Uid)
30 | 	}
31 | 
32 | 	return nil
33 | }
34 | 


--------------------------------------------------------------------------------
/internal/backend/crypto/age/agent/client_windows.go:
--------------------------------------------------------------------------------
1 | //go:build windows
2 | 
3 | package agent
4 | 
5 | func (c *Client) checkSocketSecurity() error {
6 | 	return nil
7 | }
8 | 


--------------------------------------------------------------------------------
/internal/backend/crypto/age/agent_starter_unix.go:
--------------------------------------------------------------------------------
 1 | //go:build !windows
 2 | 
 3 | package age
 4 | 
 5 | import (
 6 | 	"context"
 7 | 	"os"
 8 | 	"os/exec"
 9 | 	"syscall"
10 | )
11 | 
12 | func startAgent(_ context.Context) error {
13 | 	cmd := exec.Command(os.Args[0], "age", "agent", "start")
14 | 	cmd.Env = os.Environ()
15 | 	cmd.Stderr = os.Stderr
16 | 	cmd.SysProcAttr = &syscall.SysProcAttr{
17 | 		Setpgid: true,
18 | 	}
19 | 
20 | 	return cmd.Start()
21 | }
22 | 


--------------------------------------------------------------------------------
/internal/backend/crypto/age/agent_starter_windows.go:
--------------------------------------------------------------------------------
 1 | //go:build windows
 2 | 
 3 | package age
 4 | 
 5 | import (
 6 | 	"context"
 7 | 	"os"
 8 | 	"os/exec"
 9 | )
10 | 
11 | func startAgent(_ context.Context) error {
12 | 	cmd := exec.Command(os.Args[0], "age", "agent", "start")
13 | 	cmd.Env = os.Environ()
14 | 	cmd.Stderr = os.Stderr
15 | 	return cmd.Start()
16 | }
17 | 


--------------------------------------------------------------------------------
/internal/backend/crypto/age/clientUI.go:
--------------------------------------------------------------------------------
 1 | package age
 2 | 
 3 | import (
 4 | 	"context"
 5 | 
 6 | 	"filippo.io/age/plugin"
 7 | 	"github.com/gopasspw/gopass/internal/cui"
 8 | 	"github.com/gopasspw/gopass/internal/out"
 9 | 	"github.com/gopasspw/gopass/pkg/termio"
10 | )
11 | 
12 | var pluginTerminalUI = &plugin.ClientUI{
13 | 	DisplayMessage: func(name, message string) error {
14 | 		out.Printf(context.Background(), "%s plugin: %s", name, message)
15 | 
16 | 		return nil
17 | 	},
18 | 	RequestValue: func(name, message string, _ bool) (string, error) {
19 | 		var err error
20 | 		defer func() {
21 | 			if err != nil {
22 | 				out.Warningf(context.Background(), "could not read value for age-plugin-%s: %v", name, err)
23 | 			}
24 | 		}()
25 | 		secret, err := termio.AskForPassword(context.Background(), "secret", false)
26 | 		if err != nil {
27 | 			return "", err
28 | 		}
29 | 
30 | 		return secret, nil
31 | 	},
32 | 	Confirm: func(name, message, yes, no string) (bool, error) {
33 | 		rep, _ := cui.GetSelection(context.Background(), message, []string{yes, no})
34 | 		if rep == yes {
35 | 			return true, nil
36 | 		}
37 | 
38 | 		return false, nil
39 | 	},
40 | 
41 | 	WaitTimer: func(name string) {
42 | 		out.Printf(context.Background(), "waiting on %s plugin...", name)
43 | 	},
44 | }
45 | 


--------------------------------------------------------------------------------
/internal/backend/crypto/age/context.go:
--------------------------------------------------------------------------------
 1 | package age
 2 | 
 3 | import "context"
 4 | 
 5 | type contextKey int
 6 | 
 7 | const (
 8 | 	ctxKeyOnlyNative contextKey = iota
 9 | )
10 | 
11 | // WithOnlyNative will return a context with the flag for only native set.
12 | func WithOnlyNative(ctx context.Context, at bool) context.Context {
13 | 	return context.WithValue(ctx, ctxKeyOnlyNative, at)
14 | }
15 | 
16 | // IsOnlyNative will return the value of the only native flag or the default
17 | // (false).
18 | func IsOnlyNative(ctx context.Context) bool {
19 | 	bv, ok := ctx.Value(ctxKeyOnlyNative).(bool)
20 | 	if !ok {
21 | 		return false
22 | 	}
23 | 
24 | 	return bv
25 | }
26 | 


--------------------------------------------------------------------------------
/internal/backend/crypto/age/context_test.go:
--------------------------------------------------------------------------------
 1 | package age
 2 | 
 3 | import (
 4 | 	"testing"
 5 | )
 6 | 
 7 | func TestWithOnlyNative(t *testing.T) {
 8 | 	ctx := t.Context()
 9 | 	ctx = WithOnlyNative(ctx, true)
10 | 
11 | 	val := ctx.Value(ctxKeyOnlyNative)
12 | 	if val == nil {
13 | 		t.Errorf("Expected value to be set, got nil")
14 | 	}
15 | 
16 | 	boolVal, ok := val.(bool)
17 | 	if !ok {
18 | 		t.Errorf("Expected value to be of type bool, got %T", val)
19 | 	}
20 | 
21 | 	if !boolVal {
22 | 		t.Errorf("Expected value to be true, got false")
23 | 	}
24 | }
25 | 
26 | func TestIsOnlyNative(t *testing.T) {
27 | 	ctx := t.Context()
28 | 
29 | 	// Test default value
30 | 	if IsOnlyNative(ctx) {
31 | 		t.Errorf("Expected default value to be false, got true")
32 | 	}
33 | 
34 | 	// Test set value
35 | 	ctx = WithOnlyNative(ctx, true)
36 | 	if !IsOnlyNative(ctx) {
37 | 		t.Errorf("Expected value to be true, got false")
38 | 	}
39 | 
40 | 	// Test reset value
41 | 	ctx = WithOnlyNative(ctx, false)
42 | 	if IsOnlyNative(ctx) {
43 | 		t.Errorf("Expected value to be false, got true")
44 | 	}
45 | }
46 | 


--------------------------------------------------------------------------------
/internal/backend/crypto/age/encrypt_test.go:
--------------------------------------------------------------------------------
 1 | package age
 2 | 
 3 | import (
 4 | 	"crypto/ed25519"
 5 | 	"fmt"
 6 | 	"sort"
 7 | 	"testing"
 8 | 
 9 | 	"filippo.io/age"
10 | 	"filippo.io/age/agessh"
11 | 	"github.com/stretchr/testify/assert"
12 | 	"github.com/stretchr/testify/require"
13 | 	"golang.org/x/crypto/ssh"
14 | )
15 | 
16 | func TestDedupe(t *testing.T) {
17 | 	t.Parallel()
18 | 
19 | 	i1, err := age.GenerateX25519Identity()
20 | 	require.NoError(t, err)
21 | 
22 | 	i2, err := age.GenerateX25519Identity()
23 | 	require.NoError(t, err)
24 | 
25 | 	i3pub, _, err := ed25519.GenerateKey(nil)
26 | 	require.NoError(t, err)
27 | 	i3ssh, err := ssh.NewPublicKey(i3pub)
28 | 	require.NoError(t, err)
29 | 	i3, err := agessh.NewEd25519Recipient(i3ssh)
30 | 	require.NoError(t, err)
31 | 
32 | 	in := []age.Recipient{i1.Recipient(), i2.Recipient(), i2.Recipient(), i3, i3}
33 | 	out := dedupe(in)
34 | 	want := []age.Recipient{i3, i3, i1.Recipient(), i2.Recipient()}
35 | 
36 | 	sort.Sort(Recipients(out))
37 | 	sort.Sort(Recipients(want))
38 | 	assert.Equal(t, want, out)
39 | }
40 | 
41 | type Recipients []age.Recipient
42 | 
43 | func (r Recipients) Len() int {
44 | 	return len(r)
45 | }
46 | 
47 | func (r Recipients) Swap(i, j int) {
48 | 	r[i], r[j] = r[j], r[i]
49 | }
50 | 
51 | func (r Recipients) Less(i, j int) bool {
52 | 	return fmt.Sprintf("%s", r[i]) < fmt.Sprintf("%s", r[j])
53 | }
54 | 


--------------------------------------------------------------------------------
/internal/backend/crypto/age/unsupported.go:
--------------------------------------------------------------------------------
 1 | package age
 2 | 
 3 | import (
 4 | 	"context"
 5 | 	"fmt"
 6 | )
 7 | 
 8 | // FormatKey returns the key id.
 9 | func (a *Age) FormatKey(ctx context.Context, id, tpl string) string {
10 | 	return id
11 | }
12 | 
13 | // Fingerprint returns the id.
14 | func (a *Age) Fingerprint(ctx context.Context, id string) string {
15 | 	return id
16 | }
17 | 
18 | // ListRecipients is not supported for the age backend.
19 | func (a *Age) ListRecipients(context.Context) ([]string, error) {
20 | 	return nil, fmt.Errorf("not implemented")
21 | }
22 | 
23 | // ReadNamesFromKey is not supported for the age backend.
24 | func (a *Age) ReadNamesFromKey(ctx context.Context, buf []byte) ([]string, error) {
25 | 	return nil, fmt.Errorf("not implemented")
26 | }
27 | 
28 | // RecipientIDs is not supported for the age backend.
29 | func (a *Age) RecipientIDs(ctx context.Context, buf []byte) ([]string, error) {
30 | 	return nil, fmt.Errorf("reading recipient IDs is not supported by the age backend by design")
31 | }
32 | 


--------------------------------------------------------------------------------
/internal/backend/crypto/doc.go:
--------------------------------------------------------------------------------
1 | // Package crypto provides a pluggable crypto backend for gopass.
2 | 
3 | package crypto
4 | 


--------------------------------------------------------------------------------
/internal/backend/crypto/gpg/cli/decrypt.go:
--------------------------------------------------------------------------------
 1 | package cli
 2 | 
 3 | import (
 4 | 	"bytes"
 5 | 	"context"
 6 | 	"io"
 7 | 	"os"
 8 | 	"os/exec"
 9 | 
10 | 	"github.com/gopasspw/gopass/pkg/debug"
11 | )
12 | 
13 | // Decrypt will try to decrypt the given file.
14 | func (g *GPG) Decrypt(ctx context.Context, ciphertext []byte) ([]byte, error) {
15 | 	ctx, cancel := context.WithTimeout(ctx, Timeout)
16 | 	defer cancel()
17 | 
18 | 	args := append(g.args, "--decrypt")
19 | 	// Useful information may appear there
20 | 	if debug.IsEnabled() {
21 | 		args = append(args, "--verbose", "--verbose")
22 | 	}
23 | 	cmd := exec.CommandContext(ctx, g.binary, args...)
24 | 	cmd.Stdin = bytes.NewReader(ciphertext)
25 | 	// If gopass-jsonapi is used, there is no way to reach this os.Stderr, so
26 | 	// we write this stderr to the log file as well.
27 | 	cmd.Stderr = io.MultiWriter(os.Stderr, debug.LogWriter)
28 | 
29 | 	debug.V(1).Log("Running %s %+v", cmd.Path, cmd.Args)
30 | 	stdout, err := cmd.Output()
31 | 	if err != nil {
32 | 		debug.Log("GPG decrypt failed: %s %+v: %+v", cmd.Path, cmd.Args, err)
33 | 	}
34 | 
35 | 	return stdout, err
36 | }
37 | 


--------------------------------------------------------------------------------
/internal/backend/crypto/gpg/cli/gpg_others_test.go:
--------------------------------------------------------------------------------
 1 | //go:build !windows
 2 | // +build !windows
 3 | 
 4 | package cli
 5 | 
 6 | import (
 7 | 	"testing"
 8 | 
 9 | 	"github.com/gopasspw/gopass/internal/config"
10 | 	"github.com/stretchr/testify/require"
11 | )
12 | 
13 | func TestEncrypt(t *testing.T) {
14 | 	t.Parallel()
15 | 
16 | 	ctx := config.NewContextInMemory()
17 | 
18 | 	g := &GPG{}
19 | 	g.binary = "true"
20 | 
21 | 	_, err := g.Encrypt(ctx, []byte("foo"), nil)
22 | 	// No recipients are configured so it will fail
23 | 	require.Error(t, err)
24 | }
25 | 
26 | func TestDecrypt(t *testing.T) {
27 | 	t.Parallel()
28 | 
29 | 	ctx := config.NewContextInMemory()
30 | 
31 | 	g := &GPG{}
32 | 	g.binary = "true"
33 | 
34 | 	_, err := g.Decrypt(ctx, []byte("foo"))
35 | 	require.NoError(t, err)
36 | }
37 | 
38 | func TestGenerateIdentity(t *testing.T) {
39 | 	t.Parallel()
40 | 
41 | 	ctx := config.NewContextInMemory()
42 | 
43 | 	g := &GPG{}
44 | 	g.binary = "true"
45 | 
46 | 	_, err := g.GenerateIdentity(ctx, "foo", "foo@bar.com", "bar")
47 | 	require.NoError(t, err)
48 | }
49 | 


--------------------------------------------------------------------------------
/internal/backend/crypto/gpg/cli/gpg_test.go:
--------------------------------------------------------------------------------
 1 | package cli
 2 | 
 3 | import (
 4 | 	"testing"
 5 | 
 6 | 	"github.com/gopasspw/gopass/internal/config"
 7 | 	"github.com/stretchr/testify/assert"
 8 | 	"github.com/stretchr/testify/require"
 9 | )
10 | 
11 | func TestGPG(t *testing.T) {
12 | 	if testing.Short() {
13 | 		t.Skip("skipping test in short mode.")
14 | 	}
15 | 
16 | 	td := t.TempDir()
17 | 	t.Setenv("GNUPGHOME", td)
18 | 
19 | 	ctx := config.NewContextInMemory()
20 | 
21 | 	var err error
22 | 	var g *GPG
23 | 
24 | 	assert.Empty(t, g.Binary())
25 | 
26 | 	g, err = New(ctx, Config{})
27 | 	require.NoError(t, err)
28 | 	assert.NotEmpty(t, g.Binary())
29 | 
30 | 	_, err = g.ListRecipients(ctx)
31 | 	require.NoError(t, err)
32 | 
33 | 	_, err = g.ListIdentities(ctx)
34 | 	require.NoError(t, err)
35 | 
36 | 	_, err = g.RecipientIDs(ctx, []byte{})
37 | 	require.Error(t, err)
38 | 
39 | 	require.NoError(t, g.Initialized(ctx))
40 | 	assert.Equal(t, "gpg", g.Name())
41 | 	assert.Equal(t, "gpg", g.Ext())
42 | 	assert.Equal(t, ".gpg-id", g.IDFile())
43 | }
44 | 


--------------------------------------------------------------------------------
/internal/backend/crypto/gpg/cli/gpg_windows_test.go:
--------------------------------------------------------------------------------
 1 | package cli
 2 | 
 3 | import (
 4 | 	"context"
 5 | 	"testing"
 6 | 
 7 | 	"github.com/gopasspw/gopass/internal/config"
 8 | 	"github.com/stretchr/testify/require"
 9 | )
10 | 
11 | func TestEncrypt(t *testing.T) {
12 | 	t.Parallel()
13 | 
14 | 	ctx, cancel := context.WithCancel(config.NewContextInMemory())
15 | 
16 | 	g := &GPG{}
17 | 	g.binary = "rundll32"
18 | 
19 | 	_, err := g.Encrypt(ctx, []byte("foo"), nil)
20 | 
21 | 	// No recipients are configured so it will fail
22 | 	require.Error(t, err)
23 | 	cancel()
24 | }
25 | 
26 | func TestDecrypt(t *testing.T) {
27 | 	t.Parallel()
28 | 
29 | 	ctx, cancel := context.WithCancel(config.NewContextInMemory())
30 | 
31 | 	g := &GPG{}
32 | 	g.binary = "rundll32"
33 | 
34 | 	_, err := g.Decrypt(ctx, []byte("foo"))
35 | 	require.NoError(t, err)
36 | 	cancel()
37 | }
38 | 
39 | func TestGenerateIdentity(t *testing.T) {
40 | 	t.Parallel()
41 | 
42 | 	ctx, cancel := context.WithCancel(config.NewContextInMemory())
43 | 
44 | 	g := &GPG{}
45 | 	g.binary = "rundll32"
46 | 
47 | 	_, err := g.GenerateIdentity(ctx, "foo", "foo@bar.com", "bar")
48 | 	require.NoError(t, err)
49 | 	cancel()
50 | }
51 | 


--------------------------------------------------------------------------------
/internal/backend/crypto/gpg/cli/loader.go:
--------------------------------------------------------------------------------
 1 | package cli
 2 | 
 3 | import (
 4 | 	"context"
 5 | 	"fmt"
 6 | 	"os"
 7 | 
 8 | 	"github.com/gopasspw/gopass/internal/backend"
 9 | 	"github.com/gopasspw/gopass/internal/backend/crypto/gpg/gpgconf"
10 | 	"github.com/gopasspw/gopass/pkg/debug"
11 | 	"github.com/gopasspw/gopass/pkg/fsutil"
12 | )
13 | 
14 | const (
15 | 	name = "gpgcli"
16 | )
17 | 
18 | func init() {
19 | 	backend.CryptoRegistry.Register(backend.GPGCLI, name, &loader{})
20 | }
21 | 
22 | type loader struct{}
23 | 
24 | // New implements backend.CryptoLoader.
25 | func (l loader) New(ctx context.Context) (backend.Crypto, error) {
26 | 	debug.Log("Using Crypto Backend: %s", name)
27 | 
28 | 	return New(ctx, Config{
29 | 		Umask:  fsutil.Umask(),
30 | 		Args:   gpgconf.GPGOpts(),
31 | 		Binary: os.Getenv("GOPASS_GPG_BINARY"),
32 | 	})
33 | }
34 | 
35 | func (l loader) Handles(ctx context.Context, s backend.Storage) error {
36 | 	if s.Exists(ctx, IDFile) {
37 | 		return nil
38 | 	}
39 | 
40 | 	return fmt.Errorf("not supported")
41 | }
42 | 
43 | func (l loader) Priority() int {
44 | 	return 1
45 | }
46 | 
47 | func (l loader) String() string {
48 | 	return name
49 | }
50 | 


--------------------------------------------------------------------------------
/internal/backend/crypto/gpg/cli/recipients_test.go:
--------------------------------------------------------------------------------
 1 | package cli
 2 | 
 3 | import (
 4 | 	"testing"
 5 | 
 6 | 	"github.com/stretchr/testify/assert"
 7 | )
 8 | 
 9 | func TestSplitPacket(t *testing.T) {
10 | 	t.Parallel()
11 | 
12 | 	for in, out := range map[string]map[string]string{
13 | 		"": {},
14 | 		":pubkey enc packet: version 3, algo 1, keyid 00F0FF00FFC00F0F": {
15 | 			"algo":    "1",
16 | 			"keyid":   "00F0FF00FFC00F0F",
17 | 			"version": "3",
18 | 		},
19 | 		":encrypted data packet:": {},
20 | 	} {
21 | 		assert.Equal(t, out, splitPacket(in))
22 | 	}
23 | }
24 | 


--------------------------------------------------------------------------------
/internal/backend/crypto/gpg/cli/version.go:
--------------------------------------------------------------------------------
 1 | package cli
 2 | 
 3 | import (
 4 | 	"context"
 5 | 
 6 | 	"github.com/blang/semver/v4"
 7 | 	"github.com/gopasspw/gopass/internal/backend/crypto/gpg/gpgconf"
 8 | )
 9 | 
10 | // Version will return GPG version information.
11 | func (g *GPG) Version(ctx context.Context) semver.Version {
12 | 	return gpgconf.Version(ctx, g.Binary())
13 | }
14 | 


--------------------------------------------------------------------------------
/internal/backend/crypto/gpg/colons/parse_fuzz.go:
--------------------------------------------------------------------------------
 1 | //go:build gofuzz
 2 | // +build gofuzz
 3 | 
 4 | package colons
 5 | 
 6 | import "bytes"
 7 | 
 8 | func Fuzz(data []byte) int {
 9 | 	if kl := Parse(bytes.NewReader(data)); len(kl) != 0 {
10 | 		return 1
11 | 	}
12 | 	return 0
13 | }
14 | 


--------------------------------------------------------------------------------
/internal/backend/crypto/gpg/colons/utils.go:
--------------------------------------------------------------------------------
 1 | package colons
 2 | 
 3 | import (
 4 | 	"strconv"
 5 | 	"time"
 6 | )
 7 | 
 8 | // parseTS parses the passed string as an Epoch int and returns
 9 | // the time struct or the zero time struct.
10 | func parseTS(str string) time.Time {
11 | 	t := time.Time{}
12 | 
13 | 	if sec, err := strconv.ParseInt(str, 10, 64); err == nil {
14 | 		t = time.Unix(sec, 0)
15 | 	}
16 | 
17 | 	return t
18 | }
19 | 
20 | // parseInt parses the passed string as an int and returns it
21 | // or 0 on errors.
22 | func parseInt(str string) int {
23 | 	i := 0
24 | 
25 | 	if iv, err := strconv.ParseInt(str, 10, 32); err == nil {
26 | 		i = int(iv)
27 | 	}
28 | 
29 | 	return i
30 | }
31 | 


--------------------------------------------------------------------------------
/internal/backend/crypto/gpg/context.go:
--------------------------------------------------------------------------------
 1 | package gpg
 2 | 
 3 | import "context"
 4 | 
 5 | type contextKey int
 6 | 
 7 | const (
 8 | 	ctxKeyAlwaysTrust contextKey = iota
 9 | 	ctxKeyUseCache
10 | )
11 | 
12 | // WithAlwaysTrust will return a context with the flag for always trust set.
13 | func WithAlwaysTrust(ctx context.Context, at bool) context.Context {
14 | 	return context.WithValue(ctx, ctxKeyAlwaysTrust, at)
15 | }
16 | 
17 | // IsAlwaysTrust will return the value of the always trust flag or the default
18 | // (false).
19 | func IsAlwaysTrust(ctx context.Context) bool {
20 | 	bv, ok := ctx.Value(ctxKeyAlwaysTrust).(bool)
21 | 	if !ok {
22 | 		return false
23 | 	}
24 | 
25 | 	return bv
26 | }
27 | 
28 | // WithUseCache returns a context with the value of NoCache set.
29 | func WithUseCache(ctx context.Context, nc bool) context.Context {
30 | 	return context.WithValue(ctx, ctxKeyUseCache, nc)
31 | }
32 | 
33 | // UseCache returns true if this request should ignore the cache.
34 | func UseCache(ctx context.Context) bool {
35 | 	nc, ok := ctx.Value(ctxKeyUseCache).(bool)
36 | 	if !ok {
37 | 		return false
38 | 	}
39 | 
40 | 	return nc
41 | }
42 | 


--------------------------------------------------------------------------------
/internal/backend/crypto/gpg/context_test.go:
--------------------------------------------------------------------------------
 1 | package gpg
 2 | 
 3 | import (
 4 | 	"testing"
 5 | 
 6 | 	"github.com/gopasspw/gopass/internal/config"
 7 | )
 8 | 
 9 | func TestAlwaysTrust(t *testing.T) {
10 | 	t.Parallel()
11 | 
12 | 	ctx := config.NewContextInMemory()
13 | 
14 | 	if IsAlwaysTrust(ctx) {
15 | 		t.Errorf("AlwaysTrust should be false")
16 | 	}
17 | 
18 | 	if !IsAlwaysTrust(WithAlwaysTrust(ctx, true)) {
19 | 		t.Errorf("AlwaysTrust should be true")
20 | 	}
21 | }
22 | 


--------------------------------------------------------------------------------
/internal/backend/crypto/gpg/doc.go:
--------------------------------------------------------------------------------
1 | // Package gpg provides a GPG crypto backend for gopass.
2 | // It does not provide a full GPG implementation, but rather
3 | // building blocks used by other packages to provide GPG
4 | // support.
5 | 
6 | package gpg
7 | 


--------------------------------------------------------------------------------
/internal/backend/crypto/gpg/gpgconf/binary.go:
--------------------------------------------------------------------------------
 1 | package gpgconf
 2 | 
 3 | import (
 4 | 	"context"
 5 | 	"os"
 6 | 
 7 | 	"github.com/gopasspw/gopass/pkg/debug"
 8 | )
 9 | 
10 | // Binary returns the GPG binary location.
11 | func Binary(ctx context.Context, bin string) (string, error) {
12 | 	if sv := os.Getenv("GOPASS_GPG_BINARY"); sv != "" {
13 | 		debug.Log("Using GOPASS_GPG_BINARY: %s", sv)
14 | 
15 | 		return sv, nil
16 | 	}
17 | 
18 | 	return detectBinary(ctx, bin)
19 | }
20 | 


--------------------------------------------------------------------------------
/internal/backend/crypto/gpg/gpgconf/binary_others.go:
--------------------------------------------------------------------------------
 1 | //go:build !windows
 2 | // +build !windows
 3 | 
 4 | package gpgconf
 5 | 
 6 | import (
 7 | 	"context"
 8 | 	"os/exec"
 9 | 
10 | 	"github.com/gopasspw/gopass/pkg/debug"
11 | 	"github.com/gopasspw/gopass/pkg/fsutil"
12 | )
13 | 
14 | func detectBinary(_ context.Context, name string) (string, error) {
15 | 	// user supplied binaries take precedence
16 | 	if name != "" {
17 | 		return exec.LookPath(name)
18 | 	}
19 | 
20 | 	// try to get the proper binary from gpgconf(1)
21 | 	p, err := Path("gpg")
22 | 	if err != nil || p == "" || !fsutil.IsFile(p) {
23 | 		debug.Log("gpgconf failed (%q), falling back to path lookup: %q", p, err)
24 | 		// otherwise fall back to the default and try
25 | 		// to look up "gpg"
26 | 		return exec.LookPath("gpg")
27 | 	}
28 | 
29 | 	debug.V(3).Log("gpgconf returned %q for gpg", p)
30 | 
31 | 	return p, nil
32 | }
33 | 


--------------------------------------------------------------------------------
/internal/backend/crypto/gpg/gpgconf/binary_windows_test.go:
--------------------------------------------------------------------------------
 1 | package gpgconf
 2 | 
 3 | import (
 4 | 	"path/filepath"
 5 | 	"testing"
 6 | 
 7 | 	"github.com/stretchr/testify/assert"
 8 | 	"github.com/stretchr/testify/require"
 9 | )
10 | 
11 | func TestDetectBinaryCandidates(t *testing.T) {
12 | 	bins, err := detectBinaryCandidates("foobar")
13 | 	require.NoError(t, err)
14 | 	// the install locations differ depending on :
15 | 	// - chocolatey install path prefix
16 | 	// - 64bit/32bit windows
17 | 	var stripped []string
18 | 	for _, bin := range bins {
19 | 		stripped = append(stripped, filepath.Base(bin))
20 | 	}
21 | 	assert.Contains(t, stripped, "gpg.exe")
22 | }
23 | 


--------------------------------------------------------------------------------
/internal/backend/crypto/gpg/gpgconf/gpgconf.go:
--------------------------------------------------------------------------------
 1 | package gpgconf
 2 | 
 3 | import (
 4 | 	"bufio"
 5 | 	"bytes"
 6 | 	"os"
 7 | 	"os/exec"
 8 | 	"strings"
 9 | )
10 | 
11 | // Path returns the path to a GPG component.
12 | func Path(key string) (string, error) {
13 | 	buf := &bytes.Buffer{}
14 | 	cmd := exec.Command("gpgconf")
15 | 	cmd.Stdout = buf
16 | 	cmd.Stderr = os.Stderr
17 | 
18 | 	if err := cmd.Run(); err != nil {
19 | 		return "", err
20 | 	}
21 | 
22 | 	key = strings.TrimSpace(strings.ToLower(key))
23 | 	sc := bufio.NewScanner(buf)
24 | 	for sc.Scan() {
25 | 		p := strings.Split(strings.TrimSpace(sc.Text()), ":")
26 | 		if len(p) < 3 {
27 | 			continue
28 | 		}
29 | 		if key == p[0] {
30 | 			return p[2], nil
31 | 		}
32 | 	}
33 | 
34 | 	return "", nil
35 | }
36 | 


--------------------------------------------------------------------------------
/internal/backend/crypto/gpg/gpgconf/utils_linux.go:
--------------------------------------------------------------------------------
 1 | //go:build linux
 2 | // +build linux
 3 | 
 4 | package gpgconf
 5 | 
 6 | import (
 7 | 	"os"
 8 | 	"syscall"
 9 | )
10 | 
11 | var fd0 = "/proc/self/fd/0"
12 | 
13 | // TTY returns the tty of the current process.
14 | // see https://www.gnupg.org/documentation/manuals/gnupg/Invoking-GPG_002dAGENT.html
15 | func TTY() string {
16 | 	dest, err := os.Readlink(fd0)
17 | 	if err != nil {
18 | 		return ""
19 | 	}
20 | 
21 | 	return dest
22 | }
23 | 
24 | // Umask sets the desired umask.
25 | func Umask(mask int) int {
26 | 	return syscall.Umask(mask)
27 | }
28 | 


--------------------------------------------------------------------------------
/internal/backend/crypto/gpg/gpgconf/utils_linux_test.go:
--------------------------------------------------------------------------------
 1 | //go:build linux
 2 | // +build linux
 3 | 
 4 | package gpgconf
 5 | 
 6 | import (
 7 | 	"testing"
 8 | 
 9 | 	"github.com/stretchr/testify/assert"
10 | )
11 | 
12 | func TestTTY(t *testing.T) {
13 | 	t.Parallel()
14 | 
15 | 	fd0 = "/tmp/foobar"
16 | 	assert.Empty(t, TTY())
17 | }
18 | 


--------------------------------------------------------------------------------
/internal/backend/crypto/gpg/gpgconf/utils_others.go:
--------------------------------------------------------------------------------
 1 | //go:build !linux && !windows
 2 | // +build !linux,!windows
 3 | 
 4 | package gpgconf
 5 | 
 6 | import (
 7 | 	"os"
 8 | 	"os/exec"
 9 | 	"syscall"
10 | )
11 | 
12 | func TTY() string {
13 | 	cmd := exec.Command("/usr/bin/tty")
14 | 	cmd.Stdin = os.Stdin
15 | 	out, err := cmd.Output()
16 | 	if err != nil {
17 | 		return ""
18 | 	}
19 | 
20 | 	return string(out)
21 | }
22 | 
23 | func Umask(mask int) int {
24 | 	return syscall.Umask(mask)
25 | }
26 | 


--------------------------------------------------------------------------------
/internal/backend/crypto/gpg/gpgconf/utils_windows.go:
--------------------------------------------------------------------------------
 1 | //go:build windows
 2 | // +build windows
 3 | 
 4 | package gpgconf
 5 | 
 6 | func TTY() string {
 7 | 	return ""
 8 | }
 9 | 
10 | func Umask(mask int) int {
11 | 	return -1
12 | }
13 | 


--------------------------------------------------------------------------------
/internal/backend/crypto/gpg/gpgconf/version.go:
--------------------------------------------------------------------------------
 1 | package gpgconf
 2 | 
 3 | import (
 4 | 	"context"
 5 | 	"os/exec"
 6 | 	"strings"
 7 | 
 8 | 	"github.com/blang/semver/v4"
 9 | )
10 | 
11 | type gpgBin struct {
12 | 	path string
13 | 	ver  semver.Version
14 | }
15 | 
16 | type byVersion []gpgBin
17 | 
18 | func (v byVersion) Len() int {
19 | 	return len(v)
20 | }
21 | 
22 | func (v byVersion) Swap(i, j int) {
23 | 	v[i], v[j] = v[j], v[i]
24 | }
25 | 
26 | func (v byVersion) Less(i, j int) bool {
27 | 	return v[i].ver.LT(v[j].ver)
28 | }
29 | 
30 | // Version return the version of the gpg binary.
31 | func Version(ctx context.Context, binary string) semver.Version {
32 | 	v := semver.Version{}
33 | 
34 | 	cmd := exec.CommandContext(ctx, binary, "--version")
35 | 	out, err := cmd.Output()
36 | 	if err != nil {
37 | 		return v
38 | 	}
39 | 
40 | 	for _, line := range strings.Split(string(out), "\n") {
41 | 		line = strings.TrimSpace(line)
42 | 		if !strings.HasPrefix(line, "gpg ") {
43 | 			continue
44 | 		}
45 | 
46 | 		p := strings.Fields(line)
47 | 		if len(p) < 1 {
48 | 			continue
49 | 		}
50 | 
51 | 		sv, err := semver.Parse(p[len(p)-1])
52 | 		if err != nil {
53 | 			continue
54 | 		}
55 | 
56 | 		return sv
57 | 	}
58 | 
59 | 	return v
60 | }
61 | 


--------------------------------------------------------------------------------
/internal/backend/crypto/gpg/gpgconf/version_test.go:
--------------------------------------------------------------------------------
 1 | package gpgconf
 2 | 
 3 | import (
 4 | 	"sort"
 5 | 	"testing"
 6 | 
 7 | 	"github.com/blang/semver/v4"
 8 | 	"github.com/stretchr/testify/require"
 9 | )
10 | 
11 | func TestSort(t *testing.T) {
12 | 	t.Parallel()
13 | 
14 | 	for _, tc := range []struct {
15 | 		name string
16 | 		in   []gpgBin
17 | 		out  []semver.Version
18 | 	}{
19 | 		{
20 | 			name: "simple",
21 | 			in: []gpgBin{
22 | 				{
23 | 					path: "/usr/local/bin/gpg",
24 | 					ver:  semver.MustParse("1.9.1"),
25 | 				},
26 | 				{
27 | 					path: "/usr/bin/gpg",
28 | 					ver:  semver.MustParse("2.4.0"),
29 | 				},
30 | 				{
31 | 					path: "/usr/local/bin/gpg2",
32 | 					ver:  semver.MustParse("2.1.11"),
33 | 				},
34 | 			},
35 | 			out: []semver.Version{
36 | 				semver.MustParse("1.9.1"),
37 | 				semver.MustParse("2.1.11"),
38 | 				semver.MustParse("2.4.0"),
39 | 			},
40 | 		},
41 | 	} {
42 | 		t.Run(tc.name, func(t *testing.T) {
43 | 			t.Parallel()
44 | 
45 | 			sort.Sort(byVersion(tc.in))
46 | 
47 | 			require.Len(t, tc.in, len(tc.out))
48 | 			for i, v := range tc.out {
49 | 				if !tc.in[i].ver.Equals(v) {
50 | 					t.Errorf("wrong sort order at %d: %s != %s", i, tc.in[i].ver, v)
51 | 				}
52 | 			}
53 | 		})
54 | 	}
55 | }
56 | 


--------------------------------------------------------------------------------
/internal/backend/crypto/gpg/identity.go:
--------------------------------------------------------------------------------
 1 | package gpg
 2 | 
 3 | import "time"
 4 | 
 5 | // Identity is a GPG identity, one key can have many IDs.
 6 | type Identity struct {
 7 | 	Name           string
 8 | 	Comment        string
 9 | 	Email          string
10 | 	CreationDate   time.Time
11 | 	ExpirationDate time.Time
12 | }
13 | 
14 | // ID returns the GPG ID format.
15 | func (i Identity) ID() string {
16 | 	out := i.Name
17 | 
18 | 	if i.Comment != "" {
19 | 		out += " (" + i.Comment + ")"
20 | 	}
21 | 
22 | 	out += " <" + i.Email + ">"
23 | 
24 | 	return out
25 | }
26 | 
27 | // String implement fmt.Stringer. This method resembles the output gpg uses
28 | // for user-ids.
29 | func (i Identity) String() string {
30 | 	return "uid                            " + i.ID()
31 | }
32 | 


--------------------------------------------------------------------------------
/internal/backend/crypto/gpg/identity_test.go:
--------------------------------------------------------------------------------
 1 | package gpg
 2 | 
 3 | import (
 4 | 	"testing"
 5 | 	"time"
 6 | 
 7 | 	"github.com/stretchr/testify/assert"
 8 | )
 9 | 
10 | func TestIdentity(t *testing.T) {
11 | 	t.Parallel()
12 | 
13 | 	id := Identity{
14 | 		Name:           "John Doe",
15 | 		Comment:        "johnny",
16 | 		Email:          "john.doe@example.org",
17 | 		CreationDate:   time.Now(),
18 | 		ExpirationDate: time.Now().Add(time.Hour),
19 | 	}
20 | 
21 | 	assert.Equal(t, "John Doe (johnny) <john.doe@example.org>", id.ID())
22 | 	assert.Equal(t, "uid                            "+id.ID(), id.String())
23 | }
24 | 


--------------------------------------------------------------------------------
/internal/backend/crypto/gpgcli.go:
--------------------------------------------------------------------------------
1 | package crypto
2 | 
3 | import _ "github.com/gopasspw/gopass/internal/backend/crypto/gpg/cli" // register gpg cli backend
4 | 


--------------------------------------------------------------------------------
/internal/backend/crypto/plain.go:
--------------------------------------------------------------------------------
1 | package crypto
2 | 
3 | import _ "github.com/gopasspw/gopass/internal/backend/crypto/plain" // register plaintext backend
4 | 


--------------------------------------------------------------------------------
/internal/backend/crypto/plain/loader.go:
--------------------------------------------------------------------------------
 1 | package plain
 2 | 
 3 | import (
 4 | 	"context"
 5 | 	"fmt"
 6 | 
 7 | 	"github.com/gopasspw/gopass/internal/backend"
 8 | 	"github.com/gopasspw/gopass/pkg/debug"
 9 | )
10 | 
11 | const (
12 | 	name = "plain"
13 | )
14 | 
15 | func init() {
16 | 	backend.CryptoRegistry.Register(backend.Plain, name, &loader{})
17 | }
18 | 
19 | type loader struct{}
20 | 
21 | // New implements backend.CryptoLoader.
22 | func (l loader) New(ctx context.Context) (backend.Crypto, error) {
23 | 	debug.Log("Using Crypto Backend: %s (NO ENCRYPTION)", name)
24 | 
25 | 	return New(), nil
26 | }
27 | 
28 | func (l loader) Handles(ctx context.Context, s backend.Storage) error {
29 | 	if s.Exists(ctx, IDFile) {
30 | 		return nil
31 | 	}
32 | 
33 | 	return fmt.Errorf("not supported")
34 | }
35 | 
36 | func (l loader) Priority() int {
37 | 	return 1000
38 | }
39 | 
40 | func (l loader) String() string {
41 | 	return name
42 | }
43 | 


--------------------------------------------------------------------------------
/internal/backend/crypto_test.go:
--------------------------------------------------------------------------------
 1 | package backend
 2 | 
 3 | import (
 4 | 	"os"
 5 | 	"path/filepath"
 6 | 	"testing"
 7 | 
 8 | 	"github.com/gopasspw/gopass/internal/config"
 9 | 	"github.com/stretchr/testify/assert"
10 | 	"github.com/stretchr/testify/require"
11 | )
12 | 
13 | func TestDetectCrypto(t *testing.T) {
14 | 	for _, tc := range []struct {
15 | 		name string
16 | 		file string
17 | 	}{
18 | 		{
19 | 			name: "plain",
20 | 			file: ".plain-id",
21 | 		},
22 | 		{
23 | 			name: "gpg",
24 | 			file: ".gpg-id",
25 | 		},
26 | 		{
27 | 			name: "age",
28 | 			file: ".age-recipients",
29 | 		},
30 | 	} {
31 | 		t.Run(tc.name, func(t *testing.T) {
32 | 			ctx := config.NewContextInMemory()
33 | 
34 | 			fsDir := filepath.Join(t.TempDir(), "fs")
35 | 			_ = os.RemoveAll(fsDir)
36 | 			require.NoError(t, os.MkdirAll(fsDir, 0o700))
37 | 			require.NoError(t, os.WriteFile(filepath.Join(fsDir, tc.file), []byte("foo"), 0o600))
38 | 
39 | 			r, err := DetectStorage(ctx, fsDir)
40 | 			require.NoError(t, err)
41 | 			assert.NotNil(t, r)
42 | 			assert.Equal(t, "fs", r.Name())
43 | 
44 | 			c, err := DetectCrypto(ctx, r)
45 | 			require.NoError(t, err, tc.name)
46 | 			require.NotNil(t, c, tc.name)
47 | 			assert.Equal(t, tc.name, c.Name())
48 | 		})
49 | 	}
50 | }
51 | 


--------------------------------------------------------------------------------
/internal/backend/doc.go:
--------------------------------------------------------------------------------
1 | // Package backend implements a registry to register differnet plugable backends for encryption and storage (incl. version control).
2 | // The actual backends are implemented in the subpackages. They register themselves in the registry with blank imports.
3 | package backend
4 | 


--------------------------------------------------------------------------------
/internal/backend/rcs_test.go:
--------------------------------------------------------------------------------
 1 | package backend
 2 | 
 3 | import (
 4 | 	"bytes"
 5 | 	"os"
 6 | 	"os/exec"
 7 | 	"path/filepath"
 8 | 	"testing"
 9 | 
10 | 	"github.com/gopasspw/gopass/internal/config"
11 | 	"github.com/gopasspw/gopass/internal/out"
12 | 	"github.com/stretchr/testify/assert"
13 | 	"github.com/stretchr/testify/require"
14 | )
15 | 
16 | func TestClone(t *testing.T) {
17 | 	ctx := config.NewContextInMemory()
18 | 
19 | 	td := t.TempDir()
20 | 
21 | 	repo := filepath.Join(td, "repo")
22 | 	require.NoError(t, os.MkdirAll(repo, 0o700))
23 | 
24 | 	store := filepath.Join(td, "store")
25 | 	require.NoError(t, os.MkdirAll(store, 0o700))
26 | 
27 | 	cmd := exec.Command("git", "init", repo)
28 | 	require.NoError(t, cmd.Run())
29 | 
30 | 	r, err := Clone(ctx, GitFS, repo, store)
31 | 	require.NoError(t, err)
32 | 	assert.NotNil(t, r)
33 | }
34 | 
35 | func TestInitRCS(t *testing.T) {
36 | 	ctx := config.NewContextInMemory()
37 | 
38 | 	td := t.TempDir()
39 | 
40 | 	buf := &bytes.Buffer{}
41 | 	out.Stdout = buf
42 | 	out.Stderr = buf
43 | 	defer func() {
44 | 		out.Stdout = os.Stdout
45 | 		out.Stderr = os.Stderr
46 | 	}()
47 | 
48 | 	gitDir := filepath.Join(td, "git")
49 | 	require.NoError(t, os.MkdirAll(filepath.Join(gitDir, ".git"), 0o700))
50 | 
51 | 	r, err := InitStorage(ctx, GitFS, gitDir)
52 | 	require.NoError(t, err)
53 | 	assert.NotNil(t, r)
54 | }
55 | 


--------------------------------------------------------------------------------
/internal/backend/storage/cryptfs.go:
--------------------------------------------------------------------------------
1 | package storage
2 | 
3 | import _ "github.com/gopasspw/gopass/internal/backend/storage/cryptfs"
4 | 


--------------------------------------------------------------------------------
/internal/backend/storage/doc.go:
--------------------------------------------------------------------------------
1 | // Package storage provides a pluggable storage backend for gopass.
2 | 
3 | package storage
4 | 


--------------------------------------------------------------------------------
/internal/backend/storage/fossilfs.go:
--------------------------------------------------------------------------------
1 | package storage
2 | 
3 | import _ "github.com/gopasspw/gopass/internal/backend/storage/fossilfs" // register fossilfs backend
4 | 


--------------------------------------------------------------------------------
/internal/backend/storage/fossilfs/context.go:
--------------------------------------------------------------------------------
 1 | package fossilfs
 2 | 
 3 | import "context"
 4 | 
 5 | type contextKey int
 6 | 
 7 | const (
 8 | 	ctxKeyPathOverride contextKey = iota
 9 | )
10 | 
11 | func withPathOverride(ctx context.Context, path string) context.Context {
12 | 	return context.WithValue(ctx, ctxKeyPathOverride, path)
13 | }
14 | 
15 | func getPathOverride(ctx context.Context, def string) string {
16 | 	if sv, ok := ctx.Value(ctxKeyPathOverride).(string); ok && sv != "" {
17 | 		return sv
18 | 	}
19 | 
20 | 	return def
21 | }
22 | 


--------------------------------------------------------------------------------
/internal/backend/storage/fossilfs/context_test.go:
--------------------------------------------------------------------------------
 1 | package fossilfs
 2 | 
 3 | import (
 4 | 	"testing"
 5 | )
 6 | 
 7 | func TestWithPathOverride(t *testing.T) {
 8 | 	ctx := t.Context()
 9 | 	path := "/test/path"
10 | 	ctx = withPathOverride(ctx, path)
11 | 
12 | 	if val, ok := ctx.Value(ctxKeyPathOverride).(string); !ok || val != path {
13 | 		t.Errorf("Expected path %s, but got %v", path, val)
14 | 	}
15 | }
16 | 
17 | func TestGetPathOverride(t *testing.T) {
18 | 	ctx := t.Context()
19 | 	defaultPath := "/default/path"
20 | 
21 | 	// Test with no override
22 | 	if path := getPathOverride(ctx, defaultPath); path != defaultPath {
23 | 		t.Errorf("Expected default path %s, but got %s", defaultPath, path)
24 | 	}
25 | 
26 | 	// Test with override
27 | 	overridePath := "/override/path"
28 | 	ctx = withPathOverride(ctx, overridePath)
29 | 	if path := getPathOverride(ctx, defaultPath); path != overridePath {
30 | 		t.Errorf("Expected override path %s, but got %s", overridePath, path)
31 | 	}
32 | }
33 | 


--------------------------------------------------------------------------------
/internal/backend/storage/fossilfs/loader.go:
--------------------------------------------------------------------------------
 1 | package fossilfs
 2 | 
 3 | import (
 4 | 	"context"
 5 | 	"fmt"
 6 | 	"path/filepath"
 7 | 
 8 | 	"github.com/gopasspw/gopass/internal/backend"
 9 | 	"github.com/gopasspw/gopass/pkg/fsutil"
10 | )
11 | 
12 | const (
13 | 	name = "fossilfs"
14 | )
15 | 
16 | func init() {
17 | 	backend.StorageRegistry.Register(backend.FossilFS, name, &loader{})
18 | }
19 | 
20 | type loader struct{}
21 | 
22 | func (l loader) New(ctx context.Context, path string) (backend.Storage, error) {
23 | 	return New(path)
24 | }
25 | 
26 | func (l loader) Open(ctx context.Context, path string) (backend.Storage, error) {
27 | 	return New(path)
28 | }
29 | 
30 | func (l loader) Clone(ctx context.Context, repo, path string) (backend.Storage, error) {
31 | 	return Clone(ctx, repo, path)
32 | }
33 | 
34 | func (l loader) Init(ctx context.Context, path string) (backend.Storage, error) {
35 | 	return Init(ctx, path, "", "")
36 | }
37 | 
38 | func (l loader) Handles(ctx context.Context, path string) error {
39 | 	path = fsutil.ExpandHomedir(path)
40 | 
41 | 	marker := filepath.Join(path, CheckoutMarker)
42 | 	if !fsutil.IsFile(marker) {
43 | 		return fmt.Errorf("no fossil checkout marker found at %s", marker)
44 | 	}
45 | 
46 | 	return nil
47 | }
48 | 
49 | func (l loader) Priority() int {
50 | 	return 12
51 | }
52 | 
53 | func (l loader) String() string {
54 | 	return name
55 | }
56 | 


--------------------------------------------------------------------------------
/internal/backend/storage/fs.go:
--------------------------------------------------------------------------------
1 | package storage
2 | 
3 | import _ "github.com/gopasspw/gopass/internal/backend/storage/fs" // register fs backend
4 | 


--------------------------------------------------------------------------------
/internal/backend/storage/fs/fsck_test.go:
--------------------------------------------------------------------------------
 1 | package fs
 2 | 
 3 | import (
 4 | 	"os"
 5 | 	"path/filepath"
 6 | 	"testing"
 7 | 
 8 | 	"github.com/gopasspw/gopass/internal/config"
 9 | 	"github.com/gopasspw/gopass/pkg/ctxutil"
10 | 	"github.com/stretchr/testify/require"
11 | )
12 | 
13 | func TestFsck(t *testing.T) {
14 | 	t.Parallel()
15 | 
16 | 	ctx := config.NewContextInMemory()
17 | 	ctx = ctxutil.WithHidden(ctx, true)
18 | 
19 | 	path := t.TempDir()
20 | 
21 | 	l := &loader{}
22 | 	s, err := l.Init(ctx, path)
23 | 	require.NoError(t, err)
24 | 	require.NoError(t, l.Handles(ctx, path))
25 | 
26 | 	for _, fn := range []string{
27 | 		filepath.Join(path, ".plain-ids"),
28 | 		filepath.Join(path, "foo", "bar"),
29 | 		filepath.Join(path, "foo", "zen"),
30 | 	} {
31 | 		require.NoError(t, os.MkdirAll(filepath.Dir(fn), 0o777))
32 | 		require.NoError(t, os.WriteFile(fn, []byte(fn), 0o663))
33 | 	}
34 | 
35 | 	require.NoError(t, s.Fsck(ctx))
36 | }
37 | 


--------------------------------------------------------------------------------
/internal/backend/storage/fs/link_test.go:
--------------------------------------------------------------------------------
 1 | package fs
 2 | 
 3 | import (
 4 | 	"testing"
 5 | 
 6 | 	"github.com/stretchr/testify/assert"
 7 | )
 8 | 
 9 | func TestLongestCommonPrefix(t *testing.T) {
10 | 	t.Parallel()
11 | 
12 | 	for _, tc := range []struct {
13 | 		Src    string
14 | 		Dst    string
15 | 		Prefix string
16 | 	}{
17 | 		{
18 | 			Src:    "foo/bar/baz/zab.txt",
19 | 			Dst:    "foo/baz/foo.txt",
20 | 			Prefix: "foo",
21 | 		},
22 | 	} {
23 | 		prefix := longestCommonPrefix(tc.Src, tc.Dst)
24 | 		assert.Equal(t, tc.Prefix, prefix)
25 | 	}
26 | }
27 | 
28 | func TestAddRel(t *testing.T) {
29 | 	t.Parallel()
30 | 
31 | 	for _, tc := range []struct {
32 | 		Src string
33 | 		Dst string
34 | 		Out string
35 | 	}{
36 | 		{
37 | 			Src: "bar/baz.txt",
38 | 			Dst: "baz/foo.txt",
39 | 			Out: "../bar/baz.txt",
40 | 		},
41 | 	} {
42 | 		assert.Equal(t, tc.Out, addRel(tc.Src, tc.Dst))
43 | 	}
44 | }
45 | 


--------------------------------------------------------------------------------
/internal/backend/storage/fs/rcs_test.go:
--------------------------------------------------------------------------------
 1 | package fs
 2 | 
 3 | import (
 4 | 	"testing"
 5 | 
 6 | 	"github.com/gopasspw/gopass/internal/config"
 7 | 	"github.com/stretchr/testify/assert"
 8 | 	"github.com/stretchr/testify/require"
 9 | )
10 | 
11 | func TestRCS(t *testing.T) {
12 | 	t.Parallel()
13 | 
14 | 	ctx := config.NewContextInMemory()
15 | 	path := t.TempDir()
16 | 
17 | 	g := New(path)
18 | 	// the fs backend does not support the RCS operations
19 | 	require.Error(t, g.Add(ctx, "foo", "bar"))
20 | 	require.Error(t, g.Commit(ctx, "foobar"))
21 | 	require.Error(t, g.Push(ctx, "foo", "bar"))
22 | 	require.Error(t, g.Pull(ctx, "foo", "bar"))
23 | 	require.NoError(t, g.Cmd(ctx, "foo", "bar"))
24 | 	require.Error(t, g.Init(ctx, "foo", "bar"))
25 | 	require.NoError(t, g.InitConfig(ctx, "foo", "bar"))
26 | 	assert.Equal(t, "fs", g.Name())
27 | 	require.Error(t, g.AddRemote(ctx, "foo", "bar"))
28 | 	revs, err := g.Revisions(ctx, "foo")
29 | 	require.Error(t, err)
30 | 	assert.Len(t, revs, 1)
31 | 	body, err := g.GetRevision(ctx, "foo", "latest")
32 | 	require.Error(t, err)
33 | 	assert.Empty(t, string(body))
34 | 	require.Error(t, g.RemoveRemote(ctx, "foo"))
35 | }
36 | 


--------------------------------------------------------------------------------
/internal/backend/storage/fs/store_others.go:
--------------------------------------------------------------------------------
 1 | //go:build !windows
 2 | // +build !windows
 3 | 
 4 | package fs
 5 | 
 6 | import (
 7 | 	"errors"
 8 | 	"os"
 9 | 	"syscall"
10 | )
11 | 
12 | func notEmptyErr(err error) bool {
13 | 	var perr *os.PathError
14 | 	if errors.As(err, &perr) {
15 | 		return errors.Is(perr.Err, syscall.ENOTEMPTY)
16 | 	}
17 | 
18 | 	return false
19 | }
20 | 


--------------------------------------------------------------------------------
/internal/backend/storage/fs/store_windows.go:
--------------------------------------------------------------------------------
 1 | package fs
 2 | 
 3 | import (
 4 | 	"os"
 5 | 	"syscall"
 6 | )
 7 | 
 8 | func notEmptyErr(err error) bool {
 9 | 	return err.(*os.PathError).Err == syscall.ERROR_DIR_NOT_EMPTY
10 | }
11 | 


--------------------------------------------------------------------------------
/internal/backend/storage/fs/walk.go:
--------------------------------------------------------------------------------
 1 | package fs
 2 | 
 3 | import (
 4 | 	"io/fs"
 5 | 	"os"
 6 | 	"path/filepath"
 7 | )
 8 | 
 9 | func walkSymlinks(path string, walkFn filepath.WalkFunc) error {
10 | 	return walk(path, path, walkFn)
11 | }
12 | 
13 | func walk(filename, linkDir string, walkFn filepath.WalkFunc) error {
14 | 	sWalkFn := func(path string, info fs.FileInfo, _ error) error {
15 | 		fname, err := filepath.Rel(filename, path)
16 | 		if err != nil {
17 | 			return err
18 | 		}
19 | 		path = filepath.Join(linkDir, fname)
20 | 
21 | 		// handle non-symlinks
22 | 		if info.Mode()&fs.ModeSymlink != fs.ModeSymlink {
23 | 			return walkFn(path, info, err)
24 | 		}
25 | 
26 | 		// handle symlinks
27 | 		destPath, err := filepath.EvalSymlinks(path)
28 | 		if err != nil {
29 | 			return err
30 | 		}
31 | 
32 | 		destInfo, err := os.Lstat(destPath)
33 | 		if err != nil {
34 | 			return walkFn(path, destInfo, err)
35 | 		}
36 | 
37 | 		if destInfo.IsDir() {
38 | 			return walk(destPath, path, walkFn)
39 | 		}
40 | 
41 | 		return walkFn(path, info, err)
42 | 	}
43 | 
44 | 	return filepath.Walk(filename, sWalkFn)
45 | }
46 | 


--------------------------------------------------------------------------------
/internal/backend/storage/gitfs.go:
--------------------------------------------------------------------------------
1 | package storage
2 | 
3 | import _ "github.com/gopasspw/gopass/internal/backend/storage/gitfs" // register gitfs backend
4 | 


--------------------------------------------------------------------------------
/internal/backend/storage/gitfs/config_test.go:
--------------------------------------------------------------------------------
 1 | package gitfs
 2 | 
 3 | import (
 4 | 	"bytes"
 5 | 	"os"
 6 | 	"path/filepath"
 7 | 	"testing"
 8 | 
 9 | 	"github.com/gopasspw/gopass/internal/config"
10 | 	"github.com/gopasspw/gopass/internal/out"
11 | 	"github.com/gopasspw/gopass/pkg/ctxutil"
12 | 	"github.com/stretchr/testify/assert"
13 | 	"github.com/stretchr/testify/require"
14 | )
15 | 
16 | func TestGitConfig(t *testing.T) {
17 | 	gitdir := filepath.Join(t.TempDir(), "git")
18 | 	require.NoError(t, os.Mkdir(gitdir, 0o755))
19 | 
20 | 	ctx := config.NewContextInMemory()
21 | 	ctx = ctxutil.WithAlwaysYes(ctx, true)
22 | 
23 | 	buf := &bytes.Buffer{}
24 | 	out.Stdout = buf
25 | 	defer func() {
26 | 		out.Stdout = os.Stdout
27 | 	}()
28 | 
29 | 	git, err := Init(ctx, gitdir, "Dead Beef", "dead.beef@example.org")
30 | 	require.NoError(t, err)
31 | 	un, err := git.ConfigGet(ctx, "user.name")
32 | 	require.NoError(t, err)
33 | 	assert.Equal(t, "Dead Beef", un)
34 | 
35 | 	require.NoError(t, git.InitConfig(ctx, "Foo Bar", "foo.bar@example.org"))
36 | 	un, err = git.ConfigGet(ctx, "user.name")
37 | 	require.NoError(t, err)
38 | 	assert.Equal(t, "Foo Bar", un)
39 | 
40 | 	require.NoError(t, git.ConfigSet(ctx, "user.name", "foo"))
41 | 	un, err = git.ConfigGet(ctx, "user.name")
42 | 	require.NoError(t, err)
43 | 	assert.Equal(t, "foo", un)
44 | }
45 | 


--------------------------------------------------------------------------------
/internal/backend/storage/gitfs/ssh_darwin.go:
--------------------------------------------------------------------------------
 1 | //go:build darwin
 2 | // +build darwin
 3 | 
 4 | package gitfs
 5 | 
 6 | // gitSSHCommand returns a SSH command instructing git to use SSH
 7 | // with persistent connections through a custom socket.
 8 | // See https://linux.die.net/man/5/ssh_config and
 9 | // https://git-scm.com/docs/git-config#Documentation/git-config.txt-coresshCommand
10 | //
11 | // Note: Setting GIT_SSH_COMMAND, possibly to an empty string, will take
12 | // precedence over this setting.
13 | //
14 | // %C is a hash of %l%h%p%r and should avoid "path too long for unix domain socket"
15 | // errors. On MacOS this doesn't always seem to work, so we're using a hardcoded
16 | // /tmp instead.
17 | func gitSSHCommand() string {
18 | 	return "ssh -oControlMaster=auto -oControlPersist=600 -oControlPath=/tmp/.ssh-%C"
19 | }
20 | 


--------------------------------------------------------------------------------
/internal/backend/storage/gitfs/ssh_others.go:
--------------------------------------------------------------------------------
 1 | //go:build !windows && !darwin
 2 | // +build !windows,!darwin
 3 | 
 4 | package gitfs
 5 | 
 6 | import "os"
 7 | 
 8 | // gitSSHCommand returns a SSH command instructing git to use SSH
 9 | // with persistent connections through a custom socket.
10 | // See https://linux.die.net/man/5/ssh_config and
11 | // https://git-scm.com/docs/git-config#Documentation/git-config.txt-coresshCommand
12 | //
13 | // Note: Setting GIT_SSH_COMMAND, possibly to an empty string, will take
14 | // precedence over this setting.
15 | //
16 | // %C is a hash of %l%h%p%r and should avoid "path too long for unix domain socket"
17 | // errors. If you still encounter this error set TMPDIR to a short path, e.g. /tmp.
18 | func gitSSHCommand() string {
19 | 	return "ssh -oControlMaster=auto -oControlPersist=600 -oControlPath=" + os.TempDir() + "/.ssh-%C"
20 | }
21 | 


--------------------------------------------------------------------------------
/internal/backend/storage/gitfs/ssh_windows.go:
--------------------------------------------------------------------------------
1 | //go:build windows
2 | // +build windows
3 | 
4 | package gitfs
5 | 
6 | func gitSSHCommand() string {
7 | 	return ""
8 | }
9 | 


--------------------------------------------------------------------------------
/internal/backend/storage/jjfs.go:
--------------------------------------------------------------------------------
1 | // Package storage registers the jjfs backend.
2 | package storage
3 | 
4 | import _ "github.com/gopasspw/gopass/internal/backend/storage/jjfs" // register jjfs backend
5 | 


--------------------------------------------------------------------------------
/internal/backend/storage/jjfs/loader.go:
--------------------------------------------------------------------------------
 1 | // Package jjfs implements a jj cli based RCS backend.
 2 | package jjfs
 3 | 
 4 | import (
 5 | 	"context"
 6 | 
 7 | 	"github.com/gopasspw/gopass/internal/backend"
 8 | 	"github.com/gopasspw/gopass/pkg/fsutil"
 9 | )
10 | 
11 | func init() {
12 | 	backend.StorageRegistry.Register(backend.JJFS, "jj", &loader{})
13 | }
14 | 
15 | type loader struct{}
16 | 
17 | func (l loader) String() string {
18 | 	return "jjfs"
19 | }
20 | 
21 | func (l loader) Priority() int {
22 | 	return 10
23 | }
24 | 
25 | func (l loader) New(ctx context.Context, path string) (backend.Storage, error) {
26 | 	return New(path)
27 | }
28 | 
29 | func (l loader) Init(ctx context.Context, path string) (backend.Storage, error) {
30 | 	return Init(ctx, path, "", "")
31 | }
32 | 
33 | func (l loader) Clone(ctx context.Context, repo, path string) (backend.Storage, error) {
34 | 	return nil, backend.ErrNotSupported
35 | }
36 | 
37 | func (l loader) Handles(ctx context.Context, path string) error {
38 | 	if fsutil.IsDir(path + "/.jj") {
39 | 		return nil
40 | 	}
41 | 
42 | 	return backend.ErrNotSupported
43 | }
44 | 


--------------------------------------------------------------------------------
/internal/backend/storage_test.go:
--------------------------------------------------------------------------------
 1 | package backend
 2 | 
 3 | import (
 4 | 	"os"
 5 | 	"path/filepath"
 6 | 	"testing"
 7 | 
 8 | 	"github.com/gopasspw/gopass/internal/config"
 9 | 	"github.com/gopasspw/gopass/pkg/ctxutil"
10 | 	"github.com/gopasspw/gopass/pkg/debug"
11 | 	"github.com/stretchr/testify/assert"
12 | 	"github.com/stretchr/testify/require"
13 | )
14 | 
15 | func TestDetectStorage(t *testing.T) {
16 | 	ctx := config.NewContextInMemory()
17 | 
18 | 	td := t.TempDir()
19 | 
20 | 	// all tests involving age should set GOPASS_HOMEDIR
21 | 	t.Setenv("GOPASS_HOMEDIR", td)
22 | 	ctx = ctxutil.WithPasswordCallback(ctx, func(_ string, _ bool) ([]byte, error) {
23 | 		debug.Log("static test password callback")
24 | 
25 | 		return []byte("gopass"), nil
26 | 	})
27 | 
28 | 	fsDir := filepath.Join(td, "fs")
29 | 	require.NoError(t, os.MkdirAll(fsDir, 0o700))
30 | 
31 | 	t.Run("detect fs", func(t *testing.T) {
32 | 		r, err := DetectStorage(ctx, fsDir)
33 | 		require.NoError(t, err)
34 | 		assert.NotNil(t, r)
35 | 		assert.Equal(t, "fs", r.Name())
36 | 	})
37 | }
38 | 


--------------------------------------------------------------------------------
/internal/cache/disk_test.go:
--------------------------------------------------------------------------------
 1 | package cache
 2 | 
 3 | import (
 4 | 	"testing"
 5 | 	"time"
 6 | 
 7 | 	"github.com/stretchr/testify/assert"
 8 | 	"github.com/stretchr/testify/require"
 9 | )
10 | 
11 | func TestOnDisk(t *testing.T) {
12 | 	t.Parallel()
13 | 
14 | 	td := t.TempDir()
15 | 
16 | 	odc, err := NewOnDiskWithDir("test", td, time.Hour)
17 | 	require.NoError(t, err)
18 | 
19 | 	require.NoError(t, odc.Set("foo", []string{"bar"}))
20 | 	res, err := odc.Get("foo")
21 | 	require.NoError(t, err)
22 | 	assert.Equal(t, []string{"bar"}, res)
23 | 
24 | 	require.Error(t, odc.Remove("bar"))
25 | 	require.NoError(t, odc.Remove("foo"))
26 | 	require.NoError(t, odc.Purge())
27 | }
28 | 
29 | func TestOnDiskExpiry(t *testing.T) {
30 | 	t.Parallel()
31 | 
32 | 	td := t.TempDir()
33 | 
34 | 	odc, err := NewOnDiskWithDir("test", td, time.Second)
35 | 	require.NoError(t, err)
36 | 	require.NoError(t, odc.Set("foo", []string{"bar"}))
37 | 	res, err := odc.Get("foo")
38 | 	require.NoError(t, err)
39 | 	assert.Equal(t, []string{"bar"}, res)
40 | 
41 | 	time.Sleep(time.Second + 100*time.Millisecond)
42 | 	res, err = odc.Get("foo")
43 | 	require.Error(t, err)
44 | 	assert.NotEqual(t, []string{"bar"}, res)
45 | }
46 | 


--------------------------------------------------------------------------------
/internal/cache/ghssh/cache.go:
--------------------------------------------------------------------------------
 1 | package ghssh
 2 | 
 3 | import (
 4 | 	"fmt"
 5 | 	"time"
 6 | 
 7 | 	"github.com/gopasspw/gopass/internal/cache"
 8 | )
 9 | 
10 | // Cache is a disk-backed GitHub SSH public key cache.
11 | type Cache struct {
12 | 	disk    *cache.OnDisk
13 | 	Timeout time.Duration
14 | }
15 | 
16 | // New creates a new github cache.
17 | func New() (*Cache, error) {
18 | 	cDir, err := cache.NewOnDisk("github-ssh", 6*time.Hour)
19 | 	if err != nil {
20 | 		return nil, err
21 | 	}
22 | 
23 | 	return &Cache{
24 | 		disk:    cDir,
25 | 		Timeout: 30 * time.Second,
26 | 	}, nil
27 | }
28 | 
29 | func (c *Cache) String() string {
30 | 	return fmt.Sprintf("Github SSH key cache (OnDisk: %s)", c.disk.String())
31 | }
32 | 


--------------------------------------------------------------------------------
/internal/cache/ghssh/cache_test.go:
--------------------------------------------------------------------------------
 1 | package ghssh
 2 | 
 3 | import (
 4 | 	"testing"
 5 | 	"time"
 6 | 
 7 | 	"github.com/stretchr/testify/assert"
 8 | 	"github.com/stretchr/testify/require"
 9 | )
10 | 
11 | func TestNew(t *testing.T) {
12 | 	// Mock GOPASS_HOMEDIR to point to a temp directory
13 | 	tempDir := t.TempDir()
14 | 	t.Setenv("GOPASS_HOMEDIR", tempDir)
15 | 
16 | 	c, err := New()
17 | 	require.NoError(t, err)
18 | 	assert.NotNil(t, c)
19 | 	assert.Equal(t, 30*time.Second, c.Timeout)
20 | 	assert.NotNil(t, c.disk)
21 | }
22 | 
23 | func TestCache_String(t *testing.T) {
24 | 	// Mock GOPASS_HOMEDIR to point to a temp directory
25 | 	tempDir := t.TempDir()
26 | 	t.Setenv("GOPASS_HOMEDIR", tempDir)
27 | 
28 | 	c, err := New()
29 | 	require.NoError(t, err)
30 | 	assert.NotNil(t, c)
31 | 
32 | 	assert.Contains(t, c.String(), "Github SSH key cache (OnDisk:")
33 | 	assert.Contains(t, c.String(), tempDir)
34 | }
35 | 


--------------------------------------------------------------------------------
/internal/config/config_windows.go:
--------------------------------------------------------------------------------
 1 | package config
 2 | 
 3 | import "github.com/gopasspw/gitconfig"
 4 | 
 5 | func init() {
 6 | 	// Disable unescaping of values. This is not strictly conformant with the
 7 | 	// git config spec, but it avoid interpreting backslashes in paths as linebreaks
 8 | 	// or tabs.
 9 | 	gitconfig.CompatMode = true
10 | }
11 | 


--------------------------------------------------------------------------------
/internal/config/context.go:
--------------------------------------------------------------------------------
 1 | package config
 2 | 
 3 | import (
 4 | 	"context"
 5 | 
 6 | 	"github.com/gopasspw/gitconfig"
 7 | 	"github.com/gopasspw/gopass/pkg/debug"
 8 | )
 9 | 
10 | type contextKey int
11 | 
12 | const (
13 | 	ctxKeyConfig contextKey = iota
14 | 	ctxKeyMountPoint
15 | )
16 | 
17 | func (c *Config) WithConfig(ctx context.Context) context.Context {
18 | 	return context.WithValue(ctx, ctxKeyConfig, c)
19 | }
20 | 
21 | func WithMount(ctx context.Context, mp string) context.Context {
22 | 	return context.WithValue(ctx, ctxKeyMountPoint, mp)
23 | }
24 | 
25 | // FromContext returns a config from a context, as well as the current mount point (store name) if found.
26 | func FromContext(ctx context.Context) (*Config, string) {
27 | 	mount := ""
28 | 	if m, found := ctx.Value(ctxKeyMountPoint).(string); found && m != "" {
29 | 		mount = m
30 | 	}
31 | 
32 | 	if c, found := ctx.Value(ctxKeyConfig).(*Config); found && c != nil {
33 | 		return c, mount
34 | 	}
35 | 
36 | 	debug.Log("no config in context, loading anew")
37 | 
38 | 	cfg := &Config{
39 | 		root: newGitconfig().LoadAll(""),
40 | 	}
41 | 	cfg.root.Preset = gitconfig.NewFromMap(defaults)
42 | 
43 | 	return cfg, mount
44 | }
45 | 


--------------------------------------------------------------------------------
/internal/config/legacy.go:
--------------------------------------------------------------------------------
 1 | package config
 2 | 
 3 | import (
 4 | 	"fmt"
 5 | 
 6 | 	"github.com/gopasspw/gopass/internal/config/legacy"
 7 | 	"github.com/gopasspw/gopass/pkg/debug"
 8 | )
 9 | 
10 | func migrateConfigs() error {
11 | 	cfg := legacy.LoadWithOptions(true, false)
12 | 	if cfg == nil {
13 | 		debug.V(2).Log("no legacy config found. not migrating.")
14 | 
15 | 		return nil
16 | 	}
17 | 
18 | 	c := newGitconfig().LoadAll(cfg.Path)
19 | 
20 | 	for k, v := range cfg.ConfigMap() {
21 | 		var fk string
22 | 		switch k {
23 | 		case "keychain":
24 | 			fk = "age.usekeychain"
25 | 		case "path":
26 | 			fk = "mounts.path"
27 | 		case "safecontent":
28 | 			fk = "show.safecontent"
29 | 		case "autoclip":
30 | 			fk = "generate.autoclip"
31 | 		case "showautoclip":
32 | 			fk = "show.autoclip"
33 | 		default:
34 | 			fk = "core." + k
35 | 		}
36 | 
37 | 		if err := c.SetGlobal(fk, v); err != nil {
38 | 			return fmt.Errorf("failed to write new config: %w", err)
39 | 		}
40 | 	}
41 | 	for alias, path := range cfg.Mounts {
42 | 		if err := c.SetGlobal(mpk(alias), path); err != nil {
43 | 			return fmt.Errorf("failed to write new config: %w", err)
44 | 		}
45 | 	}
46 | 
47 | 	debug.Log("migrated legacy config from %s", cfg.ConfigPath)
48 | 
49 | 	return nil
50 | }
51 | 


--------------------------------------------------------------------------------
/internal/config/location_test.go:
--------------------------------------------------------------------------------
 1 | package config
 2 | 
 3 | import (
 4 | 	"path/filepath"
 5 | 	"runtime"
 6 | 	"testing"
 7 | 
 8 | 	"github.com/gopasspw/gopass/pkg/appdir"
 9 | 	"github.com/stretchr/testify/assert"
10 | )
11 | 
12 | func TestPwStoreDirNoEnv(t *testing.T) {
13 | 	if runtime.GOOS != "windows" {
14 | 		t.Setenv("GOPASS_HOMEDIR", "/tmp")
15 | 	}
16 | 
17 | 	baseDir := filepath.Join(appdir.UserHome(), ".local", "share", "gopass", "stores")
18 | 	if runtime.GOOS == "windows" {
19 | 		baseDir = filepath.Join(appdir.UserHome(), "AppData", "Local", "gopass", "stores")
20 | 	}
21 | 
22 | 	for in, out := range map[string]string{
23 | 		"":                          filepath.Join(baseDir, "root"),
24 | 		"work":                      filepath.Join(baseDir, "work"),
25 | 		filepath.Join("foo", "bar"): filepath.Join(baseDir, "foo-bar"),
26 | 	} {
27 | 		assert.Equal(t, out, PwStoreDir(in), in, "mount "+in)
28 | 	}
29 | }
30 | 
31 | func TestDirectory(t *testing.T) {
32 | 	t.Parallel()
33 | 
34 | 	loc := configLocation()
35 | 	dir := filepath.Dir(loc)
36 | 	assert.Equal(t, dir, Directory())
37 | }
38 | 


--------------------------------------------------------------------------------
/internal/create/helpers.go:
--------------------------------------------------------------------------------
 1 | package create
 2 | 
 3 | import (
 4 | 	"fmt"
 5 | 	"net/url"
 6 | 	"strconv"
 7 | 	"strings"
 8 | 
 9 | 	"github.com/fatih/color"
10 | 	"github.com/gopasspw/gopass/pkg/debug"
11 | 	"github.com/gopasspw/gopass/pkg/fsutil"
12 | )
13 | 
14 | func fmtfn(d int, n string, t string) string {
15 | 	strlen := 40 - d
16 | 	// indent - [N] - text (trailing spaces)
17 | 	fmtStr := "%" + strconv.Itoa(d) + "s%s %-" + strconv.Itoa(strlen) + "s"
18 | 	debug.Log("d: %d, n: %q, t: %q, strlen: %d, fmtStr: %q", d, n, t, strlen, fmtStr)
19 | 
20 | 	return fmt.Sprintf(fmtStr, "", color.GreenString("["+n+"]"), t)
21 | }
22 | 
23 | // extractHostname tries to extract the hostname from a URL in a filepath-safe
24 | // way for use in the name of a secret.
25 | func extractHostname(in string) string {
26 | 	if in == "" {
27 | 		return ""
28 | 	}
29 | 	// help url.Parse by adding a scheme if one is missing. This should still
30 | 	// allow for any scheme, but by default we assume http (only for parsing)
31 | 	urlStr := in
32 | 	if !strings.Contains(urlStr, "://") {
33 | 		urlStr = "http://" + urlStr
34 | 	}
35 | 
36 | 	u, err := url.Parse(urlStr)
37 | 	if err == nil {
38 | 		if ch := fsutil.CleanFilename(u.Hostname()); ch != "" {
39 | 			return ch
40 | 		}
41 | 	}
42 | 
43 | 	return fsutil.CleanFilename(in)
44 | }
45 | 


--------------------------------------------------------------------------------
/internal/cui/actions.go:
--------------------------------------------------------------------------------
 1 | package cui
 2 | 
 3 | import (
 4 | 	"context"
 5 | 	"errors"
 6 | 
 7 | 	"github.com/urfave/cli/v2"
 8 | )
 9 | 
10 | // Action is a action which can be selected.
11 | type Action struct {
12 | 	Name string
13 | 	Fn   func(context.Context, *cli.Context) error
14 | }
15 | 
16 | // Actions is a list of actions.
17 | type Actions []Action
18 | 
19 | // Selection return the list of actions.
20 | func (ca Actions) Selection() []string {
21 | 	keys := make([]string, 0, len(ca))
22 | 	for _, a := range ca {
23 | 		keys = append(keys, a.Name)
24 | 	}
25 | 
26 | 	return keys
27 | }
28 | 
29 | // Run executes the selected action.
30 | func (ca Actions) Run(ctx context.Context, c *cli.Context, i int) error {
31 | 	if len(ca) < i || i >= len(ca) {
32 | 		return errors.New("action not found")
33 | 	}
34 | 	if ca[i].Fn == nil {
35 | 		return errors.New("action invalid")
36 | 	}
37 | 
38 | 	return ca[i].Fn(ctx, c)
39 | }
40 | 


--------------------------------------------------------------------------------
/internal/cui/actions_test.go:
--------------------------------------------------------------------------------
 1 | package cui
 2 | 
 3 | import (
 4 | 	"context"
 5 | 	"testing"
 6 | 
 7 | 	"github.com/gopasspw/gopass/internal/config"
 8 | 	"github.com/stretchr/testify/assert"
 9 | 	"github.com/stretchr/testify/require"
10 | 	"github.com/urfave/cli/v2"
11 | )
12 | 
13 | func TestCreateActions(t *testing.T) {
14 | 	t.Parallel()
15 | 
16 | 	ctx := config.NewContextInMemory()
17 | 	cas := Actions{
18 | 		{
19 | 			Name: "foo",
20 | 		},
21 | 		{
22 | 			Name: "bar",
23 | 			Fn: func(context.Context, *cli.Context) error {
24 | 				return nil
25 | 			},
26 | 		},
27 | 	}
28 | 	assert.Equal(t, []string{"foo", "bar"}, cas.Selection())
29 | 	require.Error(t, cas.Run(ctx, nil, 0))
30 | 	require.NoError(t, cas.Run(ctx, nil, 1))
31 | 	require.Error(t, cas.Run(ctx, nil, 2))
32 | 	require.Error(t, cas.Run(ctx, nil, 66))
33 | }
34 | 


--------------------------------------------------------------------------------
/internal/cui/cui.go:
--------------------------------------------------------------------------------
 1 | // Package cui provides a simple command line user interface
 2 | // for gopass. It is used to interact with the user.
 3 | package cui
 4 | 
 5 | import (
 6 | 	"context"
 7 | 	"errors"
 8 | 	"fmt"
 9 | 
10 | 	"github.com/fatih/color"
11 | 	"github.com/gopasspw/gopass/pkg/ctxutil"
12 | 	"github.com/gopasspw/gopass/pkg/termio"
13 | )
14 | 
15 | // GetSelection show a navigable multiple-choice list to the user
16 | // and returns the selected entry along with the action.
17 | func GetSelection(ctx context.Context, prompt string, choices []string) (string, int) {
18 | 	if ctxutil.IsAlwaysYes(ctx) || !ctxutil.IsInteractive(ctx) {
19 | 		return "impossible", 0
20 | 	}
21 | 
22 | 	for i, c := range choices {
23 | 		fmt.Print(color.GreenString("[%  d]", i))
24 | 		fmt.Printf(" %s\n", c)
25 | 	}
26 | 	fmt.Println()
27 | 	var i int
28 | 	for {
29 | 		var err error
30 | 		i, err = termio.AskForInt(ctx, prompt, 0)
31 | 		if err == nil && i < len(choices) {
32 | 			break
33 | 		}
34 | 		if errors.Is(err, termio.ErrAborted) {
35 | 			return "aborted", 0
36 | 		}
37 | 		if err != nil {
38 | 			fmt.Println(err.Error())
39 | 		}
40 | 	}
41 | 	fmt.Println(i)
42 | 
43 | 	return "default", i
44 | }
45 | 


--------------------------------------------------------------------------------
/internal/cui/cui_test.go:
--------------------------------------------------------------------------------
 1 | package cui
 2 | 
 3 | import (
 4 | 	"testing"
 5 | 
 6 | 	"github.com/gopasspw/gopass/internal/config"
 7 | 	"github.com/gopasspw/gopass/pkg/ctxutil"
 8 | 	"github.com/stretchr/testify/assert"
 9 | )
10 | 
11 | func TestGetSelection(t *testing.T) {
12 | 	t.Parallel()
13 | 
14 | 	ctx := config.NewContextInMemory()
15 | 	ctx = ctxutil.WithInteractive(ctx, false)
16 | 
17 | 	act, sel := GetSelection(ctx, "foo", []string{"foo", "bar"})
18 | 	assert.Equal(t, "impossible", act)
19 | 	assert.Equal(t, 0, sel)
20 | }
21 | 


--------------------------------------------------------------------------------
/internal/diff/diff.go:
--------------------------------------------------------------------------------
 1 | // Package diff implements diffing of two lists.
 2 | package diff
 3 | 
 4 | // Stat returnes the number of items added to and removed from the first to
 5 | // the second list.
 6 | func Stat[K comparable](l, r []K) (int, int) {
 7 | 	added, removed := List(l, r)
 8 | 
 9 | 	return len(added), len(removed)
10 | }
11 | 
12 | // List returns two lists, the first one contains the items that were added from left
13 | // to right, the second one contains the items that were removed from left to right.
14 | func List[K comparable](l, r []K) ([]K, []K) {
15 | 	ml := listToMap(l)
16 | 	mr := listToMap(r)
17 | 
18 | 	var added []K
19 | 
20 | 	for k := range mr {
21 | 		if _, found := ml[k]; !found {
22 | 			added = append(added, k)
23 | 		}
24 | 	}
25 | 
26 | 	var removed []K
27 | 
28 | 	for k := range ml {
29 | 		if _, found := mr[k]; !found {
30 | 			removed = append(removed, k)
31 | 		}
32 | 	}
33 | 
34 | 	return added, removed
35 | }
36 | 
37 | func listToMap[K comparable](l []K) map[K]struct{} {
38 | 	m := make(map[K]struct{}, len(l))
39 | 	for _, e := range l {
40 | 		m[e] = struct{}{}
41 | 	}
42 | 
43 | 	return m
44 | }
45 | 


--------------------------------------------------------------------------------
/internal/editor/edit_linux.go:
--------------------------------------------------------------------------------
 1 | //go:build linux
 2 | // +build linux
 3 | 
 4 | package editor
 5 | 
 6 | import (
 7 | 	"os"
 8 | 	"os/exec"
 9 | 
10 | 	"github.com/gopasspw/gopass/internal/config"
11 | 	"github.com/gopasspw/gopass/pkg/debug"
12 | 	"github.com/urfave/cli/v2"
13 | )
14 | 
15 | // Path return the name/path of the preferred editor.
16 | func Path(c *cli.Context) string {
17 | 	if c != nil {
18 | 		if ed := c.String("editor"); ed != "" {
19 | 			debug.Log("Using editor from command line: %s", ed)
20 | 
21 | 			return ed
22 | 		}
23 | 	}
24 | 	if ed := config.String(c.Context, "edit.editor"); ed != "" {
25 | 		debug.Log("Using editor from config: %s", ed)
26 | 
27 | 		return ed
28 | 	}
29 | 	if ed := os.Getenv("EDITOR"); ed != "" {
30 | 		debug.Log("Using editor from $EDITOR: %s", ed)
31 | 
32 | 		return ed
33 | 	}
34 | 	if p, err := exec.LookPath("editor"); err == nil {
35 | 		debug.Log("Using editor from $PATH: %s", p)
36 | 
37 | 		return p
38 | 	}
39 | 	// if neither EDITOR is set nor "editor" available we'll just assume that vi
40 | 	// is installed. If this fails the user will have to set `$EDITOR`.
41 | 	debug.Log("Using default editor: %s", "vi")
42 | 
43 | 	return "vi"
44 | }
45 | 


--------------------------------------------------------------------------------
/internal/editor/edit_others.go:
--------------------------------------------------------------------------------
 1 | //go:build !linux && !windows
 2 | // +build !linux,!windows
 3 | 
 4 | package editor
 5 | 
 6 | import (
 7 | 	"os"
 8 | 
 9 | 	"github.com/gopasspw/gopass/internal/config"
10 | 	"github.com/urfave/cli/v2"
11 | )
12 | 
13 | // Path return the name/path of the preferred editor.
14 | func Path(c *cli.Context) string {
15 | 	if c != nil {
16 | 		if ed := c.String("editor"); ed != "" {
17 | 			return ed
18 | 		}
19 | 	}
20 | 
21 | 	if ed := config.String(c.Context, "edit.editor"); ed != "" {
22 | 		return ed
23 | 	}
24 | 
25 | 	if ed := os.Getenv("EDITOR"); ed != "" {
26 | 		return ed
27 | 	}
28 | 
29 | 	// given, this is a very opinionated default, but this should be available
30 | 	// on virtually any UNIX system and the user can still set EDITOR to get
31 | 	// his favorite one
32 | 	return "vi"
33 | }
34 | 


--------------------------------------------------------------------------------
/internal/editor/edit_test.go:
--------------------------------------------------------------------------------
 1 | package editor
 2 | 
 3 | import (
 4 | 	"bytes"
 5 | 	"os"
 6 | 	"testing"
 7 | 
 8 | 	"github.com/gopasspw/gopass/internal/config"
 9 | 	"github.com/gopasspw/gopass/internal/out"
10 | 	"github.com/gopasspw/gopass/pkg/ctxutil"
11 | 	"github.com/stretchr/testify/require"
12 | )
13 | 
14 | func TestEdit(t *testing.T) {
15 | 	ctx := config.NewContextInMemory()
16 | 	ctx = ctxutil.WithAlwaysYes(ctx, true)
17 | 	ctx = ctxutil.WithTerminal(ctx, false)
18 | 
19 | 	buf := &bytes.Buffer{}
20 | 	out.Stdout = buf
21 | 	defer func() {
22 | 		out.Stdout = os.Stdout
23 | 	}()
24 | 
25 | 	_, err := Invoke(ctx, "true", []byte{})
26 | 	require.Error(t, err)
27 | 	buf.Reset()
28 | }
29 | 


--------------------------------------------------------------------------------
/internal/editor/edit_windows.go:
--------------------------------------------------------------------------------
 1 | //go:build windows
 2 | // +build windows
 3 | 
 4 | package editor
 5 | 
 6 | import (
 7 | 	"os"
 8 | 
 9 | 	"github.com/gopasspw/gopass/internal/config"
10 | 	"github.com/urfave/cli/v2"
11 | )
12 | 
13 | // Path return the name/path of the preferred editor
14 | func Path(c *cli.Context) string {
15 | 	if c != nil {
16 | 		if ed := c.String("editor"); ed != "" {
17 | 			return ed
18 | 		}
19 | 	}
20 | 	if ed := config.String(c.Context, "edit.editor"); ed != "" {
21 | 		return ed
22 | 	}
23 | 	if ed := os.Getenv("EDITOR"); ed != "" {
24 | 		return ed
25 | 	}
26 | 	return "notepad.exe"
27 | }
28 | 


--------------------------------------------------------------------------------
/internal/env/doc.go:
--------------------------------------------------------------------------------
1 | // Package env provides a way to validate the environment
2 | // and the configuration of gopass.
3 | 
4 | package env
5 | 


--------------------------------------------------------------------------------
/internal/env/env_darwin.go:
--------------------------------------------------------------------------------
 1 | //go:build darwin
 2 | // +build darwin
 3 | 
 4 | package env
 5 | 
 6 | import (
 7 | 	"bytes"
 8 | 	"context"
 9 | 	"fmt"
10 | 	"io"
11 | 	"os"
12 | 	"os/exec"
13 | 	"strings"
14 | )
15 | 
16 | var (
17 | 	// Stdin is exported for tests.
18 | 	Stdin io.Reader = os.Stdin
19 | 	// Stderr is exported for tests.
20 | 	Stderr io.Writer = os.Stderr
21 | )
22 | 
23 | // Check validates the runtime environment on MacOS.
24 | // It checks if the keychain is used.
25 | func Check(ctx context.Context) (string, error) {
26 | 	buf := &bytes.Buffer{}
27 | 
28 | 	cmd := exec.CommandContext(ctx, "defaults", "read", "org.gpgtools.common", "UseKeychain")
29 | 	cmd.Stdin = Stdin
30 | 	cmd.Stdout = buf
31 | 	cmd.Stderr = Stderr
32 | 
33 | 	if err := cmd.Run(); err != nil {
34 | 		return "", fmt.Errorf("`default read org.gpgtools.common UseKeychain` failed: %w", err)
35 | 	}
36 | 
37 | 	// if the keychain is not used, we can skip the rest
38 | 	if strings.ToUpper(strings.TrimSpace(buf.String())) == "NO" {
39 | 		return "", nil
40 | 	}
41 | 
42 | 	// gpg uses the keychain to store the passphrase, warn once in a while that users
43 | 	// might want to change that because it's not secure.
44 | 	return "pinentry-mac will use the MacOS Keychain to store your passphrase indefinitely. Consider running 'defaults write org.gpgtools.common UseKeychain NO' to disable that.", nil
45 | }
46 | 


--------------------------------------------------------------------------------
/internal/env/env_others.go:
--------------------------------------------------------------------------------
 1 | //go:build !darwin
 2 | // +build !darwin
 3 | 
 4 | package env
 5 | 
 6 | import "context"
 7 | 
 8 | // Check does nothing on these OSes, yet.
 9 | func Check(ctx context.Context) (string, error) {
10 | 	return "", nil
11 | }
12 | 


--------------------------------------------------------------------------------
/internal/notify/doc.go:
--------------------------------------------------------------------------------
1 | // Package notify provides a notification system for gopass.
2 | 
3 | package notify
4 | 


--------------------------------------------------------------------------------
/internal/notify/notify_dbus.go:
--------------------------------------------------------------------------------
 1 | //go:build linux
 2 | // +build linux
 3 | 
 4 | package notify
 5 | 
 6 | import (
 7 | 	"context"
 8 | 	"os"
 9 | 
10 | 	"github.com/godbus/dbus/v5"
11 | 	"github.com/gopasspw/gopass/internal/config"
12 | 	"github.com/gopasspw/gopass/pkg/debug"
13 | )
14 | 
15 | // Notify displays a desktop notification with dbus.
16 | func Notify(ctx context.Context, subj, msg string) error {
17 | 	if os.Getenv("GOPASS_NO_NOTIFY") != "" || !config.Bool(ctx, "core.notifications") {
18 | 		debug.Log("Notifications disabled")
19 | 
20 | 		return nil
21 | 	}
22 | 	conn, err := dbus.SessionBus()
23 | 	if err != nil {
24 | 		debug.Log("DBus failure: %s", err)
25 | 
26 | 		return err
27 | 	}
28 | 
29 | 	obj := conn.Object("org.freedesktop.Notifications", "/org/freedesktop/Notifications")
30 | 	call := obj.Call("org.freedesktop.Notifications.Notify", 0, "gopass", uint32(0), iconURI(ctx), subj, msg, []string{}, map[string]dbus.Variant{"transient": dbus.MakeVariant(true)}, int32(3000))
31 | 	if call.Err != nil {
32 | 		debug.Log("DBus notification failure: %s", call.Err)
33 | 
34 | 		return call.Err
35 | 	}
36 | 
37 | 	return nil
38 | }
39 | 


--------------------------------------------------------------------------------
/internal/notify/notify_others.go:
--------------------------------------------------------------------------------
 1 | //go:build !linux && !windows && !darwin
 2 | // +build !linux,!windows,!darwin
 3 | 
 4 | package notify
 5 | 
 6 | import (
 7 | 	"context"
 8 | 	"fmt"
 9 | 	"runtime"
10 | )
11 | 
12 | // Notify is not yet implemented on this platform
13 | func Notify(ctx context.Context, subj, msg string) error {
14 | 	return fmt.Errorf("GOOS %s not yet supported", runtime.GOOS)
15 | }
16 | 


--------------------------------------------------------------------------------
/internal/notify/notify_test.go:
--------------------------------------------------------------------------------
 1 | package notify
 2 | 
 3 | import (
 4 | 	"image/png"
 5 | 	"os"
 6 | 	"strings"
 7 | 	"testing"
 8 | 
 9 | 	"github.com/gopasspw/gopass/internal/config"
10 | 	"github.com/stretchr/testify/require"
11 | )
12 | 
13 | func TestNotify(t *testing.T) {
14 | 	ctx := config.NewContextInMemory()
15 | 
16 | 	t.Setenv("GOPASS_NO_NOTIFY", "true")
17 | 	require.NoError(t, Notify(ctx, "foo", "bar"))
18 | }
19 | 
20 | func TestIcon(t *testing.T) {
21 | 	t.Parallel()
22 | 
23 | 	ctx := t.Context()
24 | 
25 | 	fn := strings.TrimPrefix(iconURI(ctx), "file://")
26 | 	require.NoError(t, os.Remove(fn))
27 | 	_ = iconURI(ctx)
28 | 	fh, err := os.Open(fn)
29 | 	require.NoError(t, err)
30 | 
31 | 	defer func() {
32 | 		require.NoError(t, fh.Close())
33 | 	}()
34 | 
35 | 	require.NotNil(t, fh)
36 | 	_, err = png.Decode(fh)
37 | 	require.NoError(t, err)
38 | }
39 | 


--------------------------------------------------------------------------------
/internal/notify/notify_windows.go:
--------------------------------------------------------------------------------
 1 | //go:build windows
 2 | // +build windows
 3 | 
 4 | package notify
 5 | 
 6 | import (
 7 | 	"context"
 8 | 	"os"
 9 | 	"os/exec"
10 | 
11 | 	"github.com/gopasspw/gopass/internal/config"
12 | )
13 | 
14 | // Notify displays a desktop notification through msg
15 | func Notify(ctx context.Context, subj, msg string) error {
16 | 	if os.Getenv("GOPASS_NO_NOTIFY") != "" || !config.Bool(ctx, "core.notifications") {
17 | 		return nil
18 | 	}
19 | 	winmsg, err := exec.LookPath("msg")
20 | 	if err != nil {
21 | 		return err
22 | 	}
23 | 
24 | 	return exec.Command(winmsg,
25 | 		"*",
26 | 		"/TIME:3",
27 | 		subj+"\n\n"+msg,
28 | 	).Start()
29 | }
30 | 


--------------------------------------------------------------------------------
/internal/out/context.go:
--------------------------------------------------------------------------------
 1 | package out
 2 | 
 3 | import "context"
 4 | 
 5 | type contextKey int
 6 | 
 7 | const (
 8 | 	ctxKeyPrefix contextKey = iota
 9 | 	ctxKeyNewline
10 | )
11 | 
12 | // WithPrefix returns a context with the given prefix set.
13 | func WithPrefix(ctx context.Context, prefix string) context.Context {
14 | 	return context.WithValue(ctx, ctxKeyPrefix, prefix)
15 | }
16 | 
17 | // AddPrefix returns a context with the given prefix added to end of the
18 | // existing prefix.
19 | func AddPrefix(ctx context.Context, prefix string) context.Context {
20 | 	if prefix == "" {
21 | 		return ctx
22 | 	}
23 | 
24 | 	pfx := Prefix(ctx)
25 | 	if pfx == "" {
26 | 		return WithPrefix(ctx, prefix)
27 | 	}
28 | 
29 | 	return WithPrefix(ctx, pfx+prefix)
30 | }
31 | 
32 | // Prefix returns the prefix or an empty string.
33 | func Prefix(ctx context.Context) string {
34 | 	sv, ok := ctx.Value(ctxKeyPrefix).(string)
35 | 	if !ok {
36 | 		return ""
37 | 	}
38 | 
39 | 	return sv
40 | }
41 | 
42 | // WithNewline returns a context with the flag value for newline set.
43 | func WithNewline(ctx context.Context, nl bool) context.Context {
44 | 	return context.WithValue(ctx, ctxKeyNewline, nl)
45 | }
46 | 
47 | // HasNewline returns the value of newline or the default (true).
48 | func HasNewline(ctx context.Context) bool {
49 | 	bv, ok := ctx.Value(ctxKeyNewline).(bool)
50 | 	if !ok {
51 | 		return true
52 | 	}
53 | 
54 | 	return bv
55 | }
56 | 


--------------------------------------------------------------------------------
/internal/out/context_test.go:
--------------------------------------------------------------------------------
 1 | package out
 2 | 
 3 | import (
 4 | 	"testing"
 5 | 
 6 | 	"github.com/gopasspw/gopass/internal/config"
 7 | 	"github.com/stretchr/testify/assert"
 8 | )
 9 | 
10 | func TestPrefix(t *testing.T) {
11 | 	t.Parallel()
12 | 
13 | 	ctx := config.NewContextInMemory()
14 | 
15 | 	assert.Empty(t, Prefix(ctx))
16 | 
17 | 	ctx = AddPrefix(ctx, "[foo] ")
18 | 	assert.Equal(t, "[foo] ", Prefix(ctx))
19 | 
20 | 	ctx = AddPrefix(ctx, "[bar] ")
21 | 	assert.Equal(t, "[foo] [bar] ", Prefix(ctx))
22 | 
23 | 	ctx = AddPrefix(ctx, "")
24 | 	assert.Equal(t, "[foo] [bar] ", Prefix(ctx))
25 | }
26 | 
27 | func TestNewline(t *testing.T) {
28 | 	t.Parallel()
29 | 
30 | 	ctx := config.NewContextInMemory()
31 | 
32 | 	assert.True(t, HasNewline(ctx))
33 | 	assert.False(t, HasNewline(WithNewline(ctx, false)))
34 | }
35 | 


--------------------------------------------------------------------------------
/internal/out/print_test.go:
--------------------------------------------------------------------------------
 1 | package out
 2 | 
 3 | import (
 4 | 	"bytes"
 5 | 	"os"
 6 | 	"testing"
 7 | 
 8 | 	"github.com/gopasspw/gopass/internal/config"
 9 | 	"github.com/gopasspw/gopass/pkg/ctxutil"
10 | 	"github.com/stretchr/testify/assert"
11 | )
12 | 
13 | func TestPrint(t *testing.T) {
14 | 	ctx := config.NewContextInMemory()
15 | 	buf := &bytes.Buffer{}
16 | 	Stdout = buf
17 | 	defer func() {
18 | 		Stdout = os.Stdout
19 | 	}()
20 | 
21 | 	Printf(ctx, "%s = %d", "foo", 42)
22 | 	assert.Equal(t, "foo = 42\n", buf.String())
23 | 	buf.Reset()
24 | 
25 | 	Printf(ctxutil.WithHidden(ctx, true), "%s = %d", "foo", 42)
26 | 	assert.Empty(t, buf.String())
27 | 	buf.Reset()
28 | 
29 | 	Printf(WithNewline(ctx, false), "%s = %d", "foo", 42)
30 | 	assert.Equal(t, "foo = 42", buf.String())
31 | 	buf.Reset()
32 | }
33 | 


--------------------------------------------------------------------------------
/internal/pwschemes/argon2i/argon2i_test.go:
--------------------------------------------------------------------------------
 1 | package argon2i
 2 | 
 3 | import (
 4 | 	"testing"
 5 | 
 6 | 	"github.com/stretchr/testify/assert"
 7 | 	"github.com/stretchr/testify/require"
 8 | )
 9 | 
10 | func TestArgon2I(t *testing.T) {
11 | 	t.Parallel()
12 | 
13 | 	pw := "foobar"
14 | 	hash, err := Generate(pw, 0)
15 | 	require.NoError(t, err)
16 | 
17 | 	t.Logf("PW: %s - Hash: %s", pw, hash)
18 | 	ok, err := Validate(pw, hash)
19 | 	require.NoError(t, err)
20 | 	assert.True(t, ok)
21 | }
22 | 


--------------------------------------------------------------------------------
/internal/pwschemes/argon2id/argon2id_test.go:
--------------------------------------------------------------------------------
 1 | package argon2id
 2 | 
 3 | import (
 4 | 	"testing"
 5 | 
 6 | 	"github.com/stretchr/testify/assert"
 7 | 	"github.com/stretchr/testify/require"
 8 | )
 9 | 
10 | func TestArgon2ID(t *testing.T) {
11 | 	t.Parallel()
12 | 
13 | 	pw := "foobar"
14 | 	hash, err := Generate(pw, 0)
15 | 	require.NoError(t, err)
16 | 
17 | 	t.Logf("PW: %s - Hash: %s", pw, hash)
18 | 	ok, err := Validate(pw, hash)
19 | 	require.NoError(t, err)
20 | 	assert.True(t, ok)
21 | }
22 | 


--------------------------------------------------------------------------------
/internal/pwschemes/bcrypt/bcrypt.go:
--------------------------------------------------------------------------------
 1 | // Package bcrypt provides a bcrypt password hashing scheme.
 2 | // It is compatible with Dovecot and other systems that use bcrypt.
 3 | package bcrypt
 4 | 
 5 | import (
 6 | 	"fmt"
 7 | 	"strings"
 8 | 
 9 | 	"golang.org/x/crypto/bcrypt"
10 | )
11 | 
12 | const (
13 | 	cost = 12
14 | )
15 | 
16 | // Prefix is set to be compatible with Dovecot. Can be set to an empty string.
17 | var Prefix = "{BLF-CRYPT}"
18 | 
19 | // Generate generates a new Bcrypt hash with recommended values for it's
20 | // cost parameter.
21 | func Generate(password string) (string, error) {
22 | 	h, err := bcrypt.GenerateFromPassword([]byte(password), cost)
23 | 	if err != nil {
24 | 		return "", fmt.Errorf("failed to generate password hash: %w", err)
25 | 	}
26 | 
27 | 	return Prefix + string(h), nil
28 | }
29 | 
30 | // Validate validates the password against the given hash.
31 | func Validate(password, hash string) error {
32 | 	hash = strings.TrimPrefix(hash, Prefix)
33 | 
34 | 	if err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)); err != nil {
35 | 		return fmt.Errorf("failed to validate password hash %s: %w", hash, err)
36 | 	}
37 | 
38 | 	return nil
39 | }
40 | 


--------------------------------------------------------------------------------
/internal/pwschemes/bcrypt/bcrypt_test.go:
--------------------------------------------------------------------------------
 1 | package bcrypt
 2 | 
 3 | import (
 4 | 	"testing"
 5 | 
 6 | 	"github.com/stretchr/testify/require"
 7 | )
 8 | 
 9 | func TestBcrypt(t *testing.T) {
10 | 	t.Parallel()
11 | 
12 | 	pw := "foobar"
13 | 
14 | 	hash, err := Generate(pw)
15 | 	require.NoError(t, err)
16 | 
17 | 	t.Logf("PW: %s - Hash: %s", pw, hash)
18 | 
19 | 	require.NoError(t, Validate(pw, hash))
20 | }
21 | 


--------------------------------------------------------------------------------
/internal/store/leaf/context_test.go:
--------------------------------------------------------------------------------
 1 | package leaf
 2 | 
 3 | import (
 4 | 	"context"
 5 | 	"testing"
 6 | 
 7 | 	"github.com/gopasspw/gopass/internal/config"
 8 | 	"github.com/stretchr/testify/assert"
 9 | )
10 | 
11 | func TestFsckCheck(t *testing.T) {
12 | 	t.Parallel()
13 | 
14 | 	ctx := config.NewContextInMemory()
15 | 
16 | 	assert.False(t, IsFsckCheck(ctx))
17 | 	assert.True(t, IsFsckCheck(WithFsckCheck(ctx, true)))
18 | 	assert.False(t, IsFsckCheck(WithFsckCheck(ctx, false)))
19 | 	assert.True(t, HasFsckCheck(WithFsckCheck(ctx, true)))
20 | }
21 | 
22 | func TestFsckForce(t *testing.T) {
23 | 	t.Parallel()
24 | 
25 | 	ctx := config.NewContextInMemory()
26 | 
27 | 	assert.False(t, IsFsckForce(ctx))
28 | 	assert.True(t, IsFsckForce(WithFsckForce(ctx, true)))
29 | 	assert.False(t, IsFsckForce(WithFsckForce(ctx, false)))
30 | 	assert.True(t, HasFsckForce(WithFsckForce(ctx, true)))
31 | }
32 | 
33 | func TestFsckFunc(t *testing.T) {
34 | 	t.Parallel()
35 | 
36 | 	ctx := config.NewContextInMemory()
37 | 
38 | 	ffunc := func(context.Context, string) bool {
39 | 		return true
40 | 	}
41 | 	assert.NotNil(t, GetFsckFunc(ctx))
42 | 	assert.True(t, GetFsckFunc(ctx)(ctx, ""))
43 | 	assert.True(t, GetFsckFunc(WithFsckFunc(ctx, ffunc))(ctx, ""))
44 | 	assert.True(t, HasFsckFunc(WithFsckFunc(ctx, ffunc)))
45 | }
46 | 


--------------------------------------------------------------------------------
/internal/store/leaf/crypto_test.go:
--------------------------------------------------------------------------------
 1 | package leaf
 2 | 
 3 | import (
 4 | 	"bytes"
 5 | 	"os"
 6 | 	"testing"
 7 | 
 8 | 	"github.com/gopasspw/gopass/internal/config"
 9 | 	"github.com/gopasspw/gopass/internal/out"
10 | 	"github.com/stretchr/testify/require"
11 | )
12 | 
13 | func TestGPG(t *testing.T) {
14 | 	ctx := config.NewContextInMemory()
15 | 
16 | 	obuf := &bytes.Buffer{}
17 | 	out.Stdout = obuf
18 | 	defer func() {
19 | 		out.Stdout = os.Stdout
20 | 	}()
21 | 
22 | 	s, err := createSubStore(t)
23 | 	require.NoError(t, err)
24 | 
25 | 	require.NoError(t, s.ImportMissingPublicKeys(ctx))
26 | 
27 | 	newRecp := "A3683834"
28 | 	err = s.AddRecipient(ctx, newRecp)
29 | 	require.NoError(t, err)
30 | 
31 | 	require.NoError(t, s.ImportMissingPublicKeys(ctx))
32 | }
33 | 


--------------------------------------------------------------------------------
/internal/store/leaf/init_test.go:
--------------------------------------------------------------------------------
 1 | package leaf
 2 | 
 3 | import (
 4 | 	"testing"
 5 | 
 6 | 	"github.com/gopasspw/gopass/internal/config"
 7 | 	"github.com/stretchr/testify/require"
 8 | )
 9 | 
10 | func TestInit(t *testing.T) {
11 | 	ctx := config.NewContextInMemory()
12 | 
13 | 	s, err := createSubStore(t)
14 | 	require.NoError(t, err)
15 | 	require.Error(t, s.Init(ctx, "", "0xDEADBEEF"))
16 | }
17 | 


--------------------------------------------------------------------------------
/internal/store/leaf/link.go:
--------------------------------------------------------------------------------
 1 | package leaf
 2 | 
 3 | import (
 4 | 	"context"
 5 | 	"errors"
 6 | 	"fmt"
 7 | 
 8 | 	"github.com/gopasspw/gopass/internal/queue"
 9 | 	"github.com/gopasspw/gopass/internal/store"
10 | 	"github.com/gopasspw/gopass/pkg/debug"
11 | )
12 | 
13 | // Link creates a symlink.
14 | func (s *Store) Link(ctx context.Context, from, to string) error {
15 | 	if !s.Exists(ctx, from) {
16 | 		return fmt.Errorf("source %q does not exists", from)
17 | 	}
18 | 
19 | 	if s.Exists(ctx, to) {
20 | 		return fmt.Errorf("destination %q already exists", to)
21 | 	}
22 | 
23 | 	if err := s.storage.Link(ctx, s.Passfile(from), s.Passfile(to)); err != nil {
24 | 		return fmt.Errorf("failed to create symlink from %q to %q: %w", from, to, err)
25 | 	}
26 | 
27 | 	debug.Log("created symlink from %q to %q", from, to)
28 | 
29 | 	if err := s.storage.Add(ctx, s.Passfile(to)); err != nil {
30 | 		if errors.Is(err, store.ErrGitNotInit) {
31 | 			return nil
32 | 		}
33 | 
34 | 		return fmt.Errorf("failed to add %q to git: %w", to, err)
35 | 	}
36 | 
37 | 	// try to enqueue this task, if the queue is not available
38 | 	// it will return the task and we will execute it inline
39 | 	t := queue.GetQueue(ctx).Add(func(ctx context.Context) (context.Context, error) {
40 | 		return nil, s.gitCommitAndPush(ctx, to)
41 | 	})
42 | 
43 | 	_, err := t(ctx)
44 | 
45 | 	return err
46 | }
47 | 


--------------------------------------------------------------------------------
/internal/store/leaf/link_test.go:
--------------------------------------------------------------------------------
 1 | package leaf
 2 | 
 3 | import (
 4 | 	"testing"
 5 | 
 6 | 	"github.com/gopasspw/gopass/internal/config"
 7 | 	"github.com/gopasspw/gopass/pkg/gopass/secrets"
 8 | 	"github.com/stretchr/testify/assert"
 9 | 	"github.com/stretchr/testify/require"
10 | )
11 | 
12 | func TestLink(t *testing.T) {
13 | 	ctx := config.NewContextInMemory()
14 | 
15 | 	s, err := createSubStore(t)
16 | 	require.NoError(t, err)
17 | 
18 | 	sec := secrets.NewAKV()
19 | 	sec.SetPassword("foo")
20 | 	_, err = sec.Write([]byte("bar"))
21 | 	require.NoError(t, err)
22 | 	require.NoError(t, s.Set(ctx, "zab/zab", sec))
23 | 
24 | 	require.NoError(t, s.Link(ctx, "zab/zab", "foo/123"))
25 | 
26 | 	p, err := s.Get(ctx, "foo/123")
27 | 	require.NoError(t, err)
28 | 	assert.Equal(t, "foo", p.Password())
29 | }
30 | 


--------------------------------------------------------------------------------
/internal/store/leaf/list.go:
--------------------------------------------------------------------------------
 1 | package leaf
 2 | 
 3 | import (
 4 | 	"context"
 5 | 	"strings"
 6 | 
 7 | 	"github.com/gopasspw/gopass/pkg/debug"
 8 | )
 9 | 
10 | // Sep is the separator used in lists to separate folders from entries.
11 | var Sep = "/"
12 | 
13 | // List will list all entries in this store.
14 | func (s *Store) List(ctx context.Context, prefix string) ([]string, error) {
15 | 	if s.storage == nil || s.crypto == nil {
16 | 		return nil, nil
17 | 	}
18 | 
19 | 	lst, err := s.storage.List(ctx, prefix)
20 | 	if err != nil {
21 | 		return nil, err
22 | 	}
23 | 
24 | 	debug.Log("Listing storage content of %s: %+v", prefix, lst)
25 | 	out := make([]string, 0, len(lst))
26 | 	cExt := "." + s.crypto.Ext()
27 | 	for _, path := range lst {
28 | 		if !strings.HasSuffix(path, cExt) {
29 | 			continue
30 | 		}
31 | 		path = strings.TrimSuffix(path, cExt)
32 | 		if s.alias != "" {
33 | 			path = s.alias + Sep + path
34 | 		}
35 | 		out = append(out, path)
36 | 	}
37 | 	debug.Log("Leaf store entries: %+v", out)
38 | 
39 | 	return out, nil
40 | }
41 | 


--------------------------------------------------------------------------------
/internal/store/leaf/read.go:
--------------------------------------------------------------------------------
 1 | package leaf
 2 | 
 3 | import (
 4 | 	"context"
 5 | 
 6 | 	"github.com/gopasspw/gopass/internal/out"
 7 | 	"github.com/gopasspw/gopass/internal/store"
 8 | 	"github.com/gopasspw/gopass/pkg/ctxutil"
 9 | 	"github.com/gopasspw/gopass/pkg/debug"
10 | 	"github.com/gopasspw/gopass/pkg/gopass"
11 | 	"github.com/gopasspw/gopass/pkg/gopass/secrets"
12 | 	"github.com/gopasspw/gopass/pkg/gopass/secrets/secparse"
13 | )
14 | 
15 | // Get returns the plaintext of a single key.
16 | func (s *Store) Get(ctx context.Context, name string) (gopass.Secret, error) {
17 | 	p := s.Passfile(name)
18 | 
19 | 	ciphertext, err := s.storage.Get(ctx, p)
20 | 	if err != nil {
21 | 		debug.Log("File %s not found: %s", p, err)
22 | 
23 | 		return nil, store.ErrNotFound
24 | 	}
25 | 
26 | 	content, err := s.crypto.Decrypt(ctx, ciphertext)
27 | 	if err != nil {
28 | 		out.Errorf(ctx, "Decryption failed: %s\n%s", err, string(content))
29 | 
30 | 		return nil, store.ErrDecrypt
31 | 	}
32 | 
33 | 	if !ctxutil.IsShowParsing(ctx) {
34 | 		debug.Log("secrets parsing is disabled. parsing as AKV")
35 | 
36 | 		return secrets.ParseAKV(content), nil
37 | 	}
38 | 
39 | 	debug.Log("secrets parsing is enabled")
40 | 
41 | 	return secparse.Parse(content)
42 | }
43 | 


--------------------------------------------------------------------------------
/internal/store/leaf/storage.go:
--------------------------------------------------------------------------------
 1 | package leaf
 2 | 
 3 | import (
 4 | 	"context"
 5 | 	"fmt"
 6 | 
 7 | 	"github.com/gopasspw/gopass/internal/backend"
 8 | 	"github.com/gopasspw/gopass/pkg/ctxutil"
 9 | )
10 | 
11 | func (s *Store) initStorageBackend(ctx context.Context) error {
12 | 	ctx = ctxutil.WithAlias(ctx, s.alias)
13 | 
14 | 	store, err := backend.DetectStorage(ctx, s.path)
15 | 	if err != nil {
16 | 		return fmt.Errorf("unknown storage backend: %w", err)
17 | 	}
18 | 
19 | 	s.storage = store
20 | 
21 | 	return nil
22 | }
23 | 


--------------------------------------------------------------------------------
/internal/store/leaf/write_test.go:
--------------------------------------------------------------------------------
 1 | package leaf
 2 | 
 3 | import (
 4 | 	"runtime"
 5 | 	"testing"
 6 | 
 7 | 	"github.com/gopasspw/gopass/internal/backend/crypto/gpg"
 8 | 	"github.com/gopasspw/gopass/internal/config"
 9 | 	"github.com/gopasspw/gopass/pkg/gopass/secrets"
10 | 	"github.com/stretchr/testify/require"
11 | )
12 | 
13 | func TestSet(t *testing.T) {
14 | 	ctx := gpg.WithAlwaysTrust(config.NewContextInMemory(), true)
15 | 
16 | 	s, err := createSubStore(t)
17 | 	require.NoError(t, err)
18 | 
19 | 	sec := secrets.NewAKV()
20 | 	sec.SetPassword("foo")
21 | 	_, err = sec.Write([]byte("bar"))
22 | 	require.NoError(t, err)
23 | 	require.NoError(t, s.Set(ctx, "zab/zab", sec))
24 | 
25 | 	if runtime.GOOS != "windows" {
26 | 		require.Error(t, s.Set(ctx, "../../../../../etc/passwd", sec))
27 | 	} else {
28 | 		require.NoError(t, s.Set(ctx, "../../../../../etc/passwd", sec))
29 | 	}
30 | 
31 | 	require.NoError(t, s.Set(ctx, "zab", sec))
32 | }
33 | 


--------------------------------------------------------------------------------
/internal/store/root/convert.go:
--------------------------------------------------------------------------------
 1 | package root
 2 | 
 3 | import (
 4 | 	"context"
 5 | 	"fmt"
 6 | 
 7 | 	"github.com/gopasspw/gopass/internal/backend"
 8 | 	"github.com/gopasspw/gopass/pkg/debug"
 9 | )
10 | 
11 | // Convert will try to convert a given mount to a different set of
12 | // backends.
13 | func (r *Store) Convert(ctx context.Context, name string, cryptoBe backend.CryptoBackend, storageBe backend.StorageBackend, move bool) error {
14 | 	sub, err := r.GetSubStore(name)
15 | 	if err != nil {
16 | 		return fmt.Errorf("mount %q not found: %w", name, err)
17 | 	}
18 | 
19 | 	debug.Log("converting %s to crypto: %s, storage: %s", name, cryptoBe, storageBe)
20 | 
21 | 	if err := sub.Convert(ctx, cryptoBe, storageBe, move); err != nil {
22 | 		return fmt.Errorf("conversion failed: %w", err)
23 | 	}
24 | 
25 | 	if name == "" {
26 | 		debug.Log("success. updating root path to %s", sub.Path())
27 | 
28 | 		return r.cfg.Set("", "mounts.path", sub.Path())
29 | 	}
30 | 
31 | 	debug.Log("success. updating path for %s to %s", name, sub.Path())
32 | 
33 | 	return r.cfg.Set("", "mounts."+name+".path", sub.Path())
34 | }
35 | 


--------------------------------------------------------------------------------
/internal/store/root/crypto.go:
--------------------------------------------------------------------------------
 1 | package root
 2 | 
 3 | import (
 4 | 	"context"
 5 | 
 6 | 	"github.com/gopasspw/gopass/internal/backend"
 7 | 	"github.com/gopasspw/gopass/pkg/debug"
 8 | )
 9 | 
10 | // Crypto returns the crypto backend.
11 | func (r *Store) Crypto(ctx context.Context, name string) backend.Crypto {
12 | 	sub, _ := r.getStore(name)
13 | 	if !sub.Valid() {
14 | 		debug.Log("Sub-Store not found for %s. Returning nil crypto backend", name)
15 | 
16 | 		return nil
17 | 	}
18 | 
19 | 	return sub.Crypto()
20 | }
21 | 


--------------------------------------------------------------------------------
/internal/store/root/crypto_test.go:
--------------------------------------------------------------------------------
 1 | package root
 2 | 
 3 | import (
 4 | 	"testing"
 5 | 
 6 | 	"github.com/fatih/color"
 7 | 	"github.com/gopasspw/gopass/internal/config"
 8 | 	"github.com/gopasspw/gopass/pkg/ctxutil"
 9 | 	"github.com/gopasspw/gopass/tests/gptest"
10 | 	"github.com/stretchr/testify/assert"
11 | 	"github.com/stretchr/testify/require"
12 | )
13 | 
14 | func TestCrypto(t *testing.T) {
15 | 	u := gptest.NewUnitTester(t)
16 | 
17 | 	ctx := config.NewContextInMemory()
18 | 	ctx = ctxutil.WithAlwaysYes(ctx, true)
19 | 	ctx = ctxutil.WithHidden(ctx, true)
20 | 	color.NoColor = true
21 | 
22 | 	rs, err := createRootStore(ctx, u)
23 | 	require.NoError(t, err)
24 | 
25 | 	assert.NotNil(t, rs.Crypto(ctx, ""))
26 | }
27 | 


--------------------------------------------------------------------------------
/internal/store/root/errors.go:
--------------------------------------------------------------------------------
 1 | package root
 2 | 
 3 | import "fmt"
 4 | 
 5 | // AlreadyMountedError is an error that is returned when
 6 | // a store is already mounted on a given mount point.
 7 | type AlreadyMountedError string
 8 | 
 9 | func (a AlreadyMountedError) Error() string {
10 | 	// important: must pass a as string(a)!
11 | 	return fmt.Sprintf("%s is already mounted", string(a))
12 | }
13 | 
14 | // NotInitializedError is an error that is returned when
15 | // a not initialized store should be mounted.
16 | type NotInitializedError struct {
17 | 	alias string
18 | 	path  string
19 | }
20 | 
21 | // Alias returns the store alias this error was generated for.
22 | func (n NotInitializedError) Alias() string { return n.alias }
23 | 
24 | // Path returns the store path this error was generated for.
25 | func (n NotInitializedError) Path() string { return n.path }
26 | 
27 | func (n NotInitializedError) Error() string {
28 | 	return fmt.Sprintf("password store %s is not initialized. Try gopass init --store %s --path %s", n.alias, n.alias, n.path)
29 | }
30 | 


--------------------------------------------------------------------------------
/internal/store/root/fsck.go:
--------------------------------------------------------------------------------
 1 | package root
 2 | 
 3 | import (
 4 | 	"context"
 5 | 	"errors"
 6 | 	"strings"
 7 | 
 8 | 	"github.com/gopasspw/gopass/internal/out"
 9 | 	"github.com/gopasspw/gopass/pkg/debug"
10 | )
11 | 
12 | // Fsck checks all stores/entries matching the given prefix.
13 | func (r *Store) Fsck(ctx context.Context, store, path string) error {
14 | 	var result []error
15 | 
16 | 	for alias, sub := range r.mounts {
17 | 		if sub == nil {
18 | 			continue
19 | 		}
20 | 
21 | 		if store != "" && alias != store {
22 | 			continue
23 | 		}
24 | 
25 | 		if path != "" && !strings.HasPrefix(path, alias+"/") {
26 | 			continue
27 | 		}
28 | 
29 | 		path = strings.TrimPrefix(path, alias+"/")
30 | 
31 | 		// check sub store
32 | 		debug.Log("Checking mount point %s", alias)
33 | 
34 | 		if err := sub.Fsck(ctx, path); err != nil {
35 | 			out.Errorf(ctx, "fsck failed on sub store %s: %s", alias, err)
36 | 			result = append(result, err)
37 | 		}
38 | 
39 | 		debug.Log("Checked mount point %s", alias)
40 | 	}
41 | 
42 | 	// check root store
43 | 	debug.Log("Checking root store")
44 | 	if err := r.store.Fsck(ctx, path); err != nil {
45 | 		out.Errorf(ctx, "fsck failed on root store: %s", err)
46 | 		result = append(result, err)
47 | 	}
48 | 
49 | 	debug.Log("Checked root store")
50 | 
51 | 	return errors.Join(result...)
52 | }
53 | 


--------------------------------------------------------------------------------
/internal/store/root/fsck_test.go:
--------------------------------------------------------------------------------
 1 | package root
 2 | 
 3 | import (
 4 | 	"testing"
 5 | 
 6 | 	"github.com/gopasspw/gopass/internal/config"
 7 | 	"github.com/gopasspw/gopass/pkg/ctxutil"
 8 | 	"github.com/gopasspw/gopass/tests/gptest"
 9 | 	"github.com/stretchr/testify/require"
10 | )
11 | 
12 | func TestFsck(t *testing.T) {
13 | 	u := gptest.NewUnitTester(t)
14 | 
15 | 	ctx := config.NewContextInMemory()
16 | 	ctx = ctxutil.WithAlwaysYes(ctx, true)
17 | 	ctx = ctxutil.WithHidden(ctx, true)
18 | 
19 | 	rs, err := createRootStore(ctx, u)
20 | 	require.NoError(t, err)
21 | 	require.NotNil(t, rs)
22 | 
23 | 	require.NoError(t, rs.Fsck(ctx, "", ""))
24 | }
25 | 


--------------------------------------------------------------------------------
/internal/store/root/init_test.go:
--------------------------------------------------------------------------------
 1 | package root
 2 | 
 3 | import (
 4 | 	"testing"
 5 | 
 6 | 	"github.com/gopasspw/gopass/internal/backend"
 7 | 	"github.com/gopasspw/gopass/internal/config"
 8 | 	"github.com/gopasspw/gopass/pkg/ctxutil"
 9 | 	"github.com/gopasspw/gopass/tests/gptest"
10 | 	"github.com/stretchr/testify/assert"
11 | 	"github.com/stretchr/testify/require"
12 | )
13 | 
14 | func TestInit(t *testing.T) {
15 | 	u := gptest.NewUnitTester(t)
16 | 
17 | 	ctx := config.NewContextInMemory()
18 | 	ctx = ctxutil.WithAlwaysYes(ctx, true)
19 | 	ctx = ctxutil.WithHidden(ctx, true)
20 | 	ctx = backend.WithCryptoBackend(ctx, backend.Plain)
21 | 
22 | 	cfg := config.NewInMemory()
23 | 	require.NoError(t, cfg.SetPath(u.StoreDir("rs")))
24 | 	rs := New(cfg)
25 | 
26 | 	inited, err := rs.IsInitialized(ctx)
27 | 	require.NoError(t, err)
28 | 	assert.False(t, inited)
29 | 	require.NoError(t, rs.Init(ctx, "", u.StoreDir("rs"), "0xDEADBEEF"))
30 | 
31 | 	inited, err = rs.IsInitialized(ctx)
32 | 	require.NoError(t, err)
33 | 	assert.True(t, inited)
34 | 	require.NoError(t, rs.Init(ctx, "rs2", u.StoreDir("rs2"), "0xDEADBEEF"))
35 | }
36 | 


--------------------------------------------------------------------------------
/internal/store/root/link.go:
--------------------------------------------------------------------------------
 1 | package root
 2 | 
 3 | import (
 4 | 	"context"
 5 | 	"fmt"
 6 | )
 7 | 
 8 | // Link creates a symlink.
 9 | func (r *Store) Link(ctx context.Context, from, to string) error {
10 | 	subFrom, fName := r.getStore(from)
11 | 	subTo, tName := r.getStore(to)
12 | 
13 | 	if !subFrom.Equals(subTo) {
14 | 		return fmt.Errorf("sylinks across stores are not supported")
15 | 	}
16 | 
17 | 	return subFrom.Link(ctx, fName, tName)
18 | }
19 | 


--------------------------------------------------------------------------------
/internal/store/root/list_test.go:
--------------------------------------------------------------------------------
 1 | package root
 2 | 
 3 | import (
 4 | 	"testing"
 5 | 
 6 | 	"github.com/fatih/color"
 7 | 	"github.com/gopasspw/gopass/internal/config"
 8 | 	"github.com/gopasspw/gopass/internal/tree"
 9 | 	"github.com/gopasspw/gopass/pkg/ctxutil"
10 | 	"github.com/gopasspw/gopass/tests/gptest"
11 | 	"github.com/stretchr/testify/assert"
12 | 	"github.com/stretchr/testify/require"
13 | )
14 | 
15 | func TestList(t *testing.T) {
16 | 	u := gptest.NewUnitTester(t)
17 | 
18 | 	ctx := config.NewContextInMemory()
19 | 	ctx = ctxutil.WithAlwaysYes(ctx, true)
20 | 	ctx = ctxutil.WithHidden(ctx, true)
21 | 	color.NoColor = true
22 | 
23 | 	rs, err := createRootStore(ctx, u)
24 | 	require.NoError(t, err)
25 | 
26 | 	es, err := rs.List(ctx, tree.INF)
27 | 	require.NoError(t, err)
28 | 	assert.Equal(t, []string{"foo"}, es)
29 | 
30 | 	sd, err := rs.HasSubDirs(ctx, "foo")
31 | 	require.NoError(t, err)
32 | 	assert.False(t, sd)
33 | 
34 | 	str, err := rs.Format(ctx, -1)
35 | 	require.NoError(t, err)
36 | 	assert.Equal(t, `gopass
37 | └── foo
38 | `, str)
39 | }
40 | 


--------------------------------------------------------------------------------
/internal/store/root/rcs_test.go:
--------------------------------------------------------------------------------
 1 | package root
 2 | 
 3 | import (
 4 | 	"testing"
 5 | 
 6 | 	"github.com/fatih/color"
 7 | 	"github.com/gopasspw/gopass/internal/config"
 8 | 	"github.com/gopasspw/gopass/pkg/ctxutil"
 9 | 	"github.com/gopasspw/gopass/tests/gptest"
10 | 	"github.com/stretchr/testify/assert"
11 | 	"github.com/stretchr/testify/require"
12 | )
13 | 
14 | func TestRCS(t *testing.T) {
15 | 	u := gptest.NewUnitTester(t)
16 | 
17 | 	ctx := config.NewContextInMemory()
18 | 	ctx = ctxutil.WithAlwaysYes(ctx, true)
19 | 	ctx = ctxutil.WithHidden(ctx, true)
20 | 	color.NoColor = true
21 | 
22 | 	rs, err := createRootStore(ctx, u)
23 | 	require.NoError(t, err)
24 | 
25 | 	require.Error(t, rs.RCSStatus(ctx, ""))
26 | 
27 | 	revs, err := rs.ListRevisions(ctx, "foo")
28 | 	require.Error(t, err)
29 | 	assert.Len(t, revs, 1)
30 | }
31 | 


--------------------------------------------------------------------------------
/internal/store/root/read.go:
--------------------------------------------------------------------------------
 1 | package root
 2 | 
 3 | import (
 4 | 	"context"
 5 | 	"fmt"
 6 | 
 7 | 	"github.com/gopasspw/gopass/pkg/ctxutil"
 8 | 	"github.com/gopasspw/gopass/pkg/gopass"
 9 | )
10 | 
11 | // Get returns the plaintext of a single key.
12 | func (r *Store) Get(ctx context.Context, name string) (gopass.Secret, error) {
13 | 	store, name := r.getStore(name)
14 | 
15 | 	sec, err := store.Get(ctx, name)
16 | 	if err != nil {
17 | 		return sec, err
18 | 	}
19 | 
20 | 	if ref, ok := sec.Ref(); ctxutil.IsFollowRef(ctx) && ok {
21 | 		refSec, err := store.Get(ctx, ref)
22 | 		if err != nil {
23 | 			return sec, fmt.Errorf("failed to read reference %s by %s: %w", ref, name, err)
24 | 		}
25 | 
26 | 		sec.SetPassword(refSec.Password())
27 | 	}
28 | 
29 | 	return sec, nil
30 | }
31 | 


--------------------------------------------------------------------------------
/internal/store/root/read_test.go:
--------------------------------------------------------------------------------
 1 | package root
 2 | 
 3 | import (
 4 | 	"testing"
 5 | 
 6 | 	"github.com/gopasspw/gopass/internal/config"
 7 | 	"github.com/gopasspw/gopass/pkg/ctxutil"
 8 | 	"github.com/gopasspw/gopass/tests/gptest"
 9 | 	"github.com/stretchr/testify/require"
10 | )
11 | 
12 | func TestGet(t *testing.T) {
13 | 	u := gptest.NewUnitTester(t)
14 | 
15 | 	ctx := config.NewContextInMemory()
16 | 	ctx = ctxutil.WithAlwaysYes(ctx, true)
17 | 	ctx = ctxutil.WithHidden(ctx, true)
18 | 
19 | 	rs, err := createRootStore(ctx, u)
20 | 	require.NoError(t, err)
21 | 
22 | 	_, err = rs.Get(ctx, "foo")
23 | 	require.NoError(t, err)
24 | }
25 | 


--------------------------------------------------------------------------------
/internal/store/root/recipients_test.go:
--------------------------------------------------------------------------------
 1 | package root
 2 | 
 3 | import (
 4 | 	"testing"
 5 | 
 6 | 	"github.com/fatih/color"
 7 | 	"github.com/gopasspw/gopass/internal/config"
 8 | 	"github.com/gopasspw/gopass/pkg/ctxutil"
 9 | 	"github.com/gopasspw/gopass/tests/gptest"
10 | 	"github.com/stretchr/testify/assert"
11 | 	"github.com/stretchr/testify/require"
12 | )
13 | 
14 | func TestRecipients(t *testing.T) {
15 | 	u := gptest.NewUnitTester(t)
16 | 
17 | 	ctx := config.NewContextInMemory()
18 | 	ctx = ctxutil.WithAlwaysYes(ctx, true)
19 | 	ctx = ctxutil.WithHidden(ctx, true)
20 | 	color.NoColor = true
21 | 
22 | 	rs, err := createRootStore(ctx, u)
23 | 	require.NoError(t, err)
24 | 
25 | 	assert.Equal(t, []string{"0xDEADBEEF"}, rs.ListRecipients(ctx, ""))
26 | 	rt, err := rs.RecipientsTree(ctx, false)
27 | 	require.NoError(t, err)
28 | 	assert.Equal(t, "gopass\n└── 0xDEADBEEF\n", rt.Format(0))
29 | }
30 | 


--------------------------------------------------------------------------------
/internal/store/root/templates_test.go:
--------------------------------------------------------------------------------
 1 | package root
 2 | 
 3 | import (
 4 | 	"testing"
 5 | 
 6 | 	"github.com/fatih/color"
 7 | 	"github.com/gopasspw/gopass/internal/config"
 8 | 	"github.com/gopasspw/gopass/pkg/ctxutil"
 9 | 	"github.com/gopasspw/gopass/tests/gptest"
10 | 	"github.com/stretchr/testify/assert"
11 | 	"github.com/stretchr/testify/require"
12 | )
13 | 
14 | func TestTemplate(t *testing.T) {
15 | 	u := gptest.NewUnitTester(t)
16 | 
17 | 	ctx := config.NewContextInMemory()
18 | 	ctx = ctxutil.WithAlwaysYes(ctx, true)
19 | 	ctx = ctxutil.WithHidden(ctx, true)
20 | 	color.NoColor = true
21 | 
22 | 	rs, err := createRootStore(ctx, u)
23 | 	require.NoError(t, err)
24 | 
25 | 	tt, err := rs.TemplateTree(ctx)
26 | 	require.NoError(t, err)
27 | 	assert.Equal(t, "gopass\n", tt.Format(0))
28 | 
29 | 	assert.False(t, rs.HasTemplate(ctx, "foo"))
30 | 	_, err = rs.GetTemplate(ctx, "foo")
31 | 	require.Error(t, err)
32 | 	require.Error(t, rs.RemoveTemplate(ctx, "foo"))
33 | 
34 | 	require.NoError(t, rs.SetTemplate(ctx, "foo", []byte("foobar")))
35 | 	assert.True(t, rs.HasTemplate(ctx, "foo"))
36 | 
37 | 	b, err := rs.GetTemplate(ctx, "foo")
38 | 	require.NoError(t, err)
39 | 	assert.Equal(t, "foobar", string(b))
40 | 
41 | 	_, b, found := rs.LookupTemplate(ctx, "foo/bar")
42 | 	assert.True(t, found)
43 | 	assert.Equal(t, "foobar", string(b))
44 | 	require.NoError(t, rs.RemoveTemplate(ctx, "foo"))
45 | }
46 | 


--------------------------------------------------------------------------------
/internal/store/root/write.go:
--------------------------------------------------------------------------------
 1 | package root
 2 | 
 3 | import (
 4 | 	"context"
 5 | 
 6 | 	"github.com/gopasspw/gopass/pkg/gopass"
 7 | )
 8 | 
 9 | // Set encodes and write the ciphertext of one entry to disk.
10 | func (r *Store) Set(ctx context.Context, name string, sec gopass.Byter) error {
11 | 	store, name := r.getStore(name)
12 | 
13 | 	return store.Set(ctx, name, sec)
14 | }
15 | 


--------------------------------------------------------------------------------
/internal/store/root/write_test.go:
--------------------------------------------------------------------------------
 1 | package root
 2 | 
 3 | import (
 4 | 	"testing"
 5 | 
 6 | 	"github.com/gopasspw/gopass/internal/config"
 7 | 	"github.com/gopasspw/gopass/pkg/ctxutil"
 8 | 	"github.com/gopasspw/gopass/pkg/gopass/secrets"
 9 | 	"github.com/gopasspw/gopass/tests/gptest"
10 | 	"github.com/stretchr/testify/require"
11 | )
12 | 
13 | func TestSet(t *testing.T) {
14 | 	u := gptest.NewUnitTester(t)
15 | 
16 | 	ctx := config.NewContextInMemory()
17 | 	ctx = ctxutil.WithAlwaysYes(ctx, true)
18 | 	ctx = ctxutil.WithHidden(ctx, true)
19 | 
20 | 	rs, err := createRootStore(ctx, u)
21 | 	require.NoError(t, err)
22 | 
23 | 	sec := secrets.NewAKV()
24 | 	sec.SetPassword("foo")
25 | 	_, err = sec.Write([]byte("bar"))
26 | 	require.NoError(t, err)
27 | 	require.NoError(t, rs.Set(ctx, "zab", sec))
28 | 
29 | 	err = rs.Set(ctx, "zab2", sec)
30 | 	require.NoError(t, err)
31 | }
32 | 


--------------------------------------------------------------------------------
/internal/store/sort.go:
--------------------------------------------------------------------------------
 1 | package store
 2 | 
 3 | import "strings"
 4 | 
 5 | // ByPathLen sorts mount points by the number of level / path separators.
 6 | type ByPathLen []string
 7 | 
 8 | func (s ByPathLen) Len() int { return len(s) }
 9 | 
10 | func (s ByPathLen) Less(i, j int) bool {
11 | 	return strings.Count(s[i], "/") < strings.Count(s[j], "/")
12 | }
13 | 
14 | func (s ByPathLen) Swap(i, j int) {
15 | 	s[i], s[j] = s[j], s[i]
16 | }
17 | 
18 | // ByLen is a list of mount points (string) that can be sorted by length.
19 | type ByLen []string
20 | 
21 | // Len return the number of mount points in the list.
22 | func (s ByLen) Len() int { return len(s) }
23 | 
24 | // Less returns if a Mount point is shorter than another.
25 | func (s ByLen) Less(i, j int) bool { return len(s[i]) > len(s[j]) }
26 | 
27 | // Swap Mount Point in the list of Mount Points.
28 | func (s ByLen) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
29 | 


--------------------------------------------------------------------------------
/internal/store/store.go:
--------------------------------------------------------------------------------
 1 | // Package store provides the interface for the gopass password store.
 2 | // It defines the methods and types used to interact with the password store.
 3 | package store
 4 | 
 5 | import (
 6 | 	"context"
 7 | )
 8 | 
 9 | // RecipientCallback is a callback to verify the list of recipients.
10 | type RecipientCallback func(context.Context, string, []string) ([]string, error)
11 | 
12 | // ImportCallback is a callback to ask the user if they want to import
13 | // a certain recipients public key into their keystore.
14 | type ImportCallback func(context.Context, string, []string) bool
15 | 
16 | // FsckCallback is a callback to ask the user to confirm certain fsck
17 | // corrective actions.
18 | type FsckCallback func(context.Context, string) bool
19 | 


--------------------------------------------------------------------------------
/internal/tpl/template.go:
--------------------------------------------------------------------------------
 1 | // Package tpl provides functions to handle templates.
 2 | // It can parse templates from various formats and generate output for them.
 3 | package tpl
 4 | 
 5 | import (
 6 | 	"bytes"
 7 | 	"context"
 8 | 	"fmt"
 9 | 	"path/filepath"
10 | 	"text/template"
11 | 
12 | 	"github.com/gopasspw/gopass/pkg/gopass"
13 | )
14 | 
15 | type kvstore interface {
16 | 	Get(context.Context, string) (gopass.Secret, error)
17 | }
18 | 
19 | type payload struct {
20 | 	Dir     string
21 | 	DirName string
22 | 	Path    string
23 | 	Name    string
24 | 	Content string
25 | }
26 | 
27 | // Execute executes the given template.
28 | func Execute(ctx context.Context, tpl, name string, content []byte, s kvstore) ([]byte, error) {
29 | 	funcs := funcMap(ctx, s)
30 | 
31 | 	dir := filepath.Dir(name)
32 | 
33 | 	pl := payload{
34 | 		Dir:     dir,
35 | 		DirName: filepath.Base(dir),
36 | 		Path:    name,
37 | 		Name:    filepath.Base(name),
38 | 		Content: string(content),
39 | 	}
40 | 
41 | 	tmpl, err := template.New(tpl).Funcs(funcs).Parse(tpl)
42 | 	if err != nil {
43 | 		return []byte{}, fmt.Errorf("failed to parse template: %w", err)
44 | 	}
45 | 
46 | 	buff := &bytes.Buffer{}
47 | 	if err := tmpl.Execute(buff, pl); err != nil {
48 | 		return []byte{}, fmt.Errorf("failed to execute template: %w", err)
49 | 	}
50 | 
51 | 	return buff.Bytes(), nil
52 | }
53 | 


--------------------------------------------------------------------------------
/internal/updater/access_others.go:
--------------------------------------------------------------------------------
 1 | //go:build !windows
 2 | // +build !windows
 3 | 
 4 | package updater
 5 | 
 6 | import "golang.org/x/sys/unix"
 7 | 
 8 | func canWrite(path string) error {
 9 | 	return unix.Access(path, unix.W_OK) //nolint:wrapcheck
10 | }
11 | 
12 | func removeOldBinary(dir, dest string) error {
13 | 	// no need, os.Rename will replace the destination
14 | 	return nil
15 | }
16 | 


--------------------------------------------------------------------------------
/internal/updater/access_windows.go:
--------------------------------------------------------------------------------
 1 | //go:build windows
 2 | // +build windows
 3 | 
 4 | package updater
 5 | 
 6 | import (
 7 | 	"fmt"
 8 | 	"os"
 9 | 	"path/filepath"
10 | )
11 | 
12 | func canWrite(path string) error {
13 | 	return nil
14 | }
15 | 
16 | // Windows won't allow us to remove the binary that's currently being executed.
17 | // So rename the binary and then the updater should be able to write it's
18 | // update to the correct location.
19 | //
20 | // See https://stackoverflow.com/a/459860
21 | func removeOldBinary(dir, dest string) error {
22 | 	bakFile := filepath.Join(dir, filepath.Base(dest)+".bak")
23 | 	// check if the bakup file already exists
24 | 	if _, err := os.Stat(bakFile); err == nil {
25 | 		// ... then remove it
26 | 		_ = os.Remove(bakFile)
27 | 	}
28 | 	// we can't remove the currently running binary, but should be able to
29 | 	// rename it.
30 | 	if err := os.Rename(dest, bakFile); err != nil {
31 | 		return fmt.Errorf("unable to rename %s to %s: %w", dest, bakFile, err)
32 | 	}
33 | 
34 | 	return nil
35 | }
36 | 


--------------------------------------------------------------------------------
/main_unix.go:
--------------------------------------------------------------------------------
 1 | //go:build !windows
 2 | // +build !windows
 3 | 
 4 | package main
 5 | 
 6 | import (
 7 | 	"os/signal"
 8 | 	"syscall"
 9 | )
10 | 
11 | func init() {
12 | 	// workaround for https://github.com/golang/go/issues/37942
13 | 	signal.Ignore(syscall.SIGURG)
14 | }
15 | 


--------------------------------------------------------------------------------
/pkg/appdir/appdir_test.go:
--------------------------------------------------------------------------------
 1 | package appdir
 2 | 
 3 | import (
 4 | 	"testing"
 5 | 
 6 | 	"github.com/stretchr/testify/assert"
 7 | )
 8 | 
 9 | func TestUserHome(t *testing.T) {
10 | 	td := t.TempDir()
11 | 	t.Setenv("GOPASS_HOMEDIR", td)
12 | 
13 | 	assert.Equal(t, td, UserHome())
14 | }
15 | 


--------------------------------------------------------------------------------
/pkg/appdir/appdir_windows.go:
--------------------------------------------------------------------------------
 1 | package appdir
 2 | 
 3 | import (
 4 | 	"os"
 5 | 	"path/filepath"
 6 | )
 7 | 
 8 | // UserConfig returns the user's config directory.
 9 | // It uses the APPDATA environment variable on Windows.
10 | // The GOPASS_HOMEDIR environment variable can be used to override the base path.
11 | func (a *Appdir) UserConfig() string {
12 | 	if hd := os.Getenv("GOPASS_HOMEDIR"); hd != "" {
13 | 		return filepath.Join(hd, ".config", a.name)
14 | 	}
15 | 
16 | 	return filepath.Join(os.Getenv("APPDATA"), a.name)
17 | }
18 | 
19 | // UserCache returns the user's cache directory.
20 | // It uses the LOCALAPPDATA environment variable on Windows.
21 | // The GOPASS_HOMEDIR environment variable can be used to override the base path.
22 | func (a *Appdir) UserCache() string {
23 | 	if hd := os.Getenv("GOPASS_HOMEDIR"); hd != "" {
24 | 		return filepath.Join(hd, ".cache", a.name)
25 | 	}
26 | 
27 | 	return filepath.Join(os.Getenv("LOCALAPPDATA"), a.name)
28 | }
29 | 
30 | // UserData returns the user's data directory.
31 | // It uses the LOCALAPPDATA environment variable on Windows.
32 | // The GOPASS_HOMEDIR environment variable can be used to override the base path.
33 | func (a *Appdir) UserData() string {
34 | 	if hd := os.Getenv("GOPASS_HOMEDIR"); hd != "" {
35 | 		return filepath.Join(hd, ".local", "share", a.name)
36 | 	}
37 | 	return filepath.Join(os.Getenv("LOCALAPPDATA"), a.name)
38 | }
39 | 


--------------------------------------------------------------------------------
/pkg/appdir/runtime_windows.go:
--------------------------------------------------------------------------------
 1 | package appdir
 2 | 
 3 | import (
 4 | 	"os"
 5 | 	"path/filepath"
 6 | )
 7 | 
 8 | // UserRuntime returns the users runtime dir
 9 | func (a *Appdir) UserRuntime() string {
10 | 	if hd := os.Getenv("GOPASS_HOMEDIR"); hd != "" {
11 | 		return filepath.Join(hd, ".run")
12 | 	}
13 | 
14 | 	return filepath.Join(os.Getenv("LOCALAPPDATA"), a.name)
15 | }
16 | 
17 | // UserRuntime returns the users runtime dir.
18 | func UserRuntime() string {
19 | 	return DefaultAppdir.UserRuntime()
20 | }
21 | 


--------------------------------------------------------------------------------
/pkg/appdir/runtime_xdg.go:
--------------------------------------------------------------------------------
 1 | //go:build !windows
 2 | // +build !windows
 3 | 
 4 | package appdir
 5 | 
 6 | import (
 7 | 	"os"
 8 | 	"path/filepath"
 9 | )
10 | 
11 | // UserRuntime returns the users runtime dir.
12 | func (a *Appdir) UserRuntime() string {
13 | 	if hd := os.Getenv("GOPASS_HOMEDIR"); hd != "" {
14 | 		return filepath.Join(hd, ".run")
15 | 	}
16 | 
17 | 	base := os.Getenv("XDG_RUNTIME_DIR")
18 | 	if base == "" {
19 | 		base = filepath.Join(os.Getenv("HOME"), ".run")
20 | 	}
21 | 
22 | 	return filepath.Join(base, a.name)
23 | }
24 | 
25 | // UserRuntime returns the users runtime dir.
26 | func UserRuntime() string {
27 | 	return DefaultAppdir.UserRuntime()
28 | }
29 | 


--------------------------------------------------------------------------------
/pkg/clipboard/clipboard_windows.go:
--------------------------------------------------------------------------------
 1 | //go:build windows
 2 | // +build windows
 3 | 
 4 | package clipboard
 5 | 
 6 | import (
 7 | 	"context"
 8 | 	"os"
 9 | 	"os/exec"
10 | 	"strconv"
11 | 
12 | 	"github.com/gopasspw/gopass/internal/config"
13 | 	"github.com/gopasspw/gopass/internal/pwschemes/argon2id"
14 | )
15 | 
16 | // clearClip will spawn a copy of gopass that waits in a detached background
17 | // process group until the timeout is expired. It will then compare the contents
18 | // of the clipboard and erase it if it still contains the data gopass copied
19 | // to it.
20 | func clearClip(ctx context.Context, name string, content []byte, timeout int) error {
21 | 	hash, err := argon2id.Generate(string(content), 0)
22 | 	if err != nil {
23 | 		return err
24 | 	}
25 | 
26 | 	cmd := exec.CommandContext(ctx, os.Args[0], "unclip", "--timeout", strconv.Itoa(timeout))
27 | 	cmd.Env = append(os.Environ(), "GOPASS_UNCLIP_NAME="+name)
28 | 	cmd.Env = append(cmd.Env, "GOPASS_UNCLIP_CHECKSUM="+hash)
29 | 	if !config.Bool(ctx, "core.notifications") {
30 | 		cmd.Env = append(cmd.Env, "GOPASS_NO_NOTIFY=true")
31 | 	}
32 | 	return cmd.Start()
33 | }
34 | 
35 | func walkFn(int, func(int)) {}
36 | 


--------------------------------------------------------------------------------
/pkg/clipboard/kill_others.go:
--------------------------------------------------------------------------------
1 | //go:build !darwin && !linux && !solaris && !windows && !freebsd
2 | // +build !darwin,!linux,!solaris,!windows,!freebsd
3 | 
4 | package clipboard
5 | 
6 | func killPrecedessors() error {
7 | 	return nil
8 | }
9 | 


--------------------------------------------------------------------------------
/pkg/clipboard/kill_ps.go:
--------------------------------------------------------------------------------
 1 | //go:build darwin || (freebsd && amd64) || linux || solaris || windows || (freebsd && arm) || (freebsd && arm64)
 2 | // +build darwin freebsd,amd64 linux solaris windows freebsd,arm freebsd,arm64
 3 | 
 4 | package clipboard
 5 | 
 6 | import (
 7 | 	"fmt"
 8 | 
 9 | 	ps "github.com/mitchellh/go-ps"
10 | )
11 | 
12 | // killPrecedessors will kill any previous "gopass unclip" invocations to avoid
13 | // erasing the clipboard prematurely in case the the same content is copied to
14 | // the clipboard repeatedly.
15 | func killPrecedessors() error {
16 | 	procs, err := ps.Processes()
17 | 	if err != nil {
18 | 		return fmt.Errorf("failed to list processes: %w", err)
19 | 	}
20 | 
21 | 	for _, proc := range procs {
22 | 		walkFn(proc.Pid(), killProc)
23 | 	}
24 | 
25 | 	return nil
26 | }
27 | 


--------------------------------------------------------------------------------
/pkg/clipboard/unclip_linux.go:
--------------------------------------------------------------------------------
 1 | //go:build linux
 2 | // +build linux
 3 | 
 4 | package clipboard
 5 | 
 6 | import (
 7 | 	"context"
 8 | 	"fmt"
 9 | 	"strings"
10 | 
11 | 	"github.com/godbus/dbus/v5"
12 | )
13 | 
14 | func clearClipboardHistory(ctx context.Context) error {
15 | 	conn, err := dbus.SessionBus()
16 | 	if err != nil {
17 | 		return fmt.Errorf("failed to connect to session bus: %w", err)
18 | 	}
19 | 
20 | 	obj := conn.Object("org.kde.klipper", "/klipper")
21 | 	call := obj.Call("org.kde.klipper.klipper.clearClipboardHistory", 0)
22 | 
23 | 	if call.Err != nil {
24 | 		if strings.HasPrefix(call.Err.Error(), "The name org.kde.klipper was not provided") {
25 | 			return nil
26 | 		}
27 | 
28 | 		if strings.HasPrefix(call.Err.Error(), "The name is not activatable") {
29 | 			return nil
30 | 		}
31 | 
32 | 		return call.Err
33 | 	}
34 | 
35 | 	return nil
36 | }
37 | 


--------------------------------------------------------------------------------
/pkg/clipboard/unclip_others.go:
--------------------------------------------------------------------------------
 1 | //go:build !linux
 2 | // +build !linux
 3 | 
 4 | package clipboard
 5 | 
 6 | import "context"
 7 | 
 8 | func clearClipboardHistory(ctx context.Context) error {
 9 | 	return nil
10 | }
11 | 


--------------------------------------------------------------------------------
/pkg/clipboard/unclip_test.go:
--------------------------------------------------------------------------------
 1 | package clipboard
 2 | 
 3 | import (
 4 | 	"bytes"
 5 | 	"os"
 6 | 	"testing"
 7 | 
 8 | 	"github.com/gopasspw/gopass/internal/config"
 9 | 	"github.com/gopasspw/gopass/internal/out"
10 | 	"github.com/gopasspw/gopass/pkg/ctxutil"
11 | 	"github.com/stretchr/testify/assert"
12 | 	"github.com/stretchr/testify/require"
13 | )
14 | 
15 | func TestNotExistingClipboardClearCommand(t *testing.T) {
16 | 	ctx := config.NewContextInMemory()
17 | 	ctx = ctxutil.WithAlwaysYes(ctx, true)
18 | 
19 | 	t.Setenv("GOPASS_CLIPBOARD_CLEAR_CMD", "not_existing_command")
20 | 
21 | 	maybeErr := Clear(ctx, "", "", false)
22 | 	require.Error(t, maybeErr)
23 | 	assert.Contains(t, maybeErr.Error(), "\"not_existing_command\": executable file not found in")
24 | }
25 | 
26 | func TestUnclip(t *testing.T) {
27 | 	t.Parallel()
28 | 
29 | 	ctx := config.NewContextInMemory()
30 | 	ctx = ctxutil.WithAlwaysYes(ctx, true)
31 | 
32 | 	buf := &bytes.Buffer{}
33 | 	out.Stdout = buf
34 | 
35 | 	defer func() {
36 | 		out.Stdout = os.Stdout
37 | 	}()
38 | 
39 | 	require.EqualError(t, Clear(ctx, "", "", false), ErrNotSupported.Error())
40 | }
41 | 


--------------------------------------------------------------------------------
/pkg/debug/doc.go:
--------------------------------------------------------------------------------
1 | // Package debug provides logging of debug information.
2 | //
3 | // This package is heavily based on github.com/restic/restic/internal/debug
4 | package debug
5 | 


--------------------------------------------------------------------------------
/pkg/fsutil/umask.go:
--------------------------------------------------------------------------------
 1 | package fsutil
 2 | 
 3 | import (
 4 | 	"os"
 5 | 	"strconv"
 6 | )
 7 | 
 8 | // Umask extracts the umask from the environment variables.
 9 | // It checks for GOPASS_UMASK and PASSWORD_STORE_UMASK.
10 | // If neither is set, it returns the default umask of 0o77.
11 | func Umask() int {
12 | 	for _, en := range []string{"GOPASS_UMASK", "PASSWORD_STORE_UMASK"} {
13 | 		um := os.Getenv(en)
14 | 		if um == "" {
15 | 			continue
16 | 		}
17 | 
18 | 		iv, err := strconv.ParseInt(um, 8, 32)
19 | 		if err != nil {
20 | 			continue
21 | 		}
22 | 
23 | 		if iv >= 0 && iv <= 0o777 {
24 | 			return int(iv)
25 | 		}
26 | 	}
27 | 
28 | 	return 0o77
29 | }
30 | 


--------------------------------------------------------------------------------
/pkg/fsutil/umask_test.go:
--------------------------------------------------------------------------------
 1 | package fsutil
 2 | 
 3 | import (
 4 | 	"testing"
 5 | 
 6 | 	"github.com/stretchr/testify/assert"
 7 | )
 8 | 
 9 | func TestUmask(t *testing.T) {
10 | 	for _, vn := range []string{"GOPASS_UMASK", "PASSWORD_STORE_UMASK"} {
11 | 		for in, out := range map[string]int{
12 | 			"002":      0o2,
13 | 			"0777":     0o777,
14 | 			"000":      0,
15 | 			"07557575": 0o77,
16 | 		} {
17 | 			t.Run(vn, func(t *testing.T) {
18 | 				t.Setenv(vn, in)
19 | 				assert.Equal(t, out, Umask())
20 | 			})
21 | 		}
22 | 	}
23 | }
24 | 


--------------------------------------------------------------------------------
/pkg/gopass/doc.go:
--------------------------------------------------------------------------------
 1 | // Package gopass contains the public gopass API.
 2 | //
 3 | // WARNING: This package is incomplete and unstable. DO NOT USE!
 4 | //
 5 | // Feel free to report feedback on API design and missing features but please
 6 | // note that bug reports will be silently ignored and the API WILL CHANGE
 7 | // WITHOUT NOTICE until this note is gone.
 8 | //
 9 | // If you want to try it anyway please look at the following examples:
10 | // * https://github.com/gopasspw/gopass-hibp
11 | // * https://github.com/gopasspw/gopass-jsonapi
12 | // * https://github.com/gopasspw/git-credential-gopass
13 | // * https://github.com/gopasspw/gopass-summon-provider
14 | package gopass
15 | 


--------------------------------------------------------------------------------
/pkg/gopass/secrets/error.go:
--------------------------------------------------------------------------------
 1 | package secrets
 2 | 
 3 | // PermanentError signals that parsing should not attempt other formats.
 4 | type PermanentError struct {
 5 | 	Err error
 6 | }
 7 | 
 8 | // Error returns the underlying error.
 9 | func (p *PermanentError) Error() string {
10 | 	return p.Err.Error()
11 | }
12 | 


--------------------------------------------------------------------------------
/pkg/gopass/secrets/ident.go:
--------------------------------------------------------------------------------
1 | package secrets
2 | 
3 | const (
4 | 	// Ident is the header of the deprecated Gopass MIME secret.
5 | 	// It is used to identify a secret as a gopass secret.
6 | 	Ident = "GOPASS-SECRET-1.0"
7 | )
8 | 


--------------------------------------------------------------------------------
/pkg/gopass/secrets/new.go:
--------------------------------------------------------------------------------
 1 | package secrets
 2 | 
 3 | import (
 4 | 	"github.com/gopasspw/gopass/pkg/gopass"
 5 | )
 6 | 
 7 | // New creates a new secret.
 8 | // It returns a new AKV secret.
 9 | func New() gopass.Secret { //nolint:ireturn
10 | 	return NewAKV()
11 | }
12 | 


--------------------------------------------------------------------------------
/pkg/gopass/secrets/secparse/.gitignore:
--------------------------------------------------------------------------------
1 | testdata/


--------------------------------------------------------------------------------
/pkg/otp/screenshot_others.go:
--------------------------------------------------------------------------------
 1 | //go:build !((arm || arm64 || amd64 || 386) && (linux || windows || (cgo && darwin) || freebsd || netbsd))
 2 | 
 3 | package otp
 4 | 
 5 | import (
 6 | 	"context"
 7 | 	"fmt"
 8 | )
 9 | 
10 | // ParseScreen will attempt to parse all available screen and will look for otpauth QR codes. It returns the first one
11 | // it has found.
12 | func ParseScreen(ctx context.Context) (string, error) {
13 | 	return "", fmt.Errorf("not supported on your platform")
14 | }
15 | 


--------------------------------------------------------------------------------
/pkg/passkey/passkey_test.go:
--------------------------------------------------------------------------------
 1 | package passkey_test
 2 | 
 3 | import (
 4 | 	"crypto/ecdsa"
 5 | 	"crypto/sha256"
 6 | 	"encoding/base64"
 7 | 	"testing"
 8 | 
 9 | 	"github.com/gopasspw/gopass/pkg/passkey"
10 | 	"github.com/stretchr/testify/assert"
11 | 	"github.com/stretchr/testify/require"
12 | )
13 | 
14 | var flags passkey.CredentialFlags = passkey.CredentialFlags{
15 | 	UserPresent:     true,
16 | 	UserVerified:    true,
17 | 	AttestationData: false,
18 | 	ExtensionData:   false,
19 | }
20 | 
21 | func TestCreate(t *testing.T) {
22 | 	cred, err := passkey.CreateCredential("test.com", "user", flags)
23 | 	require.NoError(t, err)
24 | 	assert.Equal(t, "test.com", cred.Rp)
25 | 	assert.Equal(t, uint32(0), cred.Counter)
26 | }
27 | 
28 | func TestGetAssertion(t *testing.T) {
29 | 	cred, err := passkey.CreateCredential("test.com", "user", flags)
30 | 	require.NoError(t, err)
31 | 
32 | 	rsp, err := cred.GetAssertion(base64.RawURLEncoding.EncodeToString([]byte("test_challenge")), "test")
33 | 	require.NoError(t, err)
34 | 
35 | 	// Verify signature
36 | 	clientDataHash := sha256.Sum256(rsp.ClientDataJSON)
37 | 
38 | 	authData := rsp.AuthenticatorData
39 | 	require.NoError(t, err)
40 | 
41 | 	message := sha256.Sum256(append(authData[:], clientDataHash[:]...))
42 | 	assert.True(t, ecdsa.VerifyASN1(&cred.SecretKey.PublicKey, message[:], rsp.Signature))
43 | }
44 | 


--------------------------------------------------------------------------------
/pkg/pinentry/cli/fallback_test.go:
--------------------------------------------------------------------------------
 1 | package cli
 2 | 
 3 | import (
 4 | 	"context"
 5 | 	"testing"
 6 | 
 7 | 	"github.com/gopasspw/gopass/pkg/termio"
 8 | 	"github.com/stretchr/testify/assert"
 9 | 	"github.com/stretchr/testify/require"
10 | )
11 | 
12 | func TestNew(t *testing.T) {
13 | 	client := New()
14 | 	assert.NotNil(t, client)
15 | 	assert.False(t, client.repeat)
16 | }
17 | 
18 | func TestSet(t *testing.T) {
19 | 	client := New()
20 | 
21 | 	err := client.Set("REPEAT")
22 | 	require.NoError(t, err)
23 | 	assert.True(t, client.repeat)
24 | 
25 | 	err = client.Set("OTHER")
26 | 	require.NoError(t, err)
27 | 	assert.True(t, client.repeat)
28 | }
29 | 
30 | func TestOption(t *testing.T) {
31 | 	client := New()
32 | 
33 | 	err := client.Option("ANY")
34 | 	require.NoError(t, err)
35 | }
36 | 
37 | func TestGetPIN(t *testing.T) {
38 | 	client := New()
39 | 
40 | 	ctx := termio.WithPassPromptFunc(t.Context(), func(ctx context.Context, s string) (string, error) {
41 | 		return "1234", nil
42 | 	})
43 | 
44 | 	pin, err := client.GetPINContext(ctx)
45 | 	require.NoError(t, err)
46 | 	assert.Equal(t, "1234", pin)
47 | }
48 | 


--------------------------------------------------------------------------------
/pkg/protect/protect.go:
--------------------------------------------------------------------------------
 1 | //go:build !openbsd
 2 | // +build !openbsd
 3 | 
 4 | // Package protect provides an interface to the pledge syscall.
 5 | // It is used to limit the system calls a process can make.
 6 | // This is used to limit the attack surface of the process.
 7 | // The pledge syscall is only available on OpenBSD.
 8 | // It is not available on other systems.
 9 | // This package is a no-op on other systems.
10 | package protect
11 | 
12 | // ProtectEnabled lets us know if we have protection or not.
13 | // It is false on all systems except OpenBSD.
14 | var ProtectEnabled = false
15 | 
16 | // Pledge on any other system than OpenBSD doesn't do anything.
17 | func Pledge(s string) error {
18 | 	return nil
19 | }
20 | 


--------------------------------------------------------------------------------
/pkg/protect/protect_openbsd.go:
--------------------------------------------------------------------------------
 1 | //go:build openbsd
 2 | // +build openbsd
 3 | 
 4 | package protect
 5 | 
 6 | import "golang.org/x/sys/unix"
 7 | 
 8 | // ProtectEnabled lets us know if we have protection or not.
 9 | // It is true on OpenBSD.
10 | var ProtectEnabled = true
11 | 
12 | // Pledge on OpenBSD lets us "promise" to only run a subset of
13 | // system calls: http://man.openbsd.org/pledge
14 | func Pledge(s string) error {
15 | 	return unix.PledgePromises(s)
16 | }
17 | 


--------------------------------------------------------------------------------
/pkg/protect/protect_test.go:
--------------------------------------------------------------------------------
 1 | package protect
 2 | 
 3 | import (
 4 | 	"testing"
 5 | 
 6 | 	"github.com/stretchr/testify/require"
 7 | )
 8 | 
 9 | func TestProtect(t *testing.T) {
10 | 	t.Parallel()
11 | 
12 | 	require.NoError(t, Pledge(""))
13 | }
14 | 


--------------------------------------------------------------------------------
/pkg/pwgen/cryptic_test.go:
--------------------------------------------------------------------------------
 1 | package pwgen
 2 | 
 3 | import (
 4 | 	"fmt"
 5 | 	"sort"
 6 | 	"testing"
 7 | 
 8 | 	"github.com/gopasspw/gopass/internal/config"
 9 | 	"github.com/gopasspw/gopass/pkg/pwgen/pwrules"
10 | 	"github.com/stretchr/testify/assert"
11 | 	"github.com/stretchr/testify/require"
12 | )
13 | 
14 | func TestCrypticForDomain(t *testing.T) {
15 | 	t.Parallel()
16 | 
17 | 	rules := pwrules.AllRules()
18 | 	keys := make([]string, 0, len(rules))
19 | 
20 | 	for k := range rules {
21 | 		keys = append(keys, k)
22 | 	}
23 | 
24 | 	sort.Strings(keys)
25 | 
26 | 	for _, domain := range keys {
27 | 		t.Run(domain, func(t *testing.T) {
28 | 			for _, length := range []int{1, 4, 8, 100} {
29 | 				tcName := fmt.Sprintf("%s: generated password with %d chars", domain, length)
30 | 				c := NewCrypticForDomain(config.NewContextInMemory(), length, domain)
31 | 				c.MaxTries = 1024
32 | 
33 | 				require.NotNil(t, c, tcName)
34 | 
35 | 				pw := c.Password()
36 | 
37 | 				assert.NotEmpty(t, pw, tcName)
38 | 				t.Logf("%s -> %s (%d)", tcName, pw, len(pw))
39 | 			}
40 | 		})
41 | 	}
42 | }
43 | 
44 | func TestUniqueChars(t *testing.T) {
45 | 	t.Parallel()
46 | 
47 | 	for in, out := range map[string]string{
48 | 		"foobar": "abfor",
49 | 		"abced":  "abcde",
50 | 	} {
51 | 		assert.Equal(t, out, uniqueChars(in))
52 | 	}
53 | }
54 | 


--------------------------------------------------------------------------------
/pkg/pwgen/external.go:
--------------------------------------------------------------------------------
 1 | package pwgen
 2 | 
 3 | import (
 4 | 	"fmt"
 5 | 	"os"
 6 | 	"os/exec"
 7 | 	"strconv"
 8 | 	"strings"
 9 | 
10 | 	shellquote "github.com/kballard/go-shellquote"
11 | )
12 | 
13 | var (
14 | 	// ErrNoExternal is returned when no external generator is set.
15 | 	ErrNoExternal = fmt.Errorf("no external generator")
16 | 	// ErrNoCommand is returned when no command is set.
17 | 	ErrNoCommand = fmt.Errorf("no command")
18 | )
19 | 
20 | // GenerateExternal will invoke an external password generator,
21 | // if set, and return its output.
22 | // The external generator is configured via the GOPASS_EXTERNAL_PWGEN environment variable.
23 | func GenerateExternal(pwlen int) (string, error) {
24 | 	c := os.Getenv("GOPASS_EXTERNAL_PWGEN")
25 | 	if c == "" {
26 | 		return "", ErrNoExternal
27 | 	}
28 | 
29 | 	cmdArgs, err := shellquote.Split(c)
30 | 	if err != nil {
31 | 		return "", fmt.Errorf("failed to split %s: %w", c, err)
32 | 	}
33 | 
34 | 	if len(cmdArgs) < 1 {
35 | 		return "", ErrNoCommand
36 | 	}
37 | 
38 | 	exe := cmdArgs[0]
39 | 	args := []string{}
40 | 
41 | 	if len(cmdArgs) > 1 {
42 | 		args = cmdArgs[1:]
43 | 	}
44 | 
45 | 	args = append(args, strconv.Itoa(pwlen))
46 | 
47 | 	out, err := exec.Command(exe, args...).Output()
48 | 	if err != nil {
49 | 		return "", fmt.Errorf("failed to execute %s %v: %w", exe, args, err)
50 | 	}
51 | 
52 | 	return strings.TrimSpace(string(out)), nil
53 | }
54 | 


--------------------------------------------------------------------------------
/pkg/pwgen/memorable.go:
--------------------------------------------------------------------------------
 1 | package pwgen
 2 | 
 3 | import "strings"
 4 | 
 5 | // GenerateMemorablePassword will generate a memorable password
 6 | // with a minimum length.
 7 | // It will use a wordlist to generate the password.
 8 | // If symbols is true, it will add symbols to the password.
 9 | // If capitals is true, it will capitalize some words.
10 | func GenerateMemorablePassword(minLength int, symbols bool, capitals bool) string {
11 | 	var sb strings.Builder
12 | 
13 | 	upper := false
14 | 
15 | 	for sb.Len() < minLength {
16 | 		// when requesting uppercase, we randomly uppercase words
17 | 		if capitals && randomInteger(2) == 0 {
18 | 			// We control the input so we can safely ignore the linter.
19 | 			sb.WriteString(strings.Title(randomWord())) //nolint:staticcheck
20 | 
21 | 			upper = true
22 | 		} else {
23 | 			sb.WriteString(randomWord())
24 | 		}
25 | 
26 | 		sb.WriteByte(Digits[randomInteger(len(Digits))])
27 | 
28 | 		if !symbols {
29 | 			continue
30 | 		}
31 | 
32 | 		sb.WriteByte(Syms[randomInteger(len(Syms))])
33 | 	}
34 | 	// If there isn't already a capitalized word, capitalize the first letter
35 | 	if capitals && !upper {
36 | 		str := sb.String()
37 | 
38 | 		return strings.Title(string(str[0])) + str[1:] //nolint:staticcheck
39 | 	}
40 | 
41 | 	return sb.String()
42 | }
43 | 
44 | func randomWord() string {
45 | 	return wordlist[randomInteger(len(wordlist))]
46 | }
47 | 


--------------------------------------------------------------------------------
/pkg/pwgen/pwgen_others_test.go:
--------------------------------------------------------------------------------
 1 | //go:build !windows
 2 | // +build !windows
 3 | 
 4 | package pwgen
 5 | 
 6 | import (
 7 | 	"testing"
 8 | 
 9 | 	"github.com/stretchr/testify/assert"
10 | 	"github.com/stretchr/testify/require"
11 | )
12 | 
13 | func TestPwgenExternal(t *testing.T) {
14 | 	t.Setenv("GOPASS_EXTERNAL_PWGEN", "echo foobar")
15 | 
16 | 	pw, err := GenerateExternal(4)
17 | 
18 | 	require.NoError(t, err)
19 | 	assert.Equal(t, "foobar 4", pw)
20 | }
21 | 


--------------------------------------------------------------------------------
/pkg/pwgen/pwgen_windows_test.go:
--------------------------------------------------------------------------------
 1 | package pwgen
 2 | 
 3 | import (
 4 | 	"testing"
 5 | 
 6 | 	"github.com/stretchr/testify/assert"
 7 | )
 8 | 
 9 | func TestPwgenExternal(t *testing.T) {
10 | 	t.Setenv("GOPASS_EXTERNAL_PWGEN", "powershell.exe -Command write-output 1234 #")
11 | 	ans, err := GenerateExternal(4)
12 | 	if err != nil {
13 | 		panic("Unable to generate using external generator")
14 | 	}
15 | 	assert.Equal(t, "1234", ans)
16 | }
17 | 


--------------------------------------------------------------------------------
/pkg/pwgen/pwrules/aliases_test.go:
--------------------------------------------------------------------------------
 1 | package pwrules
 2 | 
 3 | import (
 4 | 	"testing"
 5 | 
 6 | 	"github.com/gopasspw/gopass/internal/config"
 7 | 	"github.com/stretchr/testify/assert"
 8 | 	"github.com/stretchr/testify/require"
 9 | )
10 | 
11 | func TestLoadCustomRules(t *testing.T) {
12 | 	t.Parallel()
13 | 
14 | 	cfg := config.NewInMemory()
15 | 	aliases := map[string]string{
16 | 		"real.com": "alias.com",
17 | 		"real.de":  "copy.de",
18 | 	}
19 | 
20 | 	for k, v := range aliases {
21 | 		require.NoError(t, cfg.Set("", "domain-alias."+k+".insteadof", v))
22 | 	}
23 | 
24 | 	ctx := t.Context()
25 | 	ctx = cfg.WithConfig(ctx)
26 | 
27 | 	a := LookupAliases(ctx, "alias.com")
28 | 	assert.Equal(t, []string{"real.com"}, a)
29 | 
30 | 	a = LookupAliases(ctx, "copy.de")
31 | 	assert.Equal(t, []string{"real.de"}, a)
32 | 
33 | 	assert.Greater(t, len(AllAliases(ctx)), 256)
34 | }
35 | 


--------------------------------------------------------------------------------
/pkg/pwgen/pwrules/change.go:
--------------------------------------------------------------------------------
 1 | package pwrules
 2 | 
 3 | import "context"
 4 | 
 5 | var changeURLs = map[string]string{}
 6 | 
 7 | func init() {
 8 | 	for k, v := range genChange {
 9 | 		// filter out invalid entries
10 | 		if v == "" {
11 | 			continue
12 | 		}
13 | 
14 | 		changeURLs[k] = v
15 | 	}
16 | }
17 | 
18 | // LookupChangeURL looks up a change URL, either directly or through
19 | // one of its known aliases.
20 | func LookupChangeURL(ctx context.Context, domain string) string {
21 | 	if u, found := changeURLs[domain]; found {
22 | 		return u
23 | 	}
24 | 
25 | 	for _, alias := range LookupAliases(ctx, domain) {
26 | 		if u, found := changeURLs[alias]; found {
27 | 			return u
28 | 		}
29 | 	}
30 | 
31 | 	return ""
32 | }
33 | 


--------------------------------------------------------------------------------
/pkg/pwgen/pwrules/change_test.go:
--------------------------------------------------------------------------------
 1 | package pwrules
 2 | 
 3 | import (
 4 | 	"testing"
 5 | 
 6 | 	"github.com/gopasspw/gopass/internal/config"
 7 | 	"github.com/stretchr/testify/assert"
 8 | )
 9 | 
10 | func TestLookupChangeURL(t *testing.T) {
11 | 	t.Parallel()
12 | 
13 | 	ctx := config.NewContextInMemory()
14 | 	assert.Equal(t, "https://account.gmx.net/ciss/security/edit/passwordChange", LookupChangeURL(ctx, "gmx.net"))
15 | }
16 | 


--------------------------------------------------------------------------------
/pkg/pwgen/pwrules/pwrules_test.go:
--------------------------------------------------------------------------------
 1 | package pwrules
 2 | 
 3 | import (
 4 | 	"testing"
 5 | 
 6 | 	"github.com/stretchr/testify/assert"
 7 | )
 8 | 
 9 | func TestParseRule(t *testing.T) {
10 | 	t.Parallel()
11 | 
12 | 	for _, tc := range []struct {
13 | 		in  string
14 | 		out Rule
15 | 	}{
16 | 		{
17 | 			in: "minlength: 8; maxlength: 20; required: upper; required: lower; required: digit; max-consecutive: 3; allowed: [@#*()+={}/?~;,.-_];",
18 | 			out: Rule{
19 | 				Minlen: 8,
20 | 				Maxlen: 20,
21 | 				Required: []string{
22 | 					"digit",
23 | 					"lower",
24 | 					"upper",
25 | 				},
26 | 				Allowed: []string{
27 | 					"[@#*()+={}/?~;,.-_]",
28 | 				},
29 | 				Maxconsec: 3,
30 | 			},
31 | 		},
32 | 		{
33 | 			in: "minlength: 7; maxlength: 16; required: lower, upper; required: digit; required: [`!@#$%^&*()+~{}'\";:<>?]];",
34 | 			out: Rule{
35 | 				Minlen: 7,
36 | 				Maxlen: 16,
37 | 				Required: []string{
38 | 					"[`!@#$%^&*()+~{}'\";:<>?]]",
39 | 					"digit",
40 | 					"lower",
41 | 					"upper",
42 | 				},
43 | 				Allowed: []string{},
44 | 			},
45 | 		},
46 | 		{
47 | 			in: "minlength: 8; maxlength: 16;",
48 | 			out: Rule{
49 | 				Minlen:   8,
50 | 				Maxlen:   16,
51 | 				Required: []string{},
52 | 				Allowed:  []string{},
53 | 			},
54 | 		},
55 | 	} {
56 | 		t.Run(tc.in, func(t *testing.T) {
57 | 			t.Parallel()
58 | 
59 | 			assert.Equal(t, tc.out, ParseRule(tc.in))
60 | 		})
61 | 	}
62 | }
63 | 


--------------------------------------------------------------------------------
/pkg/pwgen/rand.go:
--------------------------------------------------------------------------------
 1 | package pwgen
 2 | 
 3 | import (
 4 | 	crand "crypto/rand"
 5 | 	"fmt"
 6 | 	"math/big"
 7 | 	"math/rand"
 8 | 	"os"
 9 | 	"time"
10 | )
11 | 
12 | func init() {
13 | 	// seed math/rand in case we have to fall back to using it
14 | 	randFallback = rand.New(rand.NewSource(time.Now().Unix() + int64(os.Getpid()+os.Getppid())))
15 | }
16 | 
17 | var randFallback *rand.Rand
18 | 
19 | func randomInteger(maxVal int) int {
20 | 	i, err := crand.Int(crand.Reader, big.NewInt(int64(maxVal)))
21 | 	if err == nil {
22 | 		return int(i.Int64())
23 | 	}
24 | 
25 | 	fmt.Fprintln(os.Stderr, "WARNING: No crypto/rand available. Falling back to PRNG")
26 | 
27 | 	return randFallback.Intn(maxVal)
28 | }
29 | 


--------------------------------------------------------------------------------
/pkg/pwgen/validate.go:
--------------------------------------------------------------------------------
 1 | package pwgen
 2 | 
 3 | import (
 4 | 	"strings"
 5 | )
 6 | 
 7 | // containsAllClasses validates that the password contains at least one
 8 | // character from each given character class. Can also contain other classes.
 9 | func containsAllClasses(pw string, classes ...string) bool {
10 | CLASSES:
11 | 	for _, class := range classes {
12 | 		for _, ch := range class {
13 | 			if strings.Contains(pw, string(ch)) {
14 | 				continue CLASSES
15 | 			}
16 | 		}
17 | 
18 | 		return false
19 | 	}
20 | 
21 | 	return true
22 | }
23 | 
24 | // containsOnlyClasses validates that the password only contains characters
25 | // from the given classes. Must not satisfy all classes.
26 | func containsOnlyClasses(pw string, classes ...string) bool {
27 | 	for _, c := range pw {
28 | 		for _, class := range classes {
29 | 			if !strings.Contains(class, string(c)) {
30 | 				return false
31 | 			}
32 | 		}
33 | 	}
34 | 
35 | 	return true
36 | }
37 | 
38 | func containsMaxConsecutive(pw string, n int) bool {
39 | 	last := ""
40 | 	repCnt := 1
41 | 
42 | 	for _, r := range pw {
43 | 		if last == string(r) {
44 | 			repCnt++
45 | 			if repCnt >= n {
46 | 				return false
47 | 			}
48 | 		} else {
49 | 			repCnt = 1
50 | 		}
51 | 
52 | 		last = string(r)
53 | 	}
54 | 
55 | 	return true
56 | }
57 | 


--------------------------------------------------------------------------------
/pkg/pwgen/validate_test.go:
--------------------------------------------------------------------------------
 1 | package pwgen
 2 | 
 3 | import (
 4 | 	"testing"
 5 | 
 6 | 	"github.com/stretchr/testify/assert"
 7 | )
 8 | 
 9 | func TestMaxConsec(t *testing.T) {
10 | 	t.Parallel()
11 | 
12 | 	// good
13 | 	for _, tc := range []string{
14 | 		"abcd",
15 | 		"foobar",
16 | 		"nope",
17 | 		"AaAa",
18 | 		"aaabbbaaa",
19 | 	} {
20 | 		assert.True(t, containsMaxConsecutive(tc, 4))
21 | 	}
22 | 	// bad
23 | 	for _, tc := range []string{
24 | 		"aaaa",
25 | 		"bbb",
26 | 		"fooobar",
27 | 		"AaaaA",
28 | 	} {
29 | 		assert.False(t, containsMaxConsecutive(tc, 3))
30 | 	}
31 | }
32 | 
33 | func TestContainsOnly(t *testing.T) {
34 | 	t.Parallel()
35 | 
36 | 	// good
37 | 	for _, tc := range []string{
38 | 		"aBcDeF",
39 | 	} {
40 | 		assert.True(t, containsOnlyClasses(tc, Upper+Lower))
41 | 	}
42 | 
43 | 	// bad
44 | 	for _, tc := range []string{
45 | 		"aBcDeF3",
46 | 	} {
47 | 		assert.False(t, containsOnlyClasses(tc, Upper+Lower))
48 | 	}
49 | }
50 | 


--------------------------------------------------------------------------------
/pkg/pwgen/xkcdgen/pwgen.go:
--------------------------------------------------------------------------------
 1 | // Package xkcdgen provides a simple wrapper around the xkcdpwgen
 2 | // package to generate random passphrases.
 3 | package xkcdgen
 4 | 
 5 | import (
 6 | 	"fmt"
 7 | 
 8 | 	"github.com/martinhoefling/goxkcdpwgen/xkcdpwgen"
 9 | )
10 | 
11 | // Random returns a random passphrase combined from four words.
12 | func Random() string {
13 | 	password, _ := RandomLength(4, "en")
14 | 
15 | 	return password
16 | }
17 | 
18 | // RandomLength returns a random passphrase combined from the desired number
19 | // of words. Words are drawn from lang.
20 | func RandomLength(length int, lang string) (string, error) {
21 | 	return RandomLengthDelim(length, " ", lang, false, false)
22 | }
23 | 
24 | // RandomLengthDelim returns a random passphrase combined from the desired number
25 | // of words and the given delimiter. Words are drawn from lang.
26 | func RandomLengthDelim(length int, delim, lang string, capitalize, numbers bool) (string, error) {
27 | 	g := xkcdpwgen.NewGenerator()
28 | 	g.SetNumWords(length)
29 | 	g.SetDelimiter(delim)
30 | 	g.SetCapitalize(delim == "" || capitalize)
31 | 	g.SetRandomNumbers(numbers)
32 | 
33 | 	if err := g.UseLangWordlist(lang); err != nil {
34 | 		return "", fmt.Errorf("failed to use wordlist for lang %s: %w", lang, err)
35 | 	}
36 | 
37 | 	return g.GeneratePasswordString(), nil
38 | }
39 | 


--------------------------------------------------------------------------------
/pkg/pwgen/xkcdgen/pwgen_test.go:
--------------------------------------------------------------------------------
 1 | package xkcdgen
 2 | 
 3 | import (
 4 | 	"strings"
 5 | 	"testing"
 6 | 
 7 | 	"github.com/stretchr/testify/require"
 8 | )
 9 | 
10 | func TestRandom(t *testing.T) {
11 | 	t.Parallel()
12 | 
13 | 	pw := Random()
14 | 	if len(pw) < 4 {
15 | 		t.Errorf("too short")
16 | 	}
17 | 
18 | 	if len(strings.Fields(pw)) < 4 {
19 | 		t.Errorf("too few words")
20 | 	}
21 | }
22 | 
23 | func TestRandomLengthDelim(t *testing.T) {
24 | 	t.Parallel()
25 | 
26 | 	_, err := RandomLengthDelim(10, " ", "cn_ZH", false, false)
27 | 	require.Error(t, err)
28 | }
29 | 


--------------------------------------------------------------------------------
/pkg/qrcon/qrcon_test.go:
--------------------------------------------------------------------------------
 1 | package qrcon
 2 | 
 3 | import (
 4 | 	"fmt"
 5 | 	"testing"
 6 | 
 7 | 	"github.com/stretchr/testify/require"
 8 | )
 9 | 
10 | func ExampleQRCode() { //nolint:testableexamples
11 | 	code, err := QRCode("foo")
12 | 	if err != nil {
13 | 		panic(err)
14 | 	}
15 | 
16 | 	fmt.Println(code)
17 | }
18 | 
19 | func TestQRCode(t *testing.T) {
20 | 	t.Parallel()
21 | 
22 | 	_, err := QRCode("https://www.gopass.pw/")
23 | 	require.NoError(t, err)
24 | }
25 | 


--------------------------------------------------------------------------------
/pkg/set/filter.go:
--------------------------------------------------------------------------------
 1 | package set
 2 | 
 3 | // Filter filters all elements in r from the input list.
 4 | func Filter[K comparable](in []K, r ...K) []K {
 5 | 	rs := Map(r)
 6 | 	var out []K
 7 | 
 8 | 	for _, i := range in {
 9 | 		if !rs[i] {
10 | 			out = append(out, i)
11 | 		}
12 | 	}
13 | 
14 | 	return out
15 | }
16 | 
17 | // Contains returns true if e is contained in the input list.
18 | func Contains[K comparable](in []K, e K) bool {
19 | 	rs := Map(in)
20 | 
21 | 	_, found := rs[e]
22 | 
23 | 	return found
24 | }
25 | 


--------------------------------------------------------------------------------
/pkg/set/filter_test.go:
--------------------------------------------------------------------------------
 1 | package set
 2 | 
 3 | import (
 4 | 	"testing"
 5 | 
 6 | 	"github.com/stretchr/testify/assert"
 7 | )
 8 | 
 9 | func TestFilter(t *testing.T) {
10 | 	t.Parallel()
11 | 
12 | 	in := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
13 | 	out := Filter(in, 6, 7, 8, 9)
14 | 
15 | 	assert.Equal(t, []int{1, 2, 3, 4, 5}, out)
16 | }
17 | 
18 | func TestFilter_EmptyInput(t *testing.T) {
19 | 	t.Parallel()
20 | 
21 | 	in := []int{}
22 | 	out := Filter(in, 1, 2, 3)
23 | 
24 | 	assert.Equal(t, []int(nil), out)
25 | }
26 | 
27 | func TestFilter_NoElementsToRemove(t *testing.T) {
28 | 	t.Parallel()
29 | 
30 | 	in := []int{1, 2, 3, 4, 5}
31 | 	out := Filter(in)
32 | 
33 | 	assert.Equal(t, []int{1, 2, 3, 4, 5}, out)
34 | }
35 | 
36 | func TestFilter_RemoveNonExistentElements(t *testing.T) {
37 | 	t.Parallel()
38 | 
39 | 	in := []int{1, 2, 3, 4, 5}
40 | 	out := Filter(in, 6, 7, 8)
41 | 
42 | 	assert.Equal(t, []int{1, 2, 3, 4, 5}, out)
43 | }
44 | 
45 | func TestContains(t *testing.T) {
46 | 	t.Parallel()
47 | 
48 | 	in := []int{1, 2, 3, 4, 5}
49 | 
50 | 	assert.True(t, Contains(in, 3))
51 | 	assert.False(t, Contains(in, 6))
52 | }
53 | 
54 | func TestContains_EmptyInput(t *testing.T) {
55 | 	t.Parallel()
56 | 
57 | 	in := []int{}
58 | 
59 | 	assert.False(t, Contains(in, 1))
60 | }
61 | 


--------------------------------------------------------------------------------
/pkg/set/map.go:
--------------------------------------------------------------------------------
 1 | package set
 2 | 
 3 | // Map takes a slice of a given type and creates a boolean map with keys
 4 | // of that type.
 5 | func Map[K comparable](in []K) map[K]bool {
 6 | 	m := make(map[K]bool, len(in))
 7 | 	for _, i := range in {
 8 | 		m[i] = true
 9 | 	}
10 | 
11 | 	return m
12 | }
13 | 
14 | // Apply applies the given function to every element of the slice.
15 | func Apply[K comparable](in []K, f func(K) K) []K {
16 | 	out := make([]K, len(in))
17 | 	for i, v := range in {
18 | 		out[i] = f(v)
19 | 	}
20 | 
21 | 	return out
22 | }
23 | 


--------------------------------------------------------------------------------
/pkg/set/map_test.go:
--------------------------------------------------------------------------------
 1 | package set
 2 | 
 3 | import (
 4 | 	"testing"
 5 | 
 6 | 	"github.com/stretchr/testify/assert"
 7 | )
 8 | 
 9 | func TestMapFunc(t *testing.T) {
10 | 	t.Parallel()
11 | 
12 | 	assert.Equal(t, map[int]bool{1: true, 2: true, 3: true}, Map([]int{1, 2, 3}))
13 | }
14 | 
15 | func TestApplyFunc(t *testing.T) {
16 | 	t.Parallel()
17 | 
18 | 	assert.Equal(t, []int{2, 3, 4}, Apply([]int{1, 2, 3}, func(i int) int { return i + 1 }))
19 | }
20 | 


--------------------------------------------------------------------------------
/pkg/set/sorted.go:
--------------------------------------------------------------------------------
 1 | package set
 2 | 
 3 | import (
 4 | 	"maps"
 5 | 	"slices"
 6 | 
 7 | 	"golang.org/x/exp/constraints"
 8 | )
 9 | 
10 | // SortedKeys returns the sorted keys of the map.
11 | // The keys are sorted in ascending order.
12 | func SortedKeys[K constraints.Ordered, V any](m map[K]V) []K {
13 | 	// sort
14 | 	keys := maps.Keys(m)
15 | 
16 | 	return slices.Sorted(keys)
17 | }
18 | 
19 | // Sorted returns a sorted set of the input.
20 | // Duplicates are removed.
21 | func Sorted[K constraints.Ordered](l []K) []K {
22 | 	return SortedFiltered(l, func(k K) bool {
23 | 		return true
24 | 	})
25 | }
26 | 
27 | // SortedFiltered returns a sorted set of the input, filtered by the predicate.
28 | // Duplicates are removed.
29 | func SortedFiltered[K constraints.Ordered](l []K, want func(K) bool) []K {
30 | 	if len(l) == 0 {
31 | 		return l
32 | 	}
33 | 
34 | 	// deduplicate
35 | 	m := make(map[K]struct{}, len(l))
36 | 	for _, k := range l {
37 | 		if !want(k) {
38 | 			continue
39 | 		}
40 | 		m[k] = struct{}{}
41 | 	}
42 | 
43 | 	// sort
44 | 	return SortedKeys(m)
45 | }
46 | 


--------------------------------------------------------------------------------
/pkg/set/sorted_test.go:
--------------------------------------------------------------------------------
 1 | package set
 2 | 
 3 | import (
 4 | 	"math/rand"
 5 | 	"testing"
 6 | 
 7 | 	"github.com/stretchr/testify/assert"
 8 | )
 9 | 
10 | func TestSorted(t *testing.T) {
11 | 	t.Parallel()
12 | 
13 | 	want := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
14 | 	in := append(want, want...)
15 | 	rand.Shuffle(len(in), func(i, j int) {
16 | 		in[i], in[j] = in[j], in[i]
17 | 	})
18 | 	assert.Equal(t, want, Sorted(in))
19 | }
20 | 
21 | func TestSortedFiltered(t *testing.T) {
22 | 	t.Parallel()
23 | 
24 | 	in := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
25 | 	in = append(in, in...)
26 | 	rand.Shuffle(len(in), func(i, j int) {
27 | 		in[i], in[j] = in[j], in[i]
28 | 	})
29 | 
30 | 	want := []int{2, 4, 6, 8, 10}
31 | 	assert.Equal(t, want, SortedFiltered(in, func(i int) bool {
32 | 		return i%2 == 0
33 | 	}))
34 | 
35 | 	assert.Equal(t, []int{}, SortedFiltered([]int{}, func(i int) bool { return true }))
36 | }
37 | 


--------------------------------------------------------------------------------
/pkg/tempfile/mount_linux.go:
--------------------------------------------------------------------------------
 1 | //go:build linux
 2 | // +build linux
 3 | 
 4 | package tempfile
 5 | 
 6 | import (
 7 | 	"context"
 8 | 	"os"
 9 | 
10 | 	"golang.org/x/sys/unix"
11 | )
12 | 
13 | var shmDir = "/dev/shm"
14 | 
15 | // tempdir returns a temporary directory suiteable for sensitive data. It tries
16 | // /dev/shm but if this isn't working it will return an empty string. Using
17 | // this with ioutil.Tempdir will ensure that we're getting the "best" tempdir.
18 | func tempdirBase() string {
19 | 	if fi, err := os.Stat(shmDir); err == nil {
20 | 		if fi.IsDir() {
21 | 			if unix.Access(shmDir, unix.W_OK) == nil {
22 | 				return shmDir
23 | 			}
24 | 		}
25 | 	}
26 | 
27 | 	return ""
28 | }
29 | 
30 | func (t *File) mount(context.Context) error {
31 | 	return nil
32 | }
33 | 
34 | func (t *File) unmount(context.Context) error {
35 | 	return nil
36 | }
37 | 


--------------------------------------------------------------------------------
/pkg/tempfile/mount_others.go:
--------------------------------------------------------------------------------
 1 | //go:build !linux && !darwin
 2 | // +build !linux,!darwin
 3 | 
 4 | package tempfile
 5 | 
 6 | import "context"
 7 | 
 8 | var shmDir = ""
 9 | 
10 | // tempdir returns a temporary directory suiteable for sensitive data. On
11 | // Windows, just return empty string for ioutil.TempFile.
12 | func tempdirBase() string {
13 | 	return ""
14 | }
15 | 
16 | func (t *File) mount(context.Context) error {
17 | 	_ = t.dev // to trick megacheck
18 | 	return nil
19 | }
20 | 
21 | func (t *File) unmount(context.Context) error {
22 | 	return nil
23 | }
24 | 


--------------------------------------------------------------------------------
/pkg/termio/context_test.go:
--------------------------------------------------------------------------------
 1 | package termio
 2 | 
 3 | import (
 4 | 	"context"
 5 | 	"testing"
 6 | 
 7 | 	"github.com/gopasspw/gopass/internal/config"
 8 | 	"github.com/stretchr/testify/assert"
 9 | 	"github.com/stretchr/testify/require"
10 | )
11 | 
12 | func TestPassPromptFunc(t *testing.T) {
13 | 	t.Parallel()
14 | 
15 | 	ctx := config.NewContextInMemory()
16 | 
17 | 	assert.False(t, HasPassPromptFunc(ctx))
18 | 	assert.NotNil(t, GetPassPromptFunc(ctx))
19 | 
20 | 	ctx = WithPassPromptFunc(ctx, func(context.Context, string) (string, error) {
21 | 		return "test", nil
22 | 	})
23 | 	assert.True(t, HasPassPromptFunc(ctx))
24 | 	assert.NotNil(t, GetPassPromptFunc(ctx))
25 | 	sv, err := GetPassPromptFunc(ctx)(ctx, "")
26 | 	require.NoError(t, err)
27 | 	assert.Equal(t, "test", sv)
28 | }
29 | 


--------------------------------------------------------------------------------
/pkg/termio/identity_test.go:
--------------------------------------------------------------------------------
 1 | package termio
 2 | 
 3 | import (
 4 | 	"testing"
 5 | 
 6 | 	"github.com/gopasspw/gopass/internal/config"
 7 | 	"github.com/stretchr/testify/assert"
 8 | )
 9 | 
10 | func TestDetectName(t *testing.T) {
11 | 	ctx := config.NewContextInMemory()
12 | 	td := t.TempDir()
13 | 	t.Setenv("XDG_CONFIG_HOME", td)
14 | 	t.Setenv("GOPASS_HOMEDIR", td)
15 | 
16 | 	t.Setenv("GIT_AUTHOR_NAME", "")
17 | 	t.Setenv("DEBFULLNAME", "")
18 | 	t.Setenv("USER", "")
19 | 
20 | 	assert.Empty(t, DetectName(ctx, nil))
21 | 
22 | 	t.Setenv("USER", "foo")
23 | 	assert.Equal(t, "foo", DetectName(ctx, nil))
24 | }
25 | 
26 | func TestDetectEmail(t *testing.T) {
27 | 	ctx := config.NewContextInMemory()
28 | 	td := t.TempDir()
29 | 	t.Setenv("XDG_CONFIG_HOME", td)
30 | 	t.Setenv("GOPASS_HOMEDIR", td)
31 | 
32 | 	t.Setenv("GIT_AUTHOR_EMAIL", "")
33 | 	t.Setenv("DEBEMAIL", "")
34 | 	t.Setenv("EMAIL", "")
35 | 
36 | 	assert.Empty(t, DetectEmail(ctx, nil))
37 | 
38 | 	t.Setenv("EMAIL", "foo@bar.de")
39 | 	assert.Equal(t, "foo@bar.de", DetectEmail(ctx, nil))
40 | }
41 | 


--------------------------------------------------------------------------------
/pkg/termio/progress_test.go:
--------------------------------------------------------------------------------
 1 | package termio
 2 | 
 3 | import (
 4 | 	"testing"
 5 | 	"time"
 6 | 
 7 | 	"github.com/stretchr/testify/assert"
 8 | )
 9 | 
10 | func ExampleProgressBar() { //nolint:testableexamples
11 | 	maxVal := 100
12 | 	pb := NewProgressBar(int64(maxVal))
13 | 
14 | 	for range maxVal + 20 {
15 | 		pb.Inc()
16 | 		pb.Add(23)
17 | 		pb.Set(42)
18 | 		time.Sleep(150 * time.Millisecond)
19 | 	}
20 | 
21 | 	time.Sleep(5 * time.Second)
22 | 	pb.Done()
23 | }
24 | 
25 | func TestProgress(t *testing.T) {
26 | 	maxVal := 2
27 | 	pb := NewProgressBar(int64(maxVal))
28 | 	pb.Hidden = true
29 | 	pb.Inc()
30 | 	assert.Equal(t, int64(1), pb.current)
31 | }
32 | 
33 | func TestProgressNil(t *testing.T) {
34 | 	t.Parallel()
35 | 
36 | 	var pb *ProgressBar
37 | 	pb.Inc()
38 | 	pb.Add(4)
39 | 	pb.Done()
40 | }
41 | 
42 | func TestProgressBytes(t *testing.T) {
43 | 	maxSize := 2 << 24
44 | 	pb := NewProgressBar(int64(maxSize))
45 | 	pb.Hidden = true
46 | 	pb.Bytes = true
47 | 
48 | 	for i := range 24 {
49 | 		pb.Set(2 << (i + 1))
50 | 	}
51 | 
52 | 	assert.Equal(t, int64(maxSize), pb.current)
53 | 	pb.Done()
54 | }
55 | 


--------------------------------------------------------------------------------
/pkg/termio/promptpass_test.go:
--------------------------------------------------------------------------------
 1 | package termio
 2 | 
 3 | import (
 4 | 	"testing"
 5 | 
 6 | 	"github.com/gopasspw/gopass/internal/config"
 7 | 	"github.com/gopasspw/gopass/pkg/ctxutil"
 8 | 	"github.com/stretchr/testify/require"
 9 | )
10 | 
11 | func TestPromptPass(t *testing.T) {
12 | 	t.Parallel()
13 | 
14 | 	ctx := config.NewContextInMemory()
15 | 	ctx = ctxutil.WithTerminal(ctx, false)
16 | 	ctx = ctxutil.WithAlwaysYes(ctx, true)
17 | 
18 | 	_, err := promptPass(ctx, "foo")
19 | 	require.NoError(t, err)
20 | }
21 | 


--------------------------------------------------------------------------------
/pkg/termio/promptpass_windows.go:
--------------------------------------------------------------------------------
 1 | //go:build windows
 2 | // +build windows
 3 | 
 4 | package termio
 5 | 
 6 | import (
 7 | 	"context"
 8 | 	"fmt"
 9 | 	"os"
10 | 
11 | 	"github.com/gopasspw/gopass/pkg/ctxutil"
12 | 	"golang.org/x/crypto/ssh/terminal"
13 | )
14 | 
15 | // promptPass will prompt user's for a password by terminal.
16 | func promptPass(ctx context.Context, prompt string) (string, error) {
17 | 	if !ctxutil.IsTerminal(ctx) {
18 | 		return AskForString(ctx, prompt, "")
19 | 	}
20 | 
21 | 	fmt.Fprintf(Stderr, "%s: ", prompt)
22 | 	passBytes, err := terminal.ReadPassword(int(os.Stdin.Fd()))
23 | 	fmt.Fprintln(Stderr, "")
24 | 	return string(passBytes), err
25 | }
26 | 


--------------------------------------------------------------------------------
/tests/age_agent_test.go:
--------------------------------------------------------------------------------
 1 | package tests
 2 | 
 3 | import (
 4 | 	"runtime"
 5 | 	"testing"
 6 | 
 7 | 	"github.com/stretchr/testify/require"
 8 | )
 9 | 
10 | func TestAgeAgent(t *testing.T) {
11 | 	if runtime.GOOS == "windows" {
12 | 		t.Skip("skipping test on windows for now")
13 | 	}
14 | 
15 | 	ts := newTester(t)
16 | 	defer ts.teardown()
17 | 
18 | 	// create a new age identity
19 | 	out, err := ts.runCmd([]string{ts.Binary, "age", "identities", "keygen", "--password", "foo"}, []byte("test\ntest\n"))
20 | 	require.NoError(t, err, out)
21 | }
22 | 


--------------------------------------------------------------------------------
/tests/audit_test.go:
--------------------------------------------------------------------------------
 1 | package tests
 2 | 
 3 | import (
 4 | 	"testing"
 5 | 
 6 | 	"github.com/stretchr/testify/assert"
 7 | 	"github.com/stretchr/testify/require"
 8 | )
 9 | 
10 | func TestAudit(t *testing.T) {
11 | 	ts := newTester(t)
12 | 	defer ts.teardown()
13 | 
14 | 	ts.initStore()
15 | 	ts.initSecrets("")
16 | 
17 | 	t.Run("audit the test store", func(t *testing.T) {
18 | 		out, err := ts.run("audit")
19 | 		require.Error(t, err)
20 | 		assert.Contains(t, out, "crunchy")
21 | 	})
22 | }
23 | 


--------------------------------------------------------------------------------
/tests/can/can_test.go:
--------------------------------------------------------------------------------
 1 | package can
 2 | 
 3 | import (
 4 | 	"testing"
 5 | 
 6 | 	"github.com/ProtonMail/go-crypto/openpgp"
 7 | 	"github.com/stretchr/testify/assert"
 8 | 	"github.com/stretchr/testify/require"
 9 | )
10 | 
11 | func TestPubring(t *testing.T) {
12 | 	t.Parallel()
13 | 
14 | 	fh, err := can.Open("gnupg/pubring.gpg")
15 | 	require.NoError(t, err)
16 | 	defer fh.Close() //nolint:errcheck
17 | 
18 | 	el, err := openpgp.ReadKeyRing(fh)
19 | 	require.NoError(t, err)
20 | 
21 | 	require.Len(t, el, 1)
22 | 	assert.Equal(t, "BE73F104", el[0].PrimaryKey.KeyIdShortString())
23 | }
24 | 


--------------------------------------------------------------------------------
/tests/can/gnupg/pubring.gpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gopasspw/gopass/ed5497331809e95f1a6d44eb5a1cce955057b64d/tests/can/gnupg/pubring.gpg


--------------------------------------------------------------------------------
/tests/can/gnupg/random_seed:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gopasspw/gopass/ed5497331809e95f1a6d44eb5a1cce955057b64d/tests/can/gnupg/random_seed


--------------------------------------------------------------------------------
/tests/can/gnupg/secring.gpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gopasspw/gopass/ed5497331809e95f1a6d44eb5a1cce955057b64d/tests/can/gnupg/secring.gpg


--------------------------------------------------------------------------------
/tests/can/gnupg/trustdb.gpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gopasspw/gopass/ed5497331809e95f1a6d44eb5a1cce955057b64d/tests/can/gnupg/trustdb.gpg


--------------------------------------------------------------------------------
/tests/delete_test.go:
--------------------------------------------------------------------------------
 1 | package tests
 2 | 
 3 | import (
 4 | 	"path/filepath"
 5 | 	"testing"
 6 | 
 7 | 	"github.com/stretchr/testify/assert"
 8 | 	"github.com/stretchr/testify/require"
 9 | )
10 | 
11 | func TestDelete(t *testing.T) {
12 | 	ts := newTester(t)
13 | 	defer ts.teardown()
14 | 
15 | 	ts.initStore()
16 | 
17 | 	out, err := ts.run("delete")
18 | 	require.Error(t, err)
19 | 	assert.Equal(t, "\nError: Usage: "+filepath.Base(ts.Binary)+" rm name\n", out)
20 | 
21 | 	out, err = ts.run("delete foobarbaz")
22 | 	require.Error(t, err)
23 | 	assert.Contains(t, out, "does not exist", out)
24 | 
25 | 	ts.initSecrets("")
26 | 
27 | 	secrets := []string{"baz", "foo/bar"}
28 | 	for _, secret := range secrets {
29 | 		out, err = ts.run("delete -f " + secret)
30 | 		require.NoError(t, err)
31 | 		assert.Empty(t, out)
32 | 
33 | 		out, err = ts.run("delete -f " + secret)
34 | 		require.Error(t, err)
35 | 		assert.Contains(t, out, "does not exist\n", out)
36 | 	}
37 | }
38 | 


--------------------------------------------------------------------------------
/tests/generate_test.go:
--------------------------------------------------------------------------------
 1 | package tests
 2 | 
 3 | import (
 4 | 	"strings"
 5 | 	"testing"
 6 | 
 7 | 	"github.com/stretchr/testify/assert"
 8 | 	"github.com/stretchr/testify/require"
 9 | )
10 | 
11 | func TestGenerate(t *testing.T) {
12 | 	ts := newTester(t)
13 | 	defer ts.teardown()
14 | 
15 | 	ts.initStore()
16 | 
17 | 	out, err := ts.run("generate")
18 | 	require.Error(t, err)
19 | 	assert.Equal(t, "\nError: please provide a password name\n", out)
20 | 
21 | 	out, err = ts.run("generate foo 0")
22 | 	require.Error(t, err)
23 | 	assert.Equal(t, "\nError: password length must not be zero\n", out)
24 | 
25 | 	out, err = ts.run("generate -p baz 42")
26 | 	require.NoError(t, err)
27 | 
28 | 	lines := strings.Split(out, "\n")
29 | 
30 | 	require.Greater(t, len(lines), 2)
31 | 	assert.Contains(t, out, "The generated password is:")
32 | 	assert.Len(t, lines[3], 42)
33 | 
34 | 	t.Setenv("GOPASS_CHARACTER_SET", "a")
35 | 
36 | 	out, err = ts.run("generate -p zab 4")
37 | 	require.NoError(t, err)
38 | 
39 | 	lines = strings.Split(out, "\n")
40 | 
41 | 	require.Greater(t, len(lines), 2)
42 | 	assert.Contains(t, out, "The generated password is:")
43 | 	assert.Equal(t, "aaaa", lines[3])
44 | }
45 | 


--------------------------------------------------------------------------------
/tests/grep_test.go:
--------------------------------------------------------------------------------
 1 | package tests
 2 | 
 3 | import (
 4 | 	"path/filepath"
 5 | 	"testing"
 6 | 
 7 | 	"github.com/stretchr/testify/assert"
 8 | 	"github.com/stretchr/testify/require"
 9 | )
10 | 
11 | func TestGrep(t *testing.T) {
12 | 	ts := newTester(t)
13 | 	defer ts.teardown()
14 | 
15 | 	ts.initStore()
16 | 
17 | 	out, err := ts.run("grep")
18 | 	require.Error(t, err)
19 | 	assert.Equal(t, "\nError: Usage: "+filepath.Base(ts.Binary)+" grep arg\n", out)
20 | 
21 | 	out, err = ts.run("grep BOOM")
22 | 	require.NoError(t, err)
23 | 	assert.Contains(t, out, "Scanned 0 secrets. 0 matches, 0 errors")
24 | 
25 | 	ts.initSecrets("")
26 | 
27 | 	out, err = ts.run("grep moar")
28 | 	require.NoError(t, err)
29 | 	assert.Contains(t, out, "fixed/secret matches")
30 | }
31 | 


--------------------------------------------------------------------------------
/tests/init_test.go:
--------------------------------------------------------------------------------
 1 | package tests
 2 | 
 3 | import (
 4 | 	"testing"
 5 | 
 6 | 	"github.com/stretchr/testify/assert"
 7 | 	"github.com/stretchr/testify/require"
 8 | )
 9 | 
10 | func TestInit(t *testing.T) {
11 | 	ts := newTester(t)
12 | 	defer ts.teardown()
13 | 
14 | 	out, err := ts.run("init")
15 | 	require.NoError(t, err)
16 | 	assert.Contains(t, out, "Initializing a new password store ...")
17 | 	assert.Contains(t, out, "initialized")
18 | 
19 | 	ts = newTester(t)
20 | 	defer ts.teardown()
21 | 
22 | 	out, err = ts.run("init " + keyID)
23 | 	require.NoError(t, err)
24 | 	assert.Contains(t, out, "initialized for")
25 | 
26 | 	ts = newTester(t)
27 | 	defer ts.teardown()
28 | 
29 | 	ts.initStore()
30 | 	// try to init again
31 | 	out, err = ts.run("init " + keyID)
32 | 	require.Error(t, err)
33 | 
34 | 	for _, o := range []string{
35 | 		"found already initialized store at ",
36 | 		"You can add secondary stores with 'gopass init --path <path to secondary store> --store <mount name>'",
37 | 	} {
38 | 		assert.Contains(t, out, o)
39 | 	}
40 | }
41 | 


--------------------------------------------------------------------------------
/tests/uninitialized_test.go:
--------------------------------------------------------------------------------
 1 | package tests
 2 | 
 3 | import (
 4 | 	"testing"
 5 | 
 6 | 	"github.com/stretchr/testify/assert"
 7 | 	"github.com/stretchr/testify/require"
 8 | )
 9 | 
10 | func TestUninitialized(t *testing.T) {
11 | 	ts := newTester(t)
12 | 	defer ts.teardown()
13 | 
14 | 	commands := []string{
15 | 		"",
16 | 		"copy",
17 | 		"cp",
18 | 		"delete",
19 | 		"edit",
20 | 		"find",
21 | 		"generate",
22 | 		"grep",
23 | 		"insert",
24 | 		"list",
25 | 		"ls",
26 | 		"mount",
27 | 		"move",
28 | 		"mv",
29 | 		"remove",
30 | 		"rm",
31 | 		"show",
32 | 	}
33 | 
34 | 	for _, command := range commands {
35 | 		t.Run(command, func(t *testing.T) {
36 | 			out, err := ts.run(command)
37 | 			require.Error(t, err)
38 | 			assert.Contains(t, out, "password-store is not initialized. Try ")
39 | 		})
40 | 	}
41 | }
42 | 


--------------------------------------------------------------------------------
/version.go:
--------------------------------------------------------------------------------
 1 | package main
 2 | 
 3 | import (
 4 | 	"strings"
 5 | 
 6 | 	"github.com/blang/semver/v4"
 7 | )
 8 | 
 9 | func getVersion() semver.Version {
10 | 	sv, err := semver.Parse(strings.TrimPrefix(version, "v"))
11 | 	if err == nil {
12 | 		return sv
13 | 	}
14 | 
15 | 	return semver.Version{
16 | 		Major: 1,
17 | 		Minor: 15,
18 | 		Patch: 18,
19 | 		Pre: []semver.PRVersion{
20 | 			{VersionStr: "git"},
21 | 		},
22 | 		Build: []string{"cc979217"},
23 | 	}
24 | }
25 | 


--------------------------------------------------------------------------------