Initial working commit
This commit is contained in:
parent
4c4020f97b
commit
79688199cb
2
.gitignore
vendored
2
.gitignore
vendored
@ -15,3 +15,5 @@
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
build/
|
||||
dist/
|
||||
|
4
.golangci.yml
Normal file
4
.golangci.yml
Normal file
@ -0,0 +1,4 @@
|
||||
---
|
||||
linters:
|
||||
disable:
|
||||
- gochecknoglobals
|
16
.pre-commit-config.yaml
Normal file
16
.pre-commit-config.yaml
Normal file
@ -0,0 +1,16 @@
|
||||
---
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v3.3.0
|
||||
hooks:
|
||||
- id: check-added-large-files
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
- id: check-merge-conflict
|
||||
- repo: git://github.com/dnephin/pre-commit-golang
|
||||
rev: v0.3.5
|
||||
hooks:
|
||||
- id: go-fmt
|
||||
- id: go-imports
|
||||
# - id: gometalinter
|
||||
- id: golangci-lint
|
47
Makefile
Normal file
47
Makefile
Normal file
@ -0,0 +1,47 @@
|
||||
VERSION ?= $(shell git describe --tags --dirty)
|
||||
NAME := yk
|
||||
GOFILES = *.go
|
||||
# Multi-arch targets are generated from this
|
||||
TARGET_ALIAS = $(NAME)-linux-amd64 $(NAME)-linux-arm $(NAME)-linux-arm64 $(NAME)-darwin-amd64
|
||||
TARGETS = $(addprefix dist/,$(TARGET_ALIAS))
|
||||
|
||||
.PHONY: default
|
||||
default: yk
|
||||
|
||||
.PHONY: all
|
||||
all: $(TARGETS)
|
||||
|
||||
.PHONY: test
|
||||
test: check
|
||||
go test .
|
||||
|
||||
.PHONY: build
|
||||
build: build/yk
|
||||
|
||||
build/yk: $(GOFILES)
|
||||
mkdir -p build
|
||||
go build -o build/yk
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -fr build/
|
||||
rm -fr dist/
|
||||
|
||||
.PHONY: check
|
||||
check:
|
||||
pre-commit run --all-files
|
||||
|
||||
.PHONY: install-hooks
|
||||
install-hooks:
|
||||
pre-commit install -f --install-hooks
|
||||
|
||||
# Distribution targets
|
||||
$(TARGETS): $(GOFILES)
|
||||
mkdir -p ./dist
|
||||
GOOS=$(word 2, $(subst -, ,$(@))) GOARCH=$(word 3, $(subst -, ,$(@))) \
|
||||
go build -ldflags '-X "main.version=${VERSION}"' -a \
|
||||
-o $@
|
||||
|
||||
.PHONY: $(TARGET_ALIAS)
|
||||
$(TARGET_ALIAS):
|
||||
$(MAKE) $(addprefix dist/,$@)
|
16
README.md
16
README.md
@ -1,3 +1,17 @@
|
||||
# yk-cli
|
||||
|
||||
Yubikey cli for retrieving TOTP codes
|
||||
Yubikey cli for retrieving TOTP codes
|
||||
|
||||
## Installation
|
||||
|
||||
Currently, there is no binary release published, but it can be built with `make build` and then you can copy `./build/yk` to somewhere in your path.
|
||||
|
||||
If you're a [`fish`](https://fishshell.com) user, you can also add `./scripts/yk.fish` to your `conf.d/` directory to get completions.
|
||||
|
||||
## Building
|
||||
|
||||
Executing `make build` will compile to `./build/yk`. Additionally, distribution builds should be possible with `make all` or by building a particular target. Eg `make ./dist/yk-darwin-amd64`. There is also an alias present and the `./dist/` prefix can be left off.
|
||||
|
||||
### Note on distribution builds
|
||||
|
||||
Currently cross compiling is not working correctly.
|
||||
|
13
go.mod
Normal file
13
go.mod
Normal file
@ -0,0 +1,13 @@
|
||||
module git.iamthefij.com/iamthefij/yk-cli
|
||||
|
||||
go 1.15
|
||||
|
||||
replace github.com/yawn/ykoath => ../ykoath
|
||||
|
||||
require (
|
||||
git.iamthefij.com/iamthefij/slog v1.2.0
|
||||
github.com/keybase/go-keychain v0.0.0-20201121013009-976c83ec27a6
|
||||
github.com/yawn/ykoath v1.0.4
|
||||
golang.org/x/sys v0.0.0-20201211002650-1f0c578a6b29 // indirect
|
||||
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf
|
||||
)
|
45
go.sum
Normal file
45
go.sum
Normal file
@ -0,0 +1,45 @@
|
||||
git.iamthefij.com/iamthefij/slog v1.2.0 h1:wPGIo4U7TauLM0ifhbXfpR6nCjoSgZEnuy3nHk4ksQc=
|
||||
git.iamthefij.com/iamthefij/slog v1.2.0/go.mod h1:1RUj4hcCompZkAxXCRfUX786tb3cM/Zpkn97dGfUfbg=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/ebfe/scard v0.0.0-20190212122703-c3d1b1916a95 h1:OM0MnUcXBysj7ZtXvThVWHMoahuKQ8FuwIdeSLcNdP4=
|
||||
github.com/ebfe/scard v0.0.0-20190212122703-c3d1b1916a95/go.mod h1:8hHvF8DlEq5kE3KWOsZQezdWq1OTOVxZArZMscS954E=
|
||||
github.com/keybase/go-keychain v0.0.0-20201121013009-976c83ec27a6 h1:Mj0fhP9dzHKPijsmli/XbXMDKe1/KWy5xKci8e3nmBg=
|
||||
github.com/keybase/go-keychain v0.0.0-20201121013009-976c83ec27a6/go.mod h1:N83iQ9rnnzi2KZuTu+0xBcD1JNWn1jSN140ggAF7HeE=
|
||||
github.com/keybase/go.dbus v0.0.0-20200324223359-a94be52c0b03/go.mod h1:a8clEhrrGV/d76/f9r2I41BwANMihfZYV9C223vaxqE=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9 h1:sYNJzB4J8toYPQTM6pAkcmBRgw9SnQKP9oXCHfgy604=
|
||||
golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201211002650-1f0c578a6b29 h1:hAYi5mzhvBeCfkgaIHGZ8R+Q04WjSW5ZvQO3BZ94dHY=
|
||||
golang.org/x/sys v0.0.0-20201211002650-1f0c578a6b29/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 h1:/ZHdbVpdR/jk3g30/d4yUL0JU9kksj8+F/bnQUVLGDM=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf h1:MZ2shdL+ZM/XzY3ZGOnh4Nlpnxz5GSOhOmtHo3iPU6M=
|
||||
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
124
main.go
Normal file
124
main.go
Normal file
@ -0,0 +1,124 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"git.iamthefij.com/iamthefij/slog"
|
||||
keychain "github.com/keybase/go-keychain"
|
||||
"github.com/yawn/ykoath"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
var (
|
||||
oath *ykoath.OATH
|
||||
serviceName = "com.iamthefij.yk-cli"
|
||||
version = "dev"
|
||||
errFailedValidation = errors.New("failed validation, password may be incorrect")
|
||||
)
|
||||
|
||||
func setPassword(s *ykoath.Select) error {
|
||||
fmt.Print("Enter Password: ")
|
||||
|
||||
bytePassword, err := term.ReadPassword(syscall.Stdin)
|
||||
if err != nil {
|
||||
slog.Error("failed reading password from input")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// get password
|
||||
key := s.DeriveKey(string(bytePassword))
|
||||
|
||||
ok, err := oath.Validate(s, key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !ok {
|
||||
return errFailedValidation
|
||||
}
|
||||
|
||||
item := keychain.NewGenericPassword(
|
||||
serviceName,
|
||||
s.DeviceID(),
|
||||
"",
|
||||
key,
|
||||
"",
|
||||
)
|
||||
item.SetSynchronizable(keychain.SynchronizableNo)
|
||||
item.SetAccessible(keychain.AccessibleWhenUnlocked)
|
||||
err = keychain.AddItem(item)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func getPassword(s *ykoath.Select) ([]byte, error) {
|
||||
return keychain.GetGenericPassword(serviceName, s.DeviceID(), "", "")
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.BoolVar(&slog.DebugLevel, "debug", false, "enable debug logging")
|
||||
showVersion := flag.Bool("version", false, "print version and exit")
|
||||
flag.Parse()
|
||||
|
||||
if *showVersion {
|
||||
fmt.Printf("Version %s\n", version)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
oath, err = ykoath.New()
|
||||
slog.FatalOnErr(err, "failed to initialize new oath")
|
||||
|
||||
defer oath.Close()
|
||||
|
||||
if slog.DebugLevel {
|
||||
oath.Debug = slog.Debug
|
||||
}
|
||||
|
||||
// Select oath to begin
|
||||
s, err := oath.Select()
|
||||
slog.FatalOnErr(err, "failed to select oath")
|
||||
|
||||
// Check to see if we are trying to set a password
|
||||
if flag.Arg(0) == "set-password" {
|
||||
err = setPassword(s)
|
||||
slog.FatalOnErr(err, "failed to save password")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// If required, authenticate with password from keychain
|
||||
if s.Challenge != nil {
|
||||
passKey, err := getPassword(s)
|
||||
slog.FatalOnErr(err, "failed retrieving password key")
|
||||
|
||||
ok, err := oath.Validate(s, passKey)
|
||||
slog.FatalOnErr(err, "validation failed")
|
||||
|
||||
if !ok {
|
||||
slog.Fatal("failed validation, password is incorrect")
|
||||
}
|
||||
}
|
||||
|
||||
if flag.Arg(0) == "list" {
|
||||
// List names only
|
||||
names, err := oath.List()
|
||||
slog.FatalOnErr(err, "failed to list names")
|
||||
|
||||
for _, name := range names {
|
||||
fmt.Println(name.Name)
|
||||
}
|
||||
} else {
|
||||
name := flag.Arg(0)
|
||||
code, err := oath.CalculateOne(name)
|
||||
slog.FatalOnErr(err, "failed to retrieve credential")
|
||||
|
||||
fmt.Println(code)
|
||||
}
|
||||
}
|
10
scripts/yk.fish
Normal file
10
scripts/yk.fish
Normal file
@ -0,0 +1,10 @@
|
||||
# Disable file completions
|
||||
complete -c yk -f
|
||||
# Add completion for setting password
|
||||
complete -c yk -a set-password -d "Set authentication password"
|
||||
# Add completion of credentials
|
||||
complete -c yk -a "(yk list)" -d "Credential to get totp for"
|
||||
|
||||
function yk-copy --description "Select a credential using fzf and copy it to clipboard"
|
||||
yk (yk list | fzf) | pbcopy
|
||||
end
|
Loading…
Reference in New Issue
Block a user