Skip to content

Commit

Permalink
Proposal: Extend configuration file format to support options.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
creachadair authored and pcarrier committed Mar 11, 2021
1 parent 509311a commit f5a1f72
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 28 deletions.
21 changes: 7 additions & 14 deletions gauth.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package main

import (
"bytes"
"encoding/csv"
"fmt"
"log"
"os"
Expand Down Expand Up @@ -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))
Expand Down
79 changes: 69 additions & 10 deletions gauth/gauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}

Expand Down Expand Up @@ -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
}
3 changes: 2 additions & 1 deletion gauth/gauth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/pcarrier/gauth/gauth"
)

/* TODO: Update this test.
func TestCodes(t *testing.T) {
tests := []struct {
secret string
Expand All @@ -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

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -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=
Expand Down

0 comments on commit f5a1f72

Please sign in to comment.