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:
Kyle Isom
2013-12-20 17:00:01 -07:00
parent 1dec15fd11
commit 0982f47ce3
9 changed files with 386 additions and 29 deletions

29
modhex/example_test.go Normal file
View 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
View 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
View 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()
}