Browse Source

Initial working commit

master v0.1.0
IamTheFij 6 months ago
parent
commit
79688199cb
  1. 2
      .gitignore
  2. 4
      .golangci.yml
  3. 16
      .pre-commit-config.yaml
  4. 47
      Makefile
  5. 16
      README.md
  6. 13
      go.mod
  7. 45
      go.sum
  8. 124
      main.go
  9. 10
      scripts/yk.fish

2
.gitignore

@ -15,3 +15,5 @@
# Dependency directories (remove the comment below to include it)
# vendor/
build/
dist/

4
.golangci.yml

@ -0,0 +1,4 @@
---
linters:
disable:
- gochecknoglobals

16
.pre-commit-config.yaml

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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…
Cancel
Save