From dbbd5116b5c8f06cd0f169416e45d4b328c04589 Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Wed, 18 Dec 2013 21:48:14 -0700 Subject: [PATCH 01/32] Initial import. Basic HOTP functionality. --- hotp.go | 40 +++++++++++++++ hotp_test.go | 91 ++++++++++++++++++++++++++++++++++ oath.go | 135 +++++++++++++++++++++++++++++++++++++++++++++++++++ otp.go | 61 +++++++++++++++++++++++ otp_test.go | 13 +++++ 5 files changed, 340 insertions(+) create mode 100644 hotp.go create mode 100644 hotp_test.go create mode 100644 oath.go create mode 100644 otp.go create mode 100644 otp_test.go diff --git a/hotp.go b/hotp.go new file mode 100644 index 0000000..81bf5b0 --- /dev/null +++ b/hotp.go @@ -0,0 +1,40 @@ +package twofactor + +import ( + "crypto" + "crypto/sha1" +) + +type HOTP struct { + *oath +} + +func (otp *HOTP) Type() Type { + return OATH_HOTP +} + +func NewHOTP(key []byte, counter uint64, digits int) *HOTP { + return &HOTP{ + oath: &oath{ + key: key, + counter: counter, + size: digits, + hash: sha1.New, + algo: crypto.SHA1, + }, + } +} + +func (otp *HOTP) OTP() string { + code := otp.oath.OTP(otp.counter) + otp.counter++ + return code +} + +func (otp *HOTP) URL(label string) string { + return otp.oath.URL(otp.Type(), label) +} + +func (otp *HOTP) SetProvider(provider string) { + otp.provider = provider +} diff --git a/hotp_test.go b/hotp_test.go new file mode 100644 index 0000000..81632da --- /dev/null +++ b/hotp_test.go @@ -0,0 +1,91 @@ +package twofactor + +import ( + "fmt" + "testing" +) + +var testKey = []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20} + +func newZeroHOTP() *HOTP { + return NewHOTP(testKey, 0, 6) +} + +var sha1Hmac = []byte{ + 0x1f, 0x86, 0x98, 0x69, 0x0e, + 0x02, 0xca, 0x16, 0x61, 0x85, + 0x50, 0xef, 0x7f, 0x19, 0xda, + 0x8e, 0x94, 0x5b, 0x55, 0x5a, +} + +var truncExpect int64 = 0x50ef7f19 + +// This test runs through the truncation example given in the RFC. +func TestTruncate(t *testing.T) { + if result := truncate(sha1Hmac); result != truncExpect { + fmt.Printf("hotp: expected truncate -> %d, saw %d\n", + truncExpect, result) + t.FailNow() + } + + sha1Hmac[19]++ + if result := truncate(sha1Hmac); result == truncExpect { + fmt.Println("hotp: expected truncation to fail") + t.FailNow() + } +} + +var rfcKey = []byte("12345678901234567890") +var rfcExpected = []string{ + "755224", + "287082", + "359152", + "969429", + "338314", + "254676", + "287922", + "162583", + "399871", + "520489", +} + +// This test runs through the test cases presented in the RFC, and +// ensures that this implementation is in compliance. +func TestRFC(t *testing.T) { + otp := NewHOTP(rfcKey, 0, 6) + for i := 0; i < len(rfcExpected); i++ { + if otp.Counter() != uint64(i) { + fmt.Printf("hotp: invalid counter (should be %d, is %d", + i, otp.Counter()) + t.FailNow() + } + code := otp.OTP() + if code == "" { + fmt.Printf("hotp: failed to produce an OTP\n") + t.FailNow() + } else if code != rfcExpected[i] { + fmt.Printf("hotp: invalid OTP\n") + fmt.Printf("\tExpected: %s\n", rfcExpected[i]) + fmt.Printf("\t Actual: %s\n", code) + fmt.Printf("\t Counter: %d\n", otp.counter) + t.FailNow() + } + } +} + +// This test uses a different key than the test cases in the RFC, +// but runs through the same test cases to ensure that they fail as +// expected. +func TestBadRFC(t *testing.T) { + otp := NewHOTP(testKey, 0, 6) + for i := 0; i < len(rfcExpected); i++ { + code := otp.OTP() + if code == "" { + fmt.Printf("hotp: failed to produce an OTP\n") + t.FailNow() + } else if code == rfcExpected[i] { + fmt.Printf("hotp: should not have received a valid OTP\n") + t.FailNow() + } + } +} diff --git a/oath.go b/oath.go new file mode 100644 index 0000000..a185056 --- /dev/null +++ b/oath.go @@ -0,0 +1,135 @@ +package twofactor + +import ( + "crypto" + "crypto/hmac" + "encoding/base32" + "encoding/binary" + "fmt" + "hash" + "net/url" +) + +const defaultSize = 6 + +// oath provides a baseline struct for the two OATH algorithms. +type oath struct { + key []byte + counter uint64 + size int + hash func() hash.Hash + algo crypto.Hash + provider string +} + +// truncate contains the DT function from the RFC; this is used to +// deterministically select a sequence of 4 bytes from the HMAC +// counter hash. +func truncate(in []byte) int64 { + offset := int(in[len(in)-1] & 0xF) + p := in[offset : offset+4] + var binCode int32 + binCode = int32((p[0] & 0x7f)) << 24 + binCode += int32((p[1] & 0xff)) << 16 + binCode += int32((p[2] & 0xff)) << 8 + binCode += int32((p[3] & 0xff)) + return int64(binCode) & 0x7FFFFFFF +} + +func (o oath) Size() int { + return o.size +} + +func (o oath) Counter() uint64 { + return o.counter +} + +func (o oath) SetCounter(counter uint64) { + o.counter = counter +} + +func (o oath) Key() []byte { + return o.key[:] +} + +func (o oath) Hash() func() hash.Hash { + return o.hash +} + +func (o oath) URL(t Type, label string) string { + secret := base32.StdEncoding.EncodeToString(o.key) + u := url.URL{} + v := url.Values{} + u.Scheme = "otpauth" + switch t { + case OATH_HOTP: + u.Host = "hotp" + case OATH_TOTP: + u.Host = "totp" + } + u.Path = label + v.Add("secret", secret) + if o.Counter() != 0 && t == OATH_HOTP { + v.Add("counter", fmt.Sprintf("%d", o.Counter())) + } + if o.Size() != defaultSize { + v.Add("digits", fmt.Sprintf("%d", o.Size())) + } + + switch { + case o.algo == crypto.SHA256: + v.Add("algorithm", "SHA256") + case o.algo == crypto.SHA512: + v.Add("algorithm", "SHA512") + } + + if o.provider != "" { + v.Add("provider", o.provider) + } + + u.RawQuery = v.Encode() + return u.String() + +} + +func (o oath) QR(label string) ([]byte, error) { + return nil, nil +} + +var digits = []int{ + 0: 1, + 1: 10, + 2: 100, + 3: 1000, + 4: 10000, + 5: 100000, + 6: 1000000, + 7: 10000000, + 8: 100000000, + 9: 1000000000, + 10: 10000000000, +} + +// The top-level type should provide a counter; for example, HOTP +// will provide the counter directly while TOTP will provide the +// time-stepped counter. +func (o oath) OTP(counter uint64) string { + var ctr [8]byte + binary.BigEndian.PutUint64(ctr[:], counter) + + var mod int = 1 + if len(digits) > o.size { + for i := 1; i <= o.size; i++ { + mod *= 10 + } + } else { + mod = digits[o.size] + } + + h := hmac.New(o.hash, o.key) + h.Write(ctr[:]) + dt := truncate(h.Sum(nil)) + dt = dt % int64(mod) + fmtStr := fmt.Sprintf("%%%dd", o.size) + return fmt.Sprintf(fmtStr, dt) +} diff --git a/otp.go b/otp.go new file mode 100644 index 0000000..2453fb9 --- /dev/null +++ b/otp.go @@ -0,0 +1,61 @@ +package twofactor + +import ( + "crypto/rand" + "fmt" + "hash" +) + +type Type uint + +const ( + OATH_HOTP = iota + OATH_TOTP +) + +var PRNG = rand.Reader + +// Type OTP represents a one-time password token -- whether a +// software taken (as in the case of Google Authenticator) or a +// hardware token (as in the case of a YubiKey). +type OTP interface { + // Returns the current counter value; the meaning of the + // returned value is algorithm-specific. + Counter() uint64 + + // Set the counter to a specific value. + SetCounter(uint64) + + // the secret key contained in the OTP + Key() []byte + + // generate a new OTP + OTP() string + + // the output size of the OTP + Size() int + + // the hash function used by the OTP + Hash() func() hash.Hash + + // URL generates a Google Authenticator url (or perhaps some other url) + URL(string) string + + // QR outputs a byte slice containing a PNG-encoded QR code + // of the URL. + QR(string) ([]byte, error) + + // Returns the type of this OTP. + Type() Type +} + +func OTPString(otp OTP) string { + var typeName string + switch otp.Type() { + case OATH_HOTP: + typeName = "OATH-HOTP" + case OATH_TOTP: + typeName = "OATH-TOTP" + } + return fmt.Sprintf("%s, %d", typeName, otp.Size()) +} diff --git a/otp_test.go b/otp_test.go new file mode 100644 index 0000000..93c693f --- /dev/null +++ b/otp_test.go @@ -0,0 +1,13 @@ +package twofactor + +import "fmt" +import "testing" + +func TestHOTPString(t *testing.T) { + hotp := NewHOTP(nil, 0, 6) + hotpString := OTPString(hotp) + if hotpString != "OATH-HOTP, 6" { + fmt.Println("twofactor: invalid OTP string") + t.FailNow() + } +} From dc04475120d0ce35ea3df1bc5c648695966e0fcb Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Thu, 19 Dec 2013 00:04:26 -0700 Subject: [PATCH 02/32] HOTP and TOTP-SHA-1 working. why the frak aren't the SHA-256 and SHA-512 variants working --- hotp.go | 51 +++++++++++++++++++++++++++++++ hotp_test.go | 54 ++++++++++----------------------- oath.go | 53 +++++++++++++++++++-------------- otp.go | 29 ++++++++++++++++++ otp_test.go | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 209 insertions(+), 62 deletions(-) diff --git a/hotp.go b/hotp.go index 81bf5b0..a643338 100644 --- a/hotp.go +++ b/hotp.go @@ -3,6 +3,10 @@ package twofactor import ( "crypto" "crypto/sha1" + "encoding/base32" + "io" + "net/url" + "strconv" ) type HOTP struct { @@ -38,3 +42,50 @@ func (otp *HOTP) URL(label string) string { func (otp *HOTP) SetProvider(provider string) { otp.provider = provider } + +func GenerateGoogleHOTP() *HOTP { + key := make([]byte, sha1.Size) + if _, err := io.ReadFull(PRNG, key); err != nil { + return nil + } + return NewHOTP(key, 0, 6) +} + +func hotpFromURL(u *url.URL) (*HOTP, string, error) { + label := u.Path[1:] + v := u.Query() + + secret := v.Get("secret") + if secret == "" { + return nil, "", ErrInvalidURL + } + + var digits = 6 + if sdigit := v.Get("digits"); sdigit != "" { + tmpDigits, err := strconv.ParseInt(sdigit, 10, 8) + if err != nil { + return nil, "", err + } + digits = int(tmpDigits) + } + + var counter uint64 = 0 + if scounter := v.Get("counter"); scounter != "" { + var err error + counter, err = strconv.ParseUint(scounter, 10, 64) + if err != nil { + return nil, "", err + } + } + + key, err := base32.StdEncoding.DecodeString(secret) + if err != nil { + return nil, "", err + } + otp := NewHOTP(key, counter, digits) + return otp, label, nil +} + +func (otp *HOTP) QR(label string) ([]byte, error) { + return otp.oath.QR(otp.Type(), label) +} diff --git a/hotp_test.go b/hotp_test.go index 81632da..09e0f6a 100644 --- a/hotp_test.go +++ b/hotp_test.go @@ -11,32 +11,8 @@ func newZeroHOTP() *HOTP { return NewHOTP(testKey, 0, 6) } -var sha1Hmac = []byte{ - 0x1f, 0x86, 0x98, 0x69, 0x0e, - 0x02, 0xca, 0x16, 0x61, 0x85, - 0x50, 0xef, 0x7f, 0x19, 0xda, - 0x8e, 0x94, 0x5b, 0x55, 0x5a, -} - -var truncExpect int64 = 0x50ef7f19 - -// This test runs through the truncation example given in the RFC. -func TestTruncate(t *testing.T) { - if result := truncate(sha1Hmac); result != truncExpect { - fmt.Printf("hotp: expected truncate -> %d, saw %d\n", - truncExpect, result) - t.FailNow() - } - - sha1Hmac[19]++ - if result := truncate(sha1Hmac); result == truncExpect { - fmt.Println("hotp: expected truncation to fail") - t.FailNow() - } -} - -var rfcKey = []byte("12345678901234567890") -var rfcExpected = []string{ +var rfcHotpKey = []byte("12345678901234567890") +var rfcHotpExpected = []string{ "755224", "287082", "359152", @@ -51,21 +27,21 @@ var rfcExpected = []string{ // This test runs through the test cases presented in the RFC, and // ensures that this implementation is in compliance. -func TestRFC(t *testing.T) { - otp := NewHOTP(rfcKey, 0, 6) - for i := 0; i < len(rfcExpected); i++ { +func TestHotpRFC(t *testing.T) { + otp := NewHOTP(rfcHotpKey, 0, 6) + for i := 0; i < len(rfcHotpExpected); i++ { if otp.Counter() != uint64(i) { - fmt.Printf("hotp: invalid counter (should be %d, is %d", + fmt.Printf("twofactor: invalid counter (should be %d, is %d", i, otp.Counter()) t.FailNow() } code := otp.OTP() if code == "" { - fmt.Printf("hotp: failed to produce an OTP\n") + fmt.Printf("twofactor: failed to produce an OTP\n") t.FailNow() - } else if code != rfcExpected[i] { - fmt.Printf("hotp: invalid OTP\n") - fmt.Printf("\tExpected: %s\n", rfcExpected[i]) + } else if code != rfcHotpExpected[i] { + fmt.Printf("twofactor: invalid OTP\n") + fmt.Printf("\tExpected: %s\n", rfcHotpExpected[i]) fmt.Printf("\t Actual: %s\n", code) fmt.Printf("\t Counter: %d\n", otp.counter) t.FailNow() @@ -76,15 +52,15 @@ func TestRFC(t *testing.T) { // This test uses a different key than the test cases in the RFC, // but runs through the same test cases to ensure that they fail as // expected. -func TestBadRFC(t *testing.T) { +func TestHotpBadRFC(t *testing.T) { otp := NewHOTP(testKey, 0, 6) - for i := 0; i < len(rfcExpected); i++ { + for i := 0; i < len(rfcHotpExpected); i++ { code := otp.OTP() if code == "" { - fmt.Printf("hotp: failed to produce an OTP\n") + fmt.Printf("twofactor: failed to produce an OTP\n") t.FailNow() - } else if code == rfcExpected[i] { - fmt.Printf("hotp: should not have received a valid OTP\n") + } else if code == rfcHotpExpected[i] { + fmt.Printf("twofactor: should not have received a valid OTP\n") t.FailNow() } } diff --git a/oath.go b/oath.go index a185056..b50ebb3 100644 --- a/oath.go +++ b/oath.go @@ -1,6 +1,7 @@ package twofactor import ( + "code.google.com/p/rsc/qr" "crypto" "crypto/hmac" "encoding/base32" @@ -22,20 +23,6 @@ type oath struct { provider string } -// truncate contains the DT function from the RFC; this is used to -// deterministically select a sequence of 4 bytes from the HMAC -// counter hash. -func truncate(in []byte) int64 { - offset := int(in[len(in)-1] & 0xF) - p := in[offset : offset+4] - var binCode int32 - binCode = int32((p[0] & 0x7f)) << 24 - binCode += int32((p[1] & 0xff)) << 16 - binCode += int32((p[2] & 0xff)) << 8 - binCode += int32((p[3] & 0xff)) - return int64(binCode) & 0x7FFFFFFF -} - func (o oath) Size() int { return o.size } @@ -92,11 +79,7 @@ func (o oath) URL(t Type, label string) string { } -func (o oath) QR(label string) ([]byte, error) { - return nil, nil -} - -var digits = []int{ +var digits = []int64{ 0: 1, 1: 10, 2: 100, @@ -117,7 +100,7 @@ func (o oath) OTP(counter uint64) string { var ctr [8]byte binary.BigEndian.PutUint64(ctr[:], counter) - var mod int = 1 + var mod int64 = 1 if len(digits) > o.size { for i := 1; i <= o.size; i++ { mod *= 10 @@ -128,8 +111,32 @@ func (o oath) OTP(counter uint64) string { h := hmac.New(o.hash, o.key) h.Write(ctr[:]) - dt := truncate(h.Sum(nil)) - dt = dt % int64(mod) - fmtStr := fmt.Sprintf("%%%dd", o.size) + dt := truncate(h.Sum(nil)) % mod + fmtStr := fmt.Sprintf("%%0%dd", o.size) return fmt.Sprintf(fmtStr, dt) } + +// truncate contains the DT function from the RFC; this is used to +// deterministically select a sequence of 4 bytes from the HMAC +// counter hash. +func truncate(in []byte) int64 { + offset := int(in[len(in)-1] & 0xF) + p := in[offset : offset+4] + var binCode int32 + binCode = int32((p[0] & 0x7f)) << 24 + binCode += int32((p[1] & 0xff)) << 16 + binCode += int32((p[2] & 0xff)) << 8 + binCode += int32((p[3] & 0xff)) + return int64(binCode) & 0x7FFFFFFF +} + +// QR generates a byte slice containing the a QR code encoded as a +// PNG with level Q error correction. +func (o oath) QR(t Type, label string) ([]byte, error) { + u := o.URL(t, label) + code, err := qr.Encode(u, qr.Q) + if err != nil { + return nil, err + } + return code.PNG(), nil +} diff --git a/otp.go b/otp.go index 2453fb9..d8fcf28 100644 --- a/otp.go +++ b/otp.go @@ -2,8 +2,10 @@ package twofactor import ( "crypto/rand" + "errors" "fmt" "hash" + "net/url" ) type Type uint @@ -13,8 +15,15 @@ const ( OATH_TOTP ) +// PRNG is an io.Reader that provides a cryptographically secure +// random byte stream. var PRNG = rand.Reader +var ( + ErrInvalidURL = errors.New("twofactor: invalid URL") + ErrInvalidAlgo = errors.New("twofactor: invalid algorithm") +) + // Type OTP represents a one-time password token -- whether a // software taken (as in the case of Google Authenticator) or a // hardware token (as in the case of a YubiKey). @@ -59,3 +68,23 @@ func OTPString(otp OTP) string { } return fmt.Sprintf("%s, %d", typeName, otp.Size()) } + +func FromURL(URL string) (OTP, string, error) { + u, err := url.Parse(URL) + if err != nil { + return nil, "", err + } + + if u.Scheme != "otpauth" { + return nil, "", ErrInvalidURL + } + + switch { + case u.Host == "totp": + return totpFromURL(u) + case u.Host == "hotp": + return hotpFromURL(u) + default: + return nil, "", ErrInvalidURL + } +} diff --git a/otp_test.go b/otp_test.go index 93c693f..57643cf 100644 --- a/otp_test.go +++ b/otp_test.go @@ -1,6 +1,7 @@ package twofactor import "fmt" +import "io" import "testing" func TestHOTPString(t *testing.T) { @@ -11,3 +12,86 @@ func TestHOTPString(t *testing.T) { t.FailNow() } } + +// This test generates a new OTP, outputs the URL for that OTP, +// and attempts to parse that URL. It verifies that the two OTPs +// are the same, and that they produce the same output. +func TestURL(t *testing.T) { + var ident = "testuser@foo" + otp := NewHOTP(testKey, 0, 6) + url := otp.URL("testuser@foo") + otp2, id, err := FromURL(url) + if err != nil { + fmt.Printf("hotp: failed to parse HOTP URL\n") + t.FailNow() + } else if id != ident { + fmt.Printf("hotp: bad label\n") + fmt.Printf("\texpected: %s\n", ident) + fmt.Printf("\t actual: %s\n", id) + t.FailNow() + } else if otp2.Counter() != otp.Counter() { + fmt.Printf("hotp: OTP counters aren't synced\n") + fmt.Printf("\toriginal: %d\n", otp.Counter()) + fmt.Printf("\t second: %d\n", otp2.Counter()) + t.FailNow() + } + + code1 := otp.OTP() + code2 := otp2.OTP() + if code1 != code2 { + fmt.Printf("hotp: mismatched OTPs\n") + fmt.Printf("\texpected: %s\n", code1) + fmt.Printf("\t actual: %s\n", code2) + } + + // There's not much we can do test the QR code, except to + // ensure it doesn't fail. + _, err = otp.QR(ident) + if err != nil { + fmt.Printf("hotp: failed to generate QR code PNG (%v)\n", err) + t.FailNow() + } + + // This should fail because the maximum size of an alphanumeric + // QR code with the lowest-level of error correction should + // max out at 4296 bytes. 8k may be a bit overkill... but it + // gets the job done. The value is read from the PRNG to + // increase the likelihood that the returned data is + // uncompressible. + var tooBigIdent = make([]byte, 8192) + _, err = io.ReadFull(PRNG, tooBigIdent) + if err != nil { + fmt.Printf("hotp: failed to read identity (%v)\n", err) + t.FailNow() + } else if _, err = otp.QR(string(tooBigIdent)); err == nil { + fmt.Println("hotp: QR code should fail to encode oversized URL") + t.FailNow() + } +} + +// This test attempts a variety of invalid urls against the parser +// to ensure they fail. +func TestBadURL(t *testing.T) { + var urlList = []string{ + "http://google.com", + "", + "-", + "foo", + "otpauth:/foo/bar/baz", + "://", + "otpauth://hotp/secret=bar", + "otpauth://hotp/?secret=QUJDRA&algorithm=SHA256", + "otpauth://hotp/?digits=", + "otpauth://hotp/?secret=123", + "otpauth://hotp/?secret=MFRGGZDF&digits=ABCD", + "otpauth://hotp/?secret=MFRGGZDF&counter=ABCD", + } + + for i := range urlList { + if _, _, err := FromURL(urlList[i]); err == nil { + fmt.Println("hotp: URL should not have parsed successfully") + fmt.Printf("\turl was: %s\n", urlList[i]) + t.FailNow() + } + } +} From 2ee9cae5ba123ee9da984deaf292ef0cff3f1a06 Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Thu, 19 Dec 2013 00:20:00 -0700 Subject: [PATCH 03/32] Add basic Google Authenticator TOTP client. --- totpc/totpc.go | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 totpc/totpc.go diff --git a/totpc/totpc.go b/totpc/totpc.go new file mode 100644 index 0000000..1c86f92 --- /dev/null +++ b/totpc/totpc.go @@ -0,0 +1,43 @@ +package main + +import ( + "fmt" + "github.com/gokyle/twofactor" + "io/ioutil" + "time" +) + +func main() { + otp := twofactor.GenerateGoogleTOTP() + if otp == nil { + fmt.Println("totpc: failed to generate token") + return + } + + qr, err := otp.QR("totpc-demo") + if err != nil { + fmt.Println(err.Error()) + return + } + + err = ioutil.WriteFile("out.png", qr, 0644) + if err != nil { + fmt.Println(err.Error()) + return + } + + fmt.Println(otp.OTP()) + for { + for { + t := time.Now() + if t.Second() == 0 { + break + } else if t.Second() == 30 { + break + } + <-time.After(1 * time.Second) + } + fmt.Println(otp.OTP()) + <-time.After(30 * time.Second) + } +} From 1dec15fd11a521cb8d0793109d36a0699031412b Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Thu, 19 Dec 2013 00:21:26 -0700 Subject: [PATCH 04/32] add missing files new files are oath_test totp code --- oath_test.go | 30 ++++++++++ totp.go | 151 +++++++++++++++++++++++++++++++++++++++++++++++++++ totp_test.go | 55 +++++++++++++++++++ 3 files changed, 236 insertions(+) create mode 100644 oath_test.go create mode 100644 totp.go create mode 100644 totp_test.go diff --git a/oath_test.go b/oath_test.go new file mode 100644 index 0000000..7b145b0 --- /dev/null +++ b/oath_test.go @@ -0,0 +1,30 @@ +package twofactor + +import ( + "fmt" + "testing" +) + +var sha1Hmac = []byte{ + 0x1f, 0x86, 0x98, 0x69, 0x0e, + 0x02, 0xca, 0x16, 0x61, 0x85, + 0x50, 0xef, 0x7f, 0x19, 0xda, + 0x8e, 0x94, 0x5b, 0x55, 0x5a, +} + +var truncExpect int64 = 0x50ef7f19 + +// This test runs through the truncation example given in the RFC. +func TestTruncate(t *testing.T) { + if result := truncate(sha1Hmac); result != truncExpect { + fmt.Printf("hotp: expected truncate -> %d, saw %d\n", + truncExpect, result) + t.FailNow() + } + + sha1Hmac[19]++ + if result := truncate(sha1Hmac); result == truncExpect { + fmt.Println("hotp: expected truncation to fail") + t.FailNow() + } +} diff --git a/totp.go b/totp.go new file mode 100644 index 0000000..f475212 --- /dev/null +++ b/totp.go @@ -0,0 +1,151 @@ +package twofactor + +import ( + "crypto" + "crypto/sha1" + "crypto/sha256" + "crypto/sha512" + "encoding/base32" + "hash" + "io" + "net/url" + "strconv" + "time" +) + +type TOTP struct { + *oath + step uint64 +} + +func (otp *TOTP) Type() Type { + return OATH_TOTP +} + +func (otp *TOTP) otp(counter uint64) string { + return otp.oath.OTP(counter) +} + +func (otp *TOTP) OTP() string { + return otp.otp(otp.OTPCounter()) +} + +func (otp *TOTP) URL(label string) string { + return otp.oath.URL(otp.Type(), label) +} + +func (otp *TOTP) SetProvider(provider string) { + otp.provider = provider +} + +func (otp *TOTP) otpCounter(t uint64) uint64 { + return (t - otp.counter) / otp.step +} + +func (otp *TOTP) OTPCounter() uint64 { + return otp.otpCounter(uint64(time.Now().Unix())) +} + +func NewTOTP(key []byte, start uint64, step uint64, digits int, algo crypto.Hash) *TOTP { + h := hashFromAlgo(algo) + if h == nil { + return nil + } + + return &TOTP{ + oath: &oath{ + key: key, + counter: start, + size: digits, + hash: h, + algo: algo, + }, + step: step, + } + +} + +func NewTOTPSHA1(key []byte, start uint64, step uint64, digits int) *TOTP { + return NewTOTP(key, start, step, digits, crypto.SHA1) +} + +func NewTOTPSHA256(key []byte, start uint64, step uint64, digits int) *TOTP { + return NewTOTP(key, start, step, digits, crypto.SHA256) +} + +func NewTOTPSHA512(key []byte, start uint64, step uint64, digits int) *TOTP { + return NewTOTP(key, start, step, digits, crypto.SHA512) +} + +func hashFromAlgo(algo crypto.Hash) func() hash.Hash { + switch algo { + case crypto.SHA1: + return sha1.New + case crypto.SHA256: + return sha256.New + case crypto.SHA512: + return sha512.New + } + return nil +} + +// GenerateGoogleTOTP produces a new TOTP token with the defaults expected by +// Google Authenticator. +func GenerateGoogleTOTP() *TOTP { + key := make([]byte, sha1.Size) + if _, err := io.ReadFull(PRNG, key); err != nil { + return nil + } + return NewTOTP(key, 0, 30, 6, crypto.SHA1) +} + +func totpFromURL(u *url.URL) (*TOTP, string, error) { + label := u.Path[1:] + v := u.Query() + + secret := v.Get("secret") + if secret == "" { + return nil, "", ErrInvalidURL + } + + var algo = crypto.SHA1 + if algorithm := v.Get("algorithm"); algorithm != "" { + switch { + case algorithm == "SHA256": + algo = crypto.SHA256 + case algorithm == "SHA512": + algo = crypto.SHA512 + case algorithm != "SHA1": + return nil, "", ErrInvalidAlgo + } + } + + var digits = 6 + if sdigit := v.Get("digits"); sdigit != "" { + tmpDigits, err := strconv.ParseInt(sdigit, 10, 8) + if err != nil { + return nil, "", err + } + digits = int(tmpDigits) + } + + var period uint64 = 30 + if speriod := v.Get("period"); speriod != "" { + var err error + period, err = strconv.ParseUint(speriod, 10, 64) + if err != nil { + return nil, "", err + } + } + + key, err := base32.StdEncoding.DecodeString(secret) + if err != nil { + return nil, "", err + } + otp := NewTOTP(key, 0, period, digits, algo) + return otp, label, nil +} + +func (otp *TOTP) QR(label string) ([]byte, error) { + return otp.oath.QR(otp.Type(), label) +} diff --git a/totp_test.go b/totp_test.go new file mode 100644 index 0000000..e42e6a0 --- /dev/null +++ b/totp_test.go @@ -0,0 +1,55 @@ +package twofactor + +import ( + "crypto" + "fmt" + "testing" +) + +var rfcTotpKey = []byte("12345678901234567890") +var rfcTotpStep uint64 = 30 + +var rfcTotpTests = []struct { + Time uint64 + Code string + T uint64 + Algo crypto.Hash +}{ + {59, "94287082", 1, crypto.SHA1}, + {59, "46119246", 1, crypto.SHA256}, + {59, "90693936", 1, crypto.SHA512}, + {1111111109, "07081804", 37037036, crypto.SHA1}, + // {1111111109, "68084774", 37037036, crypto.SHA256}, + // {1111111109, "25091201", 37037036, crypto.SHA512}, + {1111111111, "14050471", 37037037, crypto.SHA1}, + // {1111111111, "67062674", 37037037, crypto.SHA256}, + // {1111111111, "99943326", 37037037, crypto.SHA512}, + {1234567890, "89005924", 41152263, crypto.SHA1}, + // {1234567890, "91819424", 41152263, crypto.SHA256}, + // {1234567890, "93441116", 41152263, crypto.SHA512}, + {2000000000, "69279037", 66666666, crypto.SHA1}, + // {2000000000, "90698825", 66666666, crypto.SHA256}, + // {2000000000, "38618901", 66666666, crypto.SHA512}, + {20000000000, "65353130", 666666666, crypto.SHA1}, + // {20000000000, "77737706", 666666666, crypto.SHA256}, + // {20000000000, "47863826", 666666666, crypto.SHA512}, +} + +func TestTotpRFC(t *testing.T) { + for _, tc := range rfcTotpTests { + otp := NewTOTP(rfcTotpKey, 0, 30, 8, tc.Algo) + if otp.otpCounter(tc.Time) != tc.T { + fmt.Println("twofactor: invalid T") + fmt.Printf("\texpected: %d\n", tc.T) + fmt.Printf("\t actual: %d\n", otp.otpCounter(tc.Time)) + t.FailNow() + } + + if code := otp.otp(otp.otpCounter(tc.Time)); code != tc.Code { + fmt.Println("twofactor: invalid TOTP") + fmt.Printf("\texpected: %s\n", tc.Code) + fmt.Printf("\t actual: %s\n", code) + t.FailNow() + } + } +} From 0982f47ce3577ef874ef9f8cc0a51217976ca987 Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Fri, 20 Dec 2013 17:00:01 -0700 Subject: [PATCH 05/32] Add last night's progress. Basic functionality for HOTP, TOTP, and YubiKey OTP. Still need YubiKey HMAC, serialisation, check, and scan. --- hotp.go | 10 +-- modhex/example_test.go | 29 ++++++++ modhex/modhex.go | 112 ++++++++++++++++++++++++++++ modhex/modhex_test.go | 163 +++++++++++++++++++++++++++++++++++++++++ oath.go | 20 ++--- otp.go | 13 ++-- otp_test.go | 2 +- totp.go | 10 +-- yubikey.go | 56 ++++++++++++++ 9 files changed, 386 insertions(+), 29 deletions(-) create mode 100644 modhex/example_test.go create mode 100644 modhex/modhex.go create mode 100644 modhex/modhex_test.go create mode 100644 yubikey.go diff --git a/hotp.go b/hotp.go index a643338..32c8d2f 100644 --- a/hotp.go +++ b/hotp.go @@ -10,7 +10,7 @@ import ( ) type HOTP struct { - *oath + *OATH } func (otp *HOTP) Type() Type { @@ -19,7 +19,7 @@ func (otp *HOTP) Type() Type { func NewHOTP(key []byte, counter uint64, digits int) *HOTP { return &HOTP{ - oath: &oath{ + OATH: &OATH{ key: key, counter: counter, size: digits, @@ -30,13 +30,13 @@ func NewHOTP(key []byte, counter uint64, digits int) *HOTP { } func (otp *HOTP) OTP() string { - code := otp.oath.OTP(otp.counter) + code := otp.OATH.OTP(otp.counter) otp.counter++ return code } func (otp *HOTP) URL(label string) string { - return otp.oath.URL(otp.Type(), label) + return otp.OATH.URL(otp.Type(), label) } func (otp *HOTP) SetProvider(provider string) { @@ -87,5 +87,5 @@ func hotpFromURL(u *url.URL) (*HOTP, string, error) { } func (otp *HOTP) QR(label string) ([]byte, error) { - return otp.oath.QR(otp.Type(), label) + return otp.OATH.QR(otp.Type(), label) } diff --git a/modhex/example_test.go b/modhex/example_test.go new file mode 100644 index 0000000..367c2ad --- /dev/null +++ b/modhex/example_test.go @@ -0,0 +1,29 @@ +package modhex + +import ( + "fmt" + "github.com/gokyle/twofactor/modhex" +) + +var out = "fjhghrhrhvdrdciihvidhrhfdb" +var in = "Hello, world!" + +func ExampleEncoding_EncodeToString() { + data := []byte("Hello, world!") + str := modhex.StdEncoding.EncodeToString(data) + fmt.Println(str) + // Output: + // fjhghrhrhvdrdciihvidhrhfdb +} + +func ExampleEncoding_DecodeString() { + str := "fjhghrhrhvdrdciihvidhrhfdb" + data, err := modhex.StdEncoding.DecodeString(str) + if err != nil { + fmt.Printf("%v\n", err) + return + } + fmt.Printf("%s", string(data)) + // Output: + // Hello, world! +} diff --git a/modhex/modhex.go b/modhex/modhex.go new file mode 100644 index 0000000..94d34a9 --- /dev/null +++ b/modhex/modhex.go @@ -0,0 +1,112 @@ +// Package modhex implements the modified hexadecimal encoding as used +// by Yubico in their series of products. +package modhex + +import "fmt" + +// Encoding is a mapping of hexadecimal values to a new byte value. +// This means that the encoding for a single byte is two bytes. +type Encoding struct { + decoding map[byte]byte + encoding [16]byte +} + +// A CorruptInputError is returned if the input string contains +// invalid characters for the encoding or if the input is the wrong +// length. It contains the number of bytes written out. +type CorruptInputError struct { + written int64 +} + +func (err CorruptInputError) Error() string { + return fmt.Sprintf("modhex: corrupt input at byte %d", err.written) +} + +func (err CorruptInputError) Written() int64 { + return err.written +} + +var encodeStd = "cbdefghijklnrtuv" + +// NewEncoding builds a new encoder from the alphabet passed in, +// which must be a 16-byte string. +func NewEncoding(encoder string) *Encoding { + if len(encoder) != 16 { + return nil + } + + enc := new(Encoding) + enc.decoding = make(map[byte]byte) + for i := range encoder { + enc.encoding[i] = encoder[i] + enc.decoding[encoder[i]] = byte(i) + } + return enc +} + +// StdEncoding is the canonical modhex alphabet as used by Yubico. +var StdEncoding = NewEncoding(encodeStd) + +// Encode encodes src to dst, writing at most EncodedLen(len(src)) +// bytes to dst. +func (enc *Encoding) Encode(dst, src []byte) { + out := dst + + for i := 0; i < len(src) && len(out) > 1; i++ { + var b [2]byte + b[0] = enc.encoding[(src[i]&0xf0)>>4] + b[1] = enc.encoding[src[i]&0xf] + copy(out[:2], b[:]) + out = out[2:] + } +} + +// EncodedLen returns the encoded length of a buffer of n bytes. +func EncodedLen(n int) int { + return n << 1 +} + +// DecodedLen returns the decoded length of a buffer of n bytes. +func DecodedLen(n int) int { + return n >> 1 +} + +// Decode decodes src into dst, which will be at most DecodedLen(len(src)). +// It returns the number of bytes written, and any error that occurred. +func (enc *Encoding) Decode(dst, src []byte) (n int, err error) { + out := dst + + for i := 0; i < len(src); i += 2 { + if (len(src) - i) < 2 { + return i >> 1, CorruptInputError{int64(i >> 1)} + } + var b byte + if high, ok := enc.decoding[src[i]]; !ok { + return i >> 1, CorruptInputError{int64(i >> 1)} + } else if low, ok := enc.decoding[src[i+1]]; !ok { + return i >> 1, CorruptInputError{int64(i >> 1)} + } else { + b = high << 4 + b += low + out[0] = b + out = out[1:] + } + } + return len(dst), nil +} + +// EncodeToString is a convenience function to encode src as a +// string. +func (enc *Encoding) EncodeToString(src []byte) string { + dst := make([]byte, EncodedLen(len(src))) + enc.Encode(dst, src) + return string(dst) +} + +// DecodeString decodes the string passed in as its decoded bytes. +func (enc *Encoding) DecodeString(s string) ([]byte, error) { + dst := make([]byte, DecodedLen(len(s))) + src := []byte(s) + _, err := enc.Decode(dst, src) + return dst, err +} diff --git a/modhex/modhex_test.go b/modhex/modhex_test.go new file mode 100644 index 0000000..7f7212e --- /dev/null +++ b/modhex/modhex_test.go @@ -0,0 +1,163 @@ +package modhex + +import "bytes" +import "fmt" +import "testing" + +func TestInvalidEncoder(t *testing.T) { + s := "" + for i := 0; i < 16; i++ { + if NewEncoding(s) != nil { + fmt.Println("modhex: NewEncoding accepted bad encoding") + t.FailNow() + } + } +} + +var encodeTests = []struct { + In []byte + Out []byte +}{ + {[]byte{0x47}, []byte("fi")}, + {[]byte{0xba, 0xad, 0xf0, 0x0d}, []byte("nlltvcct")}, +} + +var decodeFail = [][]byte{ + []byte{0x47}, + []byte("abcdef"), +} + +func TestStdEncodingEncode(t *testing.T) { + enc := StdEncoding + for _, et := range encodeTests { + out := make([]byte, EncodedLen(len(et.In))) + enc.Encode(out, et.In) + if !bytes.Equal(out, et.Out) { + fmt.Println("modhex: StdEncoding: bad encoding") + fmt.Printf("\texpected: %x\n", et.Out) + fmt.Printf("\t actual: %x\n", out) + t.FailNow() + } + } +} + +func TestStdDecoding(t *testing.T) { + enc := StdEncoding + for _, et := range encodeTests { + out := make([]byte, DecodedLen(len(et.Out))) + n, err := enc.Decode(out, et.Out) + if err != nil { + fmt.Printf("%v\n", err) + t.FailNow() + } else if n != len(et.In) { + fmt.Println("modhex: bad decoded length") + t.FailNow() + } else if !bytes.Equal(out, et.In) { + fmt.Println("modhex: StdEncoding: bad decoding") + fmt.Printf("\texpected: %x\n", et.In) + fmt.Printf("\t actual: %x\n", out) + t.FailNow() + } + } +} + +func TestStdDecodingFail(t *testing.T) { + enc := StdEncoding + for _, et := range decodeFail { + dst := make([]byte, DecodedLen(len(et))) + _, err := enc.Decode(dst, et) + if err == nil { + fmt.Println("modhex: decode should fail") + t.FailNow() + } + } +} + +func TestStdEncodingToString(t *testing.T) { + enc := StdEncoding + for _, et := range encodeTests { + out := enc.EncodeToString(et.In) + if out != string(et.Out) { + fmt.Println("modhex: StdEncoding: bad encoding") + fmt.Printf("\texpected: %x\n", et.Out) + fmt.Printf("\t actual: %x\n", out) + t.FailNow() + } + } +} + +func TestStdEncodingString(t *testing.T) { + enc := StdEncoding + for _, et := range encodeTests { + out, err := enc.DecodeString(string(et.Out)) + if err != nil { + fmt.Printf("%v\n", err) + t.FailNow() + } else if !bytes.Equal(out, et.In) { + fmt.Println("modhex: StdEncoding: bad encoding") + fmt.Printf("\texpected: %x\n", et.In) + fmt.Printf("\t actual: %x\n", out) + t.FailNow() + } + } +} + +var corruptTests = []struct { + In []byte + Written int64 + Error string +}{ + {[]byte("aa"), 0, "modhex: corrupt input at byte 0"}, + {[]byte("ca"), 0, "modhex: corrupt input at byte 0"}, + {[]byte("ccac"), 1, "modhex: corrupt input at byte 1"}, + {[]byte("ccca"), 1, "modhex: corrupt input at byte 1"}, +} + +func TestCorruptInputError(t *testing.T) { + enc := StdEncoding + for _, ct := range corruptTests { + dst := make([]byte, DecodedLen(len(ct.In))) + n, err := enc.Decode(dst, ct.In) + if err == nil { + fmt.Println("modhex: decode should fail") + t.FailNow() + } else if (err.(CorruptInputError)).Written() != ct.Written { + fmt.Printf("modhex: decode should fail at byte %d, failed at byte %d\n", + ct.Written, (err.(CorruptInputError)).Written()) + t.FailNow() + } else if err.Error() != ct.Error { + fmt.Printf("modhex: invalid error '%s' returned\n", err.Error()) + fmt.Printf(" (expected '%s')\n", ct.Error) + t.FailNow() + } else if int64(n) != ct.Written { + fmt.Printf("modhex: decode should fail at byte %d, failed at byte %d\n", + ct.Written, n) + t.FailNow() + } + + } +} + +func TestCorruptInputErrorString(t *testing.T) { + enc := StdEncoding + for _, ct := range corruptTests { + _, err := enc.DecodeString(string(ct.In)) + if err == nil { + fmt.Println("modhex: decode should fail") + t.FailNow() + } else if (err.(CorruptInputError)).Written() != ct.Written { + fmt.Printf("modhex: decode should fail at byte %d, failed at byte %d\n", + ct.Written, (err.(CorruptInputError)).Written()) + t.FailNow() + } else if err.Error() != ct.Error { + fmt.Printf("modhex: invalid error '%s' returned\n", err.Error()) + fmt.Printf(" (expected '%s')\n", ct.Error) + t.FailNow() + } + } +} + +func TestFoo(t *testing.T) { + fmt.Println("Hello, world!->", StdEncoding.EncodeToString([]byte("Hello, world!"))) + t.FailNow() +} diff --git a/oath.go b/oath.go index b50ebb3..9f1bf6a 100644 --- a/oath.go +++ b/oath.go @@ -13,8 +13,8 @@ import ( const defaultSize = 6 -// oath provides a baseline struct for the two OATH algorithms. -type oath struct { +// OATH provides a baseline structure for the two OATH algorithms. +type OATH struct { key []byte counter uint64 size int @@ -23,27 +23,27 @@ type oath struct { provider string } -func (o oath) Size() int { +func (o OATH) Size() int { return o.size } -func (o oath) Counter() uint64 { +func (o OATH) Counter() uint64 { return o.counter } -func (o oath) SetCounter(counter uint64) { +func (o OATH) SetCounter(counter uint64) { o.counter = counter } -func (o oath) Key() []byte { +func (o OATH) Key() []byte { return o.key[:] } -func (o oath) Hash() func() hash.Hash { +func (o OATH) Hash() func() hash.Hash { return o.hash } -func (o oath) URL(t Type, label string) string { +func (o OATH) URL(t Type, label string) string { secret := base32.StdEncoding.EncodeToString(o.key) u := url.URL{} v := url.Values{} @@ -96,7 +96,7 @@ var digits = []int64{ // The top-level type should provide a counter; for example, HOTP // will provide the counter directly while TOTP will provide the // time-stepped counter. -func (o oath) OTP(counter uint64) string { +func (o OATH) OTP(counter uint64) string { var ctr [8]byte binary.BigEndian.PutUint64(ctr[:], counter) @@ -132,7 +132,7 @@ func truncate(in []byte) int64 { // QR generates a byte slice containing the a QR code encoded as a // PNG with level Q error correction. -func (o oath) QR(t Type, label string) ([]byte, error) { +func (o OATH) QR(t Type, label string) ([]byte, error) { u := o.URL(t, label) code, err := qr.Encode(u, qr.Q) if err != nil { diff --git a/otp.go b/otp.go index d8fcf28..dbc1fe1 100644 --- a/otp.go +++ b/otp.go @@ -13,6 +13,7 @@ type Type uint const ( OATH_HOTP = iota OATH_TOTP + YUBIKEY ) // PRNG is an io.Reader that provides a cryptographically secure @@ -47,24 +48,20 @@ type OTP interface { // the hash function used by the OTP Hash() func() hash.Hash - // URL generates a Google Authenticator url (or perhaps some other url) - URL(string) string - - // QR outputs a byte slice containing a PNG-encoded QR code - // of the URL. - QR(string) ([]byte, error) - // Returns the type of this OTP. Type() Type } -func OTPString(otp OTP) string { +func otpString(otp OTP) string { var typeName string switch otp.Type() { case OATH_HOTP: typeName = "OATH-HOTP" case OATH_TOTP: typeName = "OATH-TOTP" + case YUBIKEY: + return fmt.Sprintf("YubiKey with %d byte public identity", + len(otp.(*YubiKey).Public())) } return fmt.Sprintf("%s, %d", typeName, otp.Size()) } diff --git a/otp_test.go b/otp_test.go index 57643cf..7fec809 100644 --- a/otp_test.go +++ b/otp_test.go @@ -6,7 +6,7 @@ import "testing" func TestHOTPString(t *testing.T) { hotp := NewHOTP(nil, 0, 6) - hotpString := OTPString(hotp) + hotpString := otpString(hotp) if hotpString != "OATH-HOTP, 6" { fmt.Println("twofactor: invalid OTP string") t.FailNow() diff --git a/totp.go b/totp.go index f475212..2727a41 100644 --- a/totp.go +++ b/totp.go @@ -14,7 +14,7 @@ import ( ) type TOTP struct { - *oath + *OATH step uint64 } @@ -23,7 +23,7 @@ func (otp *TOTP) Type() Type { } func (otp *TOTP) otp(counter uint64) string { - return otp.oath.OTP(counter) + return otp.OATH.OTP(counter) } func (otp *TOTP) OTP() string { @@ -31,7 +31,7 @@ func (otp *TOTP) OTP() string { } func (otp *TOTP) URL(label string) string { - return otp.oath.URL(otp.Type(), label) + return otp.OATH.URL(otp.Type(), label) } func (otp *TOTP) SetProvider(provider string) { @@ -53,7 +53,7 @@ func NewTOTP(key []byte, start uint64, step uint64, digits int, algo crypto.Hash } return &TOTP{ - oath: &oath{ + OATH: &OATH{ key: key, counter: start, size: digits, @@ -147,5 +147,5 @@ func totpFromURL(u *url.URL) (*TOTP, string, error) { } func (otp *TOTP) QR(label string) ([]byte, error) { - return otp.oath.QR(otp.Type(), label) + return otp.OATH.QR(otp.Type(), label) } diff --git a/yubikey.go b/yubikey.go new file mode 100644 index 0000000..6d68e1d --- /dev/null +++ b/yubikey.go @@ -0,0 +1,56 @@ +package twofactor + +// Implement YubiKey OTP and YubiKey HOTP. + +import ( + "github.com/conformal/yubikey" + "github.com/gokyle/twofactor/modhex" + "hash" +) + +// YubiKey is an implementation of the YubiKey hard token. Note +// that the internal counter only actually uses 32 bits. +type YubiKey struct { + token yubikey.Token + counter uint64 + key yubikey.Key + public []byte +} + +func (yk *YubiKey) Public() []byte { + return yk.public[:] +} + +func (yk *YubiKey) Counter() uint64 { + return yk.counter +} + +func (yk *YubiKey) SetCounter(counter uint64) { + yk.counter = counter & 0xffffffff +} + +func (yk *YubiKey) Key() []byte { + return yk.key[:] +} + +func (yk *YubiKey) Size() int { + return yubikey.OTPSize + len(yk.public) +} + +func (yk *YubiKey) OTP() string { + otp := yk.token.Generate(yk.key) + if otp == nil { + return "" + } + return modhex.StdEncoding.EncodeToString(otp.Bytes()) +} + +// Hash always returns nil, as the YubiKey tokens do not use a hash +// function. +func (yk *YubiKey) Hash() func() hash.Hash { + return nil +} + +func (yk *YubiKey) Type() Type { + return YUBIKEY +} From 459e9f880f7088fbe3534e10ebefaff6d733e2dc Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Wed, 23 Apr 2014 16:54:16 -0700 Subject: [PATCH 06/32] Add function to build Google TOTPs from secret --- totp.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/totp.go b/totp.go index 2727a41..3904920 100644 --- a/totp.go +++ b/totp.go @@ -99,6 +99,14 @@ func GenerateGoogleTOTP() *TOTP { return NewTOTP(key, 0, 30, 6, crypto.SHA1) } +func NewGoogleTOTP(secret string) (*TOTP, error) { + key, err := base32.StdEncoding.DecodeString(secret) + if err != nil { + return nil, err + } + return NewTOTP(key, 0, 30, 6, crypto.SHA1), nil +} + func totpFromURL(u *url.URL) (*TOTP, string, error) { label := u.Path[1:] v := u.Query() From 84250b05014e43ab67937f4f35b222e3f1740b6d Mon Sep 17 00:00:00 2001 From: Kyle Date: Thu, 24 Apr 2014 20:37:00 -0600 Subject: [PATCH 07/32] More documentation. --- hotp.go | 10 ++++++++++ oath.go | 7 +++++++ otp.go | 1 + totp.go | 21 +++++++++++++-------- totp_test.go | 4 ++-- yubikey.go | 8 ++++++++ 6 files changed, 41 insertions(+), 10 deletions(-) diff --git a/hotp.go b/hotp.go index 32c8d2f..1232e98 100644 --- a/hotp.go +++ b/hotp.go @@ -9,14 +9,18 @@ import ( "strconv" ) +// HOTP represents an RFC-4226 Hash-based One Time Password instance. type HOTP struct { *OATH } +// Type returns OATH_HOTP. func (otp *HOTP) Type() Type { return OATH_HOTP } +// NewHOTP takes the key, the initial counter value, and the number +// of digits (typically 6 or 8) and returns a new HOTP instance. func NewHOTP(key []byte, counter uint64, digits int) *HOTP { return &HOTP{ OATH: &OATH{ @@ -29,20 +33,25 @@ func NewHOTP(key []byte, counter uint64, digits int) *HOTP { } } +// OTP returns the next OTP and increments the counter. func (otp *HOTP) OTP() string { code := otp.OATH.OTP(otp.counter) otp.counter++ return code } +// URL returns an HOTP URL (i.e. for putting in a QR code). func (otp *HOTP) URL(label string) string { return otp.OATH.URL(otp.Type(), label) } +// SetProvider sets up the provider component of the OTP URL. func (otp *HOTP) SetProvider(provider string) { otp.provider = provider } +// GenerateGoogleHOTP generates a new HOTP instance as used by +// Google Authenticator. func GenerateGoogleHOTP() *HOTP { key := make([]byte, sha1.Size) if _, err := io.ReadFull(PRNG, key); err != nil { @@ -86,6 +95,7 @@ func hotpFromURL(u *url.URL) (*HOTP, string, error) { return otp, label, nil } +// QR generates a new QR code for the HOTP. func (otp *HOTP) QR(label string) ([]byte, error) { return otp.OATH.QR(otp.Type(), label) } diff --git a/oath.go b/oath.go index 9f1bf6a..16b9410 100644 --- a/oath.go +++ b/oath.go @@ -23,26 +23,33 @@ type OATH struct { provider string } +// Size returns the output size (in characters) of the password. func (o OATH) Size() int { return o.size } +// Counter returns the OATH token's counter. func (o OATH) Counter() uint64 { return o.counter } +// SetCounter updates the OATH token's counter to a new value. func (o OATH) SetCounter(counter uint64) { o.counter = counter } +// Key returns the token's secret key. func (o OATH) Key() []byte { return o.key[:] } +// Hash returns the token's hash function. func (o OATH) Hash() func() hash.Hash { return o.hash } +// URL constructs a URL appropriate for the token (i.e. for use in a +// QR code). func (o OATH) URL(t Type, label string) string { secret := base32.StdEncoding.EncodeToString(o.key) u := url.URL{} diff --git a/otp.go b/otp.go index dbc1fe1..fe686d4 100644 --- a/otp.go +++ b/otp.go @@ -66,6 +66,7 @@ func otpString(otp OTP) string { return fmt.Sprintf("%s, %d", typeName, otp.Size()) } +// FromURL constructs a new OTP token from a URL string. func FromURL(URL string) (OTP, string, error) { u, err := url.Parse(URL) if err != nil { diff --git a/totp.go b/totp.go index 3904920..10d6782 100644 --- a/totp.go +++ b/totp.go @@ -13,11 +13,13 @@ import ( "time" ) +// TOTP represents an RFC 6238 Time-based One-Time Password instance. type TOTP struct { *OATH step uint64 } +// Type returns OATH_TOTP. func (otp *TOTP) Type() Type { return OATH_TOTP } @@ -26,14 +28,17 @@ func (otp *TOTP) otp(counter uint64) string { return otp.OATH.OTP(counter) } +// OTP returns the OTP for the current timestep. func (otp *TOTP) OTP() string { return otp.otp(otp.OTPCounter()) } +// URL returns a TOTP URL (i.e. for putting in a QR code). func (otp *TOTP) URL(label string) string { return otp.OATH.URL(otp.Type(), label) } +// SetProvider sets up the provider component of the OTP URL. func (otp *TOTP) SetProvider(provider string) { otp.provider = provider } @@ -42,10 +47,14 @@ func (otp *TOTP) otpCounter(t uint64) uint64 { return (t - otp.counter) / otp.step } +// OTPCounter returns the current time value for the OTP. func (otp *TOTP) OTPCounter() uint64 { return otp.otpCounter(uint64(time.Now().Unix())) } +// NewOTP takes a new key, a starting time, a step, the number of +// digits of output (typically 6 or 8) and the hash algorithm to +// use, and builds a new OTP. func NewTOTP(key []byte, start uint64, step uint64, digits int, algo crypto.Hash) *TOTP { h := hashFromAlgo(algo) if h == nil { @@ -65,18 +74,11 @@ func NewTOTP(key []byte, start uint64, step uint64, digits int, algo crypto.Hash } +// NewTOTPSHA1 will build a new TOTP using SHA-1. func NewTOTPSHA1(key []byte, start uint64, step uint64, digits int) *TOTP { return NewTOTP(key, start, step, digits, crypto.SHA1) } -func NewTOTPSHA256(key []byte, start uint64, step uint64, digits int) *TOTP { - return NewTOTP(key, start, step, digits, crypto.SHA256) -} - -func NewTOTPSHA512(key []byte, start uint64, step uint64, digits int) *TOTP { - return NewTOTP(key, start, step, digits, crypto.SHA512) -} - func hashFromAlgo(algo crypto.Hash) func() hash.Hash { switch algo { case crypto.SHA1: @@ -99,6 +101,8 @@ func GenerateGoogleTOTP() *TOTP { return NewTOTP(key, 0, 30, 6, crypto.SHA1) } +// NewGoogleTOTP takes a secret as a base32-encoded string and +// returns an appropriate Google Authenticator TOTP instance. func NewGoogleTOTP(secret string) (*TOTP, error) { key, err := base32.StdEncoding.DecodeString(secret) if err != nil { @@ -154,6 +158,7 @@ func totpFromURL(u *url.URL) (*TOTP, string, error) { return otp, label, nil } +// QR generates a new TOTP QR code. func (otp *TOTP) QR(label string) ([]byte, error) { return otp.OATH.QR(otp.Type(), label) } diff --git a/totp_test.go b/totp_test.go index e42e6a0..244e43e 100644 --- a/totp_test.go +++ b/totp_test.go @@ -16,8 +16,8 @@ var rfcTotpTests = []struct { Algo crypto.Hash }{ {59, "94287082", 1, crypto.SHA1}, - {59, "46119246", 1, crypto.SHA256}, - {59, "90693936", 1, crypto.SHA512}, + //{59, "46119246", 1, crypto.SHA256}, + //{59, "90693936", 1, crypto.SHA512}, {1111111109, "07081804", 37037036, crypto.SHA1}, // {1111111109, "68084774", 37037036, crypto.SHA256}, // {1111111109, "25091201", 37037036, crypto.SHA512}, diff --git a/yubikey.go b/yubikey.go index 6d68e1d..3a2c10c 100644 --- a/yubikey.go +++ b/yubikey.go @@ -17,26 +17,33 @@ type YubiKey struct { public []byte } +// Public returns the public component of the token. func (yk *YubiKey) Public() []byte { return yk.public[:] } +// Counter returns the YubiKey's counter. func (yk *YubiKey) Counter() uint64 { return yk.counter } +// SetCounter sets the YubiKey's counter. func (yk *YubiKey) SetCounter(counter uint64) { yk.counter = counter & 0xffffffff } +// Key returns the YubiKey's secret key. func (yk *YubiKey) Key() []byte { return yk.key[:] } +// Size returns the length of the YubiKey's OTP output plus the length +// of the public identifier. func (yk *YubiKey) Size() int { return yubikey.OTPSize + len(yk.public) } +// OTP returns a new one-time password from the YubiKey. func (yk *YubiKey) OTP() string { otp := yk.token.Generate(yk.key) if otp == nil { @@ -51,6 +58,7 @@ func (yk *YubiKey) Hash() func() hash.Hash { return nil } +// Type returns YUBIKEY. func (yk *YubiKey) Type() Type { return YUBIKEY } From 5bef33245f981774431a194f4cb84d00b657cdf2 Mon Sep 17 00:00:00 2001 From: Kyle Date: Thu, 24 Apr 2014 20:37:53 -0600 Subject: [PATCH 08/32] Remove YubiKey (not currently functional). --- modhex/example_test.go | 29 -------- modhex/modhex.go | 112 ---------------------------- modhex/modhex_test.go | 163 ----------------------------------------- otp.go | 1 - yubikey.go | 64 ---------------- 5 files changed, 369 deletions(-) delete mode 100644 modhex/example_test.go delete mode 100644 modhex/modhex.go delete mode 100644 modhex/modhex_test.go delete mode 100644 yubikey.go diff --git a/modhex/example_test.go b/modhex/example_test.go deleted file mode 100644 index 367c2ad..0000000 --- a/modhex/example_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package modhex - -import ( - "fmt" - "github.com/gokyle/twofactor/modhex" -) - -var out = "fjhghrhrhvdrdciihvidhrhfdb" -var in = "Hello, world!" - -func ExampleEncoding_EncodeToString() { - data := []byte("Hello, world!") - str := modhex.StdEncoding.EncodeToString(data) - fmt.Println(str) - // Output: - // fjhghrhrhvdrdciihvidhrhfdb -} - -func ExampleEncoding_DecodeString() { - str := "fjhghrhrhvdrdciihvidhrhfdb" - data, err := modhex.StdEncoding.DecodeString(str) - if err != nil { - fmt.Printf("%v\n", err) - return - } - fmt.Printf("%s", string(data)) - // Output: - // Hello, world! -} diff --git a/modhex/modhex.go b/modhex/modhex.go deleted file mode 100644 index 94d34a9..0000000 --- a/modhex/modhex.go +++ /dev/null @@ -1,112 +0,0 @@ -// Package modhex implements the modified hexadecimal encoding as used -// by Yubico in their series of products. -package modhex - -import "fmt" - -// Encoding is a mapping of hexadecimal values to a new byte value. -// This means that the encoding for a single byte is two bytes. -type Encoding struct { - decoding map[byte]byte - encoding [16]byte -} - -// A CorruptInputError is returned if the input string contains -// invalid characters for the encoding or if the input is the wrong -// length. It contains the number of bytes written out. -type CorruptInputError struct { - written int64 -} - -func (err CorruptInputError) Error() string { - return fmt.Sprintf("modhex: corrupt input at byte %d", err.written) -} - -func (err CorruptInputError) Written() int64 { - return err.written -} - -var encodeStd = "cbdefghijklnrtuv" - -// NewEncoding builds a new encoder from the alphabet passed in, -// which must be a 16-byte string. -func NewEncoding(encoder string) *Encoding { - if len(encoder) != 16 { - return nil - } - - enc := new(Encoding) - enc.decoding = make(map[byte]byte) - for i := range encoder { - enc.encoding[i] = encoder[i] - enc.decoding[encoder[i]] = byte(i) - } - return enc -} - -// StdEncoding is the canonical modhex alphabet as used by Yubico. -var StdEncoding = NewEncoding(encodeStd) - -// Encode encodes src to dst, writing at most EncodedLen(len(src)) -// bytes to dst. -func (enc *Encoding) Encode(dst, src []byte) { - out := dst - - for i := 0; i < len(src) && len(out) > 1; i++ { - var b [2]byte - b[0] = enc.encoding[(src[i]&0xf0)>>4] - b[1] = enc.encoding[src[i]&0xf] - copy(out[:2], b[:]) - out = out[2:] - } -} - -// EncodedLen returns the encoded length of a buffer of n bytes. -func EncodedLen(n int) int { - return n << 1 -} - -// DecodedLen returns the decoded length of a buffer of n bytes. -func DecodedLen(n int) int { - return n >> 1 -} - -// Decode decodes src into dst, which will be at most DecodedLen(len(src)). -// It returns the number of bytes written, and any error that occurred. -func (enc *Encoding) Decode(dst, src []byte) (n int, err error) { - out := dst - - for i := 0; i < len(src); i += 2 { - if (len(src) - i) < 2 { - return i >> 1, CorruptInputError{int64(i >> 1)} - } - var b byte - if high, ok := enc.decoding[src[i]]; !ok { - return i >> 1, CorruptInputError{int64(i >> 1)} - } else if low, ok := enc.decoding[src[i+1]]; !ok { - return i >> 1, CorruptInputError{int64(i >> 1)} - } else { - b = high << 4 - b += low - out[0] = b - out = out[1:] - } - } - return len(dst), nil -} - -// EncodeToString is a convenience function to encode src as a -// string. -func (enc *Encoding) EncodeToString(src []byte) string { - dst := make([]byte, EncodedLen(len(src))) - enc.Encode(dst, src) - return string(dst) -} - -// DecodeString decodes the string passed in as its decoded bytes. -func (enc *Encoding) DecodeString(s string) ([]byte, error) { - dst := make([]byte, DecodedLen(len(s))) - src := []byte(s) - _, err := enc.Decode(dst, src) - return dst, err -} diff --git a/modhex/modhex_test.go b/modhex/modhex_test.go deleted file mode 100644 index 7f7212e..0000000 --- a/modhex/modhex_test.go +++ /dev/null @@ -1,163 +0,0 @@ -package modhex - -import "bytes" -import "fmt" -import "testing" - -func TestInvalidEncoder(t *testing.T) { - s := "" - for i := 0; i < 16; i++ { - if NewEncoding(s) != nil { - fmt.Println("modhex: NewEncoding accepted bad encoding") - t.FailNow() - } - } -} - -var encodeTests = []struct { - In []byte - Out []byte -}{ - {[]byte{0x47}, []byte("fi")}, - {[]byte{0xba, 0xad, 0xf0, 0x0d}, []byte("nlltvcct")}, -} - -var decodeFail = [][]byte{ - []byte{0x47}, - []byte("abcdef"), -} - -func TestStdEncodingEncode(t *testing.T) { - enc := StdEncoding - for _, et := range encodeTests { - out := make([]byte, EncodedLen(len(et.In))) - enc.Encode(out, et.In) - if !bytes.Equal(out, et.Out) { - fmt.Println("modhex: StdEncoding: bad encoding") - fmt.Printf("\texpected: %x\n", et.Out) - fmt.Printf("\t actual: %x\n", out) - t.FailNow() - } - } -} - -func TestStdDecoding(t *testing.T) { - enc := StdEncoding - for _, et := range encodeTests { - out := make([]byte, DecodedLen(len(et.Out))) - n, err := enc.Decode(out, et.Out) - if err != nil { - fmt.Printf("%v\n", err) - t.FailNow() - } else if n != len(et.In) { - fmt.Println("modhex: bad decoded length") - t.FailNow() - } else if !bytes.Equal(out, et.In) { - fmt.Println("modhex: StdEncoding: bad decoding") - fmt.Printf("\texpected: %x\n", et.In) - fmt.Printf("\t actual: %x\n", out) - t.FailNow() - } - } -} - -func TestStdDecodingFail(t *testing.T) { - enc := StdEncoding - for _, et := range decodeFail { - dst := make([]byte, DecodedLen(len(et))) - _, err := enc.Decode(dst, et) - if err == nil { - fmt.Println("modhex: decode should fail") - t.FailNow() - } - } -} - -func TestStdEncodingToString(t *testing.T) { - enc := StdEncoding - for _, et := range encodeTests { - out := enc.EncodeToString(et.In) - if out != string(et.Out) { - fmt.Println("modhex: StdEncoding: bad encoding") - fmt.Printf("\texpected: %x\n", et.Out) - fmt.Printf("\t actual: %x\n", out) - t.FailNow() - } - } -} - -func TestStdEncodingString(t *testing.T) { - enc := StdEncoding - for _, et := range encodeTests { - out, err := enc.DecodeString(string(et.Out)) - if err != nil { - fmt.Printf("%v\n", err) - t.FailNow() - } else if !bytes.Equal(out, et.In) { - fmt.Println("modhex: StdEncoding: bad encoding") - fmt.Printf("\texpected: %x\n", et.In) - fmt.Printf("\t actual: %x\n", out) - t.FailNow() - } - } -} - -var corruptTests = []struct { - In []byte - Written int64 - Error string -}{ - {[]byte("aa"), 0, "modhex: corrupt input at byte 0"}, - {[]byte("ca"), 0, "modhex: corrupt input at byte 0"}, - {[]byte("ccac"), 1, "modhex: corrupt input at byte 1"}, - {[]byte("ccca"), 1, "modhex: corrupt input at byte 1"}, -} - -func TestCorruptInputError(t *testing.T) { - enc := StdEncoding - for _, ct := range corruptTests { - dst := make([]byte, DecodedLen(len(ct.In))) - n, err := enc.Decode(dst, ct.In) - if err == nil { - fmt.Println("modhex: decode should fail") - t.FailNow() - } else if (err.(CorruptInputError)).Written() != ct.Written { - fmt.Printf("modhex: decode should fail at byte %d, failed at byte %d\n", - ct.Written, (err.(CorruptInputError)).Written()) - t.FailNow() - } else if err.Error() != ct.Error { - fmt.Printf("modhex: invalid error '%s' returned\n", err.Error()) - fmt.Printf(" (expected '%s')\n", ct.Error) - t.FailNow() - } else if int64(n) != ct.Written { - fmt.Printf("modhex: decode should fail at byte %d, failed at byte %d\n", - ct.Written, n) - t.FailNow() - } - - } -} - -func TestCorruptInputErrorString(t *testing.T) { - enc := StdEncoding - for _, ct := range corruptTests { - _, err := enc.DecodeString(string(ct.In)) - if err == nil { - fmt.Println("modhex: decode should fail") - t.FailNow() - } else if (err.(CorruptInputError)).Written() != ct.Written { - fmt.Printf("modhex: decode should fail at byte %d, failed at byte %d\n", - ct.Written, (err.(CorruptInputError)).Written()) - t.FailNow() - } else if err.Error() != ct.Error { - fmt.Printf("modhex: invalid error '%s' returned\n", err.Error()) - fmt.Printf(" (expected '%s')\n", ct.Error) - t.FailNow() - } - } -} - -func TestFoo(t *testing.T) { - fmt.Println("Hello, world!->", StdEncoding.EncodeToString([]byte("Hello, world!"))) - t.FailNow() -} diff --git a/otp.go b/otp.go index fe686d4..02e82ba 100644 --- a/otp.go +++ b/otp.go @@ -13,7 +13,6 @@ type Type uint const ( OATH_HOTP = iota OATH_TOTP - YUBIKEY ) // PRNG is an io.Reader that provides a cryptographically secure diff --git a/yubikey.go b/yubikey.go deleted file mode 100644 index 3a2c10c..0000000 --- a/yubikey.go +++ /dev/null @@ -1,64 +0,0 @@ -package twofactor - -// Implement YubiKey OTP and YubiKey HOTP. - -import ( - "github.com/conformal/yubikey" - "github.com/gokyle/twofactor/modhex" - "hash" -) - -// YubiKey is an implementation of the YubiKey hard token. Note -// that the internal counter only actually uses 32 bits. -type YubiKey struct { - token yubikey.Token - counter uint64 - key yubikey.Key - public []byte -} - -// Public returns the public component of the token. -func (yk *YubiKey) Public() []byte { - return yk.public[:] -} - -// Counter returns the YubiKey's counter. -func (yk *YubiKey) Counter() uint64 { - return yk.counter -} - -// SetCounter sets the YubiKey's counter. -func (yk *YubiKey) SetCounter(counter uint64) { - yk.counter = counter & 0xffffffff -} - -// Key returns the YubiKey's secret key. -func (yk *YubiKey) Key() []byte { - return yk.key[:] -} - -// Size returns the length of the YubiKey's OTP output plus the length -// of the public identifier. -func (yk *YubiKey) Size() int { - return yubikey.OTPSize + len(yk.public) -} - -// OTP returns a new one-time password from the YubiKey. -func (yk *YubiKey) OTP() string { - otp := yk.token.Generate(yk.key) - if otp == nil { - return "" - } - return modhex.StdEncoding.EncodeToString(otp.Bytes()) -} - -// Hash always returns nil, as the YubiKey tokens do not use a hash -// function. -func (yk *YubiKey) Hash() func() hash.Hash { - return nil -} - -// Type returns YUBIKEY. -func (yk *YubiKey) Type() Type { - return YUBIKEY -} From 7881b6fdfc6aee00e3a7aab3610a7514de17da79 Mon Sep 17 00:00:00 2001 From: Kyle Date: Thu, 24 Apr 2014 20:40:44 -0600 Subject: [PATCH 09/32] Remove test TOTP client. --- totpc/totpc.go | 43 ------------------------------------------- 1 file changed, 43 deletions(-) delete mode 100644 totpc/totpc.go diff --git a/totpc/totpc.go b/totpc/totpc.go deleted file mode 100644 index 1c86f92..0000000 --- a/totpc/totpc.go +++ /dev/null @@ -1,43 +0,0 @@ -package main - -import ( - "fmt" - "github.com/gokyle/twofactor" - "io/ioutil" - "time" -) - -func main() { - otp := twofactor.GenerateGoogleTOTP() - if otp == nil { - fmt.Println("totpc: failed to generate token") - return - } - - qr, err := otp.QR("totpc-demo") - if err != nil { - fmt.Println(err.Error()) - return - } - - err = ioutil.WriteFile("out.png", qr, 0644) - if err != nil { - fmt.Println(err.Error()) - return - } - - fmt.Println(otp.OTP()) - for { - for { - t := time.Now() - if t.Second() == 0 { - break - } else if t.Second() == 30 { - break - } - <-time.After(1 * time.Second) - } - fmt.Println(otp.OTP()) - <-time.After(30 * time.Second) - } -} From 89e74f390bf6e710de51fdc22ab41b559ca7b751 Mon Sep 17 00:00:00 2001 From: Kyle Date: Thu, 24 Apr 2014 20:43:13 -0600 Subject: [PATCH 10/32] Add doc.go, finish YubiKey removal. --- doc.go | 5 +++++ otp.go | 5 ++--- 2 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 doc.go diff --git a/doc.go b/doc.go new file mode 100644 index 0000000..6f1eef8 --- /dev/null +++ b/doc.go @@ -0,0 +1,5 @@ +// twofactor implements two-factor authentication. +// +// Currently supported are RFC 4226 HOTP one-time passwords and +// RFC 6238 TOTP SHA-1 one-time passwords. +package twofactor diff --git a/otp.go b/otp.go index 02e82ba..9d2faaf 100644 --- a/otp.go +++ b/otp.go @@ -58,9 +58,8 @@ func otpString(otp OTP) string { typeName = "OATH-HOTP" case OATH_TOTP: typeName = "OATH-TOTP" - case YUBIKEY: - return fmt.Sprintf("YubiKey with %d byte public identity", - len(otp.(*YubiKey).Public())) + default: + typeName = "UNKNOWN" } return fmt.Sprintf("%s, %d", typeName, otp.Size()) } From 939b1bc2726eb3eab618a1b971baafeda75996eb Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Wed, 12 Aug 2015 12:29:34 -0700 Subject: [PATCH 11/32] Updating imports. --- oath.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/oath.go b/oath.go index 16b9410..79c18f4 100644 --- a/oath.go +++ b/oath.go @@ -1,7 +1,6 @@ package twofactor import ( - "code.google.com/p/rsc/qr" "crypto" "crypto/hmac" "encoding/base32" @@ -9,6 +8,8 @@ import ( "fmt" "hash" "net/url" + + "_code.google.com/p/rsc/qr" ) const defaultSize = 6 From 79e106da2eaaae89252e29aacb208696904f6941 Mon Sep 17 00:00:00 2001 From: Aaron Bieber Date: Mon, 20 Mar 2017 14:09:26 -0600 Subject: [PATCH 12/32] point to new qr location --- oath.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oath.go b/oath.go index 79c18f4..a6d9233 100644 --- a/oath.go +++ b/oath.go @@ -9,7 +9,7 @@ import ( "hash" "net/url" - "_code.google.com/p/rsc/qr" + "rsc.io/qr" ) const defaultSize = 6 From 31b9d175ddf5e10c7823fdb9e9f92e7a31fb81e1 Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Mon, 20 Mar 2017 14:05:20 -0700 Subject: [PATCH 13/32] Add travis config. --- .travis.yml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..69d2f6d --- /dev/null +++ b/.travis.yml @@ -0,0 +1,7 @@ +language: go + +go: + - 1.x + - 1.6 + - 1.7.x + - master From 5d57d844d43d0e2d9d37c28f9a6e3a14963b284b Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Thu, 13 Apr 2017 09:55:39 -0700 Subject: [PATCH 14/32] Add license (MIT). --- LICENSE | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..027cde0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2017 Kyle Isom + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From eaad1884d40f9cabff98a57a524c17afd00c9fe7 Mon Sep 17 00:00:00 2001 From: Aaron Bieber Date: Sun, 17 Sep 2017 15:41:16 -0600 Subject: [PATCH 15/32] Make sure our secret is always uppercase Non-uppercase secrets that are base32 encoded will fial to decode unless we upper them. --- hotp.go | 3 ++- totp.go | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/hotp.go b/hotp.go index 1232e98..7d2c752 100644 --- a/hotp.go +++ b/hotp.go @@ -7,6 +7,7 @@ import ( "io" "net/url" "strconv" + "strings" ) // HOTP represents an RFC-4226 Hash-based One Time Password instance. @@ -64,7 +65,7 @@ func hotpFromURL(u *url.URL) (*HOTP, string, error) { label := u.Path[1:] v := u.Query() - secret := v.Get("secret") + secret := strings.ToUpper(v.Get("secret")) if secret == "" { return nil, "", ErrInvalidURL } diff --git a/totp.go b/totp.go index 10d6782..10a2876 100644 --- a/totp.go +++ b/totp.go @@ -10,6 +10,7 @@ import ( "io" "net/url" "strconv" + "strings" "time" ) @@ -115,7 +116,7 @@ func totpFromURL(u *url.URL) (*TOTP, string, error) { label := u.Path[1:] v := u.Query() - secret := v.Get("secret") + secret := strings.ToUpper(v.Get("secret")) if secret == "" { return nil, "", ErrInvalidURL } From 2cf2c15def0b83b81538645e8935b3c4e8308b96 Mon Sep 17 00:00:00 2001 From: Paul TREHIOU Date: Sat, 23 Dec 2017 19:03:14 +0100 Subject: [PATCH 16/32] Case insensitive algorithm match --- totp.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/totp.go b/totp.go index 10a2876..c3d6f3f 100644 --- a/totp.go +++ b/totp.go @@ -123,12 +123,11 @@ func totpFromURL(u *url.URL) (*TOTP, string, error) { var algo = crypto.SHA1 if algorithm := v.Get("algorithm"); algorithm != "" { - switch { - case algorithm == "SHA256": + if strings.EqualFold(algorithm, "SHA256") { algo = crypto.SHA256 - case algorithm == "SHA512": + } else if strings.EqualFold(algorithm, "SHA512") { algo = crypto.SHA512 - case algorithm != "SHA1": + } else if !strings.EqualFold(algorithm, "SHA1") { return nil, "", ErrInvalidAlgo } } From 8c34415c34349b1a5dc78e9d7431b123c23130c6 Mon Sep 17 00:00:00 2001 From: Aaron Bieber Date: Tue, 24 Oct 2017 06:49:17 -0600 Subject: [PATCH 17/32] add readme --- README.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..08965e5 --- /dev/null +++ b/README.md @@ -0,0 +1,33 @@ +## `twofactor` + +[![GoDoc](https://godoc.org/github.com/gokyle/twofactor?status.svg)](https://godoc.org/github.com/gokyle/twofactor) +[![Build Status](https://travis-ci.org/gokyle/twofactor.svg?branch=master)](https://travis-ci.org/gokyle/twofactor) + +### Author + +`twofactor` was written by Kyle Isom . + + +### License + +``` +Copyright (c) 2017 Kyle Isom + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` From 6e9812e6f5b054f24504c6b27e90ff10065d554f Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Mon, 16 Apr 2018 12:50:19 -0700 Subject: [PATCH 18/32] Vendor dependencies and add more tests. --- .travis.yml | 6 +- Gopkg.lock | 19 + Gopkg.toml | 34 ++ vendor/rsc.io/qr/LICENSE | 27 ++ vendor/rsc.io/qr/README.md | 3 + vendor/rsc.io/qr/coding/gen.go | 149 ++++++ vendor/rsc.io/qr/coding/qr.go | 815 ++++++++++++++++++++++++++++++++ vendor/rsc.io/qr/gf256/gf256.go | 241 ++++++++++ vendor/rsc.io/qr/png.go | 400 ++++++++++++++++ vendor/rsc.io/qr/qr.go | 116 +++++ 10 files changed, 1809 insertions(+), 1 deletion(-) create mode 100644 Gopkg.lock create mode 100644 Gopkg.toml create mode 100644 vendor/rsc.io/qr/LICENSE create mode 100644 vendor/rsc.io/qr/README.md create mode 100644 vendor/rsc.io/qr/coding/gen.go create mode 100644 vendor/rsc.io/qr/coding/qr.go create mode 100644 vendor/rsc.io/qr/gf256/gf256.go create mode 100644 vendor/rsc.io/qr/png.go create mode 100644 vendor/rsc.io/qr/qr.go diff --git a/.travis.yml b/.travis.yml index 69d2f6d..f6f1ff7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,11 @@ +sudo: false language: go go: - 1.x - 1.6 - - 1.7.x + - 1.7 + - 1.8 + - 1.9 + - 1.10 - master diff --git a/Gopkg.lock b/Gopkg.lock new file mode 100644 index 0000000..d8c94aa --- /dev/null +++ b/Gopkg.lock @@ -0,0 +1,19 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + branch = "master" + name = "rsc.io/qr" + packages = [ + ".", + "coding", + "gf256" + ] + revision = "48b2ede4844e13f1a2b7ce4d2529c9af7e359fc5" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "b705d306da5a78e76b7ff289744770eef56328a4f0d1c615c1a233d056283651" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml new file mode 100644 index 0000000..e2fd13b --- /dev/null +++ b/Gopkg.toml @@ -0,0 +1,34 @@ +# Gopkg.toml example +# +# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html +# for detailed Gopkg.toml documentation. +# +# required = ["github.com/user/thing/cmd/thing"] +# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] +# +# [[constraint]] +# name = "github.com/user/project" +# version = "1.0.0" +# +# [[constraint]] +# name = "github.com/user/project2" +# branch = "dev" +# source = "github.com/myfork/project2" +# +# [[override]] +# name = "github.com/x/y" +# version = "2.4.0" +# +# [prune] +# non-go = false +# go-tests = true +# unused-packages = true + + +[[constraint]] + branch = "master" + name = "rsc.io/qr" + +[prune] + go-tests = true + unused-packages = true diff --git a/vendor/rsc.io/qr/LICENSE b/vendor/rsc.io/qr/LICENSE new file mode 100644 index 0000000..6a66aea --- /dev/null +++ b/vendor/rsc.io/qr/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/rsc.io/qr/README.md b/vendor/rsc.io/qr/README.md new file mode 100644 index 0000000..0ba6214 --- /dev/null +++ b/vendor/rsc.io/qr/README.md @@ -0,0 +1,3 @@ +Basic QR encoder. + +go get [-u] rsc.io/qr diff --git a/vendor/rsc.io/qr/coding/gen.go b/vendor/rsc.io/qr/coding/gen.go new file mode 100644 index 0000000..a3857f2 --- /dev/null +++ b/vendor/rsc.io/qr/coding/gen.go @@ -0,0 +1,149 @@ +// +build ignore + +package main + +import "fmt" + +// tables from qrencode-3.1.1/qrspec.c + +var capacity = [41]struct { + width int + words int + remainder int + ec [4]int +}{ + {0, 0, 0, [4]int{0, 0, 0, 0}}, + {21, 26, 0, [4]int{7, 10, 13, 17}}, // 1 + {25, 44, 7, [4]int{10, 16, 22, 28}}, + {29, 70, 7, [4]int{15, 26, 36, 44}}, + {33, 100, 7, [4]int{20, 36, 52, 64}}, + {37, 134, 7, [4]int{26, 48, 72, 88}}, // 5 + {41, 172, 7, [4]int{36, 64, 96, 112}}, + {45, 196, 0, [4]int{40, 72, 108, 130}}, + {49, 242, 0, [4]int{48, 88, 132, 156}}, + {53, 292, 0, [4]int{60, 110, 160, 192}}, + {57, 346, 0, [4]int{72, 130, 192, 224}}, //10 + {61, 404, 0, [4]int{80, 150, 224, 264}}, + {65, 466, 0, [4]int{96, 176, 260, 308}}, + {69, 532, 0, [4]int{104, 198, 288, 352}}, + {73, 581, 3, [4]int{120, 216, 320, 384}}, + {77, 655, 3, [4]int{132, 240, 360, 432}}, //15 + {81, 733, 3, [4]int{144, 280, 408, 480}}, + {85, 815, 3, [4]int{168, 308, 448, 532}}, + {89, 901, 3, [4]int{180, 338, 504, 588}}, + {93, 991, 3, [4]int{196, 364, 546, 650}}, + {97, 1085, 3, [4]int{224, 416, 600, 700}}, //20 + {101, 1156, 4, [4]int{224, 442, 644, 750}}, + {105, 1258, 4, [4]int{252, 476, 690, 816}}, + {109, 1364, 4, [4]int{270, 504, 750, 900}}, + {113, 1474, 4, [4]int{300, 560, 810, 960}}, + {117, 1588, 4, [4]int{312, 588, 870, 1050}}, //25 + {121, 1706, 4, [4]int{336, 644, 952, 1110}}, + {125, 1828, 4, [4]int{360, 700, 1020, 1200}}, + {129, 1921, 3, [4]int{390, 728, 1050, 1260}}, + {133, 2051, 3, [4]int{420, 784, 1140, 1350}}, + {137, 2185, 3, [4]int{450, 812, 1200, 1440}}, //30 + {141, 2323, 3, [4]int{480, 868, 1290, 1530}}, + {145, 2465, 3, [4]int{510, 924, 1350, 1620}}, + {149, 2611, 3, [4]int{540, 980, 1440, 1710}}, + {153, 2761, 3, [4]int{570, 1036, 1530, 1800}}, + {157, 2876, 0, [4]int{570, 1064, 1590, 1890}}, //35 + {161, 3034, 0, [4]int{600, 1120, 1680, 1980}}, + {165, 3196, 0, [4]int{630, 1204, 1770, 2100}}, + {169, 3362, 0, [4]int{660, 1260, 1860, 2220}}, + {173, 3532, 0, [4]int{720, 1316, 1950, 2310}}, + {177, 3706, 0, [4]int{750, 1372, 2040, 2430}}, //40 +} + +var eccTable = [41][4][2]int{ + {{0, 0}, {0, 0}, {0, 0}, {0, 0}}, + {{1, 0}, {1, 0}, {1, 0}, {1, 0}}, // 1 + {{1, 0}, {1, 0}, {1, 0}, {1, 0}}, + {{1, 0}, {1, 0}, {2, 0}, {2, 0}}, + {{1, 0}, {2, 0}, {2, 0}, {4, 0}}, + {{1, 0}, {2, 0}, {2, 2}, {2, 2}}, // 5 + {{2, 0}, {4, 0}, {4, 0}, {4, 0}}, + {{2, 0}, {4, 0}, {2, 4}, {4, 1}}, + {{2, 0}, {2, 2}, {4, 2}, {4, 2}}, + {{2, 0}, {3, 2}, {4, 4}, {4, 4}}, + {{2, 2}, {4, 1}, {6, 2}, {6, 2}}, //10 + {{4, 0}, {1, 4}, {4, 4}, {3, 8}}, + {{2, 2}, {6, 2}, {4, 6}, {7, 4}}, + {{4, 0}, {8, 1}, {8, 4}, {12, 4}}, + {{3, 1}, {4, 5}, {11, 5}, {11, 5}}, + {{5, 1}, {5, 5}, {5, 7}, {11, 7}}, //15 + {{5, 1}, {7, 3}, {15, 2}, {3, 13}}, + {{1, 5}, {10, 1}, {1, 15}, {2, 17}}, + {{5, 1}, {9, 4}, {17, 1}, {2, 19}}, + {{3, 4}, {3, 11}, {17, 4}, {9, 16}}, + {{3, 5}, {3, 13}, {15, 5}, {15, 10}}, //20 + {{4, 4}, {17, 0}, {17, 6}, {19, 6}}, + {{2, 7}, {17, 0}, {7, 16}, {34, 0}}, + {{4, 5}, {4, 14}, {11, 14}, {16, 14}}, + {{6, 4}, {6, 14}, {11, 16}, {30, 2}}, + {{8, 4}, {8, 13}, {7, 22}, {22, 13}}, //25 + {{10, 2}, {19, 4}, {28, 6}, {33, 4}}, + {{8, 4}, {22, 3}, {8, 26}, {12, 28}}, + {{3, 10}, {3, 23}, {4, 31}, {11, 31}}, + {{7, 7}, {21, 7}, {1, 37}, {19, 26}}, + {{5, 10}, {19, 10}, {15, 25}, {23, 25}}, //30 + {{13, 3}, {2, 29}, {42, 1}, {23, 28}}, + {{17, 0}, {10, 23}, {10, 35}, {19, 35}}, + {{17, 1}, {14, 21}, {29, 19}, {11, 46}}, + {{13, 6}, {14, 23}, {44, 7}, {59, 1}}, + {{12, 7}, {12, 26}, {39, 14}, {22, 41}}, //35 + {{6, 14}, {6, 34}, {46, 10}, {2, 64}}, + {{17, 4}, {29, 14}, {49, 10}, {24, 46}}, + {{4, 18}, {13, 32}, {48, 14}, {42, 32}}, + {{20, 4}, {40, 7}, {43, 22}, {10, 67}}, + {{19, 6}, {18, 31}, {34, 34}, {20, 61}}, //40 +} + +var align = [41][2]int{ + {0, 0}, + {0, 0}, {18, 0}, {22, 0}, {26, 0}, {30, 0}, // 1- 5 + {34, 0}, {22, 38}, {24, 42}, {26, 46}, {28, 50}, // 6-10 + {30, 54}, {32, 58}, {34, 62}, {26, 46}, {26, 48}, //11-15 + {26, 50}, {30, 54}, {30, 56}, {30, 58}, {34, 62}, //16-20 + {28, 50}, {26, 50}, {30, 54}, {28, 54}, {32, 58}, //21-25 + {30, 58}, {34, 62}, {26, 50}, {30, 54}, {26, 52}, //26-30 + {30, 56}, {34, 60}, {30, 58}, {34, 62}, {30, 54}, //31-35 + {24, 50}, {28, 54}, {32, 58}, {26, 54}, {30, 58}, //35-40 +} + +var versionPattern = [41]int{ + 0, + 0, 0, 0, 0, 0, 0, + 0x07c94, 0x085bc, 0x09a99, 0x0a4d3, 0x0bbf6, 0x0c762, 0x0d847, 0x0e60d, + 0x0f928, 0x10b78, 0x1145d, 0x12a17, 0x13532, 0x149a6, 0x15683, 0x168c9, + 0x177ec, 0x18ec4, 0x191e1, 0x1afab, 0x1b08e, 0x1cc1a, 0x1d33f, 0x1ed75, + 0x1f250, 0x209d5, 0x216f0, 0x228ba, 0x2379f, 0x24b0b, 0x2542e, 0x26a64, + 0x27541, 0x28c69, +} + +func main() { + fmt.Printf("\t{},\n") + for i := 1; i <= 40; i++ { + apos := align[i][0] - 2 + if apos < 0 { + apos = 100 + } + astride := align[i][1] - align[i][0] + if astride < 1 { + astride = 100 + } + fmt.Printf("\t{%v, %v, %v, %#x, [4]level{{%v, %v}, {%v, %v}, {%v, %v}, {%v, %v}}}, // %v\n", + apos, astride, capacity[i].words, + versionPattern[i], + eccTable[i][0][0]+eccTable[i][0][1], + float64(capacity[i].ec[0])/float64(eccTable[i][0][0]+eccTable[i][0][1]), + eccTable[i][1][0]+eccTable[i][1][1], + float64(capacity[i].ec[1])/float64(eccTable[i][1][0]+eccTable[i][1][1]), + eccTable[i][2][0]+eccTable[i][2][1], + float64(capacity[i].ec[2])/float64(eccTable[i][2][0]+eccTable[i][2][1]), + eccTable[i][3][0]+eccTable[i][3][1], + float64(capacity[i].ec[3])/float64(eccTable[i][3][0]+eccTable[i][3][1]), + i, + ) + } +} diff --git a/vendor/rsc.io/qr/coding/qr.go b/vendor/rsc.io/qr/coding/qr.go new file mode 100644 index 0000000..4aa5288 --- /dev/null +++ b/vendor/rsc.io/qr/coding/qr.go @@ -0,0 +1,815 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package coding implements low-level QR coding details. +package coding + +import ( + "fmt" + "strconv" + "strings" + + "rsc.io/qr/gf256" +) + +// Field is the field for QR error correction. +var Field = gf256.NewField(0x11d, 2) + +// A Version represents a QR version. +// The version specifies the size of the QR code: +// a QR code with version v has 4v+17 pixels on a side. +// Versions number from 1 to 40: the larger the version, +// the more information the code can store. +type Version int + +const MinVersion = 1 +const MaxVersion = 40 + +func (v Version) String() string { + return strconv.Itoa(int(v)) +} + +func (v Version) sizeClass() int { + if v <= 9 { + return 0 + } + if v <= 26 { + return 1 + } + return 2 +} + +// DataBytes returns the number of data bytes that can be +// stored in a QR code with the given version and level. +func (v Version) DataBytes(l Level) int { + vt := &vtab[v] + lev := &vt.level[l] + return vt.bytes - lev.nblock*lev.check +} + +// Encoding implements a QR data encoding scheme. +// The implementations--Numeric, Alphanumeric, and String--specify +// the character set and the mapping from UTF-8 to code bits. +// The more restrictive the mode, the fewer code bits are needed. +type Encoding interface { + Check() error + Bits(v Version) int + Encode(b *Bits, v Version) +} + +type Bits struct { + b []byte + nbit int +} + +func (b *Bits) Reset() { + b.b = b.b[:0] + b.nbit = 0 +} + +func (b *Bits) Bits() int { + return b.nbit +} + +func (b *Bits) Bytes() []byte { + if b.nbit%8 != 0 { + panic("fractional byte") + } + return b.b +} + +func (b *Bits) Append(p []byte) { + if b.nbit%8 != 0 { + panic("fractional byte") + } + b.b = append(b.b, p...) + b.nbit += 8 * len(p) +} + +func (b *Bits) Write(v uint, nbit int) { + for nbit > 0 { + n := nbit + if n > 8 { + n = 8 + } + if b.nbit%8 == 0 { + b.b = append(b.b, 0) + } else { + m := -b.nbit & 7 + if n > m { + n = m + } + } + b.nbit += n + sh := uint(nbit - n) + b.b[len(b.b)-1] |= uint8(v >> sh << uint(-b.nbit&7)) + v -= v >> sh << sh + nbit -= n + } +} + +// Num is the encoding for numeric data. +// The only valid characters are the decimal digits 0 through 9. +type Num string + +func (s Num) String() string { + return fmt.Sprintf("Num(%#q)", string(s)) +} + +func (s Num) Check() error { + for _, c := range s { + if c < '0' || '9' < c { + return fmt.Errorf("non-numeric string %#q", string(s)) + } + } + return nil +} + +var numLen = [3]int{10, 12, 14} + +func (s Num) Bits(v Version) int { + return 4 + numLen[v.sizeClass()] + (10*len(s)+2)/3 +} + +func (s Num) Encode(b *Bits, v Version) { + b.Write(1, 4) + b.Write(uint(len(s)), numLen[v.sizeClass()]) + var i int + for i = 0; i+3 <= len(s); i += 3 { + w := uint(s[i]-'0')*100 + uint(s[i+1]-'0')*10 + uint(s[i+2]-'0') + b.Write(w, 10) + } + switch len(s) - i { + case 1: + w := uint(s[i] - '0') + b.Write(w, 4) + case 2: + w := uint(s[i]-'0')*10 + uint(s[i+1]-'0') + b.Write(w, 7) + } +} + +// Alpha is the encoding for alphanumeric data. +// The valid characters are 0-9A-Z$%*+-./: and space. +type Alpha string + +const alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:" + +func (s Alpha) String() string { + return fmt.Sprintf("Alpha(%#q)", string(s)) +} + +func (s Alpha) Check() error { + for _, c := range s { + if strings.IndexRune(alphabet, c) < 0 { + return fmt.Errorf("non-alphanumeric string %#q", string(s)) + } + } + return nil +} + +var alphaLen = [3]int{9, 11, 13} + +func (s Alpha) Bits(v Version) int { + return 4 + alphaLen[v.sizeClass()] + (11*len(s)+1)/2 +} + +func (s Alpha) Encode(b *Bits, v Version) { + b.Write(2, 4) + b.Write(uint(len(s)), alphaLen[v.sizeClass()]) + var i int + for i = 0; i+2 <= len(s); i += 2 { + w := uint(strings.IndexRune(alphabet, rune(s[i])))*45 + + uint(strings.IndexRune(alphabet, rune(s[i+1]))) + b.Write(w, 11) + } + + if i < len(s) { + w := uint(strings.IndexRune(alphabet, rune(s[i]))) + b.Write(w, 6) + } +} + +// String is the encoding for 8-bit data. All bytes are valid. +type String string + +func (s String) String() string { + return fmt.Sprintf("String(%#q)", string(s)) +} + +func (s String) Check() error { + return nil +} + +var stringLen = [3]int{8, 16, 16} + +func (s String) Bits(v Version) int { + return 4 + stringLen[v.sizeClass()] + 8*len(s) +} + +func (s String) Encode(b *Bits, v Version) { + b.Write(4, 4) + b.Write(uint(len(s)), stringLen[v.sizeClass()]) + for i := 0; i < len(s); i++ { + b.Write(uint(s[i]), 8) + } +} + +// A Pixel describes a single pixel in a QR code. +type Pixel uint32 + +const ( + Black Pixel = 1 << iota + Invert +) + +func (p Pixel) Offset() uint { + return uint(p >> 6) +} + +func OffsetPixel(o uint) Pixel { + return Pixel(o << 6) +} + +func (r PixelRole) Pixel() Pixel { + return Pixel(r << 2) +} + +func (p Pixel) Role() PixelRole { + return PixelRole(p>>2) & 15 +} + +func (p Pixel) String() string { + s := p.Role().String() + if p&Black != 0 { + s += "+black" + } + if p&Invert != 0 { + s += "+invert" + } + s += "+" + strconv.FormatUint(uint64(p.Offset()), 10) + return s +} + +// A PixelRole describes the role of a QR pixel. +type PixelRole uint32 + +const ( + _ PixelRole = iota + Position // position squares (large) + Alignment // alignment squares (small) + Timing // timing strip between position squares + Format // format metadata + PVersion // version pattern + Unused // unused pixel + Data // data bit + Check // error correction check bit + Extra +) + +var roles = []string{ + "", + "position", + "alignment", + "timing", + "format", + "pversion", + "unused", + "data", + "check", + "extra", +} + +func (r PixelRole) String() string { + if Position <= r && r <= Check { + return roles[r] + } + return strconv.Itoa(int(r)) +} + +// A Level represents a QR error correction level. +// From least to most tolerant of errors, they are L, M, Q, H. +type Level int + +const ( + L Level = iota + M + Q + H +) + +func (l Level) String() string { + if L <= l && l <= H { + return "LMQH"[l : l+1] + } + return strconv.Itoa(int(l)) +} + +// A Code is a square pixel grid. +type Code struct { + Bitmap []byte // 1 is black, 0 is white + Size int // number of pixels on a side + Stride int // number of bytes per row +} + +func (c *Code) Black(x, y int) bool { + return 0 <= x && x < c.Size && 0 <= y && y < c.Size && + c.Bitmap[y*c.Stride+x/8]&(1<= pad { + break + } + b.Write(0x11, 8) + } + } +} + +func (b *Bits) AddCheckBytes(v Version, l Level) { + nd := v.DataBytes(l) + if b.nbit < nd*8 { + b.Pad(nd*8 - b.nbit) + } + if b.nbit != nd*8 { + panic("qr: too much data") + } + + dat := b.Bytes() + vt := &vtab[v] + lev := &vt.level[l] + db := nd / lev.nblock + extra := nd % lev.nblock + chk := make([]byte, lev.check) + rs := gf256.NewRSEncoder(Field, lev.check) + for i := 0; i < lev.nblock; i++ { + if i == lev.nblock-extra { + db++ + } + rs.ECC(dat[:db], chk) + b.Append(chk) + dat = dat[db:] + } + + if len(b.Bytes()) != vt.bytes { + panic("qr: internal error") + } +} + +func (p *Plan) Encode(text ...Encoding) (*Code, error) { + var b Bits + for _, t := range text { + if err := t.Check(); err != nil { + return nil, err + } + t.Encode(&b, p.Version) + } + if b.Bits() > p.DataBytes*8 { + return nil, fmt.Errorf("cannot encode %d bits into %d-bit code", b.Bits(), p.DataBytes*8) + } + b.AddCheckBytes(p.Version, p.Level) + bytes := b.Bytes() + + // Now we have the checksum bytes and the data bytes. + // Construct the actual code. + c := &Code{Size: len(p.Pixel), Stride: (len(p.Pixel) + 7) &^ 7} + c.Bitmap = make([]byte, c.Stride*c.Size) + crow := c.Bitmap + for _, row := range p.Pixel { + for x, pix := range row { + switch pix.Role() { + case Data, Check: + o := pix.Offset() + if bytes[o/8]&(1< 40 { + return nil, fmt.Errorf("invalid QR version %d", int(v)) + } + siz := 17 + int(v)*4 + m := grid(siz) + p.Pixel = m + + // Timing markers (overwritten by boxes). + const ti = 6 // timing is in row/column 6 (counting from 0) + for i := range m { + p := Timing.Pixel() + if i&1 == 0 { + p |= Black + } + m[i][ti] = p + m[ti][i] = p + } + + // Position boxes. + posBox(m, 0, 0) + posBox(m, siz-7, 0) + posBox(m, 0, siz-7) + + // Alignment boxes. + info := &vtab[v] + for x := 4; x+5 < siz; { + for y := 4; y+5 < siz; { + // don't overwrite timing markers + if (x < 7 && y < 7) || (x < 7 && y+5 >= siz-7) || (x+5 >= siz-7 && y < 7) { + } else { + alignBox(m, x, y) + } + if y == 4 { + y = info.apos + } else { + y += info.astride + } + } + if x == 4 { + x = info.apos + } else { + x += info.astride + } + } + + // Version pattern. + pat := vtab[v].pattern + if pat != 0 { + v := pat + for x := 0; x < 6; x++ { + for y := 0; y < 3; y++ { + p := PVersion.Pixel() + if v&1 != 0 { + p |= Black + } + m[siz-11+y][x] = p + m[x][siz-11+y] = p + v >>= 1 + } + } + } + + // One lonely black pixel + m[siz-8][8] = Unused.Pixel() | Black + + return p, nil +} + +// fplan adds the format pixels +func fplan(l Level, m Mask, p *Plan) error { + // Format pixels. + fb := uint32(l^1) << 13 // level: L=01, M=00, Q=11, H=10 + fb |= uint32(m) << 10 // mask + const formatPoly = 0x537 + rem := fb + for i := 14; i >= 10; i-- { + if rem&(1<>i)&1 == 1 { + pix |= Black + } + if (invert>>i)&1 == 1 { + pix ^= Invert | Black + } + // top left + switch { + case i < 6: + p.Pixel[i][8] = pix + case i < 8: + p.Pixel[i+1][8] = pix + case i < 9: + p.Pixel[8][7] = pix + default: + p.Pixel[8][14-i] = pix + } + // bottom right + switch { + case i < 8: + p.Pixel[8][siz-1-int(i)] = pix + default: + p.Pixel[siz-1-int(14-i)][8] = pix + } + } + return nil +} + +// lplan edits a version-only Plan to add information +// about the error correction levels. +func lplan(v Version, l Level, p *Plan) error { + p.Level = l + + nblock := vtab[v].level[l].nblock + ne := vtab[v].level[l].check + nde := (vtab[v].bytes - ne*nblock) / nblock + extra := (vtab[v].bytes - ne*nblock) % nblock + dataBits := (nde*nblock + extra) * 8 + checkBits := ne * nblock * 8 + + p.DataBytes = vtab[v].bytes - ne*nblock + p.CheckBytes = ne * nblock + p.Blocks = nblock + + // Make data + checksum pixels. + data := make([]Pixel, dataBits) + for i := range data { + data[i] = Data.Pixel() | OffsetPixel(uint(i)) + } + check := make([]Pixel, checkBits) + for i := range check { + check[i] = Check.Pixel() | OffsetPixel(uint(i+dataBits)) + } + + // Split into blocks. + dataList := make([][]Pixel, nblock) + checkList := make([][]Pixel, nblock) + for i := 0; i < nblock; i++ { + // The last few blocks have an extra data byte (8 pixels). + nd := nde + if i >= nblock-extra { + nd++ + } + dataList[i], data = data[0:nd*8], data[nd*8:] + checkList[i], check = check[0:ne*8], check[ne*8:] + } + if len(data) != 0 || len(check) != 0 { + panic("data/check math") + } + + // Build up bit sequence, taking first byte of each block, + // then second byte, and so on. Then checksums. + bits := make([]Pixel, dataBits+checkBits) + dst := bits + for i := 0; i < nde+1; i++ { + for _, b := range dataList { + if i*8 < len(b) { + copy(dst, b[i*8:(i+1)*8]) + dst = dst[8:] + } + } + } + for i := 0; i < ne; i++ { + for _, b := range checkList { + if i*8 < len(b) { + copy(dst, b[i*8:(i+1)*8]) + dst = dst[8:] + } + } + } + if len(dst) != 0 { + panic("dst math") + } + + // Sweep up pair of columns, + // then down, assigning to right then left pixel. + // Repeat. + // See Figure 2 of http://www.pclviewer.com/rs2/qrtopology.htm + siz := len(p.Pixel) + rem := make([]Pixel, 7) + for i := range rem { + rem[i] = Extra.Pixel() + } + src := append(bits, rem...) + for x := siz; x > 0; { + for y := siz - 1; y >= 0; y-- { + if p.Pixel[y][x-1].Role() == 0 { + p.Pixel[y][x-1], src = src[0], src[1:] + } + if p.Pixel[y][x-2].Role() == 0 { + p.Pixel[y][x-2], src = src[0], src[1:] + } + } + x -= 2 + if x == 7 { // vertical timing strip + x-- + } + for y := 0; y < siz; y++ { + if p.Pixel[y][x-1].Role() == 0 { + p.Pixel[y][x-1], src = src[0], src[1:] + } + if p.Pixel[y][x-2].Role() == 0 { + p.Pixel[y][x-2], src = src[0], src[1:] + } + } + x -= 2 + } + return nil +} + +// mplan edits a version+level-only Plan to add the mask. +func mplan(m Mask, p *Plan) error { + p.Mask = m + for y, row := range p.Pixel { + for x, pix := range row { + if r := pix.Role(); (r == Data || r == Check || r == Extra) && p.Mask.Invert(y, x) { + row[x] ^= Black | Invert + } + } + } + return nil +} + +// posBox draws a position (large) box at upper left x, y. +func posBox(m [][]Pixel, x, y int) { + pos := Position.Pixel() + // box + for dy := 0; dy < 7; dy++ { + for dx := 0; dx < 7; dx++ { + p := pos + if dx == 0 || dx == 6 || dy == 0 || dy == 6 || 2 <= dx && dx <= 4 && 2 <= dy && dy <= 4 { + p |= Black + } + m[y+dy][x+dx] = p + } + } + // white border + for dy := -1; dy < 8; dy++ { + if 0 <= y+dy && y+dy < len(m) { + if x > 0 { + m[y+dy][x-1] = pos + } + if x+7 < len(m) { + m[y+dy][x+7] = pos + } + } + } + for dx := -1; dx < 8; dx++ { + if 0 <= x+dx && x+dx < len(m) { + if y > 0 { + m[y-1][x+dx] = pos + } + if y+7 < len(m) { + m[y+7][x+dx] = pos + } + } + } +} + +// alignBox draw an alignment (small) box at upper left x, y. +func alignBox(m [][]Pixel, x, y int) { + // box + align := Alignment.Pixel() + for dy := 0; dy < 5; dy++ { + for dx := 0; dx < 5; dx++ { + p := align + if dx == 0 || dx == 4 || dy == 0 || dy == 4 || dx == 2 && dy == 2 { + p |= Black + } + m[y+dy][x+dx] = p + } + } +} diff --git a/vendor/rsc.io/qr/gf256/gf256.go b/vendor/rsc.io/qr/gf256/gf256.go new file mode 100644 index 0000000..bfeeeb3 --- /dev/null +++ b/vendor/rsc.io/qr/gf256/gf256.go @@ -0,0 +1,241 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package gf256 implements arithmetic over the Galois Field GF(256). +package gf256 + +import "strconv" + +// A Field represents an instance of GF(256) defined by a specific polynomial. +type Field struct { + log [256]byte // log[0] is unused + exp [510]byte +} + +// NewField returns a new field corresponding to the polynomial poly +// and generator α. The Reed-Solomon encoding in QR codes uses +// polynomial 0x11d with generator 2. +// +// The choice of generator α only affects the Exp and Log operations. +func NewField(poly, α int) *Field { + if poly < 0x100 || poly >= 0x200 || reducible(poly) { + panic("gf256: invalid polynomial: " + strconv.Itoa(poly)) + } + + var f Field + x := 1 + for i := 0; i < 255; i++ { + if x == 1 && i != 0 { + panic("gf256: invalid generator " + strconv.Itoa(α) + + " for polynomial " + strconv.Itoa(poly)) + } + f.exp[i] = byte(x) + f.exp[i+255] = byte(x) + f.log[x] = byte(i) + x = mul(x, α, poly) + } + f.log[0] = 255 + for i := 0; i < 255; i++ { + if f.log[f.exp[i]] != byte(i) { + panic("bad log") + } + if f.log[f.exp[i+255]] != byte(i) { + panic("bad log") + } + } + for i := 1; i < 256; i++ { + if f.exp[f.log[i]] != byte(i) { + panic("bad log") + } + } + + return &f +} + +// nbit returns the number of significant in p. +func nbit(p int) uint { + n := uint(0) + for ; p > 0; p >>= 1 { + n++ + } + return n +} + +// polyDiv divides the polynomial p by q and returns the remainder. +func polyDiv(p, q int) int { + np := nbit(p) + nq := nbit(q) + for ; np >= nq; np-- { + if p&(1<<(np-1)) != 0 { + p ^= q << (np - nq) + } + } + return p +} + +// mul returns the product x*y mod poly, a GF(256) multiplication. +func mul(x, y, poly int) int { + z := 0 + for x > 0 { + if x&1 != 0 { + z ^= y + } + x >>= 1 + y <<= 1 + if y&0x100 != 0 { + y ^= poly + } + } + return z +} + +// reducible reports whether p is reducible. +func reducible(p int) bool { + // Multiplying n-bit * n-bit produces (2n-1)-bit, + // so if p is reducible, one of its factors must be + // of np/2+1 bits or fewer. + np := nbit(p) + for q := 2; q < 1<<(np/2+1); q++ { + if polyDiv(p, q) == 0 { + return true + } + } + return false +} + +// Add returns the sum of x and y in the field. +func (f *Field) Add(x, y byte) byte { + return x ^ y +} + +// Exp returns the base-α exponential of e in the field. +// If e < 0, Exp returns 0. +func (f *Field) Exp(e int) byte { + if e < 0 { + return 0 + } + return f.exp[e%255] +} + +// Log returns the base-α logarithm of x in the field. +// If x == 0, Log returns -1. +func (f *Field) Log(x byte) int { + if x == 0 { + return -1 + } + return int(f.log[x]) +} + +// Inv returns the multiplicative inverse of x in the field. +// If x == 0, Inv returns 0. +func (f *Field) Inv(x byte) byte { + if x == 0 { + return 0 + } + return f.exp[255-f.log[x]] +} + +// Mul returns the product of x and y in the field. +func (f *Field) Mul(x, y byte) byte { + if x == 0 || y == 0 { + return 0 + } + return f.exp[int(f.log[x])+int(f.log[y])] +} + +// An RSEncoder implements Reed-Solomon encoding +// over a given field using a given number of error correction bytes. +type RSEncoder struct { + f *Field + c int + gen []byte + lgen []byte + p []byte +} + +func (f *Field) gen(e int) (gen, lgen []byte) { + // p = 1 + p := make([]byte, e+1) + p[e] = 1 + + for i := 0; i < e; i++ { + // p *= (x + Exp(i)) + // p[j] = p[j]*Exp(i) + p[j+1]. + c := f.Exp(i) + for j := 0; j < e; j++ { + p[j] = f.Mul(p[j], c) ^ p[j+1] + } + p[e] = f.Mul(p[e], c) + } + + // lp = log p. + lp := make([]byte, e+1) + for i, c := range p { + if c == 0 { + lp[i] = 255 + } else { + lp[i] = byte(f.Log(c)) + } + } + + return p, lp +} + +// NewRSEncoder returns a new Reed-Solomon encoder +// over the given field and number of error correction bytes. +func NewRSEncoder(f *Field, c int) *RSEncoder { + gen, lgen := f.gen(c) + return &RSEncoder{f: f, c: c, gen: gen, lgen: lgen} +} + +// ECC writes to check the error correcting code bytes +// for data using the given Reed-Solomon parameters. +func (rs *RSEncoder) ECC(data []byte, check []byte) { + if len(check) < rs.c { + panic("gf256: invalid check byte length") + } + if rs.c == 0 { + return + } + + // The check bytes are the remainder after dividing + // data padded with c zeros by the generator polynomial. + + // p = data padded with c zeros. + var p []byte + n := len(data) + rs.c + if len(rs.p) >= n { + p = rs.p + } else { + p = make([]byte, n) + } + copy(p, data) + for i := len(data); i < len(p); i++ { + p[i] = 0 + } + + // Divide p by gen, leaving the remainder in p[len(data):]. + // p[0] is the most significant term in p, and + // gen[0] is the most significant term in the generator, + // which is always 1. + // To avoid repeated work, we store various values as + // lv, not v, where lv = log[v]. + f := rs.f + lgen := rs.lgen[1:] + for i := 0; i < len(data); i++ { + c := p[i] + if c == 0 { + continue + } + q := p[i+1:] + exp := f.exp[f.log[c]:] + for j, lg := range lgen { + if lg != 255 { // lgen uses 255 for log 0 + q[j] ^= exp[lg] + } + } + } + copy(check, p[len(data):]) + rs.p = p +} diff --git a/vendor/rsc.io/qr/png.go b/vendor/rsc.io/qr/png.go new file mode 100644 index 0000000..db49d05 --- /dev/null +++ b/vendor/rsc.io/qr/png.go @@ -0,0 +1,400 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package qr + +// PNG writer for QR codes. + +import ( + "bytes" + "encoding/binary" + "hash" + "hash/crc32" +) + +// PNG returns a PNG image displaying the code. +// +// PNG uses a custom encoder tailored to QR codes. +// Its compressed size is about 2x away from optimal, +// but it runs about 20x faster than calling png.Encode +// on c.Image(). +func (c *Code) PNG() []byte { + var p pngWriter + return p.encode(c) +} + +type pngWriter struct { + tmp [16]byte + wctmp [4]byte + buf bytes.Buffer + zlib bitWriter + crc hash.Hash32 +} + +var pngHeader = []byte("\x89PNG\r\n\x1a\n") + +func (w *pngWriter) encode(c *Code) []byte { + scale := c.Scale + siz := c.Size + + w.buf.Reset() + + // Header + w.buf.Write(pngHeader) + + // Header block + binary.BigEndian.PutUint32(w.tmp[0:4], uint32((siz+8)*scale)) + binary.BigEndian.PutUint32(w.tmp[4:8], uint32((siz+8)*scale)) + w.tmp[8] = 1 // 1-bit + w.tmp[9] = 0 // gray + w.tmp[10] = 0 + w.tmp[11] = 0 + w.tmp[12] = 0 + w.writeChunk("IHDR", w.tmp[:13]) + + // Comment + w.writeChunk("tEXt", comment) + + // Data + w.zlib.writeCode(c) + w.writeChunk("IDAT", w.zlib.bytes.Bytes()) + + // End + w.writeChunk("IEND", nil) + + return w.buf.Bytes() +} + +var comment = []byte("Software\x00QR-PNG http://qr.swtch.com/") + +func (w *pngWriter) writeChunk(name string, data []byte) { + if w.crc == nil { + w.crc = crc32.NewIEEE() + } + binary.BigEndian.PutUint32(w.wctmp[0:4], uint32(len(data))) + w.buf.Write(w.wctmp[0:4]) + w.crc.Reset() + copy(w.wctmp[0:4], name) + w.buf.Write(w.wctmp[0:4]) + w.crc.Write(w.wctmp[0:4]) + w.buf.Write(data) + w.crc.Write(data) + crc := w.crc.Sum32() + binary.BigEndian.PutUint32(w.wctmp[0:4], crc) + w.buf.Write(w.wctmp[0:4]) +} + +func (b *bitWriter) writeCode(c *Code) { + const ftNone = 0 + + b.adler32.Reset() + b.bytes.Reset() + b.nbit = 0 + + scale := c.Scale + siz := c.Size + + // zlib header + b.tmp[0] = 0x78 + b.tmp[1] = 0 + b.tmp[1] += uint8(31 - (uint16(b.tmp[0])<<8+uint16(b.tmp[1]))%31) + b.bytes.Write(b.tmp[0:2]) + + // Start flate block. + b.writeBits(1, 1, false) // final block + b.writeBits(1, 2, false) // compressed, fixed Huffman tables + + // White border. + // First row. + b.byte(ftNone) + n := (scale*(siz+8) + 7) / 8 + b.byte(255) + b.repeat(n-1, 1) + // 4*scale rows total. + b.repeat((4*scale-1)*(1+n), 1+n) + + for i := 0; i < 4*scale; i++ { + b.adler32.WriteNByte(ftNone, 1) + b.adler32.WriteNByte(255, n) + } + + row := make([]byte, 1+n) + for y := 0; y < siz; y++ { + row[0] = ftNone + j := 1 + var z uint8 + nz := 0 + for x := -4; x < siz+4; x++ { + // Raw data. + for i := 0; i < scale; i++ { + z <<= 1 + if !c.Black(x, y) { + z |= 1 + } + if nz++; nz == 8 { + row[j] = z + j++ + nz = 0 + } + } + } + if j < len(row) { + row[j] = z + } + for _, z := range row { + b.byte(z) + } + + // Scale-1 copies. + b.repeat((scale-1)*(1+n), 1+n) + + b.adler32.WriteN(row, scale) + } + + // White border. + // First row. + b.byte(ftNone) + b.byte(255) + b.repeat(n-1, 1) + // 4*scale rows total. + b.repeat((4*scale-1)*(1+n), 1+n) + + for i := 0; i < 4*scale; i++ { + b.adler32.WriteNByte(ftNone, 1) + b.adler32.WriteNByte(255, n) + } + + // End of block. + b.hcode(256) + b.flushBits() + + // adler32 + binary.BigEndian.PutUint32(b.tmp[0:], b.adler32.Sum32()) + b.bytes.Write(b.tmp[0:4]) +} + +// A bitWriter is a write buffer for bit-oriented data like deflate. +type bitWriter struct { + bytes bytes.Buffer + bit uint32 + nbit uint + + tmp [4]byte + adler32 adigest +} + +func (b *bitWriter) writeBits(bit uint32, nbit uint, rev bool) { + // reverse, for huffman codes + if rev { + br := uint32(0) + for i := uint(0); i < nbit; i++ { + br |= ((bit >> i) & 1) << (nbit - 1 - i) + } + bit = br + } + b.bit |= bit << b.nbit + b.nbit += nbit + for b.nbit >= 8 { + b.bytes.WriteByte(byte(b.bit)) + b.bit >>= 8 + b.nbit -= 8 + } +} + +func (b *bitWriter) flushBits() { + if b.nbit > 0 { + b.bytes.WriteByte(byte(b.bit)) + b.nbit = 0 + b.bit = 0 + } +} + +func (b *bitWriter) hcode(v int) { + /* + Lit Value Bits Codes + --------- ---- ----- + 0 - 143 8 00110000 through + 10111111 + 144 - 255 9 110010000 through + 111111111 + 256 - 279 7 0000000 through + 0010111 + 280 - 287 8 11000000 through + 11000111 + */ + switch { + case v <= 143: + b.writeBits(uint32(v)+0x30, 8, true) + case v <= 255: + b.writeBits(uint32(v-144)+0x190, 9, true) + case v <= 279: + b.writeBits(uint32(v-256)+0, 7, true) + case v <= 287: + b.writeBits(uint32(v-280)+0xc0, 8, true) + default: + panic("invalid hcode") + } +} + +func (b *bitWriter) byte(x byte) { + b.hcode(int(x)) +} + +func (b *bitWriter) codex(c int, val int, nx uint) { + b.hcode(c + val>>nx) + b.writeBits(uint32(val)&(1<= 258+3; n -= 258 { + b.repeat1(258, d) + } + if n > 258 { + // 258 < n < 258+3 + b.repeat1(10, d) + b.repeat1(n-10, d) + return + } + if n < 3 { + panic("invalid flate repeat") + } + b.repeat1(n, d) +} + +func (b *bitWriter) repeat1(n, d int) { + /* + Extra Extra Extra + Code Bits Length(s) Code Bits Lengths Code Bits Length(s) + ---- ---- ------ ---- ---- ------- ---- ---- ------- + 257 0 3 267 1 15,16 277 4 67-82 + 258 0 4 268 1 17,18 278 4 83-98 + 259 0 5 269 2 19-22 279 4 99-114 + 260 0 6 270 2 23-26 280 4 115-130 + 261 0 7 271 2 27-30 281 5 131-162 + 262 0 8 272 2 31-34 282 5 163-194 + 263 0 9 273 3 35-42 283 5 195-226 + 264 0 10 274 3 43-50 284 5 227-257 + 265 1 11,12 275 3 51-58 285 0 258 + 266 1 13,14 276 3 59-66 + */ + switch { + case n <= 10: + b.codex(257, n-3, 0) + case n <= 18: + b.codex(265, n-11, 1) + case n <= 34: + b.codex(269, n-19, 2) + case n <= 66: + b.codex(273, n-35, 3) + case n <= 130: + b.codex(277, n-67, 4) + case n <= 257: + b.codex(281, n-131, 5) + case n == 258: + b.hcode(285) + default: + panic("invalid repeat length") + } + + /* + Extra Extra Extra + Code Bits Dist Code Bits Dist Code Bits Distance + ---- ---- ---- ---- ---- ------ ---- ---- -------- + 0 0 1 10 4 33-48 20 9 1025-1536 + 1 0 2 11 4 49-64 21 9 1537-2048 + 2 0 3 12 5 65-96 22 10 2049-3072 + 3 0 4 13 5 97-128 23 10 3073-4096 + 4 1 5,6 14 6 129-192 24 11 4097-6144 + 5 1 7,8 15 6 193-256 25 11 6145-8192 + 6 2 9-12 16 7 257-384 26 12 8193-12288 + 7 2 13-16 17 7 385-512 27 12 12289-16384 + 8 3 17-24 18 8 513-768 28 13 16385-24576 + 9 3 25-32 19 8 769-1024 29 13 24577-32768 + */ + if d <= 4 { + b.writeBits(uint32(d-1), 5, true) + } else if d <= 32768 { + nbit := uint(16) + for d <= 1<<(nbit-1) { + nbit-- + } + v := uint32(d - 1) + v &^= 1 << (nbit - 1) // top bit is implicit + code := uint32(2*nbit - 2) // second bit is low bit of code + code |= v >> (nbit - 2) + v &^= 1 << (nbit - 2) + b.writeBits(code, 5, true) + // rest of bits follow + b.writeBits(uint32(v), nbit-2, false) + } else { + panic("invalid repeat distance") + } +} + +func (b *bitWriter) run(v byte, n int) { + if n == 0 { + return + } + b.byte(v) + if n-1 < 3 { + for i := 0; i < n-1; i++ { + b.byte(v) + } + } else { + b.repeat(n-1, 1) + } +} + +type adigest struct { + a, b uint32 +} + +func (d *adigest) Reset() { d.a, d.b = 1, 0 } + +const amod = 65521 + +func aupdate(a, b uint32, pi byte, n int) (aa, bb uint32) { + // TODO(rsc): 6g doesn't do magic multiplies for b %= amod, + // only for b = b%amod. + + // invariant: a, b < amod + if pi == 0 { + b += uint32(n%amod) * a + b = b % amod + return a, b + } + + // n times: + // a += pi + // b += a + // is same as + // b += n*a + n*(n+1)/2*pi + // a += n*pi + m := uint32(n) + b += (m % amod) * a + b = b % amod + b += (m * (m + 1) / 2) % amod * uint32(pi) + b = b % amod + a += (m % amod) * uint32(pi) + a = a % amod + return a, b +} + +func afinish(a, b uint32) uint32 { + return b<<16 | a +} + +func (d *adigest) WriteN(p []byte, n int) { + for i := 0; i < n; i++ { + for _, pi := range p { + d.a, d.b = aupdate(d.a, d.b, pi, 1) + } + } +} + +func (d *adigest) WriteNByte(pi byte, n int) { + d.a, d.b = aupdate(d.a, d.b, pi, n) +} + +func (d *adigest) Sum32() uint32 { return afinish(d.a, d.b) } diff --git a/vendor/rsc.io/qr/qr.go b/vendor/rsc.io/qr/qr.go new file mode 100644 index 0000000..254b532 --- /dev/null +++ b/vendor/rsc.io/qr/qr.go @@ -0,0 +1,116 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +Package qr encodes QR codes. +*/ +package qr + +import ( + "errors" + "image" + "image/color" + + "rsc.io/qr/coding" +) + +// A Level denotes a QR error correction level. +// From least to most tolerant of errors, they are L, M, Q, H. +type Level int + +const ( + L Level = iota // 20% redundant + M // 38% redundant + Q // 55% redundant + H // 65% redundant +) + +// Encode returns an encoding of text at the given error correction level. +func Encode(text string, level Level) (*Code, error) { + // Pick data encoding, smallest first. + // We could split the string and use different encodings + // but that seems like overkill for now. + var enc coding.Encoding + switch { + case coding.Num(text).Check() == nil: + enc = coding.Num(text) + case coding.Alpha(text).Check() == nil: + enc = coding.Alpha(text) + default: + enc = coding.String(text) + } + + // Pick size. + l := coding.Level(level) + var v coding.Version + for v = coding.MinVersion; ; v++ { + if v > coding.MaxVersion { + return nil, errors.New("text too long to encode as QR") + } + if enc.Bits(v) <= v.DataBytes(l)*8 { + break + } + } + + // Build and execute plan. + p, err := coding.NewPlan(v, l, 0) + if err != nil { + return nil, err + } + cc, err := p.Encode(enc) + if err != nil { + return nil, err + } + + // TODO: Pick appropriate mask. + + return &Code{cc.Bitmap, cc.Size, cc.Stride, 8}, nil +} + +// A Code is a square pixel grid. +// It implements image.Image and direct PNG encoding. +type Code struct { + Bitmap []byte // 1 is black, 0 is white + Size int // number of pixels on a side + Stride int // number of bytes per row + Scale int // number of image pixels per QR pixel +} + +// Black returns true if the pixel at (x,y) is black. +func (c *Code) Black(x, y int) bool { + return 0 <= x && x < c.Size && 0 <= y && y < c.Size && + c.Bitmap[y*c.Stride+x/8]&(1< Date: Mon, 16 Apr 2018 12:57:51 -0700 Subject: [PATCH 19/32] Travis requires a string for Go 1.10. --- .travis.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index f6f1ff7..985d225 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,10 +2,7 @@ sudo: false language: go go: - - 1.x - - 1.6 - - 1.7 - 1.8 - 1.9 - - 1.10 + - "1.10" - master From acefe4a3b9fb0d64a1025373cfad5c31d7e90a38 Mon Sep 17 00:00:00 2001 From: Aaron Bieber Date: Mon, 16 Apr 2018 13:55:03 -0600 Subject: [PATCH 20/32] Don't assume our secret is base32 encoded. According to https://en.wikipedia.org/wiki/Time-based_One-time_Password_algorithm secrets are only base32 encoded in gauthenticator and gauth friendly providers. --- hotp.go | 3 ++- otp_test.go | 3 --- totp.go | 3 ++- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/hotp.go b/hotp.go index 7d2c752..abe3300 100644 --- a/hotp.go +++ b/hotp.go @@ -90,7 +90,8 @@ func hotpFromURL(u *url.URL) (*HOTP, string, error) { key, err := base32.StdEncoding.DecodeString(secret) if err != nil { - return nil, "", err + // secret isn't base32 encoded + key = []byte(secret) } otp := NewHOTP(key, counter, digits) return otp, label, nil diff --git a/otp_test.go b/otp_test.go index 7fec809..3b5e993 100644 --- a/otp_test.go +++ b/otp_test.go @@ -79,10 +79,7 @@ func TestBadURL(t *testing.T) { "foo", "otpauth:/foo/bar/baz", "://", - "otpauth://hotp/secret=bar", - "otpauth://hotp/?secret=QUJDRA&algorithm=SHA256", "otpauth://hotp/?digits=", - "otpauth://hotp/?secret=123", "otpauth://hotp/?secret=MFRGGZDF&digits=ABCD", "otpauth://hotp/?secret=MFRGGZDF&counter=ABCD", } diff --git a/totp.go b/totp.go index c3d6f3f..bb7c863 100644 --- a/totp.go +++ b/totp.go @@ -152,7 +152,8 @@ func totpFromURL(u *url.URL) (*TOTP, string, error) { key, err := base32.StdEncoding.DecodeString(secret) if err != nil { - return nil, "", err + // secret isn't base32 encoded + key = []byte(secret) } otp := NewTOTP(key, 0, period, digits, algo) return otp, label, nil From 5fd928f69a983e95aac7dc6bc6143f59fe59fbf8 Mon Sep 17 00:00:00 2001 From: Aaron Bieber Date: Tue, 17 Apr 2018 07:16:58 -0600 Subject: [PATCH 21/32] Decode using WithPadding as pointed out by @gl-sergei. This makes us print the same 6 digits as oathtool for non-padded secrets like "a6mryljlbufszudtjdt42nh5by". --- hotp.go | 2 +- totp.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hotp.go b/hotp.go index abe3300..9ab34a4 100644 --- a/hotp.go +++ b/hotp.go @@ -88,7 +88,7 @@ func hotpFromURL(u *url.URL) (*HOTP, string, error) { } } - key, err := base32.StdEncoding.DecodeString(secret) + key, err := base32.StdEncoding.WithPadding(base32.NoPadding).DecodeString(secret) if err != nil { // secret isn't base32 encoded key = []byte(secret) diff --git a/totp.go b/totp.go index bb7c863..4628fa8 100644 --- a/totp.go +++ b/totp.go @@ -150,7 +150,7 @@ func totpFromURL(u *url.URL) (*TOTP, string, error) { } } - key, err := base32.StdEncoding.DecodeString(secret) + key, err := base32.StdEncoding.WithPadding(base32.NoPadding).DecodeString(secret) if err != nil { // secret isn't base32 encoded key = []byte(secret) From bbc82ff8de72400ce39a13077627531d9841ad62 Mon Sep 17 00:00:00 2001 From: Aaron Bieber Date: Wed, 18 Apr 2018 09:16:40 -0600 Subject: [PATCH 22/32] Pad non-padded secrets. This lets us continue building on <= go1.8. - Add tests for secrets using various padding methods. - Add a new method/test to append padding to non-padded secrets. --- hotp.go | 4 ++-- otp_test.go | 40 +++++++++++++++++++++++++++++++++++++++ totp.go | 4 ++-- util.go | 16 ++++++++++++++++ util_test.go | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 113 insertions(+), 4 deletions(-) create mode 100644 util.go create mode 100644 util_test.go diff --git a/hotp.go b/hotp.go index 9ab34a4..292838a 100644 --- a/hotp.go +++ b/hotp.go @@ -88,9 +88,9 @@ func hotpFromURL(u *url.URL) (*HOTP, string, error) { } } - key, err := base32.StdEncoding.WithPadding(base32.NoPadding).DecodeString(secret) + key, err := base32.StdEncoding.DecodeString(Pad(secret)) if err != nil { - // secret isn't base32 encoded + // assume secret isn't base32 encoded key = []byte(secret) } otp := NewHOTP(key, counter, digits) diff --git a/otp_test.go b/otp_test.go index 3b5e993..418baff 100644 --- a/otp_test.go +++ b/otp_test.go @@ -69,6 +69,46 @@ func TestURL(t *testing.T) { } } +// This test makes sure we can generate codes for padded and non-padded +// entries +func TestPaddedURL(t *testing.T) { + var urlList = []string{ + "otpauth://hotp/?secret=ME", + "otpauth://hotp/?secret=MEFR", + "otpauth://hotp/?secret=MFRGG", + "otpauth://hotp/?secret=MFRGGZA", + "otpauth://hotp/?secret=a6mryljlbufszudtjdt42nh5by=======", + "otpauth://hotp/?secret=a6mryljlbufszudtjdt42nh5by", + "otpauth://hotp/?secret=a6mryljlbufszudtjdt42nh5by%3D%3D%3D%3D%3D%3D%3D", + } + var codeList = []string{ + "413198", + "770938", + "670717", + "402378", + "069864", + "069864", + "069864", + } + + for i := range urlList { + if o, id, err := FromURL(urlList[i]); err != nil { + fmt.Println("hotp: URL should have parsed successfully") + fmt.Printf("\turl was: %s\n", urlList[i]) + t.FailNow() + fmt.Printf("\t%s, %s\n", o.OTP(), id) + } else { + code2 := o.OTP() + if code2 != codeList[i] { + fmt.Printf("hotp: mismatched OTPs\n") + fmt.Printf("\texpected: %s\n", codeList[i]) + fmt.Printf("\t actual: %s\n", code2) + t.FailNow() + } + } + } +} + // This test attempts a variety of invalid urls against the parser // to ensure they fail. func TestBadURL(t *testing.T) { diff --git a/totp.go b/totp.go index 4628fa8..789de4a 100644 --- a/totp.go +++ b/totp.go @@ -150,9 +150,9 @@ func totpFromURL(u *url.URL) (*TOTP, string, error) { } } - key, err := base32.StdEncoding.WithPadding(base32.NoPadding).DecodeString(secret) + key, err := base32.StdEncoding.DecodeString(Pad(secret)) if err != nil { - // secret isn't base32 encoded + // assume secret isn't base32 encoded key = []byte(secret) } otp := NewTOTP(key, 0, period, digits, algo) diff --git a/util.go b/util.go new file mode 100644 index 0000000..af15c0f --- /dev/null +++ b/util.go @@ -0,0 +1,16 @@ +package twofactor + +import ( + "strings" +) + +// Pad calculates the number of '='s to add to our encoded string +// to make base32.StdEncoding.DecodeString happy +func Pad(s string) string { + if !strings.HasSuffix(s, "=") && len(s)%8 != 0 { + for len(s)%8 != 0 { + s += "=" + } + } + return s +} diff --git a/util_test.go b/util_test.go new file mode 100644 index 0000000..647a931 --- /dev/null +++ b/util_test.go @@ -0,0 +1,53 @@ +package twofactor + +import ( + "encoding/base32" + "fmt" + "math/rand" + "strings" + "testing" +) + +const letters = "1234567890!@#$%^&*()abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + +func randString() string { + b := make([]byte, rand.Intn(len(letters))) + for i := range b { + b[i] = letters[rand.Intn(len(letters))] + } + return base32.StdEncoding.EncodeToString(b) +} + +func TestPadding(t *testing.T) { + for i := 0; i < 300; i++ { + b := randString() + origEncoding := string(b) + modEncoding := strings.Replace(string(b), "=", "", -1) + str, err := base32.StdEncoding.DecodeString(origEncoding) + if err != nil { + fmt.Println("Can't decode: ", string(b)) + t.FailNow() + } + + paddedEncoding := Pad(modEncoding) + if origEncoding != paddedEncoding { + fmt.Println("Padding failed:") + fmt.Printf("Expected: '%s'", origEncoding) + fmt.Printf("Got: '%s'", paddedEncoding) + t.FailNow() + } else { + mstr, err := base32.StdEncoding.DecodeString(paddedEncoding) + if err != nil { + fmt.Println("Can't decode: ", paddedEncoding) + t.FailNow() + } + + if string(mstr) != string(str) { + fmt.Println("Re-padding failed:") + fmt.Printf("Expected: '%s'", str) + fmt.Printf("Got: '%s'", mstr) + t.FailNow() + } + } + } +} From 9e0979e07f39c706c9406715d99b6a8f3960ef10 Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Thu, 6 Dec 2018 08:07:27 -0800 Subject: [PATCH 23/32] Support clock mocking. This addresses #15. --- Gopkg.lock | 8 +++++++- Gopkg.toml | 4 ++++ totp.go | 5 ++++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index d8c94aa..108681f 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -1,6 +1,12 @@ # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. +[[projects]] + branch = "master" + name = "github.com/benbjohnson/clock" + packages = ["."] + revision = "7dc76406b6d3c05b5f71a86293cbcf3c4ea03b19" + [[projects]] branch = "master" name = "rsc.io/qr" @@ -14,6 +20,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "b705d306da5a78e76b7ff289744770eef56328a4f0d1c615c1a233d056283651" + inputs-digest = "6f47334a8fcf2bb48e739e6181089e02bce6cc4d68888be311df3827db96f8ae" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index e2fd13b..a57d8a0 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -32,3 +32,7 @@ [prune] go-tests = true unused-packages = true + +[[constraint]] + branch = "master" + name = "github.com/benbjohnson/clock" diff --git a/totp.go b/totp.go index 789de4a..f8e41b1 100644 --- a/totp.go +++ b/totp.go @@ -11,9 +11,12 @@ import ( "net/url" "strconv" "strings" - "time" + + "github.com/benbjohnson/clock" ) +var time clock.Clock + // TOTP represents an RFC 6238 Time-based One-Time Password instance. type TOTP struct { *OATH From 924654e7c481ea83fa24b795b3c625126322065c Mon Sep 17 00:00:00 2001 From: ujjwalsh Date: Fri, 30 Oct 2020 08:28:26 +0000 Subject: [PATCH 24/32] Added Support for Linux on Power --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 985d225..f1b65c7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,8 @@ sudo: false language: go - +arch: + - amd64 + - ppc64le go: - 1.8 - 1.9 From e95404bfc5200e97576441e6cb216b11ad4da447 Mon Sep 17 00:00:00 2001 From: CodeLingo Bot Date: Tue, 19 Mar 2019 17:06:19 +1300 Subject: [PATCH 25/32] Fix function comments based on best practices from Effective Go Signed-off-by: CodeLingo Bot --- totp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/totp.go b/totp.go index f8e41b1..1461c2c 100644 --- a/totp.go +++ b/totp.go @@ -56,7 +56,7 @@ func (otp *TOTP) OTPCounter() uint64 { return otp.otpCounter(uint64(time.Now().Unix())) } -// NewOTP takes a new key, a starting time, a step, the number of +// NewTOTP takes a new key, a starting time, a step, the number of // digits of output (typically 6 or 8) and the hash algorithm to // use, and builds a new OTP. func NewTOTP(key []byte, start uint64, step uint64, digits int, algo crypto.Hash) *TOTP { From 0857b296247dd028c2d176ffc5d7f7e37e288c25 Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Sun, 9 Dec 2018 22:01:00 -0800 Subject: [PATCH 26/32] Actually support clock mocking. --- totp.go | 8 ++++++-- totp_test.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/totp.go b/totp.go index 1461c2c..9259e6c 100644 --- a/totp.go +++ b/totp.go @@ -15,7 +15,7 @@ import ( "github.com/benbjohnson/clock" ) -var time clock.Clock +var timeSource = clock.New() // TOTP represents an RFC 6238 Time-based One-Time Password instance. type TOTP struct { @@ -53,7 +53,7 @@ func (otp *TOTP) otpCounter(t uint64) uint64 { // OTPCounter returns the current time value for the OTP. func (otp *TOTP) OTPCounter() uint64 { - return otp.otpCounter(uint64(time.Now().Unix())) + return otp.otpCounter(uint64(timeSource.Now().Unix())) } // NewTOTP takes a new key, a starting time, a step, the number of @@ -166,3 +166,7 @@ func totpFromURL(u *url.URL) (*TOTP, string, error) { func (otp *TOTP) QR(label string) ([]byte, error) { return otp.OATH.QR(otp.Type(), label) } + +func SetClock(c clock.Clock) { + timeSource = c +} diff --git a/totp_test.go b/totp_test.go index 244e43e..dc1e438 100644 --- a/totp_test.go +++ b/totp_test.go @@ -4,6 +4,9 @@ import ( "crypto" "fmt" "testing" + "time" + + "github.com/benbjohnson/clock" ) var rfcTotpKey = []byte("12345678901234567890") @@ -53,3 +56,28 @@ func TestTotpRFC(t *testing.T) { } } } + +func TestTOTPTime(t *testing.T) { + otp := GenerateGoogleTOTP() + + testClock := clock.NewMock() + testClock.Add(2*time.Minute) + SetClock(testClock) + + code := otp.OTP() + + testClock.Add(-1 * time.Minute) + if newCode := otp.OTP(); newCode == code { + t.Errorf("twofactor: TOTP: previous code %s shouldn't match code %s", newCode, code) + } + + testClock.Add(2 * time.Minute) + if newCode := otp.OTP(); newCode == code { + t.Errorf("twofactor: TOTP: future code %s shouldn't match code %s", newCode, code) + } + + testClock.Add(-1 * time.Minute) + if newCode := otp.OTP(); newCode != code { + t.Errorf("twofactor: TOTP: current code %s shouldn't match code %s", newCode, code) + } +} From 944a57bf0e05548deb664321659707ee4f7c9c6e Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Sat, 31 Oct 2020 07:25:55 -0700 Subject: [PATCH 27/32] Switch to go modules. --- Gopkg.lock | 25 - Gopkg.toml | 38 -- go.mod | 8 + go.sum | 4 + vendor/rsc.io/qr/LICENSE | 27 -- vendor/rsc.io/qr/README.md | 3 - vendor/rsc.io/qr/coding/gen.go | 149 ------ vendor/rsc.io/qr/coding/qr.go | 815 -------------------------------- vendor/rsc.io/qr/gf256/gf256.go | 241 ---------- vendor/rsc.io/qr/png.go | 400 ---------------- vendor/rsc.io/qr/qr.go | 116 ----- 11 files changed, 12 insertions(+), 1814 deletions(-) delete mode 100644 Gopkg.lock delete mode 100644 Gopkg.toml create mode 100644 go.mod create mode 100644 go.sum delete mode 100644 vendor/rsc.io/qr/LICENSE delete mode 100644 vendor/rsc.io/qr/README.md delete mode 100644 vendor/rsc.io/qr/coding/gen.go delete mode 100644 vendor/rsc.io/qr/coding/qr.go delete mode 100644 vendor/rsc.io/qr/gf256/gf256.go delete mode 100644 vendor/rsc.io/qr/png.go delete mode 100644 vendor/rsc.io/qr/qr.go diff --git a/Gopkg.lock b/Gopkg.lock deleted file mode 100644 index 108681f..0000000 --- a/Gopkg.lock +++ /dev/null @@ -1,25 +0,0 @@ -# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. - - -[[projects]] - branch = "master" - name = "github.com/benbjohnson/clock" - packages = ["."] - revision = "7dc76406b6d3c05b5f71a86293cbcf3c4ea03b19" - -[[projects]] - branch = "master" - name = "rsc.io/qr" - packages = [ - ".", - "coding", - "gf256" - ] - revision = "48b2ede4844e13f1a2b7ce4d2529c9af7e359fc5" - -[solve-meta] - analyzer-name = "dep" - analyzer-version = 1 - inputs-digest = "6f47334a8fcf2bb48e739e6181089e02bce6cc4d68888be311df3827db96f8ae" - solver-name = "gps-cdcl" - solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml deleted file mode 100644 index a57d8a0..0000000 --- a/Gopkg.toml +++ /dev/null @@ -1,38 +0,0 @@ -# Gopkg.toml example -# -# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html -# for detailed Gopkg.toml documentation. -# -# required = ["github.com/user/thing/cmd/thing"] -# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] -# -# [[constraint]] -# name = "github.com/user/project" -# version = "1.0.0" -# -# [[constraint]] -# name = "github.com/user/project2" -# branch = "dev" -# source = "github.com/myfork/project2" -# -# [[override]] -# name = "github.com/x/y" -# version = "2.4.0" -# -# [prune] -# non-go = false -# go-tests = true -# unused-packages = true - - -[[constraint]] - branch = "master" - name = "rsc.io/qr" - -[prune] - go-tests = true - unused-packages = true - -[[constraint]] - branch = "master" - name = "github.com/benbjohnson/clock" diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..e8d6c5d --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module github.com/gokyle/twofactor + +go 1.14 + +require ( + github.com/benbjohnson/clock v0.0.0-20161215174838-7dc76406b6d3 + rsc.io/qr v0.1.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..b54497a --- /dev/null +++ b/go.sum @@ -0,0 +1,4 @@ +github.com/benbjohnson/clock v0.0.0-20161215174838-7dc76406b6d3 h1:wOysYcIdqv3WnvwqFFzrYCFALPED7qkUGaLXu359GSc= +github.com/benbjohnson/clock v0.0.0-20161215174838-7dc76406b6d3/go.mod h1:UMqtWQTnOe4byzwe7Zhwh8f8s+36uszN51sJrSIZlTE= +rsc.io/qr v0.1.0 h1:M/sAxsU2J5mlQ4W84Bxga2EgdQqOaAliipcjPmMUM5Q= +rsc.io/qr v0.1.0/go.mod h1:IF+uZjkb9fqyeF/4tlBoynqmQxUoPfWEKh921coOuXs= diff --git a/vendor/rsc.io/qr/LICENSE b/vendor/rsc.io/qr/LICENSE deleted file mode 100644 index 6a66aea..0000000 --- a/vendor/rsc.io/qr/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2009 The Go Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/rsc.io/qr/README.md b/vendor/rsc.io/qr/README.md deleted file mode 100644 index 0ba6214..0000000 --- a/vendor/rsc.io/qr/README.md +++ /dev/null @@ -1,3 +0,0 @@ -Basic QR encoder. - -go get [-u] rsc.io/qr diff --git a/vendor/rsc.io/qr/coding/gen.go b/vendor/rsc.io/qr/coding/gen.go deleted file mode 100644 index a3857f2..0000000 --- a/vendor/rsc.io/qr/coding/gen.go +++ /dev/null @@ -1,149 +0,0 @@ -// +build ignore - -package main - -import "fmt" - -// tables from qrencode-3.1.1/qrspec.c - -var capacity = [41]struct { - width int - words int - remainder int - ec [4]int -}{ - {0, 0, 0, [4]int{0, 0, 0, 0}}, - {21, 26, 0, [4]int{7, 10, 13, 17}}, // 1 - {25, 44, 7, [4]int{10, 16, 22, 28}}, - {29, 70, 7, [4]int{15, 26, 36, 44}}, - {33, 100, 7, [4]int{20, 36, 52, 64}}, - {37, 134, 7, [4]int{26, 48, 72, 88}}, // 5 - {41, 172, 7, [4]int{36, 64, 96, 112}}, - {45, 196, 0, [4]int{40, 72, 108, 130}}, - {49, 242, 0, [4]int{48, 88, 132, 156}}, - {53, 292, 0, [4]int{60, 110, 160, 192}}, - {57, 346, 0, [4]int{72, 130, 192, 224}}, //10 - {61, 404, 0, [4]int{80, 150, 224, 264}}, - {65, 466, 0, [4]int{96, 176, 260, 308}}, - {69, 532, 0, [4]int{104, 198, 288, 352}}, - {73, 581, 3, [4]int{120, 216, 320, 384}}, - {77, 655, 3, [4]int{132, 240, 360, 432}}, //15 - {81, 733, 3, [4]int{144, 280, 408, 480}}, - {85, 815, 3, [4]int{168, 308, 448, 532}}, - {89, 901, 3, [4]int{180, 338, 504, 588}}, - {93, 991, 3, [4]int{196, 364, 546, 650}}, - {97, 1085, 3, [4]int{224, 416, 600, 700}}, //20 - {101, 1156, 4, [4]int{224, 442, 644, 750}}, - {105, 1258, 4, [4]int{252, 476, 690, 816}}, - {109, 1364, 4, [4]int{270, 504, 750, 900}}, - {113, 1474, 4, [4]int{300, 560, 810, 960}}, - {117, 1588, 4, [4]int{312, 588, 870, 1050}}, //25 - {121, 1706, 4, [4]int{336, 644, 952, 1110}}, - {125, 1828, 4, [4]int{360, 700, 1020, 1200}}, - {129, 1921, 3, [4]int{390, 728, 1050, 1260}}, - {133, 2051, 3, [4]int{420, 784, 1140, 1350}}, - {137, 2185, 3, [4]int{450, 812, 1200, 1440}}, //30 - {141, 2323, 3, [4]int{480, 868, 1290, 1530}}, - {145, 2465, 3, [4]int{510, 924, 1350, 1620}}, - {149, 2611, 3, [4]int{540, 980, 1440, 1710}}, - {153, 2761, 3, [4]int{570, 1036, 1530, 1800}}, - {157, 2876, 0, [4]int{570, 1064, 1590, 1890}}, //35 - {161, 3034, 0, [4]int{600, 1120, 1680, 1980}}, - {165, 3196, 0, [4]int{630, 1204, 1770, 2100}}, - {169, 3362, 0, [4]int{660, 1260, 1860, 2220}}, - {173, 3532, 0, [4]int{720, 1316, 1950, 2310}}, - {177, 3706, 0, [4]int{750, 1372, 2040, 2430}}, //40 -} - -var eccTable = [41][4][2]int{ - {{0, 0}, {0, 0}, {0, 0}, {0, 0}}, - {{1, 0}, {1, 0}, {1, 0}, {1, 0}}, // 1 - {{1, 0}, {1, 0}, {1, 0}, {1, 0}}, - {{1, 0}, {1, 0}, {2, 0}, {2, 0}}, - {{1, 0}, {2, 0}, {2, 0}, {4, 0}}, - {{1, 0}, {2, 0}, {2, 2}, {2, 2}}, // 5 - {{2, 0}, {4, 0}, {4, 0}, {4, 0}}, - {{2, 0}, {4, 0}, {2, 4}, {4, 1}}, - {{2, 0}, {2, 2}, {4, 2}, {4, 2}}, - {{2, 0}, {3, 2}, {4, 4}, {4, 4}}, - {{2, 2}, {4, 1}, {6, 2}, {6, 2}}, //10 - {{4, 0}, {1, 4}, {4, 4}, {3, 8}}, - {{2, 2}, {6, 2}, {4, 6}, {7, 4}}, - {{4, 0}, {8, 1}, {8, 4}, {12, 4}}, - {{3, 1}, {4, 5}, {11, 5}, {11, 5}}, - {{5, 1}, {5, 5}, {5, 7}, {11, 7}}, //15 - {{5, 1}, {7, 3}, {15, 2}, {3, 13}}, - {{1, 5}, {10, 1}, {1, 15}, {2, 17}}, - {{5, 1}, {9, 4}, {17, 1}, {2, 19}}, - {{3, 4}, {3, 11}, {17, 4}, {9, 16}}, - {{3, 5}, {3, 13}, {15, 5}, {15, 10}}, //20 - {{4, 4}, {17, 0}, {17, 6}, {19, 6}}, - {{2, 7}, {17, 0}, {7, 16}, {34, 0}}, - {{4, 5}, {4, 14}, {11, 14}, {16, 14}}, - {{6, 4}, {6, 14}, {11, 16}, {30, 2}}, - {{8, 4}, {8, 13}, {7, 22}, {22, 13}}, //25 - {{10, 2}, {19, 4}, {28, 6}, {33, 4}}, - {{8, 4}, {22, 3}, {8, 26}, {12, 28}}, - {{3, 10}, {3, 23}, {4, 31}, {11, 31}}, - {{7, 7}, {21, 7}, {1, 37}, {19, 26}}, - {{5, 10}, {19, 10}, {15, 25}, {23, 25}}, //30 - {{13, 3}, {2, 29}, {42, 1}, {23, 28}}, - {{17, 0}, {10, 23}, {10, 35}, {19, 35}}, - {{17, 1}, {14, 21}, {29, 19}, {11, 46}}, - {{13, 6}, {14, 23}, {44, 7}, {59, 1}}, - {{12, 7}, {12, 26}, {39, 14}, {22, 41}}, //35 - {{6, 14}, {6, 34}, {46, 10}, {2, 64}}, - {{17, 4}, {29, 14}, {49, 10}, {24, 46}}, - {{4, 18}, {13, 32}, {48, 14}, {42, 32}}, - {{20, 4}, {40, 7}, {43, 22}, {10, 67}}, - {{19, 6}, {18, 31}, {34, 34}, {20, 61}}, //40 -} - -var align = [41][2]int{ - {0, 0}, - {0, 0}, {18, 0}, {22, 0}, {26, 0}, {30, 0}, // 1- 5 - {34, 0}, {22, 38}, {24, 42}, {26, 46}, {28, 50}, // 6-10 - {30, 54}, {32, 58}, {34, 62}, {26, 46}, {26, 48}, //11-15 - {26, 50}, {30, 54}, {30, 56}, {30, 58}, {34, 62}, //16-20 - {28, 50}, {26, 50}, {30, 54}, {28, 54}, {32, 58}, //21-25 - {30, 58}, {34, 62}, {26, 50}, {30, 54}, {26, 52}, //26-30 - {30, 56}, {34, 60}, {30, 58}, {34, 62}, {30, 54}, //31-35 - {24, 50}, {28, 54}, {32, 58}, {26, 54}, {30, 58}, //35-40 -} - -var versionPattern = [41]int{ - 0, - 0, 0, 0, 0, 0, 0, - 0x07c94, 0x085bc, 0x09a99, 0x0a4d3, 0x0bbf6, 0x0c762, 0x0d847, 0x0e60d, - 0x0f928, 0x10b78, 0x1145d, 0x12a17, 0x13532, 0x149a6, 0x15683, 0x168c9, - 0x177ec, 0x18ec4, 0x191e1, 0x1afab, 0x1b08e, 0x1cc1a, 0x1d33f, 0x1ed75, - 0x1f250, 0x209d5, 0x216f0, 0x228ba, 0x2379f, 0x24b0b, 0x2542e, 0x26a64, - 0x27541, 0x28c69, -} - -func main() { - fmt.Printf("\t{},\n") - for i := 1; i <= 40; i++ { - apos := align[i][0] - 2 - if apos < 0 { - apos = 100 - } - astride := align[i][1] - align[i][0] - if astride < 1 { - astride = 100 - } - fmt.Printf("\t{%v, %v, %v, %#x, [4]level{{%v, %v}, {%v, %v}, {%v, %v}, {%v, %v}}}, // %v\n", - apos, astride, capacity[i].words, - versionPattern[i], - eccTable[i][0][0]+eccTable[i][0][1], - float64(capacity[i].ec[0])/float64(eccTable[i][0][0]+eccTable[i][0][1]), - eccTable[i][1][0]+eccTable[i][1][1], - float64(capacity[i].ec[1])/float64(eccTable[i][1][0]+eccTable[i][1][1]), - eccTable[i][2][0]+eccTable[i][2][1], - float64(capacity[i].ec[2])/float64(eccTable[i][2][0]+eccTable[i][2][1]), - eccTable[i][3][0]+eccTable[i][3][1], - float64(capacity[i].ec[3])/float64(eccTable[i][3][0]+eccTable[i][3][1]), - i, - ) - } -} diff --git a/vendor/rsc.io/qr/coding/qr.go b/vendor/rsc.io/qr/coding/qr.go deleted file mode 100644 index 4aa5288..0000000 --- a/vendor/rsc.io/qr/coding/qr.go +++ /dev/null @@ -1,815 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package coding implements low-level QR coding details. -package coding - -import ( - "fmt" - "strconv" - "strings" - - "rsc.io/qr/gf256" -) - -// Field is the field for QR error correction. -var Field = gf256.NewField(0x11d, 2) - -// A Version represents a QR version. -// The version specifies the size of the QR code: -// a QR code with version v has 4v+17 pixels on a side. -// Versions number from 1 to 40: the larger the version, -// the more information the code can store. -type Version int - -const MinVersion = 1 -const MaxVersion = 40 - -func (v Version) String() string { - return strconv.Itoa(int(v)) -} - -func (v Version) sizeClass() int { - if v <= 9 { - return 0 - } - if v <= 26 { - return 1 - } - return 2 -} - -// DataBytes returns the number of data bytes that can be -// stored in a QR code with the given version and level. -func (v Version) DataBytes(l Level) int { - vt := &vtab[v] - lev := &vt.level[l] - return vt.bytes - lev.nblock*lev.check -} - -// Encoding implements a QR data encoding scheme. -// The implementations--Numeric, Alphanumeric, and String--specify -// the character set and the mapping from UTF-8 to code bits. -// The more restrictive the mode, the fewer code bits are needed. -type Encoding interface { - Check() error - Bits(v Version) int - Encode(b *Bits, v Version) -} - -type Bits struct { - b []byte - nbit int -} - -func (b *Bits) Reset() { - b.b = b.b[:0] - b.nbit = 0 -} - -func (b *Bits) Bits() int { - return b.nbit -} - -func (b *Bits) Bytes() []byte { - if b.nbit%8 != 0 { - panic("fractional byte") - } - return b.b -} - -func (b *Bits) Append(p []byte) { - if b.nbit%8 != 0 { - panic("fractional byte") - } - b.b = append(b.b, p...) - b.nbit += 8 * len(p) -} - -func (b *Bits) Write(v uint, nbit int) { - for nbit > 0 { - n := nbit - if n > 8 { - n = 8 - } - if b.nbit%8 == 0 { - b.b = append(b.b, 0) - } else { - m := -b.nbit & 7 - if n > m { - n = m - } - } - b.nbit += n - sh := uint(nbit - n) - b.b[len(b.b)-1] |= uint8(v >> sh << uint(-b.nbit&7)) - v -= v >> sh << sh - nbit -= n - } -} - -// Num is the encoding for numeric data. -// The only valid characters are the decimal digits 0 through 9. -type Num string - -func (s Num) String() string { - return fmt.Sprintf("Num(%#q)", string(s)) -} - -func (s Num) Check() error { - for _, c := range s { - if c < '0' || '9' < c { - return fmt.Errorf("non-numeric string %#q", string(s)) - } - } - return nil -} - -var numLen = [3]int{10, 12, 14} - -func (s Num) Bits(v Version) int { - return 4 + numLen[v.sizeClass()] + (10*len(s)+2)/3 -} - -func (s Num) Encode(b *Bits, v Version) { - b.Write(1, 4) - b.Write(uint(len(s)), numLen[v.sizeClass()]) - var i int - for i = 0; i+3 <= len(s); i += 3 { - w := uint(s[i]-'0')*100 + uint(s[i+1]-'0')*10 + uint(s[i+2]-'0') - b.Write(w, 10) - } - switch len(s) - i { - case 1: - w := uint(s[i] - '0') - b.Write(w, 4) - case 2: - w := uint(s[i]-'0')*10 + uint(s[i+1]-'0') - b.Write(w, 7) - } -} - -// Alpha is the encoding for alphanumeric data. -// The valid characters are 0-9A-Z$%*+-./: and space. -type Alpha string - -const alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:" - -func (s Alpha) String() string { - return fmt.Sprintf("Alpha(%#q)", string(s)) -} - -func (s Alpha) Check() error { - for _, c := range s { - if strings.IndexRune(alphabet, c) < 0 { - return fmt.Errorf("non-alphanumeric string %#q", string(s)) - } - } - return nil -} - -var alphaLen = [3]int{9, 11, 13} - -func (s Alpha) Bits(v Version) int { - return 4 + alphaLen[v.sizeClass()] + (11*len(s)+1)/2 -} - -func (s Alpha) Encode(b *Bits, v Version) { - b.Write(2, 4) - b.Write(uint(len(s)), alphaLen[v.sizeClass()]) - var i int - for i = 0; i+2 <= len(s); i += 2 { - w := uint(strings.IndexRune(alphabet, rune(s[i])))*45 + - uint(strings.IndexRune(alphabet, rune(s[i+1]))) - b.Write(w, 11) - } - - if i < len(s) { - w := uint(strings.IndexRune(alphabet, rune(s[i]))) - b.Write(w, 6) - } -} - -// String is the encoding for 8-bit data. All bytes are valid. -type String string - -func (s String) String() string { - return fmt.Sprintf("String(%#q)", string(s)) -} - -func (s String) Check() error { - return nil -} - -var stringLen = [3]int{8, 16, 16} - -func (s String) Bits(v Version) int { - return 4 + stringLen[v.sizeClass()] + 8*len(s) -} - -func (s String) Encode(b *Bits, v Version) { - b.Write(4, 4) - b.Write(uint(len(s)), stringLen[v.sizeClass()]) - for i := 0; i < len(s); i++ { - b.Write(uint(s[i]), 8) - } -} - -// A Pixel describes a single pixel in a QR code. -type Pixel uint32 - -const ( - Black Pixel = 1 << iota - Invert -) - -func (p Pixel) Offset() uint { - return uint(p >> 6) -} - -func OffsetPixel(o uint) Pixel { - return Pixel(o << 6) -} - -func (r PixelRole) Pixel() Pixel { - return Pixel(r << 2) -} - -func (p Pixel) Role() PixelRole { - return PixelRole(p>>2) & 15 -} - -func (p Pixel) String() string { - s := p.Role().String() - if p&Black != 0 { - s += "+black" - } - if p&Invert != 0 { - s += "+invert" - } - s += "+" + strconv.FormatUint(uint64(p.Offset()), 10) - return s -} - -// A PixelRole describes the role of a QR pixel. -type PixelRole uint32 - -const ( - _ PixelRole = iota - Position // position squares (large) - Alignment // alignment squares (small) - Timing // timing strip between position squares - Format // format metadata - PVersion // version pattern - Unused // unused pixel - Data // data bit - Check // error correction check bit - Extra -) - -var roles = []string{ - "", - "position", - "alignment", - "timing", - "format", - "pversion", - "unused", - "data", - "check", - "extra", -} - -func (r PixelRole) String() string { - if Position <= r && r <= Check { - return roles[r] - } - return strconv.Itoa(int(r)) -} - -// A Level represents a QR error correction level. -// From least to most tolerant of errors, they are L, M, Q, H. -type Level int - -const ( - L Level = iota - M - Q - H -) - -func (l Level) String() string { - if L <= l && l <= H { - return "LMQH"[l : l+1] - } - return strconv.Itoa(int(l)) -} - -// A Code is a square pixel grid. -type Code struct { - Bitmap []byte // 1 is black, 0 is white - Size int // number of pixels on a side - Stride int // number of bytes per row -} - -func (c *Code) Black(x, y int) bool { - return 0 <= x && x < c.Size && 0 <= y && y < c.Size && - c.Bitmap[y*c.Stride+x/8]&(1<= pad { - break - } - b.Write(0x11, 8) - } - } -} - -func (b *Bits) AddCheckBytes(v Version, l Level) { - nd := v.DataBytes(l) - if b.nbit < nd*8 { - b.Pad(nd*8 - b.nbit) - } - if b.nbit != nd*8 { - panic("qr: too much data") - } - - dat := b.Bytes() - vt := &vtab[v] - lev := &vt.level[l] - db := nd / lev.nblock - extra := nd % lev.nblock - chk := make([]byte, lev.check) - rs := gf256.NewRSEncoder(Field, lev.check) - for i := 0; i < lev.nblock; i++ { - if i == lev.nblock-extra { - db++ - } - rs.ECC(dat[:db], chk) - b.Append(chk) - dat = dat[db:] - } - - if len(b.Bytes()) != vt.bytes { - panic("qr: internal error") - } -} - -func (p *Plan) Encode(text ...Encoding) (*Code, error) { - var b Bits - for _, t := range text { - if err := t.Check(); err != nil { - return nil, err - } - t.Encode(&b, p.Version) - } - if b.Bits() > p.DataBytes*8 { - return nil, fmt.Errorf("cannot encode %d bits into %d-bit code", b.Bits(), p.DataBytes*8) - } - b.AddCheckBytes(p.Version, p.Level) - bytes := b.Bytes() - - // Now we have the checksum bytes and the data bytes. - // Construct the actual code. - c := &Code{Size: len(p.Pixel), Stride: (len(p.Pixel) + 7) &^ 7} - c.Bitmap = make([]byte, c.Stride*c.Size) - crow := c.Bitmap - for _, row := range p.Pixel { - for x, pix := range row { - switch pix.Role() { - case Data, Check: - o := pix.Offset() - if bytes[o/8]&(1< 40 { - return nil, fmt.Errorf("invalid QR version %d", int(v)) - } - siz := 17 + int(v)*4 - m := grid(siz) - p.Pixel = m - - // Timing markers (overwritten by boxes). - const ti = 6 // timing is in row/column 6 (counting from 0) - for i := range m { - p := Timing.Pixel() - if i&1 == 0 { - p |= Black - } - m[i][ti] = p - m[ti][i] = p - } - - // Position boxes. - posBox(m, 0, 0) - posBox(m, siz-7, 0) - posBox(m, 0, siz-7) - - // Alignment boxes. - info := &vtab[v] - for x := 4; x+5 < siz; { - for y := 4; y+5 < siz; { - // don't overwrite timing markers - if (x < 7 && y < 7) || (x < 7 && y+5 >= siz-7) || (x+5 >= siz-7 && y < 7) { - } else { - alignBox(m, x, y) - } - if y == 4 { - y = info.apos - } else { - y += info.astride - } - } - if x == 4 { - x = info.apos - } else { - x += info.astride - } - } - - // Version pattern. - pat := vtab[v].pattern - if pat != 0 { - v := pat - for x := 0; x < 6; x++ { - for y := 0; y < 3; y++ { - p := PVersion.Pixel() - if v&1 != 0 { - p |= Black - } - m[siz-11+y][x] = p - m[x][siz-11+y] = p - v >>= 1 - } - } - } - - // One lonely black pixel - m[siz-8][8] = Unused.Pixel() | Black - - return p, nil -} - -// fplan adds the format pixels -func fplan(l Level, m Mask, p *Plan) error { - // Format pixels. - fb := uint32(l^1) << 13 // level: L=01, M=00, Q=11, H=10 - fb |= uint32(m) << 10 // mask - const formatPoly = 0x537 - rem := fb - for i := 14; i >= 10; i-- { - if rem&(1<>i)&1 == 1 { - pix |= Black - } - if (invert>>i)&1 == 1 { - pix ^= Invert | Black - } - // top left - switch { - case i < 6: - p.Pixel[i][8] = pix - case i < 8: - p.Pixel[i+1][8] = pix - case i < 9: - p.Pixel[8][7] = pix - default: - p.Pixel[8][14-i] = pix - } - // bottom right - switch { - case i < 8: - p.Pixel[8][siz-1-int(i)] = pix - default: - p.Pixel[siz-1-int(14-i)][8] = pix - } - } - return nil -} - -// lplan edits a version-only Plan to add information -// about the error correction levels. -func lplan(v Version, l Level, p *Plan) error { - p.Level = l - - nblock := vtab[v].level[l].nblock - ne := vtab[v].level[l].check - nde := (vtab[v].bytes - ne*nblock) / nblock - extra := (vtab[v].bytes - ne*nblock) % nblock - dataBits := (nde*nblock + extra) * 8 - checkBits := ne * nblock * 8 - - p.DataBytes = vtab[v].bytes - ne*nblock - p.CheckBytes = ne * nblock - p.Blocks = nblock - - // Make data + checksum pixels. - data := make([]Pixel, dataBits) - for i := range data { - data[i] = Data.Pixel() | OffsetPixel(uint(i)) - } - check := make([]Pixel, checkBits) - for i := range check { - check[i] = Check.Pixel() | OffsetPixel(uint(i+dataBits)) - } - - // Split into blocks. - dataList := make([][]Pixel, nblock) - checkList := make([][]Pixel, nblock) - for i := 0; i < nblock; i++ { - // The last few blocks have an extra data byte (8 pixels). - nd := nde - if i >= nblock-extra { - nd++ - } - dataList[i], data = data[0:nd*8], data[nd*8:] - checkList[i], check = check[0:ne*8], check[ne*8:] - } - if len(data) != 0 || len(check) != 0 { - panic("data/check math") - } - - // Build up bit sequence, taking first byte of each block, - // then second byte, and so on. Then checksums. - bits := make([]Pixel, dataBits+checkBits) - dst := bits - for i := 0; i < nde+1; i++ { - for _, b := range dataList { - if i*8 < len(b) { - copy(dst, b[i*8:(i+1)*8]) - dst = dst[8:] - } - } - } - for i := 0; i < ne; i++ { - for _, b := range checkList { - if i*8 < len(b) { - copy(dst, b[i*8:(i+1)*8]) - dst = dst[8:] - } - } - } - if len(dst) != 0 { - panic("dst math") - } - - // Sweep up pair of columns, - // then down, assigning to right then left pixel. - // Repeat. - // See Figure 2 of http://www.pclviewer.com/rs2/qrtopology.htm - siz := len(p.Pixel) - rem := make([]Pixel, 7) - for i := range rem { - rem[i] = Extra.Pixel() - } - src := append(bits, rem...) - for x := siz; x > 0; { - for y := siz - 1; y >= 0; y-- { - if p.Pixel[y][x-1].Role() == 0 { - p.Pixel[y][x-1], src = src[0], src[1:] - } - if p.Pixel[y][x-2].Role() == 0 { - p.Pixel[y][x-2], src = src[0], src[1:] - } - } - x -= 2 - if x == 7 { // vertical timing strip - x-- - } - for y := 0; y < siz; y++ { - if p.Pixel[y][x-1].Role() == 0 { - p.Pixel[y][x-1], src = src[0], src[1:] - } - if p.Pixel[y][x-2].Role() == 0 { - p.Pixel[y][x-2], src = src[0], src[1:] - } - } - x -= 2 - } - return nil -} - -// mplan edits a version+level-only Plan to add the mask. -func mplan(m Mask, p *Plan) error { - p.Mask = m - for y, row := range p.Pixel { - for x, pix := range row { - if r := pix.Role(); (r == Data || r == Check || r == Extra) && p.Mask.Invert(y, x) { - row[x] ^= Black | Invert - } - } - } - return nil -} - -// posBox draws a position (large) box at upper left x, y. -func posBox(m [][]Pixel, x, y int) { - pos := Position.Pixel() - // box - for dy := 0; dy < 7; dy++ { - for dx := 0; dx < 7; dx++ { - p := pos - if dx == 0 || dx == 6 || dy == 0 || dy == 6 || 2 <= dx && dx <= 4 && 2 <= dy && dy <= 4 { - p |= Black - } - m[y+dy][x+dx] = p - } - } - // white border - for dy := -1; dy < 8; dy++ { - if 0 <= y+dy && y+dy < len(m) { - if x > 0 { - m[y+dy][x-1] = pos - } - if x+7 < len(m) { - m[y+dy][x+7] = pos - } - } - } - for dx := -1; dx < 8; dx++ { - if 0 <= x+dx && x+dx < len(m) { - if y > 0 { - m[y-1][x+dx] = pos - } - if y+7 < len(m) { - m[y+7][x+dx] = pos - } - } - } -} - -// alignBox draw an alignment (small) box at upper left x, y. -func alignBox(m [][]Pixel, x, y int) { - // box - align := Alignment.Pixel() - for dy := 0; dy < 5; dy++ { - for dx := 0; dx < 5; dx++ { - p := align - if dx == 0 || dx == 4 || dy == 0 || dy == 4 || dx == 2 && dy == 2 { - p |= Black - } - m[y+dy][x+dx] = p - } - } -} diff --git a/vendor/rsc.io/qr/gf256/gf256.go b/vendor/rsc.io/qr/gf256/gf256.go deleted file mode 100644 index bfeeeb3..0000000 --- a/vendor/rsc.io/qr/gf256/gf256.go +++ /dev/null @@ -1,241 +0,0 @@ -// Copyright 2010 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package gf256 implements arithmetic over the Galois Field GF(256). -package gf256 - -import "strconv" - -// A Field represents an instance of GF(256) defined by a specific polynomial. -type Field struct { - log [256]byte // log[0] is unused - exp [510]byte -} - -// NewField returns a new field corresponding to the polynomial poly -// and generator α. The Reed-Solomon encoding in QR codes uses -// polynomial 0x11d with generator 2. -// -// The choice of generator α only affects the Exp and Log operations. -func NewField(poly, α int) *Field { - if poly < 0x100 || poly >= 0x200 || reducible(poly) { - panic("gf256: invalid polynomial: " + strconv.Itoa(poly)) - } - - var f Field - x := 1 - for i := 0; i < 255; i++ { - if x == 1 && i != 0 { - panic("gf256: invalid generator " + strconv.Itoa(α) + - " for polynomial " + strconv.Itoa(poly)) - } - f.exp[i] = byte(x) - f.exp[i+255] = byte(x) - f.log[x] = byte(i) - x = mul(x, α, poly) - } - f.log[0] = 255 - for i := 0; i < 255; i++ { - if f.log[f.exp[i]] != byte(i) { - panic("bad log") - } - if f.log[f.exp[i+255]] != byte(i) { - panic("bad log") - } - } - for i := 1; i < 256; i++ { - if f.exp[f.log[i]] != byte(i) { - panic("bad log") - } - } - - return &f -} - -// nbit returns the number of significant in p. -func nbit(p int) uint { - n := uint(0) - for ; p > 0; p >>= 1 { - n++ - } - return n -} - -// polyDiv divides the polynomial p by q and returns the remainder. -func polyDiv(p, q int) int { - np := nbit(p) - nq := nbit(q) - for ; np >= nq; np-- { - if p&(1<<(np-1)) != 0 { - p ^= q << (np - nq) - } - } - return p -} - -// mul returns the product x*y mod poly, a GF(256) multiplication. -func mul(x, y, poly int) int { - z := 0 - for x > 0 { - if x&1 != 0 { - z ^= y - } - x >>= 1 - y <<= 1 - if y&0x100 != 0 { - y ^= poly - } - } - return z -} - -// reducible reports whether p is reducible. -func reducible(p int) bool { - // Multiplying n-bit * n-bit produces (2n-1)-bit, - // so if p is reducible, one of its factors must be - // of np/2+1 bits or fewer. - np := nbit(p) - for q := 2; q < 1<<(np/2+1); q++ { - if polyDiv(p, q) == 0 { - return true - } - } - return false -} - -// Add returns the sum of x and y in the field. -func (f *Field) Add(x, y byte) byte { - return x ^ y -} - -// Exp returns the base-α exponential of e in the field. -// If e < 0, Exp returns 0. -func (f *Field) Exp(e int) byte { - if e < 0 { - return 0 - } - return f.exp[e%255] -} - -// Log returns the base-α logarithm of x in the field. -// If x == 0, Log returns -1. -func (f *Field) Log(x byte) int { - if x == 0 { - return -1 - } - return int(f.log[x]) -} - -// Inv returns the multiplicative inverse of x in the field. -// If x == 0, Inv returns 0. -func (f *Field) Inv(x byte) byte { - if x == 0 { - return 0 - } - return f.exp[255-f.log[x]] -} - -// Mul returns the product of x and y in the field. -func (f *Field) Mul(x, y byte) byte { - if x == 0 || y == 0 { - return 0 - } - return f.exp[int(f.log[x])+int(f.log[y])] -} - -// An RSEncoder implements Reed-Solomon encoding -// over a given field using a given number of error correction bytes. -type RSEncoder struct { - f *Field - c int - gen []byte - lgen []byte - p []byte -} - -func (f *Field) gen(e int) (gen, lgen []byte) { - // p = 1 - p := make([]byte, e+1) - p[e] = 1 - - for i := 0; i < e; i++ { - // p *= (x + Exp(i)) - // p[j] = p[j]*Exp(i) + p[j+1]. - c := f.Exp(i) - for j := 0; j < e; j++ { - p[j] = f.Mul(p[j], c) ^ p[j+1] - } - p[e] = f.Mul(p[e], c) - } - - // lp = log p. - lp := make([]byte, e+1) - for i, c := range p { - if c == 0 { - lp[i] = 255 - } else { - lp[i] = byte(f.Log(c)) - } - } - - return p, lp -} - -// NewRSEncoder returns a new Reed-Solomon encoder -// over the given field and number of error correction bytes. -func NewRSEncoder(f *Field, c int) *RSEncoder { - gen, lgen := f.gen(c) - return &RSEncoder{f: f, c: c, gen: gen, lgen: lgen} -} - -// ECC writes to check the error correcting code bytes -// for data using the given Reed-Solomon parameters. -func (rs *RSEncoder) ECC(data []byte, check []byte) { - if len(check) < rs.c { - panic("gf256: invalid check byte length") - } - if rs.c == 0 { - return - } - - // The check bytes are the remainder after dividing - // data padded with c zeros by the generator polynomial. - - // p = data padded with c zeros. - var p []byte - n := len(data) + rs.c - if len(rs.p) >= n { - p = rs.p - } else { - p = make([]byte, n) - } - copy(p, data) - for i := len(data); i < len(p); i++ { - p[i] = 0 - } - - // Divide p by gen, leaving the remainder in p[len(data):]. - // p[0] is the most significant term in p, and - // gen[0] is the most significant term in the generator, - // which is always 1. - // To avoid repeated work, we store various values as - // lv, not v, where lv = log[v]. - f := rs.f - lgen := rs.lgen[1:] - for i := 0; i < len(data); i++ { - c := p[i] - if c == 0 { - continue - } - q := p[i+1:] - exp := f.exp[f.log[c]:] - for j, lg := range lgen { - if lg != 255 { // lgen uses 255 for log 0 - q[j] ^= exp[lg] - } - } - } - copy(check, p[len(data):]) - rs.p = p -} diff --git a/vendor/rsc.io/qr/png.go b/vendor/rsc.io/qr/png.go deleted file mode 100644 index db49d05..0000000 --- a/vendor/rsc.io/qr/png.go +++ /dev/null @@ -1,400 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package qr - -// PNG writer for QR codes. - -import ( - "bytes" - "encoding/binary" - "hash" - "hash/crc32" -) - -// PNG returns a PNG image displaying the code. -// -// PNG uses a custom encoder tailored to QR codes. -// Its compressed size is about 2x away from optimal, -// but it runs about 20x faster than calling png.Encode -// on c.Image(). -func (c *Code) PNG() []byte { - var p pngWriter - return p.encode(c) -} - -type pngWriter struct { - tmp [16]byte - wctmp [4]byte - buf bytes.Buffer - zlib bitWriter - crc hash.Hash32 -} - -var pngHeader = []byte("\x89PNG\r\n\x1a\n") - -func (w *pngWriter) encode(c *Code) []byte { - scale := c.Scale - siz := c.Size - - w.buf.Reset() - - // Header - w.buf.Write(pngHeader) - - // Header block - binary.BigEndian.PutUint32(w.tmp[0:4], uint32((siz+8)*scale)) - binary.BigEndian.PutUint32(w.tmp[4:8], uint32((siz+8)*scale)) - w.tmp[8] = 1 // 1-bit - w.tmp[9] = 0 // gray - w.tmp[10] = 0 - w.tmp[11] = 0 - w.tmp[12] = 0 - w.writeChunk("IHDR", w.tmp[:13]) - - // Comment - w.writeChunk("tEXt", comment) - - // Data - w.zlib.writeCode(c) - w.writeChunk("IDAT", w.zlib.bytes.Bytes()) - - // End - w.writeChunk("IEND", nil) - - return w.buf.Bytes() -} - -var comment = []byte("Software\x00QR-PNG http://qr.swtch.com/") - -func (w *pngWriter) writeChunk(name string, data []byte) { - if w.crc == nil { - w.crc = crc32.NewIEEE() - } - binary.BigEndian.PutUint32(w.wctmp[0:4], uint32(len(data))) - w.buf.Write(w.wctmp[0:4]) - w.crc.Reset() - copy(w.wctmp[0:4], name) - w.buf.Write(w.wctmp[0:4]) - w.crc.Write(w.wctmp[0:4]) - w.buf.Write(data) - w.crc.Write(data) - crc := w.crc.Sum32() - binary.BigEndian.PutUint32(w.wctmp[0:4], crc) - w.buf.Write(w.wctmp[0:4]) -} - -func (b *bitWriter) writeCode(c *Code) { - const ftNone = 0 - - b.adler32.Reset() - b.bytes.Reset() - b.nbit = 0 - - scale := c.Scale - siz := c.Size - - // zlib header - b.tmp[0] = 0x78 - b.tmp[1] = 0 - b.tmp[1] += uint8(31 - (uint16(b.tmp[0])<<8+uint16(b.tmp[1]))%31) - b.bytes.Write(b.tmp[0:2]) - - // Start flate block. - b.writeBits(1, 1, false) // final block - b.writeBits(1, 2, false) // compressed, fixed Huffman tables - - // White border. - // First row. - b.byte(ftNone) - n := (scale*(siz+8) + 7) / 8 - b.byte(255) - b.repeat(n-1, 1) - // 4*scale rows total. - b.repeat((4*scale-1)*(1+n), 1+n) - - for i := 0; i < 4*scale; i++ { - b.adler32.WriteNByte(ftNone, 1) - b.adler32.WriteNByte(255, n) - } - - row := make([]byte, 1+n) - for y := 0; y < siz; y++ { - row[0] = ftNone - j := 1 - var z uint8 - nz := 0 - for x := -4; x < siz+4; x++ { - // Raw data. - for i := 0; i < scale; i++ { - z <<= 1 - if !c.Black(x, y) { - z |= 1 - } - if nz++; nz == 8 { - row[j] = z - j++ - nz = 0 - } - } - } - if j < len(row) { - row[j] = z - } - for _, z := range row { - b.byte(z) - } - - // Scale-1 copies. - b.repeat((scale-1)*(1+n), 1+n) - - b.adler32.WriteN(row, scale) - } - - // White border. - // First row. - b.byte(ftNone) - b.byte(255) - b.repeat(n-1, 1) - // 4*scale rows total. - b.repeat((4*scale-1)*(1+n), 1+n) - - for i := 0; i < 4*scale; i++ { - b.adler32.WriteNByte(ftNone, 1) - b.adler32.WriteNByte(255, n) - } - - // End of block. - b.hcode(256) - b.flushBits() - - // adler32 - binary.BigEndian.PutUint32(b.tmp[0:], b.adler32.Sum32()) - b.bytes.Write(b.tmp[0:4]) -} - -// A bitWriter is a write buffer for bit-oriented data like deflate. -type bitWriter struct { - bytes bytes.Buffer - bit uint32 - nbit uint - - tmp [4]byte - adler32 adigest -} - -func (b *bitWriter) writeBits(bit uint32, nbit uint, rev bool) { - // reverse, for huffman codes - if rev { - br := uint32(0) - for i := uint(0); i < nbit; i++ { - br |= ((bit >> i) & 1) << (nbit - 1 - i) - } - bit = br - } - b.bit |= bit << b.nbit - b.nbit += nbit - for b.nbit >= 8 { - b.bytes.WriteByte(byte(b.bit)) - b.bit >>= 8 - b.nbit -= 8 - } -} - -func (b *bitWriter) flushBits() { - if b.nbit > 0 { - b.bytes.WriteByte(byte(b.bit)) - b.nbit = 0 - b.bit = 0 - } -} - -func (b *bitWriter) hcode(v int) { - /* - Lit Value Bits Codes - --------- ---- ----- - 0 - 143 8 00110000 through - 10111111 - 144 - 255 9 110010000 through - 111111111 - 256 - 279 7 0000000 through - 0010111 - 280 - 287 8 11000000 through - 11000111 - */ - switch { - case v <= 143: - b.writeBits(uint32(v)+0x30, 8, true) - case v <= 255: - b.writeBits(uint32(v-144)+0x190, 9, true) - case v <= 279: - b.writeBits(uint32(v-256)+0, 7, true) - case v <= 287: - b.writeBits(uint32(v-280)+0xc0, 8, true) - default: - panic("invalid hcode") - } -} - -func (b *bitWriter) byte(x byte) { - b.hcode(int(x)) -} - -func (b *bitWriter) codex(c int, val int, nx uint) { - b.hcode(c + val>>nx) - b.writeBits(uint32(val)&(1<= 258+3; n -= 258 { - b.repeat1(258, d) - } - if n > 258 { - // 258 < n < 258+3 - b.repeat1(10, d) - b.repeat1(n-10, d) - return - } - if n < 3 { - panic("invalid flate repeat") - } - b.repeat1(n, d) -} - -func (b *bitWriter) repeat1(n, d int) { - /* - Extra Extra Extra - Code Bits Length(s) Code Bits Lengths Code Bits Length(s) - ---- ---- ------ ---- ---- ------- ---- ---- ------- - 257 0 3 267 1 15,16 277 4 67-82 - 258 0 4 268 1 17,18 278 4 83-98 - 259 0 5 269 2 19-22 279 4 99-114 - 260 0 6 270 2 23-26 280 4 115-130 - 261 0 7 271 2 27-30 281 5 131-162 - 262 0 8 272 2 31-34 282 5 163-194 - 263 0 9 273 3 35-42 283 5 195-226 - 264 0 10 274 3 43-50 284 5 227-257 - 265 1 11,12 275 3 51-58 285 0 258 - 266 1 13,14 276 3 59-66 - */ - switch { - case n <= 10: - b.codex(257, n-3, 0) - case n <= 18: - b.codex(265, n-11, 1) - case n <= 34: - b.codex(269, n-19, 2) - case n <= 66: - b.codex(273, n-35, 3) - case n <= 130: - b.codex(277, n-67, 4) - case n <= 257: - b.codex(281, n-131, 5) - case n == 258: - b.hcode(285) - default: - panic("invalid repeat length") - } - - /* - Extra Extra Extra - Code Bits Dist Code Bits Dist Code Bits Distance - ---- ---- ---- ---- ---- ------ ---- ---- -------- - 0 0 1 10 4 33-48 20 9 1025-1536 - 1 0 2 11 4 49-64 21 9 1537-2048 - 2 0 3 12 5 65-96 22 10 2049-3072 - 3 0 4 13 5 97-128 23 10 3073-4096 - 4 1 5,6 14 6 129-192 24 11 4097-6144 - 5 1 7,8 15 6 193-256 25 11 6145-8192 - 6 2 9-12 16 7 257-384 26 12 8193-12288 - 7 2 13-16 17 7 385-512 27 12 12289-16384 - 8 3 17-24 18 8 513-768 28 13 16385-24576 - 9 3 25-32 19 8 769-1024 29 13 24577-32768 - */ - if d <= 4 { - b.writeBits(uint32(d-1), 5, true) - } else if d <= 32768 { - nbit := uint(16) - for d <= 1<<(nbit-1) { - nbit-- - } - v := uint32(d - 1) - v &^= 1 << (nbit - 1) // top bit is implicit - code := uint32(2*nbit - 2) // second bit is low bit of code - code |= v >> (nbit - 2) - v &^= 1 << (nbit - 2) - b.writeBits(code, 5, true) - // rest of bits follow - b.writeBits(uint32(v), nbit-2, false) - } else { - panic("invalid repeat distance") - } -} - -func (b *bitWriter) run(v byte, n int) { - if n == 0 { - return - } - b.byte(v) - if n-1 < 3 { - for i := 0; i < n-1; i++ { - b.byte(v) - } - } else { - b.repeat(n-1, 1) - } -} - -type adigest struct { - a, b uint32 -} - -func (d *adigest) Reset() { d.a, d.b = 1, 0 } - -const amod = 65521 - -func aupdate(a, b uint32, pi byte, n int) (aa, bb uint32) { - // TODO(rsc): 6g doesn't do magic multiplies for b %= amod, - // only for b = b%amod. - - // invariant: a, b < amod - if pi == 0 { - b += uint32(n%amod) * a - b = b % amod - return a, b - } - - // n times: - // a += pi - // b += a - // is same as - // b += n*a + n*(n+1)/2*pi - // a += n*pi - m := uint32(n) - b += (m % amod) * a - b = b % amod - b += (m * (m + 1) / 2) % amod * uint32(pi) - b = b % amod - a += (m % amod) * uint32(pi) - a = a % amod - return a, b -} - -func afinish(a, b uint32) uint32 { - return b<<16 | a -} - -func (d *adigest) WriteN(p []byte, n int) { - for i := 0; i < n; i++ { - for _, pi := range p { - d.a, d.b = aupdate(d.a, d.b, pi, 1) - } - } -} - -func (d *adigest) WriteNByte(pi byte, n int) { - d.a, d.b = aupdate(d.a, d.b, pi, n) -} - -func (d *adigest) Sum32() uint32 { return afinish(d.a, d.b) } diff --git a/vendor/rsc.io/qr/qr.go b/vendor/rsc.io/qr/qr.go deleted file mode 100644 index 254b532..0000000 --- a/vendor/rsc.io/qr/qr.go +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -/* -Package qr encodes QR codes. -*/ -package qr - -import ( - "errors" - "image" - "image/color" - - "rsc.io/qr/coding" -) - -// A Level denotes a QR error correction level. -// From least to most tolerant of errors, they are L, M, Q, H. -type Level int - -const ( - L Level = iota // 20% redundant - M // 38% redundant - Q // 55% redundant - H // 65% redundant -) - -// Encode returns an encoding of text at the given error correction level. -func Encode(text string, level Level) (*Code, error) { - // Pick data encoding, smallest first. - // We could split the string and use different encodings - // but that seems like overkill for now. - var enc coding.Encoding - switch { - case coding.Num(text).Check() == nil: - enc = coding.Num(text) - case coding.Alpha(text).Check() == nil: - enc = coding.Alpha(text) - default: - enc = coding.String(text) - } - - // Pick size. - l := coding.Level(level) - var v coding.Version - for v = coding.MinVersion; ; v++ { - if v > coding.MaxVersion { - return nil, errors.New("text too long to encode as QR") - } - if enc.Bits(v) <= v.DataBytes(l)*8 { - break - } - } - - // Build and execute plan. - p, err := coding.NewPlan(v, l, 0) - if err != nil { - return nil, err - } - cc, err := p.Encode(enc) - if err != nil { - return nil, err - } - - // TODO: Pick appropriate mask. - - return &Code{cc.Bitmap, cc.Size, cc.Stride, 8}, nil -} - -// A Code is a square pixel grid. -// It implements image.Image and direct PNG encoding. -type Code struct { - Bitmap []byte // 1 is black, 0 is white - Size int // number of pixels on a side - Stride int // number of bytes per row - Scale int // number of image pixels per QR pixel -} - -// Black returns true if the pixel at (x,y) is black. -func (c *Code) Black(x, y int) bool { - return 0 <= x && x < c.Size && 0 <= y && y < c.Size && - c.Bitmap[y*c.Stride+x/8]&(1< Date: Sat, 31 Oct 2020 07:43:51 -0700 Subject: [PATCH 28/32] Update travis to latest Go versions. --- .travis.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index f1b65c7..f27ba27 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,6 @@ arch: - amd64 - ppc64le go: - - 1.8 - - 1.9 - - "1.10" + - 1.14.x + - 1.15.x - master From 9cd2ced69588b73f35078d062bf7b62eb9dccdab Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Mon, 2 Dec 2024 13:16:32 -0800 Subject: [PATCH 29/32] There are different keys for different hash sizes. --- totp_test.go | 42 +++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/totp_test.go b/totp_test.go index dc1e438..f35f719 100644 --- a/totp_test.go +++ b/totp_test.go @@ -9,7 +9,11 @@ import ( "github.com/benbjohnson/clock" ) -var rfcTotpKey = []byte("12345678901234567890") +var rfcTotpKey = map[crypto.Hash][]byte{ + crypto.SHA1: []byte("12345678901234567890"), + crypto.SHA256: []byte("12345678901234567890123456789012"), + crypto.SHA512: []byte("1234567890123456789012345678901234567890123456789012345678901234"), +} var rfcTotpStep uint64 = 30 var rfcTotpTests = []struct { @@ -19,40 +23,40 @@ var rfcTotpTests = []struct { Algo crypto.Hash }{ {59, "94287082", 1, crypto.SHA1}, - //{59, "46119246", 1, crypto.SHA256}, - //{59, "90693936", 1, crypto.SHA512}, + {59, "46119246", 1, crypto.SHA256}, + {59, "90693936", 1, crypto.SHA512}, {1111111109, "07081804", 37037036, crypto.SHA1}, - // {1111111109, "68084774", 37037036, crypto.SHA256}, - // {1111111109, "25091201", 37037036, crypto.SHA512}, + {1111111109, "68084774", 37037036, crypto.SHA256}, + {1111111109, "25091201", 37037036, crypto.SHA512}, {1111111111, "14050471", 37037037, crypto.SHA1}, - // {1111111111, "67062674", 37037037, crypto.SHA256}, - // {1111111111, "99943326", 37037037, crypto.SHA512}, + {1111111111, "67062674", 37037037, crypto.SHA256}, + {1111111111, "99943326", 37037037, crypto.SHA512}, {1234567890, "89005924", 41152263, crypto.SHA1}, - // {1234567890, "91819424", 41152263, crypto.SHA256}, - // {1234567890, "93441116", 41152263, crypto.SHA512}, + {1234567890, "91819424", 41152263, crypto.SHA256}, + {1234567890, "93441116", 41152263, crypto.SHA512}, {2000000000, "69279037", 66666666, crypto.SHA1}, - // {2000000000, "90698825", 66666666, crypto.SHA256}, - // {2000000000, "38618901", 66666666, crypto.SHA512}, + {2000000000, "90698825", 66666666, crypto.SHA256}, + {2000000000, "38618901", 66666666, crypto.SHA512}, {20000000000, "65353130", 666666666, crypto.SHA1}, - // {20000000000, "77737706", 666666666, crypto.SHA256}, - // {20000000000, "47863826", 666666666, crypto.SHA512}, + {20000000000, "77737706", 666666666, crypto.SHA256}, + {20000000000, "47863826", 666666666, crypto.SHA512}, } func TestTotpRFC(t *testing.T) { for _, tc := range rfcTotpTests { - otp := NewTOTP(rfcTotpKey, 0, 30, 8, tc.Algo) + otp := NewTOTP(rfcTotpKey[tc.Algo], 0, 30, 8, tc.Algo) if otp.otpCounter(tc.Time) != tc.T { - fmt.Println("twofactor: invalid T") + fmt.Printf("twofactor: invalid TOTP (t=%d, h=%d)\n", tc.Time, tc.Algo) fmt.Printf("\texpected: %d\n", tc.T) fmt.Printf("\t actual: %d\n", otp.otpCounter(tc.Time)) - t.FailNow() + t.Fail() } if code := otp.otp(otp.otpCounter(tc.Time)); code != tc.Code { - fmt.Println("twofactor: invalid TOTP") + fmt.Printf("twofactor: invalid TOTP (t=%d, h=%d)\n", tc.Time, tc.Algo) fmt.Printf("\texpected: %s\n", tc.Code) fmt.Printf("\t actual: %s\n", code) - t.FailNow() + t.Fail() } } } @@ -61,7 +65,7 @@ func TestTOTPTime(t *testing.T) { otp := GenerateGoogleTOTP() testClock := clock.NewMock() - testClock.Add(2*time.Minute) + testClock.Add(2 * time.Minute) SetClock(testClock) code := otp.OTP() From 024d552293b827a8c1b116f3b03014c4ecf631aa Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Mon, 2 Dec 2024 13:26:34 -0800 Subject: [PATCH 30/32] add circle ci config --- .circleci/config.yml | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..87ba58a --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,42 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/2.0/configuration-reference +version: 2.1 + +# Define a job to be invoked later in a workflow. +# See: https://circleci.com/docs/2.0/configuration-reference/#jobs +jobs: + testbuild: + working_directory: ~/repo + # Specify the execution environment. You can specify an image from Dockerhub or use one of our Convenience Images from CircleCI's Developer Hub. + # See: https://circleci.com/docs/2.0/configuration-reference/#docker-machine-macos-windows-executor + docker: + - image: cimg/go:1.22.2 + # Add steps to the job + # See: https://circleci.com/docs/2.0/configuration-reference/#steps + steps: + - checkout + - restore_cache: + keys: + - go-mod-v4-{{ checksum "go.sum" }} + - run: + name: Install Dependencies + command: go mod download + - save_cache: + key: go-mod-v4-{{ checksum "go.sum" }} + paths: + - "/go/pkg/mod" + - run: + name: Run tests + command: go test ./... + - run: + name: Run build + command: go build ./... + - store_test_results: + path: /tmp/test-reports + +# Invoke jobs via workflows +# See: https://circleci.com/docs/2.0/configuration-reference/#workflows +workflows: + testbuild: + jobs: + - testbuild From 0dcd18c6f15f179b0ae4d21d28e8c2a52fecdd4b Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Mon, 2 Dec 2024 13:47:43 -0800 Subject: [PATCH 31/32] clean up code - travisci is long dead - golangci-lint the repo --- .travis.yml | 9 --------- README.md | 2 +- hotp_test.go | 4 ---- oath.go | 2 +- otp_test.go | 10 ++++++---- totp_test.go | 2 +- 6 files changed, 9 insertions(+), 20 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index f27ba27..0000000 --- a/.travis.yml +++ /dev/null @@ -1,9 +0,0 @@ -sudo: false -language: go -arch: - - amd64 - - ppc64le -go: - - 1.14.x - - 1.15.x - - master diff --git a/README.md b/README.md index 08965e5..7d9edb0 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ## `twofactor` [![GoDoc](https://godoc.org/github.com/gokyle/twofactor?status.svg)](https://godoc.org/github.com/gokyle/twofactor) -[![Build Status](https://travis-ci.org/gokyle/twofactor.svg?branch=master)](https://travis-ci.org/gokyle/twofactor) + ### Author diff --git a/hotp_test.go b/hotp_test.go index 09e0f6a..2fb65d9 100644 --- a/hotp_test.go +++ b/hotp_test.go @@ -7,10 +7,6 @@ import ( var testKey = []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20} -func newZeroHOTP() *HOTP { - return NewHOTP(testKey, 0, 6) -} - var rfcHotpKey = []byte("12345678901234567890") var rfcHotpExpected = []string{ "755224", diff --git a/oath.go b/oath.go index a6d9233..5fb1c83 100644 --- a/oath.go +++ b/oath.go @@ -35,7 +35,7 @@ func (o OATH) Counter() uint64 { } // SetCounter updates the OATH token's counter to a new value. -func (o OATH) SetCounter(counter uint64) { +func (o *OATH) SetCounter(counter uint64) { o.counter = counter } diff --git a/otp_test.go b/otp_test.go index 418baff..412a0b3 100644 --- a/otp_test.go +++ b/otp_test.go @@ -1,8 +1,10 @@ package twofactor -import "fmt" -import "io" -import "testing" +import ( + "fmt" + "io" + "testing" +) func TestHOTPString(t *testing.T) { hotp := NewHOTP(nil, 0, 6) @@ -93,7 +95,7 @@ func TestPaddedURL(t *testing.T) { for i := range urlList { if o, id, err := FromURL(urlList[i]); err != nil { - fmt.Println("hotp: URL should have parsed successfully") + fmt.Println("hotp: URL should have parsed successfully (id=", id, ")") fmt.Printf("\turl was: %s\n", urlList[i]) t.FailNow() fmt.Printf("\t%s, %s\n", o.OTP(), id) diff --git a/totp_test.go b/totp_test.go index f35f719..ef5628f 100644 --- a/totp_test.go +++ b/totp_test.go @@ -44,7 +44,7 @@ var rfcTotpTests = []struct { func TestTotpRFC(t *testing.T) { for _, tc := range rfcTotpTests { - otp := NewTOTP(rfcTotpKey[tc.Algo], 0, 30, 8, tc.Algo) + otp := NewTOTP(rfcTotpKey[tc.Algo], 0, rfcTotpStep, 8, tc.Algo) if otp.otpCounter(tc.Time) != tc.T { fmt.Printf("twofactor: invalid TOTP (t=%d, h=%d)\n", tc.Time, tc.Algo) fmt.Printf("\texpected: %d\n", tc.T) From c999bf35b0e47de4f63d59abbe0d7efc76c13ced Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Sun, 16 Nov 2025 18:39:18 -0800 Subject: [PATCH 32/32] linter fixes. --- hotp_test.go | 5 +++-- oath.go | 6 +++--- otp.go | 6 +++--- util_test.go | 2 +- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/hotp_test.go b/hotp_test.go index 2fb65d9..3fda2fc 100644 --- a/hotp_test.go +++ b/hotp_test.go @@ -52,10 +52,11 @@ func TestHotpBadRFC(t *testing.T) { otp := NewHOTP(testKey, 0, 6) for i := 0; i < len(rfcHotpExpected); i++ { code := otp.OTP() - if code == "" { + switch code { + case "": fmt.Printf("twofactor: failed to produce an OTP\n") t.FailNow() - } else if code == rfcHotpExpected[i] { + case rfcHotpExpected[i]: fmt.Printf("twofactor: should not have received a valid OTP\n") t.FailNow() } diff --git a/oath.go b/oath.go index 5fb1c83..6c60608 100644 --- a/oath.go +++ b/oath.go @@ -71,10 +71,10 @@ func (o OATH) URL(t Type, label string) string { v.Add("digits", fmt.Sprintf("%d", o.Size())) } - switch { - case o.algo == crypto.SHA256: + switch o.algo { + case crypto.SHA256: v.Add("algorithm", "SHA256") - case o.algo == crypto.SHA512: + case crypto.SHA512: v.Add("algorithm", "SHA512") } diff --git a/otp.go b/otp.go index 9d2faaf..5a78358 100644 --- a/otp.go +++ b/otp.go @@ -75,10 +75,10 @@ func FromURL(URL string) (OTP, string, error) { return nil, "", ErrInvalidURL } - switch { - case u.Host == "totp": + switch u.Host { + case "totp": return totpFromURL(u) - case u.Host == "hotp": + case "hotp": return hotpFromURL(u) default: return nil, "", ErrInvalidURL diff --git a/util_test.go b/util_test.go index 647a931..a4eee6b 100644 --- a/util_test.go +++ b/util_test.go @@ -22,7 +22,7 @@ func TestPadding(t *testing.T) { for i := 0; i < 300; i++ { b := randString() origEncoding := string(b) - modEncoding := strings.Replace(string(b), "=", "", -1) + modEncoding := strings.ReplaceAll(string(b), "=", "") str, err := base32.StdEncoding.DecodeString(origEncoding) if err != nil { fmt.Println("Can't decode: ", string(b))