Basic functionality for HOTP, TOTP, and YubiKey OTP. Still need YubiKey HMAC, serialisation, check, and scan.
113 lines
2.9 KiB
Go
113 lines
2.9 KiB
Go
// 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
|
|
}
|