Add last night's progress.
Basic functionality for HOTP, TOTP, and YubiKey OTP. Still need YubiKey HMAC, serialisation, check, and scan.
This commit is contained in:
10
hotp.go
10
hotp.go
@@ -10,7 +10,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type HOTP struct {
|
type HOTP struct {
|
||||||
*oath
|
*OATH
|
||||||
}
|
}
|
||||||
|
|
||||||
func (otp *HOTP) Type() Type {
|
func (otp *HOTP) Type() Type {
|
||||||
@@ -19,7 +19,7 @@ func (otp *HOTP) Type() Type {
|
|||||||
|
|
||||||
func NewHOTP(key []byte, counter uint64, digits int) *HOTP {
|
func NewHOTP(key []byte, counter uint64, digits int) *HOTP {
|
||||||
return &HOTP{
|
return &HOTP{
|
||||||
oath: &oath{
|
OATH: &OATH{
|
||||||
key: key,
|
key: key,
|
||||||
counter: counter,
|
counter: counter,
|
||||||
size: digits,
|
size: digits,
|
||||||
@@ -30,13 +30,13 @@ func NewHOTP(key []byte, counter uint64, digits int) *HOTP {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (otp *HOTP) OTP() string {
|
func (otp *HOTP) OTP() string {
|
||||||
code := otp.oath.OTP(otp.counter)
|
code := otp.OATH.OTP(otp.counter)
|
||||||
otp.counter++
|
otp.counter++
|
||||||
return code
|
return code
|
||||||
}
|
}
|
||||||
|
|
||||||
func (otp *HOTP) URL(label string) string {
|
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) {
|
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) {
|
func (otp *HOTP) QR(label string) ([]byte, error) {
|
||||||
return otp.oath.QR(otp.Type(), label)
|
return otp.OATH.QR(otp.Type(), label)
|
||||||
}
|
}
|
||||||
|
|||||||
29
modhex/example_test.go
Normal file
29
modhex/example_test.go
Normal file
@@ -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!
|
||||||
|
}
|
||||||
112
modhex/modhex.go
Normal file
112
modhex/modhex.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
163
modhex/modhex_test.go
Normal file
163
modhex/modhex_test.go
Normal file
@@ -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()
|
||||||
|
}
|
||||||
20
oath.go
20
oath.go
@@ -13,8 +13,8 @@ import (
|
|||||||
|
|
||||||
const defaultSize = 6
|
const defaultSize = 6
|
||||||
|
|
||||||
// oath provides a baseline struct for the two OATH algorithms.
|
// OATH provides a baseline structure for the two OATH algorithms.
|
||||||
type oath struct {
|
type OATH struct {
|
||||||
key []byte
|
key []byte
|
||||||
counter uint64
|
counter uint64
|
||||||
size int
|
size int
|
||||||
@@ -23,27 +23,27 @@ type oath struct {
|
|||||||
provider string
|
provider string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o oath) Size() int {
|
func (o OATH) Size() int {
|
||||||
return o.size
|
return o.size
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o oath) Counter() uint64 {
|
func (o OATH) Counter() uint64 {
|
||||||
return o.counter
|
return o.counter
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o oath) SetCounter(counter uint64) {
|
func (o OATH) SetCounter(counter uint64) {
|
||||||
o.counter = counter
|
o.counter = counter
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o oath) Key() []byte {
|
func (o OATH) Key() []byte {
|
||||||
return o.key[:]
|
return o.key[:]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o oath) Hash() func() hash.Hash {
|
func (o OATH) Hash() func() hash.Hash {
|
||||||
return o.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)
|
secret := base32.StdEncoding.EncodeToString(o.key)
|
||||||
u := url.URL{}
|
u := url.URL{}
|
||||||
v := url.Values{}
|
v := url.Values{}
|
||||||
@@ -96,7 +96,7 @@ var digits = []int64{
|
|||||||
// The top-level type should provide a counter; for example, HOTP
|
// The top-level type should provide a counter; for example, HOTP
|
||||||
// will provide the counter directly while TOTP will provide the
|
// will provide the counter directly while TOTP will provide the
|
||||||
// time-stepped counter.
|
// time-stepped counter.
|
||||||
func (o oath) OTP(counter uint64) string {
|
func (o OATH) OTP(counter uint64) string {
|
||||||
var ctr [8]byte
|
var ctr [8]byte
|
||||||
binary.BigEndian.PutUint64(ctr[:], counter)
|
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
|
// QR generates a byte slice containing the a QR code encoded as a
|
||||||
// PNG with level Q error correction.
|
// 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)
|
u := o.URL(t, label)
|
||||||
code, err := qr.Encode(u, qr.Q)
|
code, err := qr.Encode(u, qr.Q)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
13
otp.go
13
otp.go
@@ -13,6 +13,7 @@ type Type uint
|
|||||||
const (
|
const (
|
||||||
OATH_HOTP = iota
|
OATH_HOTP = iota
|
||||||
OATH_TOTP
|
OATH_TOTP
|
||||||
|
YUBIKEY
|
||||||
)
|
)
|
||||||
|
|
||||||
// PRNG is an io.Reader that provides a cryptographically secure
|
// PRNG is an io.Reader that provides a cryptographically secure
|
||||||
@@ -47,24 +48,20 @@ type OTP interface {
|
|||||||
// the hash function used by the OTP
|
// the hash function used by the OTP
|
||||||
Hash() func() hash.Hash
|
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.
|
// Returns the type of this OTP.
|
||||||
Type() Type
|
Type() Type
|
||||||
}
|
}
|
||||||
|
|
||||||
func OTPString(otp OTP) string {
|
func otpString(otp OTP) string {
|
||||||
var typeName string
|
var typeName string
|
||||||
switch otp.Type() {
|
switch otp.Type() {
|
||||||
case OATH_HOTP:
|
case OATH_HOTP:
|
||||||
typeName = "OATH-HOTP"
|
typeName = "OATH-HOTP"
|
||||||
case OATH_TOTP:
|
case OATH_TOTP:
|
||||||
typeName = "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())
|
return fmt.Sprintf("%s, %d", typeName, otp.Size())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import "testing"
|
|||||||
|
|
||||||
func TestHOTPString(t *testing.T) {
|
func TestHOTPString(t *testing.T) {
|
||||||
hotp := NewHOTP(nil, 0, 6)
|
hotp := NewHOTP(nil, 0, 6)
|
||||||
hotpString := OTPString(hotp)
|
hotpString := otpString(hotp)
|
||||||
if hotpString != "OATH-HOTP, 6" {
|
if hotpString != "OATH-HOTP, 6" {
|
||||||
fmt.Println("twofactor: invalid OTP string")
|
fmt.Println("twofactor: invalid OTP string")
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
|
|||||||
10
totp.go
10
totp.go
@@ -14,7 +14,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type TOTP struct {
|
type TOTP struct {
|
||||||
*oath
|
*OATH
|
||||||
step uint64
|
step uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ func (otp *TOTP) Type() Type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (otp *TOTP) otp(counter uint64) string {
|
func (otp *TOTP) otp(counter uint64) string {
|
||||||
return otp.oath.OTP(counter)
|
return otp.OATH.OTP(counter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (otp *TOTP) OTP() string {
|
func (otp *TOTP) OTP() string {
|
||||||
@@ -31,7 +31,7 @@ func (otp *TOTP) OTP() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (otp *TOTP) URL(label string) 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) {
|
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{
|
return &TOTP{
|
||||||
oath: &oath{
|
OATH: &OATH{
|
||||||
key: key,
|
key: key,
|
||||||
counter: start,
|
counter: start,
|
||||||
size: digits,
|
size: digits,
|
||||||
@@ -147,5 +147,5 @@ func totpFromURL(u *url.URL) (*TOTP, string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (otp *TOTP) QR(label string) ([]byte, error) {
|
func (otp *TOTP) QR(label string) ([]byte, error) {
|
||||||
return otp.oath.QR(otp.Type(), label)
|
return otp.OATH.QR(otp.Type(), label)
|
||||||
}
|
}
|
||||||
|
|||||||
56
yubikey.go
Normal file
56
yubikey.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user