
No functional changes are intended; the main package now imports the library and uses it, but the implementation is unchanged. Specific highlights: - Change the names of the functions to avert stutter following the advice of Effective Go: https://golang.org/doc/effective_go.html#package-names - Reorganize the helpers in main so control flow is easier to follow. - Add documentation comments.
122 lines
2.7 KiB
Go
122 lines
2.7 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/aes"
|
|
"crypto/cipher"
|
|
"crypto/sha256"
|
|
"encoding/csv"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"os/user"
|
|
"path"
|
|
"strings"
|
|
"syscall"
|
|
|
|
"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
|
|
|
|
wordSize := 0
|
|
for _, record := range cfg {
|
|
actualSize := len([]rune(record[0]))
|
|
if actualSize > wordSize {
|
|
wordSize = actualSize
|
|
}
|
|
}
|
|
|
|
var header = "prev curr next"
|
|
fmt.Println(leftPad(header, " ", wordSize+1))
|
|
for _, record := range cfg {
|
|
name := record[0]
|
|
secret := normalizeSecret(record[1])
|
|
prevToken := authCodeOrDie(secret, prevTS)
|
|
currentToken := authCodeOrDie(secret, currentTS)
|
|
nextToken := authCodeOrDie(secret, nextTS)
|
|
fmt.Printf("%-*s %s %s %s\n", wordSize, name, prevToken, currentToken, nextToken)
|
|
}
|
|
fmt.Printf("[%-29s]\n", strings.Repeat("=", progress))
|
|
}
|
|
|
|
func leftPad(s string, padStr string, pLen int) string {
|
|
return strings.Repeat(padStr, pLen) + s
|
|
}
|
|
|
|
// 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
|
|
}
|