
Instead of manually computing column sizes, use the tabwriter library package. The output is the same, but requires less fiddly space manipulation.
113 lines
2.5 KiB
Go
113 lines
2.5 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/aes"
|
|
"crypto/cipher"
|
|
"crypto/sha256"
|
|
"encoding/csv"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"os/user"
|
|
"path"
|
|
"strings"
|
|
"syscall"
|
|
"text/tabwriter"
|
|
|
|
"github.com/pcarrier/gauth/gauth"
|
|
"golang.org/x/crypto/ssh/terminal"
|
|
)
|
|
|
|
func main() {
|
|
user, e := user.Current()
|
|
if e != nil {
|
|
log.Fatal(e)
|
|
}
|
|
cfgPath := path.Join(user.HomeDir, ".config/gauth.csv")
|
|
|
|
cfgContent, e := ioutil.ReadFile(cfgPath)
|
|
if e != nil {
|
|
log.Fatal(e)
|
|
}
|
|
|
|
// Support for 'openssl enc -aes-128-cbc -md sha256 -pass pass:'
|
|
if bytes.HasPrefix(cfgContent, []byte("Salted__")) {
|
|
fmt.Printf("Encryption password: ")
|
|
passwd, e := terminal.ReadPassword(int(syscall.Stdin))
|
|
fmt.Printf("\n")
|
|
if e != nil {
|
|
log.Fatal(e)
|
|
}
|
|
salt := cfgContent[8:16]
|
|
rest := cfgContent[16:]
|
|
salting := sha256.New()
|
|
salting.Write([]byte(passwd))
|
|
salting.Write(salt)
|
|
sum := salting.Sum(nil)
|
|
key := sum[:16]
|
|
iv := sum[16:]
|
|
block, e := aes.NewCipher(key)
|
|
if e != nil {
|
|
log.Fatal(e)
|
|
}
|
|
|
|
mode := cipher.NewCBCDecrypter(block, iv)
|
|
mode.CryptBlocks(rest, rest)
|
|
// Remove padding
|
|
i := len(rest) - 1
|
|
for rest[i] < 16 {
|
|
i--
|
|
}
|
|
cfgContent = rest[:i]
|
|
}
|
|
|
|
cfgReader := csv.NewReader(bytes.NewReader(cfgContent))
|
|
// Unix-style tabular
|
|
cfgReader.Comma = ':'
|
|
|
|
cfg, e := cfgReader.ReadAll()
|
|
if e != nil {
|
|
log.Fatal(e)
|
|
}
|
|
|
|
currentTS, progress := gauth.IndexNow()
|
|
prevTS := currentTS - 1
|
|
nextTS := currentTS + 1
|
|
|
|
tw := tabwriter.NewWriter(os.Stdout, 0, 8, 1, ' ', 0)
|
|
fmt.Fprintln(tw, "\tprev\tcurr\tnext")
|
|
for _, record := range cfg {
|
|
name := record[0]
|
|
secret := normalizeSecret(record[1])
|
|
prevToken := authCodeOrDie(secret, prevTS)
|
|
currentToken := authCodeOrDie(secret, currentTS)
|
|
nextToken := authCodeOrDie(secret, nextTS)
|
|
fmt.Fprintf(tw, "%s\t%s\t%s\t%s\n", name, prevToken, currentToken, nextToken)
|
|
}
|
|
tw.Flush()
|
|
fmt.Printf("[%-29s]\n", strings.Repeat("=", progress))
|
|
}
|
|
|
|
// normalizeSecret cleans up whitespace and adds any missing padding to sec to
|
|
// use it as an OTP seed.
|
|
func normalizeSecret(sec string) string {
|
|
noPadding := strings.ToUpper(strings.Replace(sec, " ", "", -1))
|
|
padLength := 8 - (len(noPadding) % 8)
|
|
if padLength < 8 {
|
|
return noPadding + strings.Repeat("=", padLength)
|
|
}
|
|
return noPadding
|
|
}
|
|
|
|
// authCodeOrDie returns a code for the specified parameters, or aborts if an
|
|
// error occurred while generating the code.
|
|
func authCodeOrDie(sec string, ts int64) string {
|
|
str, e := gauth.Code(sec, ts)
|
|
if e != nil {
|
|
log.Fatal(e)
|
|
}
|
|
return str
|
|
}
|