gauth/gauth.go
M. J. Fromberger 87aa5234c1 Separate OTP generation into a library.
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.
2019-06-01 18:07:27 -07:00

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
}