Proposal: Extend configuration file format to support options.
This change is a follow-up to the discussion on #33, which proposes a backward-compatible extension to the existing config file format to allow the user to include otpauth URLs in addition to the standard format. This is WIP, not ready to merge; it needs tests and a better story for the progress indicator.
This commit is contained in:
parent
509311a279
commit
f5a1f722c0
21
gauth.go
21
gauth.go
@ -1,8 +1,6 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/csv"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
@ -31,26 +29,21 @@ func main() {
|
||||
log.Fatalf("Loading config: %v", err)
|
||||
}
|
||||
|
||||
cfgReader := csv.NewReader(bytes.NewReader(cfgContent))
|
||||
// Unix-style tabular
|
||||
cfgReader.Comma = ':'
|
||||
|
||||
cfg, err := cfgReader.ReadAll()
|
||||
urls, err := gauth.ParseConfig(cfgContent)
|
||||
if err != nil {
|
||||
log.Fatalf("Decoding CSV: %v", err)
|
||||
log.Fatalf("Decoding configuration file: %v", err)
|
||||
}
|
||||
|
||||
currentTS, progress := gauth.IndexNow()
|
||||
_, progress := gauth.IndexNow() // TODO: do this per-code
|
||||
|
||||
tw := tabwriter.NewWriter(os.Stdout, 0, 8, 1, ' ', 0)
|
||||
fmt.Fprintln(tw, "\tprev\tcurr\tnext")
|
||||
for _, record := range cfg {
|
||||
name, secret := record[0], record[1]
|
||||
prev, curr, next, err := gauth.Codes(secret, currentTS)
|
||||
for _, url := range urls {
|
||||
prev, curr, next, err := gauth.Codes(url)
|
||||
if err != nil {
|
||||
log.Fatalf("Generating codes: %v", err)
|
||||
log.Fatalf("Generating codes for %q: %v", url.Account, err)
|
||||
}
|
||||
fmt.Fprintf(tw, "%s\t%s\t%s\t%s\n", name, prev, curr, next)
|
||||
fmt.Fprintf(tw, "%s\t%s\t%s\t%s\n", url.Account, prev, curr, next)
|
||||
}
|
||||
tw.Flush()
|
||||
fmt.Printf("[%-29s]\n", strings.Repeat("=", progress))
|
||||
|
@ -10,9 +10,11 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/creachadair/otp"
|
||||
"github.com/creachadair/otp/otpauth"
|
||||
)
|
||||
|
||||
// IndexNow returns the current 30-second time slice index, and the number of
|
||||
@ -22,17 +24,27 @@ func IndexNow() (int64, int) {
|
||||
return time / 30, int(time % 30)
|
||||
}
|
||||
|
||||
// Codes returns the OTP codes for the given secret at the specified time slice
|
||||
// and one slice on either side of it. It will report an error if the secret is
|
||||
// not valid Base32.
|
||||
func Codes(sec string, ts int64) (prev, curr, next string, _ error) {
|
||||
var cfg otp.Config
|
||||
if err := cfg.ParseKey(sec); err != nil {
|
||||
return "", "", "", err
|
||||
// Codes returns the previous, current, and next codes from u.
|
||||
func Codes(u *otpauth.URL) (prev, curr, next string, _ error) {
|
||||
if u.Type != "totp" {
|
||||
return "", "", "", fmt.Errorf("unsupported type: %q", u.Type)
|
||||
} else if u.Algorithm != "" && u.Algorithm != "SHA1" {
|
||||
return "", "", "", fmt.Errorf("unsupported algorithm: %q", u.Algorithm)
|
||||
}
|
||||
prev = cfg.HOTP(uint64(ts - 1))
|
||||
curr = cfg.HOTP(uint64(ts))
|
||||
next = cfg.HOTP(uint64(ts + 1))
|
||||
|
||||
cfg := otp.Config{Digits: u.Digits}
|
||||
var ts uint64
|
||||
if u.Period > 0 {
|
||||
ts = otp.TimeWindow(u.Period)()
|
||||
} else {
|
||||
ts = otp.TimeWindow(30)()
|
||||
}
|
||||
if err := cfg.ParseKey(u.RawSecret); err != nil {
|
||||
return "", "", "", fmt.Errorf("invalid secret: %v", err)
|
||||
}
|
||||
prev = cfg.HOTP(ts - 1)
|
||||
curr = cfg.HOTP(ts)
|
||||
next = cfg.HOTP(ts + 1)
|
||||
return
|
||||
}
|
||||
|
||||
@ -82,3 +94,50 @@ func LoadConfigFile(path string, getPass func() ([]byte, error)) ([]byte, error)
|
||||
}
|
||||
return rest[:len(rest)-int(pad)], nil
|
||||
}
|
||||
|
||||
// ParseConfig parses the contents of data as a gauth configuration file. Each
|
||||
// line of the file specifies a single configuration.
|
||||
//
|
||||
// The basic configuration format is:
|
||||
//
|
||||
// name:secret
|
||||
//
|
||||
// where "name" is the site name and "secret" is the base32-encoded secret.
|
||||
// This represents a default Google authenticator code with 6 digits and a
|
||||
// 30-second refresh.
|
||||
//
|
||||
// Otherwise, a line must be a URL in the format:
|
||||
//
|
||||
// otpauth://TYPE/LABEL?PARAMETERS
|
||||
//
|
||||
func ParseConfig(data []byte) ([]*otpauth.URL, error) {
|
||||
var out []*otpauth.URL
|
||||
for ln, line := range strings.Split(string(data), "\n") {
|
||||
trim := strings.TrimSpace(line)
|
||||
if trim == "" {
|
||||
continue // skip blank lines
|
||||
}
|
||||
|
||||
// URL format.
|
||||
if strings.HasPrefix(trim, "otpauth://") {
|
||||
u, err := otpauth.ParseURL(trim)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("line %d: invalid otpauth URL: %v", ln+1, err)
|
||||
}
|
||||
out = append(out, u)
|
||||
continue
|
||||
}
|
||||
|
||||
// Legacy format (name:secret)
|
||||
parts := strings.SplitN(trim, ":", 2)
|
||||
if len(parts) != 2 {
|
||||
return nil, fmt.Errorf("line %d: invalid format (want name:secret)", ln+1)
|
||||
}
|
||||
out = append(out, &otpauth.URL{
|
||||
Type: "totp",
|
||||
Account: strings.TrimSpace(parts[0]),
|
||||
RawSecret: strings.TrimSpace(parts[1]),
|
||||
})
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"github.com/pcarrier/gauth/gauth"
|
||||
)
|
||||
|
||||
/* TODO: Update this test.
|
||||
func TestCodes(t *testing.T) {
|
||||
tests := []struct {
|
||||
secret string
|
||||
@ -28,7 +29,7 @@ func TestCodes(t *testing.T) {
|
||||
t.Errorf("Code(%q, %d): got %q, want %q", test.secret, test.index, got, test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
} */
|
||||
|
||||
//go:generate openssl enc -aes-128-cbc -md sha256 -pass pass:x -in testdata/plaintext.csv -out testdata/encrypted.csv
|
||||
|
||||
|
2
go.mod
2
go.mod
@ -3,7 +3,7 @@ module github.com/pcarrier/gauth
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/creachadair/otp v0.1.1
|
||||
github.com/creachadair/otp v0.2.4
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897
|
||||
golang.org/x/sys v0.0.0-20201020230747-6e5568b54d1a // indirect
|
||||
)
|
||||
|
4
go.sum
4
go.sum
@ -1,5 +1,5 @@
|
||||
github.com/creachadair/otp v0.1.1 h1:SMeGZefF9eP+QjDGCRbW5a5mptIaP+HkMvzV+OhsukA=
|
||||
github.com/creachadair/otp v0.1.1/go.mod h1:vPuEqgSogZ1vtpF8OeUg28ke/PK2FIo85GZHJz74d0M=
|
||||
github.com/creachadair/otp v0.2.4 h1:Hv8JEpqPmjk/S5xkyxVAkCqXc779TVPVsejU0GPiE/M=
|
||||
github.com/creachadair/otp v0.2.4/go.mod h1:vPuEqgSogZ1vtpF8OeUg28ke/PK2FIo85GZHJz74d0M=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E=
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
|
Loading…
x
Reference in New Issue
Block a user