Merge pull request #37 from gravitational/alex/hotp

Alex/hotp
This commit is contained in:
alexlyulkov 2015-10-23 14:35:16 -07:00
commit 52c23248d4
55 changed files with 7771 additions and 232 deletions

4
.gitignore vendored
View file

@ -22,4 +22,6 @@ _testmain.go
*.exe
*.test
*.prof
flymake*
flymake*
QR.png

18
Godeps/Godeps.json generated
View file

@ -15,6 +15,16 @@
"Comment": "null-236",
"Rev": "69e2a90ed92d03812364aeb947b7068dc42e561e"
},
{
"ImportPath": "code.google.com/p/rsc/gf256",
"Comment": "null-258",
"Rev": "2d8aa6027fab93979a3b4ecdbeb1ba430b489318"
},
{
"ImportPath": "code.google.com/p/rsc/qr",
"Comment": "null-258",
"Rev": "2d8aa6027fab93979a3b4ecdbeb1ba430b489318"
},
{
"ImportPath": "github.com/alecthomas/template",
"Rev": "b867cc6ab45cece8143cfcc6fc9c77cf3f2c23c0"
@ -51,6 +61,10 @@
"Comment": "v0.4.6-6-g4734e7a",
"Rev": "4734e7aca379f0d7fcdf04fbb2101696a4b45ce8"
},
{
"ImportPath": "github.com/gokyle/hotp",
"Rev": "cab5c36e945092b6fb703b6c785bb06fd0537a0d"
},
{
"ImportPath": "github.com/gravitational/configure",
"Rev": "725b3af8847feced5316be064a297d7c8a1052b3"
@ -63,10 +77,6 @@
"ImportPath": "github.com/gravitational/log",
"Rev": "8e1bbf1bba0515719dbb17adf1da20b5390ac8da"
},
{
"ImportPath": "github.com/gravitational/orbit/lib/utils",
"Rev": "fe21737572762319f5e106adf361eac625ca1bc7"
},
{
"ImportPath": "github.com/gravitational/roundtrip",
"Rev": "e7bee8354256fe4ae0d847e4c5d946d3e86f22af"

View file

@ -0,0 +1,85 @@
// Copyright 2012 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.
// This file contains a straightforward implementation of
// Reed-Solomon encoding, along with a benchmark.
// It goes with http://research.swtch.com/field.
//
// For an optimized implementation, see gf256.go.
package gf256
import (
"bytes"
"fmt"
"testing"
)
// BlogECC writes to check the error correcting code bytes
// for data using the given Reed-Solomon parameters.
func BlogECC(rs *RSEncoder, m []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(m) + rs.c
if len(rs.p) >= n {
p = rs.p
} else {
p = make([]byte, n)
}
copy(p, m)
for i := len(m); i < len(p); i++ {
p[i] = 0
}
gen := rs.gen
// 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.
for i := 0; i < len(m); i++ {
k := f.Mul(p[i], f.Inv(gen[0])) // k = pi / g0
// p -= k·g
for j, g := range gen {
p[i+j] = f.Add(p[i+j], f.Mul(k, g))
}
}
copy(check, p[len(m):])
rs.p = p
}
func BenchmarkBlogECC(b *testing.B) {
data := []byte{0x10, 0x20, 0x0c, 0x56, 0x61, 0x80, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0x10, 0x20, 0x0c, 0x56, 0x61, 0x80, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11}
check := []byte{0x29, 0x41, 0xb3, 0x93, 0x8, 0xe8, 0xa3, 0xe7, 0x63, 0x8f}
out := make([]byte, len(check))
rs := NewRSEncoder(f, len(check))
for i := 0; i < b.N; i++ {
BlogECC(rs, data, out)
}
b.SetBytes(int64(len(data)))
if !bytes.Equal(out, check) {
fmt.Printf("have %#v want %#v\n", out, check)
}
}
func TestBlogECC(t *testing.T) {
data := []byte{0x10, 0x20, 0x0c, 0x56, 0x61, 0x80, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11}
check := []byte{0xa5, 0x24, 0xd4, 0xc1, 0xed, 0x36, 0xc7, 0x87, 0x2c, 0x55}
out := make([]byte, len(check))
rs := NewRSEncoder(f, len(check))
BlogECC(rs, data, out)
if !bytes.Equal(out, check) {
t.Errorf("have %x want %x", out, check)
}
}

View file

@ -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
}

View file

@ -0,0 +1,194 @@
// 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
import (
"bytes"
"fmt"
"testing"
)
var f = NewField(0x11d, 2) // x^8 + x^4 + x^3 + x^2 + 1
func TestBasic(t *testing.T) {
if f.Exp(0) != 1 || f.Exp(1) != 2 || f.Exp(255) != 1 {
panic("bad Exp")
}
}
func TestECC(t *testing.T) {
data := []byte{0x10, 0x20, 0x0c, 0x56, 0x61, 0x80, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11}
check := []byte{0xa5, 0x24, 0xd4, 0xc1, 0xed, 0x36, 0xc7, 0x87, 0x2c, 0x55}
out := make([]byte, len(check))
rs := NewRSEncoder(f, len(check))
rs.ECC(data, out)
if !bytes.Equal(out, check) {
t.Errorf("have %x want %x", out, check)
}
}
func TestLinear(t *testing.T) {
d1 := []byte{0x00, 0x00}
c1 := []byte{0x00, 0x00}
out := make([]byte, len(c1))
rs := NewRSEncoder(f, len(c1))
if rs.ECC(d1, out); !bytes.Equal(out, c1) {
t.Errorf("ECBytes(%x, %d) = %x, want 0", d1, len(c1), out)
}
d2 := []byte{0x00, 0x01}
c2 := make([]byte, 2)
rs.ECC(d2, c2)
d3 := []byte{0x00, 0x02}
c3 := make([]byte, 2)
rs.ECC(d3, c3)
cx := make([]byte, 2)
for i := range cx {
cx[i] = c2[i] ^ c3[i]
}
d4 := []byte{0x00, 0x03}
c4 := make([]byte, 2)
rs.ECC(d4, c4)
if !bytes.Equal(cx, c4) {
t.Errorf("ECBytes(%x, 2) = %x\nECBytes(%x, 2) = %x\nxor = %x\nECBytes(%x, 2) = %x",
d2, c2, d3, c3, cx, d4, c4)
}
}
func TestGaussJordan(t *testing.T) {
rs := NewRSEncoder(f, 2)
m := make([][]byte, 16)
for i := range m {
m[i] = make([]byte, 4)
m[i][i/8] = 1 << uint(i%8)
rs.ECC(m[i][:2], m[i][2:])
}
if false {
fmt.Printf("---\n")
for _, row := range m {
fmt.Printf("%x\n", row)
}
}
b := []uint{0, 1, 2, 3, 12, 13, 14, 15, 20, 21, 22, 23, 24, 25, 26, 27}
for i := 0; i < 16; i++ {
bi := b[i]
if m[i][bi/8]&(1<<(7-bi%8)) == 0 {
for j := i + 1; ; j++ {
if j >= len(m) {
t.Errorf("lost track for %d", bi)
break
}
if m[j][bi/8]&(1<<(7-bi%8)) != 0 {
m[i], m[j] = m[j], m[i]
break
}
}
}
for j := i + 1; j < len(m); j++ {
if m[j][bi/8]&(1<<(7-bi%8)) != 0 {
for k := range m[j] {
m[j][k] ^= m[i][k]
}
}
}
}
if false {
fmt.Printf("---\n")
for _, row := range m {
fmt.Printf("%x\n", row)
}
}
for i := 15; i >= 0; i-- {
bi := b[i]
for j := i - 1; j >= 0; j-- {
if m[j][bi/8]&(1<<(7-bi%8)) != 0 {
for k := range m[j] {
m[j][k] ^= m[i][k]
}
}
}
}
if false {
fmt.Printf("---\n")
for _, row := range m {
fmt.Printf("%x", row)
out := make([]byte, 2)
if rs.ECC(row[:2], out); !bytes.Equal(out, row[2:]) {
fmt.Printf(" - want %x", out)
}
fmt.Printf("\n")
}
}
}
func BenchmarkECC(b *testing.B) {
data := []byte{0x10, 0x20, 0x0c, 0x56, 0x61, 0x80, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0x10, 0x20, 0x0c, 0x56, 0x61, 0x80, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11}
check := []byte{0x29, 0x41, 0xb3, 0x93, 0x8, 0xe8, 0xa3, 0xe7, 0x63, 0x8f}
out := make([]byte, len(check))
rs := NewRSEncoder(f, len(check))
for i := 0; i < b.N; i++ {
rs.ECC(data, out)
}
b.SetBytes(int64(len(data)))
if !bytes.Equal(out, check) {
fmt.Printf("have %#v want %#v\n", out, check)
}
}
func TestGen(t *testing.T) {
for i := 0; i < 256; i++ {
_, lg := f.gen(i)
if lg[0] != 0 {
t.Errorf("#%d: %x", i, lg)
}
}
}
func TestReducible(t *testing.T) {
var count = []int{1, 2, 3, 6, 9, 18, 30, 56, 99, 186} // oeis.org/A1037
for i, want := range count {
n := 0
for p := 1 << uint(i+2); p < 1<<uint(i+3); p++ {
if !reducible(p) {
n++
}
}
if n != want {
t.Errorf("#reducible(%d-bit) = %d, want %d", i+2, n, want)
}
}
}
func TestExhaustive(t *testing.T) {
for poly := 0x100; poly < 0x200; poly++ {
if reducible(poly) {
continue
}
α := 2
for !generates(α, poly) {
α++
}
f := NewField(poly, α)
for p := 0; p < 256; p++ {
for q := 0; q < 256; q++ {
fm := int(f.Mul(byte(p), byte(q)))
pm := mul(p, q, poly)
if fm != pm {
t.Errorf("NewField(%#x).Mul(%#x, %#x) = %#x, want %#x", poly, p, q, fm, pm)
}
}
}
}
}
func generates(α, poly int) bool {
x := α
for i := 0; i < 254; i++ {
if x == 1 {
return false
}
x = mul(x, α, poly)
}
return true
}

View file

@ -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,
)
}
}

View file

@ -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"
"github.com/gravitational/teleport/Godeps/_workspace/src/code.google.com/p/rsc/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<<uint(7-x&7)) != 0
}
// A Mask describes a mask that is applied to the QR
// code to avoid QR artifacts being interpreted as
// alignment and timing patterns (such as the squares
// in the corners). Valid masks are integers from 0 to 7.
type Mask int
// http://www.swetake.com/qr/qr5_en.html
var mfunc = []func(int, int) bool{
func(i, j int) bool { return (i+j)%2 == 0 },
func(i, j int) bool { return i%2 == 0 },
func(i, j int) bool { return j%3 == 0 },
func(i, j int) bool { return (i+j)%3 == 0 },
func(i, j int) bool { return (i/2+j/3)%2 == 0 },
func(i, j int) bool { return i*j%2+i*j%3 == 0 },
func(i, j int) bool { return (i*j%2+i*j%3)%2 == 0 },
func(i, j int) bool { return (i*j%3+(i+j)%2)%2 == 0 },
}
func (m Mask) Invert(y, x int) bool {
if m < 0 {
return false
}
return mfunc[m](y, x)
}
// A Plan describes how to construct a QR code
// with a specific version, level, and mask.
type Plan struct {
Version Version
Level Level
Mask Mask
DataBytes int // number of data bytes
CheckBytes int // number of error correcting (checksum) bytes
Blocks int // number of data blocks
Pixel [][]Pixel // pixel map
}
// NewPlan returns a Plan for a QR code with the given
// version, level, and mask.
func NewPlan(version Version, level Level, mask Mask) (*Plan, error) {
p, err := vplan(version)
if err != nil {
return nil, err
}
if err := fplan(level, mask, p); err != nil {
return nil, err
}
if err := lplan(version, level, p); err != nil {
return nil, err
}
if err := mplan(mask, p); err != nil {
return nil, err
}
return p, nil
}
func (b *Bits) Pad(n int) {
if n < 0 {
panic("qr: invalid pad size")
}
if n <= 4 {
b.Write(0, n)
} else {
b.Write(0, 4)
n -= 4
n -= -b.Bits() & 7
b.Write(0, -b.Bits()&7)
pad := n / 8
for i := 0; i < pad; i += 2 {
b.Write(0xec, 8)
if i+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<<uint(7-o&7)) != 0 {
pix ^= Black
}
}
if pix&Black != 0 {
crow[x/8] |= 1 << uint(7-x&7)
}
}
crow = crow[c.Stride:]
}
return c, nil
}
// A version describes metadata associated with a version.
type version struct {
apos int
astride int
bytes int
pattern int
level [4]level
}
type level struct {
nblock int
check int
}
var vtab = []version{
{},
{100, 100, 26, 0x0, [4]level{{1, 7}, {1, 10}, {1, 13}, {1, 17}}}, // 1
{16, 100, 44, 0x0, [4]level{{1, 10}, {1, 16}, {1, 22}, {1, 28}}}, // 2
{20, 100, 70, 0x0, [4]level{{1, 15}, {1, 26}, {2, 18}, {2, 22}}}, // 3
{24, 100, 100, 0x0, [4]level{{1, 20}, {2, 18}, {2, 26}, {4, 16}}}, // 4
{28, 100, 134, 0x0, [4]level{{1, 26}, {2, 24}, {4, 18}, {4, 22}}}, // 5
{32, 100, 172, 0x0, [4]level{{2, 18}, {4, 16}, {4, 24}, {4, 28}}}, // 6
{20, 16, 196, 0x7c94, [4]level{{2, 20}, {4, 18}, {6, 18}, {5, 26}}}, // 7
{22, 18, 242, 0x85bc, [4]level{{2, 24}, {4, 22}, {6, 22}, {6, 26}}}, // 8
{24, 20, 292, 0x9a99, [4]level{{2, 30}, {5, 22}, {8, 20}, {8, 24}}}, // 9
{26, 22, 346, 0xa4d3, [4]level{{4, 18}, {5, 26}, {8, 24}, {8, 28}}}, // 10
{28, 24, 404, 0xbbf6, [4]level{{4, 20}, {5, 30}, {8, 28}, {11, 24}}}, // 11
{30, 26, 466, 0xc762, [4]level{{4, 24}, {8, 22}, {10, 26}, {11, 28}}}, // 12
{32, 28, 532, 0xd847, [4]level{{4, 26}, {9, 22}, {12, 24}, {16, 22}}}, // 13
{24, 20, 581, 0xe60d, [4]level{{4, 30}, {9, 24}, {16, 20}, {16, 24}}}, // 14
{24, 22, 655, 0xf928, [4]level{{6, 22}, {10, 24}, {12, 30}, {18, 24}}}, // 15
{24, 24, 733, 0x10b78, [4]level{{6, 24}, {10, 28}, {17, 24}, {16, 30}}}, // 16
{28, 24, 815, 0x1145d, [4]level{{6, 28}, {11, 28}, {16, 28}, {19, 28}}}, // 17
{28, 26, 901, 0x12a17, [4]level{{6, 30}, {13, 26}, {18, 28}, {21, 28}}}, // 18
{28, 28, 991, 0x13532, [4]level{{7, 28}, {14, 26}, {21, 26}, {25, 26}}}, // 19
{32, 28, 1085, 0x149a6, [4]level{{8, 28}, {16, 26}, {20, 30}, {25, 28}}}, // 20
{26, 22, 1156, 0x15683, [4]level{{8, 28}, {17, 26}, {23, 28}, {25, 30}}}, // 21
{24, 24, 1258, 0x168c9, [4]level{{9, 28}, {17, 28}, {23, 30}, {34, 24}}}, // 22
{28, 24, 1364, 0x177ec, [4]level{{9, 30}, {18, 28}, {25, 30}, {30, 30}}}, // 23
{26, 26, 1474, 0x18ec4, [4]level{{10, 30}, {20, 28}, {27, 30}, {32, 30}}}, // 24
{30, 26, 1588, 0x191e1, [4]level{{12, 26}, {21, 28}, {29, 30}, {35, 30}}}, // 25
{28, 28, 1706, 0x1afab, [4]level{{12, 28}, {23, 28}, {34, 28}, {37, 30}}}, // 26
{32, 28, 1828, 0x1b08e, [4]level{{12, 30}, {25, 28}, {34, 30}, {40, 30}}}, // 27
{24, 24, 1921, 0x1cc1a, [4]level{{13, 30}, {26, 28}, {35, 30}, {42, 30}}}, // 28
{28, 24, 2051, 0x1d33f, [4]level{{14, 30}, {28, 28}, {38, 30}, {45, 30}}}, // 29
{24, 26, 2185, 0x1ed75, [4]level{{15, 30}, {29, 28}, {40, 30}, {48, 30}}}, // 30
{28, 26, 2323, 0x1f250, [4]level{{16, 30}, {31, 28}, {43, 30}, {51, 30}}}, // 31
{32, 26, 2465, 0x209d5, [4]level{{17, 30}, {33, 28}, {45, 30}, {54, 30}}}, // 32
{28, 28, 2611, 0x216f0, [4]level{{18, 30}, {35, 28}, {48, 30}, {57, 30}}}, // 33
{32, 28, 2761, 0x228ba, [4]level{{19, 30}, {37, 28}, {51, 30}, {60, 30}}}, // 34
{28, 24, 2876, 0x2379f, [4]level{{19, 30}, {38, 28}, {53, 30}, {63, 30}}}, // 35
{22, 26, 3034, 0x24b0b, [4]level{{20, 30}, {40, 28}, {56, 30}, {66, 30}}}, // 36
{26, 26, 3196, 0x2542e, [4]level{{21, 30}, {43, 28}, {59, 30}, {70, 30}}}, // 37
{30, 26, 3362, 0x26a64, [4]level{{22, 30}, {45, 28}, {62, 30}, {74, 30}}}, // 38
{24, 28, 3532, 0x27541, [4]level{{24, 30}, {47, 28}, {65, 30}, {77, 30}}}, // 39
{28, 28, 3706, 0x28c69, [4]level{{25, 30}, {49, 28}, {68, 30}, {81, 30}}}, // 40
}
func grid(siz int) [][]Pixel {
m := make([][]Pixel, siz)
pix := make([]Pixel, siz*siz)
for i := range m {
m[i], pix = pix[:siz], pix[siz:]
}
return m
}
// vplan creates a Plan for the given version.
func vplan(v Version) (*Plan, error) {
p := &Plan{Version: v}
if v < 1 || v > 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<<uint(i)) != 0 {
rem ^= formatPoly << uint(i-10)
}
}
fb |= rem
invert := uint32(0x5412)
siz := len(p.Pixel)
for i := uint(0); i < 15; i++ {
pix := Format.Pixel() + OffsetPixel(i)
if (fb>>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
}
}
}

View file

@ -0,0 +1,133 @@
// 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
import (
"bytes"
"testing"
"github.com/gravitational/teleport/Godeps/_workspace/src/code.google.com/p/rsc/gf256"
"github.com/gravitational/teleport/Godeps/_workspace/src/code.google.com/p/rsc/qr/libqrencode"
)
func test(t *testing.T, v Version, l Level, text ...Encoding) bool {
s := ""
ty := libqrencode.EightBit
switch x := text[0].(type) {
case String:
s = string(x)
case Alpha:
s = string(x)
ty = libqrencode.Alphanumeric
case Num:
s = string(x)
ty = libqrencode.Numeric
}
key, err := libqrencode.Encode(libqrencode.Version(v), libqrencode.Level(l), ty, s)
if err != nil {
t.Errorf("libqrencode.Encode(%v, %v, %d, %#q): %v", v, l, ty, s, err)
return false
}
mask := (^key.Pixel[8][2]&1)<<2 | (key.Pixel[8][3]&1)<<1 | (^key.Pixel[8][4] & 1)
p, err := NewPlan(v, l, Mask(mask))
if err != nil {
t.Errorf("NewPlan(%v, L, %d): %v", v, err, mask)
return false
}
if len(p.Pixel) != len(key.Pixel) {
t.Errorf("%v: NewPlan uses %dx%d, libqrencode uses %dx%d", v, len(p.Pixel), len(p.Pixel), len(key.Pixel), len(key.Pixel))
return false
}
c, err := p.Encode(text...)
if err != nil {
t.Errorf("Encode: %v", err)
return false
}
badpix := 0
Pixel:
for y, prow := range p.Pixel {
for x, pix := range prow {
pix &^= Black
if c.Black(x, y) {
pix |= Black
}
keypix := key.Pixel[y][x]
want := Pixel(0)
switch {
case keypix&libqrencode.Finder != 0:
want = Position.Pixel()
case keypix&libqrencode.Alignment != 0:
want = Alignment.Pixel()
case keypix&libqrencode.Timing != 0:
want = Timing.Pixel()
case keypix&libqrencode.Format != 0:
want = Format.Pixel()
want |= OffsetPixel(pix.Offset()) // sic
want |= pix & Invert
case keypix&libqrencode.PVersion != 0:
want = PVersion.Pixel()
case keypix&libqrencode.DataECC != 0:
if pix.Role() == Check || pix.Role() == Extra {
want = pix.Role().Pixel()
} else {
want = Data.Pixel()
}
want |= OffsetPixel(pix.Offset())
want |= pix & Invert
default:
want = Unused.Pixel()
}
if keypix&libqrencode.Black != 0 {
want |= Black
}
if pix != want {
t.Errorf("%v/%v: Pixel[%d][%d] = %v, want %v %#x", v, mask, y, x, pix, want, keypix)
if badpix++; badpix >= 100 {
t.Errorf("stopping after %d bad pixels", badpix)
break Pixel
}
}
}
}
return badpix == 0
}
var input = []Encoding{
String("hello"),
Num("1"),
Num("12"),
Num("123"),
Alpha("AB"),
Alpha("ABC"),
}
func TestVersion(t *testing.T) {
badvers := 0
Version:
for v := Version(1); v <= 40; v++ {
for l := L; l <= H; l++ {
for _, in := range input {
if !test(t, v, l, in) {
if badvers++; badvers >= 10 {
t.Errorf("stopping after %d bad versions", badvers)
break Version
}
}
}
}
}
}
func TestEncode(t *testing.T) {
data := []byte{0x10, 0x20, 0x0c, 0x56, 0x61, 0x80, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11}
check := []byte{0xa5, 0x24, 0xd4, 0xc1, 0xed, 0x36, 0xc7, 0x87, 0x2c, 0x55}
rs := gf256.NewRSEncoder(Field, len(check))
out := make([]byte, len(check))
rs.ECC(data, out)
if !bytes.Equal(out, check) {
t.Errorf("have %x want %x", out, check)
}
}

View file

@ -0,0 +1,149 @@
// 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 libqrencode wraps the C libqrencode library.
// The qr package (in this package's parent directory)
// does not use any C wrapping. This code is here only
// for use during that package's tests.
package libqrencode
/*
#cgo LDFLAGS: -lqrencode
#include <qrencode.h>
*/
import "C"
import (
"fmt"
"image"
"image/color"
"unsafe"
)
type Version int
type Mode int
const (
Numeric Mode = C.QR_MODE_NUM
Alphanumeric Mode = C.QR_MODE_AN
EightBit Mode = C.QR_MODE_8
)
type Level int
const (
L Level = C.QR_ECLEVEL_L
M Level = C.QR_ECLEVEL_M
Q Level = C.QR_ECLEVEL_Q
H Level = C.QR_ECLEVEL_H
)
type Pixel int
const (
Black Pixel = 1 << iota
DataECC
Format
PVersion
Timing
Alignment
Finder
NonData
)
type Code struct {
Version int
Width int
Pixel [][]Pixel
Scale int
}
func (*Code) ColorModel() color.Model {
return color.RGBAModel
}
func (c *Code) Bounds() image.Rectangle {
d := (c.Width + 8) * c.Scale
return image.Rect(0, 0, d, d)
}
var (
white color.Color = color.RGBA{0xFF, 0xFF, 0xFF, 0xFF}
black color.Color = color.RGBA{0x00, 0x00, 0x00, 0xFF}
blue color.Color = color.RGBA{0x00, 0x00, 0x80, 0xFF}
red color.Color = color.RGBA{0xFF, 0x40, 0x40, 0xFF}
yellow color.Color = color.RGBA{0xFF, 0xFF, 0x00, 0xFF}
gray color.Color = color.RGBA{0x80, 0x80, 0x80, 0xFF}
green color.Color = color.RGBA{0x22, 0x8B, 0x22, 0xFF}
)
func (c *Code) At(x, y int) color.Color {
x = x/c.Scale - 4
y = y/c.Scale - 4
if 0 <= x && x < c.Width && 0 <= y && y < c.Width {
switch p := c.Pixel[y][x]; {
case p&Black == 0:
// nothing
case p&DataECC != 0:
return black
case p&Format != 0:
return blue
case p&PVersion != 0:
return red
case p&Timing != 0:
return yellow
case p&Alignment != 0:
return gray
case p&Finder != 0:
return green
}
}
return white
}
type Chunk struct {
Mode Mode
Text string
}
func Encode(version Version, level Level, mode Mode, text string) (*Code, error) {
return EncodeChunk(version, level, Chunk{mode, text})
}
func EncodeChunk(version Version, level Level, chunk ...Chunk) (*Code, error) {
qi, err := C.QRinput_new2(C.int(version), C.QRecLevel(level))
if qi == nil {
return nil, fmt.Errorf("QRinput_new2: %v", err)
}
defer C.QRinput_free(qi)
for _, ch := range chunk {
data := []byte(ch.Text)
n, err := C.QRinput_append(qi, C.QRencodeMode(ch.Mode), C.int(len(data)), (*C.uchar)(&data[0]))
if n < 0 {
return nil, fmt.Errorf("QRinput_append %q: %v", data, err)
}
}
qc, err := C.QRcode_encodeInput(qi)
if qc == nil {
return nil, fmt.Errorf("QRinput_encodeInput: %v", err)
}
c := &Code{
Version: int(qc.version),
Width: int(qc.width),
Scale: 16,
}
pix := make([]Pixel, c.Width*c.Width)
cdat := (*[1000 * 1000]byte)(unsafe.Pointer(qc.data))[:len(pix)]
for i := range pix {
pix[i] = Pixel(cdat[i])
}
c.Pixel = make([][]Pixel, c.Width)
for i := range c.Pixel {
c.Pixel[i] = pix[i*c.Width : (i+1)*c.Width]
}
return c, nil
}

400
Godeps/_workspace/src/code.google.com/p/rsc/qr/png.go generated vendored Normal file
View file

@ -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<<nx-1), nx, false)
}
func (b *bitWriter) repeat(n, d int) {
for ; n >= 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) }

View file

@ -0,0 +1,73 @@
// 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
import (
"bytes"
"image"
"image/color"
"image/png"
"io/ioutil"
"testing"
)
func TestPNG(t *testing.T) {
c, err := Encode("hello, world", L)
if err != nil {
t.Fatal(err)
}
pngdat := c.PNG()
if true {
ioutil.WriteFile("x.png", pngdat, 0666)
}
m, err := png.Decode(bytes.NewBuffer(pngdat))
if err != nil {
t.Fatal(err)
}
gm := m.(*image.Gray)
scale := c.Scale
siz := c.Size
nbad := 0
for y := 0; y < scale*(8+siz); y++ {
for x := 0; x < scale*(8+siz); x++ {
v := byte(255)
if c.Black(x/scale-4, y/scale-4) {
v = 0
}
if gv := gm.At(x, y).(color.Gray).Y; gv != v {
t.Errorf("%d,%d = %d, want %d", x, y, gv, v)
if nbad++; nbad >= 20 {
t.Fatalf("too many bad pixels")
}
}
}
}
}
func BenchmarkPNG(b *testing.B) {
c, err := Encode("0123456789012345678901234567890123456789", L)
if err != nil {
panic(err)
}
var bytes []byte
for i := 0; i < b.N; i++ {
bytes = c.PNG()
}
b.SetBytes(int64(len(bytes)))
}
func BenchmarkImagePNG(b *testing.B) {
c, err := Encode("0123456789012345678901234567890123456789", L)
if err != nil {
panic(err)
}
var buf bytes.Buffer
for i := 0; i < b.N; i++ {
buf.Reset()
png.Encode(&buf, c.Image())
}
b.SetBytes(int64(buf.Len()))
}

116
Godeps/_workspace/src/code.google.com/p/rsc/qr/qr.go generated vendored Normal file
View file

@ -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"
"github.com/gravitational/teleport/Godeps/_workspace/src/code.google.com/p/rsc/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<<uint(7-x&7)) != 0
}
// Image returns an Image displaying the code.
func (c *Code) Image() image.Image {
return &codeImage{c}
}
// codeImage implements image.Image
type codeImage struct {
*Code
}
var (
whiteColor color.Color = color.Gray{0xFF}
blackColor color.Color = color.Gray{0x00}
)
func (c *codeImage) Bounds() image.Rectangle {
d := (c.Size + 8) * c.Scale
return image.Rect(0, 0, d, d)
}
func (c *codeImage) At(x, y int) color.Color {
if c.Black(x, y) {
return blackColor
}
return whiteColor
}
func (c *codeImage) ColorModel() color.Model {
return color.GrayModel
}

View file

@ -0,0 +1,505 @@
package web
import (
"bytes"
"fmt"
"image"
"image/color"
"image/draw"
"image/png"
"net/http"
"strconv"
"strings"
"code.google.com/p/freetype-go/freetype"
"code.google.com/p/rsc/appfs/fs"
"github.com/gravitational/teleport/Godeps/_workspace/src/code.google.com/p/rsc/qr"
"github.com/gravitational/teleport/Godeps/_workspace/src/code.google.com/p/rsc/qr/coding"
)
func makeImage(req *http.Request, caption, font string, pt, size, border, scale int, f func(x, y int) uint32) *image.RGBA {
d := (size + 2*border) * scale
csize := 0
if caption != "" {
if pt == 0 {
pt = 11
}
csize = pt * 2
}
c := image.NewRGBA(image.Rect(0, 0, d, d+csize))
// white
u := &image.Uniform{C: color.White}
draw.Draw(c, c.Bounds(), u, image.ZP, draw.Src)
for y := 0; y < size; y++ {
for x := 0; x < size; x++ {
r := image.Rect((x+border)*scale, (y+border)*scale, (x+border+1)*scale, (y+border+1)*scale)
rgba := f(x, y)
u.C = color.RGBA{byte(rgba >> 24), byte(rgba >> 16), byte(rgba >> 8), byte(rgba)}
draw.Draw(c, r, u, image.ZP, draw.Src)
}
}
if csize != 0 {
if font == "" {
font = "data/luxisr.ttf"
}
ctxt := fs.NewContext(req)
dat, _, err := ctxt.Read(font)
if err != nil {
panic(err)
}
tfont, err := freetype.ParseFont(dat)
if err != nil {
panic(err)
}
ft := freetype.NewContext()
ft.SetDst(c)
ft.SetDPI(100)
ft.SetFont(tfont)
ft.SetFontSize(float64(pt))
ft.SetSrc(image.NewUniform(color.Black))
ft.SetClip(image.Rect(0, 0, 0, 0))
wid, err := ft.DrawString(caption, freetype.Pt(0, 0))
if err != nil {
panic(err)
}
p := freetype.Pt(d, d+3*pt/2)
p.X -= wid.X
p.X /= 2
ft.SetClip(c.Bounds())
ft.DrawString(caption, p)
}
return c
}
func makeFrame(req *http.Request, font string, pt, vers, l, scale, dots int) image.Image {
lev := coding.Level(l)
p, err := coding.NewPlan(coding.Version(vers), lev, 0)
if err != nil {
panic(err)
}
nd := p.DataBytes / p.Blocks
nc := p.CheckBytes / p.Blocks
extra := p.DataBytes - nd*p.Blocks
cap := fmt.Sprintf("QR v%d, %s", vers, lev)
if dots > 0 {
cap = fmt.Sprintf("QR v%d order, from bottom right", vers)
}
m := makeImage(req, cap, font, pt, len(p.Pixel), 0, scale, func(x, y int) uint32 {
pix := p.Pixel[y][x]
switch pix.Role() {
case coding.Data:
if dots > 0 {
return 0xffffffff
}
off := int(pix.Offset() / 8)
nd := nd
var i int
for i = 0; i < p.Blocks; i++ {
if i == extra {
nd++
}
if off < nd {
break
}
off -= nd
}
return blockColors[i%len(blockColors)]
case coding.Check:
if dots > 0 {
return 0xffffffff
}
i := (int(pix.Offset()/8) - p.DataBytes) / nc
return dark(blockColors[i%len(blockColors)])
}
if pix&coding.Black != 0 {
return 0x000000ff
}
return 0xffffffff
})
if dots > 0 {
b := m.Bounds()
for y := 0; y <= len(p.Pixel); y++ {
for x := 0; x < b.Dx(); x++ {
m.SetRGBA(x, y*scale-(y/len(p.Pixel)), color.RGBA{127, 127, 127, 255})
}
}
for x := 0; x <= len(p.Pixel); x++ {
for y := 0; y < b.Dx(); y++ {
m.SetRGBA(x*scale-(x/len(p.Pixel)), y, color.RGBA{127, 127, 127, 255})
}
}
order := make([]image.Point, (p.DataBytes+p.CheckBytes)*8+1)
for y, row := range p.Pixel {
for x, pix := range row {
if r := pix.Role(); r != coding.Data && r != coding.Check {
continue
}
// draw.Draw(m, m.Bounds().Add(image.Pt(x*scale, y*scale)), dot, image.ZP, draw.Over)
order[pix.Offset()] = image.Point{x*scale + scale/2, y*scale + scale/2}
}
}
for mode := 0; mode < 2; mode++ {
for i, p := range order {
q := order[i+1]
if q.X == 0 {
break
}
line(m, p, q, mode)
}
}
}
return m
}
func line(m *image.RGBA, p, q image.Point, mode int) {
x := 0
y := 0
dx := q.X - p.X
dy := q.Y - p.Y
xsign := +1
ysign := +1
if dx < 0 {
xsign = -1
dx = -dx
}
if dy < 0 {
ysign = -1
dy = -dy
}
pt := func() {
switch mode {
case 0:
for dx := -2; dx <= 2; dx++ {
for dy := -2; dy <= 2; dy++ {
if dy*dx <= -4 || dy*dx >= 4 {
continue
}
m.SetRGBA(p.X+x*xsign+dx, p.Y+y*ysign+dy, color.RGBA{255, 192, 192, 255})
}
}
case 1:
m.SetRGBA(p.X+x*xsign, p.Y+y*ysign, color.RGBA{128, 0, 0, 255})
}
}
if dx > dy {
for x < dx || y < dy {
pt()
x++
if float64(x)*float64(dy)/float64(dx)-float64(y) > 0.5 {
y++
}
}
} else {
for x < dx || y < dy {
pt()
y++
if float64(y)*float64(dx)/float64(dy)-float64(x) > 0.5 {
x++
}
}
}
pt()
}
func pngEncode(c image.Image) []byte {
var b bytes.Buffer
png.Encode(&b, c)
return b.Bytes()
}
// Frame handles a request for a single QR frame.
func Frame(w http.ResponseWriter, req *http.Request) {
arg := func(s string) int { x, _ := strconv.Atoi(req.FormValue(s)); return x }
v := arg("v")
scale := arg("scale")
if scale == 0 {
scale = 8
}
w.Header().Set("Cache-Control", "public, max-age=3600")
w.Write(pngEncode(makeFrame(req, req.FormValue("font"), arg("pt"), v, arg("l"), scale, arg("dots"))))
}
// Frames handles a request for multiple QR frames.
func Frames(w http.ResponseWriter, req *http.Request) {
vs := strings.Split(req.FormValue("v"), ",")
arg := func(s string) int { x, _ := strconv.Atoi(req.FormValue(s)); return x }
scale := arg("scale")
if scale == 0 {
scale = 8
}
font := req.FormValue("font")
pt := arg("pt")
dots := arg("dots")
var images []image.Image
l := arg("l")
for _, v := range vs {
l := l
if i := strings.Index(v, "."); i >= 0 {
l, _ = strconv.Atoi(v[i+1:])
v = v[:i]
}
vv, _ := strconv.Atoi(v)
images = append(images, makeFrame(req, font, pt, vv, l, scale, dots))
}
b := images[len(images)-1].Bounds()
dx := arg("dx")
if dx == 0 {
dx = b.Dx()
}
x, y := 0, 0
xmax := 0
sep := arg("sep")
if sep == 0 {
sep = 10
}
var points []image.Point
for i, m := range images {
if x > 0 {
x += sep
}
if x > 0 && x+m.Bounds().Dx() > dx {
y += sep + images[i-1].Bounds().Dy()
x = 0
}
points = append(points, image.Point{x, y})
x += m.Bounds().Dx()
if x > xmax {
xmax = x
}
}
c := image.NewRGBA(image.Rect(0, 0, xmax, y+b.Dy()))
for i, m := range images {
draw.Draw(c, c.Bounds().Add(points[i]), m, image.ZP, draw.Src)
}
w.Header().Set("Cache-Control", "public, max-age=3600")
w.Write(pngEncode(c))
}
// Mask handles a request for a single QR mask.
func Mask(w http.ResponseWriter, req *http.Request) {
arg := func(s string) int { x, _ := strconv.Atoi(req.FormValue(s)); return x }
v := arg("v")
m := arg("m")
scale := arg("scale")
if scale == 0 {
scale = 8
}
w.Header().Set("Cache-Control", "public, max-age=3600")
w.Write(pngEncode(makeMask(req, req.FormValue("font"), arg("pt"), v, m, scale)))
}
// Masks handles a request for multiple QR masks.
func Masks(w http.ResponseWriter, req *http.Request) {
arg := func(s string) int { x, _ := strconv.Atoi(req.FormValue(s)); return x }
v := arg("v")
scale := arg("scale")
if scale == 0 {
scale = 8
}
font := req.FormValue("font")
pt := arg("pt")
var mm []image.Image
for m := 0; m < 8; m++ {
mm = append(mm, makeMask(req, font, pt, v, m, scale))
}
dx := mm[0].Bounds().Dx()
dy := mm[0].Bounds().Dy()
sep := arg("sep")
if sep == 0 {
sep = 10
}
c := image.NewRGBA(image.Rect(0, 0, (dx+sep)*4-sep, (dy+sep)*2-sep))
for m := 0; m < 8; m++ {
x := (m % 4) * (dx + sep)
y := (m / 4) * (dy + sep)
draw.Draw(c, c.Bounds().Add(image.Pt(x, y)), mm[m], image.ZP, draw.Src)
}
w.Header().Set("Cache-Control", "public, max-age=3600")
w.Write(pngEncode(c))
}
var maskName = []string{
"(x+y) % 2",
"y % 2",
"x % 3",
"(x+y) % 3",
"(y/2 + x/3) % 2",
"xy%2 + xy%3",
"(xy%2 + xy%3) % 2",
"(xy%3 + (x+y)%2) % 2",
}
func makeMask(req *http.Request, font string, pt int, vers, mask, scale int) image.Image {
p, err := coding.NewPlan(coding.Version(vers), coding.L, coding.Mask(mask))
if err != nil {
panic(err)
}
m := makeImage(req, maskName[mask], font, pt, len(p.Pixel), 0, scale, func(x, y int) uint32 {
pix := p.Pixel[y][x]
switch pix.Role() {
case coding.Data, coding.Check:
if pix&coding.Invert != 0 {
return 0x000000ff
}
}
return 0xffffffff
})
return m
}
var blockColors = []uint32{
0x7777ffff,
0xffff77ff,
0xff7777ff,
0x77ffffff,
0x1e90ffff,
0xffffe0ff,
0x8b6969ff,
0x77ff77ff,
0x9b30ffff,
0x00bfffff,
0x90e890ff,
0xfff68fff,
0xffec8bff,
0xffa07aff,
0xffa54fff,
0xeee8aaff,
0x98fb98ff,
0xbfbfbfff,
0x54ff9fff,
0xffaeb9ff,
0xb23aeeff,
0xbbffffff,
0x7fffd4ff,
0xff7a7aff,
0x00007fff,
}
func dark(x uint32) uint32 {
r, g, b, a := byte(x>>24), byte(x>>16), byte(x>>8), byte(x)
r = r/2 + r/4
g = g/2 + g/4
b = b/2 + b/4
return uint32(r)<<24 | uint32(g)<<16 | uint32(b)<<8 | uint32(a)
}
func clamp(x int) byte {
if x < 0 {
return 0
}
if x > 255 {
return 255
}
return byte(x)
}
func max(x, y int) int {
if x > y {
return x
}
return y
}
// Arrow handles a request for an arrow pointing in a given direction.
func Arrow(w http.ResponseWriter, req *http.Request) {
arg := func(s string) int { x, _ := strconv.Atoi(req.FormValue(s)); return x }
dir := arg("dir")
size := arg("size")
if size == 0 {
size = 50
}
del := size / 10
m := image.NewRGBA(image.Rect(0, 0, size, size))
if dir == 4 {
draw.Draw(m, m.Bounds(), image.Black, image.ZP, draw.Src)
draw.Draw(m, image.Rect(5, 5, size-5, size-5), image.White, image.ZP, draw.Src)
}
pt := func(x, y int, c color.RGBA) {
switch dir {
case 0:
m.SetRGBA(x, y, c)
case 1:
m.SetRGBA(y, size-1-x, c)
case 2:
m.SetRGBA(size-1-x, size-1-y, c)
case 3:
m.SetRGBA(size-1-y, x, c)
}
}
for y := 0; y < size/2; y++ {
for x := 0; x < del && x < y; x++ {
pt(x, y, color.RGBA{0, 0, 0, 255})
}
for x := del; x < y-del; x++ {
pt(x, y, color.RGBA{128, 128, 255, 255})
}
for x := max(y-del, 0); x <= y; x++ {
pt(x, y, color.RGBA{0, 0, 0, 255})
}
}
for y := size / 2; y < size; y++ {
for x := 0; x < del && x < size-1-y; x++ {
pt(x, y, color.RGBA{0, 0, 0, 255})
}
for x := del; x < size-1-y-del; x++ {
pt(x, y, color.RGBA{128, 128, 192, 255})
}
for x := max(size-1-y-del, 0); x <= size-1-y; x++ {
pt(x, y, color.RGBA{0, 0, 0, 255})
}
}
w.Header().Set("Cache-Control", "public, max-age=3600")
w.Write(pngEncode(m))
}
// Encode encodes a string using the given version, level, and mask.
func Encode(w http.ResponseWriter, req *http.Request) {
val := func(s string) int {
v, _ := strconv.Atoi(req.FormValue(s))
return v
}
l := coding.Level(val("l"))
v := coding.Version(val("v"))
enc := coding.String(req.FormValue("t"))
m := coding.Mask(val("m"))
p, err := coding.NewPlan(v, l, m)
if err != nil {
panic(err)
}
cc, err := p.Encode(enc)
if err != nil {
panic(err)
}
c := &qr.Code{Bitmap: cc.Bitmap, Size: cc.Size, Stride: cc.Stride, Scale: 8}
w.Header().Set("Content-Type", "image/png")
w.Header().Set("Cache-Control", "public, max-age=3600")
w.Write(c.PNG())
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,152 @@
// 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 resize
import (
"image"
"image/color"
)
// average convert the sums to averages and returns the result.
func average(sum []uint64, w, h int, n uint64) *image.RGBA {
ret := image.NewRGBA(image.Rect(0, 0, w, h))
for y := 0; y < h; y++ {
for x := 0; x < w; x++ {
index := 4 * (y*w + x)
pix := ret.Pix[y*ret.Stride+x*4:]
pix[0] = uint8(sum[index+0] / n)
pix[1] = uint8(sum[index+1] / n)
pix[2] = uint8(sum[index+2] / n)
pix[3] = uint8(sum[index+3] / n)
}
}
return ret
}
// ResizeRGBA returns a scaled copy of the RGBA image slice r of m.
// The returned image has width w and height h.
func ResizeRGBA(m *image.RGBA, r image.Rectangle, w, h int) *image.RGBA {
ww, hh := uint64(w), uint64(h)
dx, dy := uint64(r.Dx()), uint64(r.Dy())
// See comment in Resize.
n, sum := dx*dy, make([]uint64, 4*w*h)
for y := r.Min.Y; y < r.Max.Y; y++ {
pix := m.Pix[(y-r.Min.Y)*m.Stride:]
for x := r.Min.X; x < r.Max.X; x++ {
// Get the source pixel.
p := pix[(x-r.Min.X)*4:]
r64 := uint64(p[0])
g64 := uint64(p[1])
b64 := uint64(p[2])
a64 := uint64(p[3])
// Spread the source pixel over 1 or more destination rows.
py := uint64(y) * hh
for remy := hh; remy > 0; {
qy := dy - (py % dy)
if qy > remy {
qy = remy
}
// Spread the source pixel over 1 or more destination columns.
px := uint64(x) * ww
index := 4 * ((py/dy)*ww + (px / dx))
for remx := ww; remx > 0; {
qx := dx - (px % dx)
if qx > remx {
qx = remx
}
qxy := qx * qy
sum[index+0] += r64 * qxy
sum[index+1] += g64 * qxy
sum[index+2] += b64 * qxy
sum[index+3] += a64 * qxy
index += 4
px += qx
remx -= qx
}
py += qy
remy -= qy
}
}
}
return average(sum, w, h, n)
}
// ResizeNRGBA returns a scaled copy of the RGBA image slice r of m.
// The returned image has width w and height h.
func ResizeNRGBA(m *image.NRGBA, r image.Rectangle, w, h int) *image.RGBA {
ww, hh := uint64(w), uint64(h)
dx, dy := uint64(r.Dx()), uint64(r.Dy())
// See comment in Resize.
n, sum := dx*dy, make([]uint64, 4*w*h)
for y := r.Min.Y; y < r.Max.Y; y++ {
pix := m.Pix[(y-r.Min.Y)*m.Stride:]
for x := r.Min.X; x < r.Max.X; x++ {
// Get the source pixel.
p := pix[(x-r.Min.X)*4:]
r64 := uint64(p[0])
g64 := uint64(p[1])
b64 := uint64(p[2])
a64 := uint64(p[3])
r64 = (r64 * a64) / 255
g64 = (g64 * a64) / 255
b64 = (b64 * a64) / 255
// Spread the source pixel over 1 or more destination rows.
py := uint64(y) * hh
for remy := hh; remy > 0; {
qy := dy - (py % dy)
if qy > remy {
qy = remy
}
// Spread the source pixel over 1 or more destination columns.
px := uint64(x) * ww
index := 4 * ((py/dy)*ww + (px / dx))
for remx := ww; remx > 0; {
qx := dx - (px % dx)
if qx > remx {
qx = remx
}
qxy := qx * qy
sum[index+0] += r64 * qxy
sum[index+1] += g64 * qxy
sum[index+2] += b64 * qxy
sum[index+3] += a64 * qxy
index += 4
px += qx
remx -= qx
}
py += qy
remy -= qy
}
}
}
return average(sum, w, h, n)
}
// Resample returns a resampled copy of the image slice r of m.
// The returned image has width w and height h.
func Resample(m image.Image, r image.Rectangle, w, h int) *image.RGBA {
if w < 0 || h < 0 {
return nil
}
if w == 0 || h == 0 || r.Dx() <= 0 || r.Dy() <= 0 {
return image.NewRGBA(image.Rect(0, 0, w, h))
}
curw, curh := r.Dx(), r.Dy()
img := image.NewRGBA(image.Rect(0, 0, w, h))
for y := 0; y < h; y++ {
for x := 0; x < w; x++ {
// Get a source pixel.
subx := x * curw / w
suby := y * curh / h
r32, g32, b32, a32 := m.At(subx, suby).RGBA()
r := uint8(r32 >> 8)
g := uint8(g32 >> 8)
b := uint8(b32 >> 8)
a := uint8(a32 >> 8)
img.SetRGBA(x, y, color.RGBA{r, g, b, a})
}
}
return img
}

13
Godeps/_workspace/src/github.com/gokyle/hotp/LICENSE generated vendored Normal file
View file

@ -0,0 +1,13 @@
Copyright (c) 2013 Kyle Isom <kyle@tyrfingr.is>
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

192
Godeps/_workspace/src/github.com/gokyle/hotp/README.md generated vendored Normal file
View file

@ -0,0 +1,192 @@
## `hotp`
This package implements the RFC 4226 OATH-HOTP algorithm; these
passwords derived from the HMAC-SHA1 of an internal counter. They
are presented as (typically) 6 or 8-digit numeric passphrases.
This package was designed to be interoperable with the Google
Authenticator app and YubiKeys programmed in OATH-HOTP mode.
Also provided is the `hotpgen` command-line program. This generates
a QR code suitable for use with the Google Authenticator application
alongside a text file containing the URL for the QR code. For more
information, see the README in the file.
See also the [godocs](https://godoc.org/github.com/gokyle/hotp/)
for this package. The [hotpweb](https://github.com/gokyle/hotpweb/)
package provides a simple webapp demonstrating the use of the Google
Authenticator interaction.
### Storing Keys
> **These keys are cryptographic secrets.** Please store them with
> all due caution! For example, they could be encrypted in a database
> using [cryptobox](https://github.com/cryptobox/gocryptobox/).
The HOTP keys can be serialised with the `Marshal` function; this preserves
a "snapshot", so to speak, of the key value. Serialisation is done
in DER-format:
```
SEQUENCE {
OCTET STRING
INTEGER
INTEGER
}
```
Serialised key values can be parsed with the `Unmarshal` function;
as a serialised key value is a snapshot, the counter state at the
time of marshalling will be restored.
If the key values are to be stored in a database, the key and counter
values must be preserved. To avoid any potention issues, the counter
value should be stored using the `Counter` method (i.e., as an
`uint64`) and key values restored with `NewHOTP`. *It is strongly
recommended that the `Key` field be stored securely.* The `Digit`
field can be stored as constant in the program, and used whenever
key values are loaded.
### Example Usages
#### Case 1: Google Authenticator
A server that wants to generate a new HOTP authentication for users
can generate a new random HOTP source; this example saves a QR code
to a file. The user can scan this QR code in with the app on their
phone and use it to generate codes for the server.
// Generate a new, random 6-digit HOTP source with an initial
// counter of 0 (the second argument, if true, will randomise
// the counter).
otp, err := GenerateHOTP(6, false)
if err != nil {
// error handling elided
}
qrCode, err := otp.QR("user@example.net")
if err != nil {
// error handling elided
}
err = ioutil.WriteFile("user@example.net.png", qrCode
if err != nil {
// error handling elided
}
After the user has imported this QR code, they can immediately begin
using codes for it. The `Check` method on an OTP source will check
whether the code is valid; if it isn't, the counter won't be
decremented to prevent the server from falling out of sync. If the
either side is suspected of falling out of sync, the `Scan` method
will look ahead a certain window of values. If it finds a valid
value, the counter is updated and the two will be in sync again.
The Google Authenticator app on Android also provides users a means
to "Check key value"; the `IntegrityCheck` method will provide the
the two values shown here (the initial code and the current counter)
that may be used to verify the integrity of the key value.
In the `testdata` directory, there are three files with the base name
of "gauth_example" that contain the HOTP key values used in the
test suite. The PNG image may be scanned in using a mobile phone,
the text file contains the URL that the QR code is based on, and
the `.key` file may be used with the `hotpcli` program. The first
several codes produced by this URL are:
* 023667, counter = 0
* 641344, counter = 1
* 419615, counter = 2
* 692589, counter = 3
* 237233, counter = 4
* 711695, counter = 5
* 620195, counter = 6
The codes may be checked against the app to ensure they are correct;
these values are used in the test suite to ensure interoperability.
#### Case 2: YubiKey
A YubiKey programmed in "OATH-HOTP" mode can also be used with this
package. The YubiKey user will need to provide their key, and
optionally their token identifier for additional security. If the
token is used across multiple sites, the `Scan` function will need
to be used (with a probably generous window) to sync the counters
initially.
When reading input from a YubiKey, the `YubiKey` method takes as
input the output directly from the token, and splits it into the
code, the token identity, and a boolean indicating whether it was
valid output. Note that `YubiKey` **does not** check whether the
code is correct; this ensures that the user can check the code with
whatever means is appropriate (i.e. `Scan` or `Check`).
In the `testdata` directory, there is a configuration file containing
the paramters for the YubiKey used to generate the test suite. This
may be used to program a YubiKey to verify the package's interoperability.
The first several codes produced by this configuration are (split
into raw output from yubikey / the code / the counter):
* cccc52345777705179, 705179, counter = 0
* cccc52345777404068, 404068, counter = 1
* cccc52345777490143, 490143, counter = 2
* cccc52345777739740, 739740, counter = 3
* cccc52345777043269, 043269, counter = 4
* cccc52345777035666, 035666, counter = 5
* cccc52345777725326, 725325, counter = 6
### TODO
* Add example code.
### Test Coverage
Test coverage is currently at 100%.
#### Current test status
[![Build Status](https://drone.io/github.com/gokyle/hotp/status.png)](https://drone.io/github.com/gokyle/hotp/latest)
### References
* [RFC 4226 - *HOTP: An HMAC-Based One-Time Password Algorithm*](http://www.ietf.org/rfc/rfc4226.txt)
is the specification of the OATH-HOTP algorithm. A copy is provided
in the package's source directory.
* The [Key URI Format](https://code.google.com/p/google-authenticator/wiki/KeyUriFormat)
page on the Google Authenticator wiki documents the URI format for
use with the Google Authenticator app. This package follows that
format when generating URLs (and by extension, QR codes).
* The [YubiKey manual](http://www.yubico.com/wp-content/uploads/2013/07/YubiKey-Manual-v3_1.pdf)
contains documentation on the YubiKey HOTP format.
### Author
`hotp` was written by Kyle Isom <kyle@tyrfingr.is>.
### License
```
Copyright (c) 2013 Kyle Isom <kyle@tyrfingr.is>
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
```

27
Godeps/_workspace/src/github.com/gokyle/hotp/doc.go generated vendored Normal file
View file

@ -0,0 +1,27 @@
/*
Package hotp implements the RFC 4226 OATH-HOTP algorithm; these
passwords derived from the HMAC-SHA1 of an internal counter. They
are presented as (typically) 6 or 8-digit numeric passphrases.
The package provides facilities for interacting with YubiKeys
programmed in OATH-HOTP mode, as well as with the Google
Authenticator application. The package also provides QR-code
generation for new OTPs.
*/
package hotp
/*
Copyright (c) 2013 Kyle Isom <kyle@tyrfingr.is>
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

315
Godeps/_workspace/src/github.com/gokyle/hotp/hotp.go generated vendored Normal file
View file

@ -0,0 +1,315 @@
package hotp
import (
"crypto/hmac"
"crypto/rand"
"crypto/sha1"
"crypto/subtle"
"encoding/asn1"
"encoding/base32"
"encoding/binary"
"errors"
"fmt"
"github.com/gravitational/teleport/Godeps/_workspace/src/code.google.com/p/rsc/qr"
"io"
"math"
"math/big"
"net/url"
"strconv"
)
// RFC 4226 specifies the counter as being 8 bytes.
const ctrSize = 8
// ErrInvalidHOTPURL is returned via FromURL; it indicates a malformed
// HOTP otpauth URL.
var ErrInvalidHOTPURL = errors.New("hotp: invalid HOTP url")
// PRNG is the source of random data; this is used by GenerateHOTP
// and should be a cryptographically-secure PRNG.
var PRNG = rand.Reader
// HOTP represents a new key value for generating one-time passwords;
// it contains the key used to construct one-time passwords and the
// counter state used in the OTP generation. Digits contains the
// number of digits that generated OTPs should output. Key is a
// cryptographic secret, and should be treated as such.
type HOTP struct {
Key []byte
counter *[ctrSize]byte
Digits int
}
// Counter returns the HOTP's 8-byte counter as an unsigned 64-bit
// integer.
func (otp HOTP) Counter() uint64 {
counter := binary.BigEndian.Uint64(otp.counter[:])
return counter
}
// Increment will increment an HOTP source's counter. This is useful
// for providers like the Google Authenticator app, which immediately
// increments the counter and uses the 0 counter value as an integrity
// check.
func (otp HOTP) Increment() {
for i := ctrSize - 1; i >= 0; i-- {
if otp.counter[i]++; otp.counter[i] != 0 {
return
}
}
}
// OTP generates a new one-time password.
func (otp HOTP) OTP() string {
h := hmac.New(sha1.New, otp.Key)
h.Write(otp.counter[:])
otp.Increment()
hash := h.Sum(nil)
result := truncate(hash)
mod := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(otp.Digits)), nil)
mod = mod.Mod(big.NewInt(result), mod)
fmtStr := fmt.Sprintf("%%0%dd", otp.Digits)
return fmt.Sprintf(fmtStr, mod.Uint64())
}
func (otp *HOTP) setCounter(counter uint64) bool {
if otp.counter == nil {
otp.counter = new([ctrSize]byte)
}
binary.BigEndian.PutUint64(otp.counter[:], counter)
return true
}
// NewHOTP intialises a new HOTP instance with the key and counter
// values. No check is done on the digits, but typical values are 6
// and 8.
func NewHOTP(key []byte, counter uint64, digits int) *HOTP {
otp := &HOTP{
Key: key,
Digits: digits,
}
otp.counter = new([ctrSize]byte)
binary.BigEndian.PutUint64(otp.counter[:], counter)
return otp
}
// URL returns a suitable URL, such as for the Google Authenticator
// app. The label is used by these apps to identify the service to
// which this OTP belongs. The digits value is ignored by the Google
// authenticator app, and is therefore elided in the resulting URL.
func (otp *HOTP) URL(label string) string {
secret := base32.StdEncoding.EncodeToString(otp.Key)
u := url.URL{}
v := url.Values{}
u.Scheme = "otpauth"
u.Host = "hotp"
u.Path = label
v.Add("secret", secret)
v.Add("counter", fmt.Sprintf("%d", otp.Counter()))
u.RawQuery = v.Encode()
return u.String()
}
// QR generates a byte slice containing the a QR code encoded as a
// PNG with level Q error correction.
func (otp *HOTP) QR(label string) ([]byte, error) {
u := otp.URL(label)
code, err := qr.Encode(u, qr.Q)
if err != nil {
return nil, err
}
return code.PNG(), nil
}
// 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
}
// FromURL parses a new HOTP from a URL string. It returns the OTP,
// the label associated with the OTP, and any errors that occurred.
func FromURL(urlString string) (*HOTP, string, error) {
u, err := url.Parse(urlString)
if err != nil {
return nil, "", err
}
if u.Scheme != "otpauth" {
return nil, "", ErrInvalidHOTPURL
} else if u.Host != "hotp" {
return nil, "", ErrInvalidHOTPURL
}
v := u.Query()
if len(v) == 0 {
return nil, "", ErrInvalidHOTPURL
}
if v.Get("secret") == "" {
return nil, "", ErrInvalidHOTPURL
} else if algo := v.Get("algorithm"); algo != "" && algo != "SHA1" {
return nil, "", ErrInvalidHOTPURL
}
var identity string
if len(u.Path) > 1 {
identity = u.Path[1:]
}
var counter uint64
if ctr := v.Get("counter"); ctr != "" {
counter, err = strconv.ParseUint(ctr, 10, 64)
if err != nil {
return nil, "", ErrInvalidHOTPURL
}
}
secret, err := base32.StdEncoding.DecodeString(v.Get("secret"))
if err != nil {
return nil, "", ErrInvalidHOTPURL
}
var digits int64 = 6
if v.Get("digits") != "" {
digits, err = strconv.ParseInt(v.Get("digits"), 10, 8)
if err != nil {
return nil, "", ErrInvalidHOTPURL
}
}
otp := NewHOTP(secret, counter, int(digits))
return otp, identity, nil
}
// GenerateHOTP will generate a randomised HOTP source; if the
// randCounter parameter is true, the counter will be randomised.
func GenerateHOTP(digits int, randCounter bool) (*HOTP, error) {
key := make([]byte, sha1.Size)
_, err := io.ReadFull(PRNG, key)
if err != nil {
return nil, err
}
var counter uint64
if randCounter {
ctr, err := rand.Int(PRNG, big.NewInt(int64(math.MaxInt64)))
if err != nil {
return nil, err
}
counter = ctr.Uint64()
}
return NewHOTP(key, counter, digits), nil
}
// YubiKey reads an OATH-HOTP string as returned by a YubiKey, and
// returns three values. The first value contains the actual OTP, the
// second value contains the YubiKey's token identifier, and the final
// value indicates whether the input string was a valid YubiKey
// OTP. This does not check whether the code is correct or not, it
// only ensures that it is well-formed output from a token and
// splits the output into the code and the public identity.
func (otp *HOTP) YubiKey(in string) (string, string, bool) {
if len(in) < otp.Digits {
return "", "", false
}
otpStart := len(in) - otp.Digits
code := in[otpStart:]
pubid := in[:otpStart]
return code, pubid, true
}
// IntegrityCheck returns two values, the base OTP and the current
// counter. This is used, for example, with the Google Authenticator
// app's "Check key value" function and can be used to verify that
// the application and the provider are in sync.
func (otp *HOTP) IntegrityCheck() (string, uint64) {
h := hmac.New(sha1.New, otp.Key)
counter := make([]byte, 8)
h.Write(counter)
hash := h.Sum(nil)
result := truncate(hash)
mod := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(otp.Digits)), nil)
mod = mod.Mod(big.NewInt(result), mod)
fmtStr := fmt.Sprintf("%%0%dd", otp.Digits)
return fmt.Sprintf(fmtStr, mod.Uint64()), otp.Counter()
}
// Scan takes a code input (i.e. from the user), and scans ahead
// within a certain window of counter values. This can be used in the
// case where the server's counter and the user's counter have fallen
// out of sync.
func (otp *HOTP) Scan(code string, window int) bool {
var valid bool
codeBytes := []byte(code)
counter := otp.Counter()
for i := 0; i < window; i++ {
genCode := []byte(otp.OTP())
if subtle.ConstantTimeCompare(codeBytes, genCode) == 1 {
valid = true
break
}
}
if !valid {
otp.setCounter(counter)
}
return valid
}
// Check takes an input code and verifies it against the OTP. If
// successful, the counter is incremented.
func (otp *HOTP) Check(code string) bool {
codeBytes := []byte(code)
genCode := []byte(otp.OTP())
if subtle.ConstantTimeCompare(codeBytes, genCode) != 1 {
otp.setCounter(otp.Counter() - 1)
return false
}
return true
}
// Marshal serialises an HOTP key value as a DER-encoded byte slice.
func Marshal(otp *HOTP) ([]byte, error) {
var asnHOTP struct {
Key []byte
Counter *big.Int
Digits int
}
asnHOTP.Key = otp.Key[:]
asnHOTP.Counter = new(big.Int).SetUint64(otp.Counter())
asnHOTP.Digits = otp.Digits
return asn1.Marshal(asnHOTP)
}
// Unmarshal parses a DER-encoded serialised HOTP key value.
func Unmarshal(in []byte) (otp *HOTP, err error) {
var asnHOTP struct {
Key []byte
Counter *big.Int
Digits int
}
_, err = asn1.Unmarshal(in, &asnHOTP)
if err != nil {
return
}
otp = &HOTP{
Key: asnHOTP.Key[:],
Digits: asnHOTP.Digits,
}
otp.setCounter(asnHOTP.Counter.Uint64())
return
}

View file

@ -0,0 +1,452 @@
package hotp
import "fmt"
import "testing"
import "bytes"
import "io"
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)
}
// This test verifies that the counter increment works as expected. As per the RFC,
// the counter should be treated as an 8-byte big-endian unsigned integer.
func TestIncrement(t *testing.T) {
otp := newZeroHOTP()
for i := 0; i < 32; i++ {
if otp.Counter() != uint64(i) {
fmt.Printf("hotp: counter should be %d, is %d\n",
i, otp.Counter())
fmt.Printf("\tcounter state: %x\n", *otp.counter)
t.FailNow()
}
otp.Increment()
}
}
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++ {
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)
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()
}
}
}
// This test takes input from a test YubiKey and ensures that the
// YubiKey functionality works as expected.
func TestYubiKey(t *testing.T) {
ykKey := []byte{
0xd4, 0xbe, 0x97, 0xac, 0xe3,
0x31, 0x72, 0x95, 0xd8, 0x95,
0xeb, 0xd6, 0xb2, 0xec, 0xa6,
0x78, 0x49, 0x79, 0x4d, 0xb3,
}
otp := NewHOTP(ykKey, 0, 6)
out := []string{
"cccc52345777705179",
"cccc52345777404068",
"cccc52345777490143",
"cccc52345777739740",
"cccc52345777043269",
"cccc52345777035666",
"cccc52345777725326",
}
codes := []string{
"705179",
"404068",
"490143",
"739740",
"043269",
"035666",
"725326",
}
ykpub := "cccc52345777"
if _, _, ok := otp.YubiKey("abcd"); ok {
fmt.Println("hotp: accepted invalid YubiKey input")
t.FailNow()
}
for i := 0; i < len(out); i++ {
code := otp.OTP()
if ykCode, pubid, ok := otp.YubiKey(out[i]); !ok {
fmt.Printf("hotp: invalid YubiKey OTP\n")
t.FailNow()
} else if ykCode != code && code != codes[i] {
fmt.Printf("hotp: YubiKey did not produce valid OTP\n")
t.FailNow()
} else if ykpub != pubid {
fmt.Printf("hotp: invalid YubiKey public ID\n")
t.FailNow()
}
}
code, counter := otp.IntegrityCheck()
if code != codes[0] {
fmt.Println("hotp: YubiKey integrity check fails (bad code)")
t.FailNow()
} else if counter != uint64(len(out)) {
fmt.Println("hotp: YubiKey integrity check fails (bad counter)")
t.FailNow()
}
}
// This test generates a new HOTP, outputs the URL for that HOTP,
// and attempts to parse that URL. It verifies that the two HOTPs
// 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://totp/foo@bar?secret=ABCD",
"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()
}
}
}
// This test uses a url generated with the `hotpgen` tool; this url
// was imported into the Google Authenticator app and the resulting
// codes generated by the app are checked here to verify interoperability.
func TestGAuth(t *testing.T) {
url := "otpauth://hotp/kyle?counter=0&secret=EXZLUP7IGHQ673ZCP32RTLRU2N427Z6L"
expected := []string{
"023667",
"641344",
"419615",
"692589",
"237233",
"711695",
"620195",
}
otp, label, err := FromURL(url)
if err != nil {
fmt.Printf("hotp: failed to parse HOTP URL\n")
t.FailNow()
} else if label != "kyle" {
fmt.Printf("hotp: invalid label")
t.FailNow()
}
otp.Increment()
// Validate codes
for i := 1; i < len(expected); i++ {
code := otp.OTP()
if otp.Counter() != uint64(i+1) {
fmt.Printf("hotp: invalid OTP counter (should be %d but is %d)",
i, otp.Counter())
t.FailNow()
} else if code != expected[i] {
fmt.Println("hotp: invalid OTP")
t.FailNow()
}
}
// Validate integrity check
code, counter := otp.IntegrityCheck()
if code != expected[0] {
fmt.Println("hotp: invalid integrity code")
t.FailNow()
} else if counter != uint64(len(expected)) {
fmt.Println("hotp: invalid integrity counter")
t.FailNow()
}
}
// This test verifies that a scan will successfully sync and update
// the OTP counter.
func TestScan(t *testing.T) {
url := "otpauth://hotp/kyle?counter=0&secret=EXZLUP7IGHQ673ZCP32RTLRU2N427Z6L"
expected := []string{
"023667",
"641344",
"419615",
"692589",
"237233",
"711695",
"620195",
}
otp, _, err := FromURL(url)
if err != nil {
fmt.Printf("hotp: failed to parse HOTP URL\n")
t.FailNow()
}
if !otp.Scan(expected[4], 10) {
fmt.Println("hotp: scan should have found code")
t.FailNow()
}
if otp.Counter() != 5 {
fmt.Println("hotp: counter was not properly synced")
t.FailNow()
}
}
// This test verifies that a scan that does not successfully sync
// will not update the OTP counter.
func TestBadScan(t *testing.T) {
url := "otpauth://hotp/kyle?counter=0&secret=EXZLUP7IGHQ673ZCP32RTLRU2N427Z6L"
expected := []string{
"023667",
"641344",
"419615",
"692589",
"237233",
"711695",
"620195",
}
otp, _, err := FromURL(url)
if err != nil {
fmt.Printf("hotp: failed to parse HOTP URL\n")
t.FailNow()
}
if otp.Scan(expected[6], 3) {
fmt.Println("hotp: scan should not have found code")
t.FailNow()
}
if otp.Counter() != 0 {
fmt.Println("hotp: counter was not properly synced")
t.FailNow()
}
}
// This test ensures that a valid code is recognised and increments
// the counter.
func TestCheck(t *testing.T) {
otp := NewHOTP(rfcKey, 0, 6)
if !otp.Check(rfcExpected[0]) {
fmt.Println("hotp: Check failed when it should have succeeded")
t.FailNow()
}
if otp.Counter() != 1 {
fmt.Println("hotp: counter should have been incremented")
t.FailNow()
}
}
// This test ensures that an invalid code is recognised as such and
// does not increment the counter.
func TestBadCheck(t *testing.T) {
otp := NewHOTP(rfcKey, 0, 6)
if otp.Check(rfcExpected[1]) {
fmt.Println("hotp: Check succeeded when it should have failed")
t.FailNow()
}
if otp.Counter() != 0 {
fmt.Println("hotp: counter should not have been incremented")
t.FailNow()
}
}
func TestSerialisation(t *testing.T) {
otp := NewHOTP(rfcKey, 123456, 8)
out, err := Marshal(otp)
if err != nil {
fmt.Printf("hotp: failed to marshal HOTP (%v)\n", err)
t.FailNow()
}
otp2, err := Unmarshal(out)
if err != nil {
fmt.Printf("hotp: failed to unmarshal HOTP (%v)\n", err)
t.FailNow()
}
if otp.Counter() != otp2.Counter() {
fmt.Println("hotp: serialisation failed to preserve counter")
t.FailNow()
}
if _, err = Unmarshal([]byte{0x0}); err == nil {
fmt.Println("hotp: Unmarshal should have failed")
t.FailNow()
}
}
func TestGenerateHOTP(t *testing.T) {
_, err := GenerateHOTP(6, false)
if err != nil {
fmt.Printf("hotp: failed to generate random key value (%v)\n", err)
t.FailNow()
}
_, err = GenerateHOTP(8, true)
if err != nil {
fmt.Printf("hotp: failed to generate random key value (%v)\n", err)
t.FailNow()
}
PRNG = new(bytes.Buffer)
_, err = GenerateHOTP(8, true)
if err == nil {
fmt.Println("hotp: should have failed to generate random key value")
t.FailNow()
}
// should read just enough for the key
PRNG = bytes.NewBufferString("abcdeabcdeabcdeabcde")
_, err = GenerateHOTP(8, true)
if err == nil {
fmt.Println("hotp: should have failed to generate random key value")
t.FailNow()
}
}
func BenchmarkFromURL(b *testing.B) {
url := "otpauth://hotp/kyle?counter=0&secret=EXZLUP7IGHQ673ZCP32RTLRU2N427Z6L"
for i := 0; i < b.N; i++ {
_, _, err := FromURL(url)
if err != nil {
fmt.Printf("hotp: failed to parse url (%v)\n", err)
b.FailNow()
}
}
}

View file

@ -0,0 +1,43 @@
hotpcli: OATH-HOTP command line tool
hotpcli reads the DER-encoded key file specified and prints out the next code,
updating the key file with the new key.
Usage:
hotpcli [-cnw] [-k keyfile] [-u url]
-c Print out an integrity check; this
displays a baseline key value and
the current counter for synchronisation.
-k keyfile Specify the file containing the
key; defaults to hotp.key.
-n Don't update the counter in the key
file.
-u url An OATH-HOTP URL containing the
key-value information to use for
the key.
-w Write the url specified with -u to the
key file and exit.
The program automatically updates the key file with the new counter;
the -n option will allow this behaviour to be overridden. To generate
a key from a url, pass the -w and -u options; the counter will not be
updated.
LICENSE
Copyright (c) 2013 Kyle Isom <kyle@tyrfingr.is>
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

View file

@ -0,0 +1,77 @@
package main
import (
"flag"
"fmt"
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gokyle/hotp"
"io/ioutil"
)
func main() {
check := flag.Bool("c", false, "do integrity check")
noUpdate := flag.Bool("n", false, "don't update counter")
keyFile := flag.String("k", "hotp.key", "key file")
url := flag.String("u", "", "URL to load new key from")
write := flag.Bool("w", false, "only write URL-loaded key to file")
flag.Parse()
var otp *hotp.HOTP
if *url != "" {
var err error
otp, _, err = hotp.FromURL(*url)
if err != nil {
fmt.Printf("[!] %v\n", err.Error())
return
}
if *write {
out, err := hotp.Marshal(otp)
if err != nil {
fmt.Printf("[!] %v\n", err.Error())
return
}
err = ioutil.WriteFile(*keyFile, out, 0600)
if err != nil {
fmt.Printf("[!] %v\n", err.Error())
return
}
return
}
} else {
in, err := ioutil.ReadFile(*keyFile)
if err != nil {
fmt.Printf("[!] %v\n", err.Error())
return
}
otp, err = hotp.Unmarshal(in)
if err != nil {
fmt.Printf("[!] %v\n", err.Error())
return
}
}
if *check {
code, counter := otp.IntegrityCheck()
fmt.Println(" code:", code)
fmt.Println("counter:", counter)
} else {
fmt.Println(otp.OTP())
}
if !*noUpdate {
out, err := hotp.Marshal(otp)
if err != nil {
fmt.Printf("[!] %v\n", err.Error())
return
}
err = ioutil.WriteFile(*keyFile, out, 0600)
if err != nil {
fmt.Printf("[!] %v\n", err.Error())
return
}
}
}

View file

@ -0,0 +1,13 @@
Copyright (c) 2013 Kyle Isom <kyle@tyrfingr.is>
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

View file

@ -0,0 +1,42 @@
hotpgen: generate new HOTP key values from the command line
Usage:
hotpgen [-d digits] [-p password] [-r] [label]
-d digits Specify the number of digits to
use, typically 6 or 8; defaults
to 6.
-r Randomise the initial counter;
defaults to false as this is
not supported by all clients.
label An optional label for the key;
for example, an email address
or username.
The program will dump out three files; a PNG image, a text file,
and a DER-encoded key file that can be used with hotpcli. The text
file contains the URL encoded in the QR code; the QR code may be
displayed to users to scan into their mobile apps. If a label is
provided, the files are named by the label (e.g. label.png and
label.txt); otherwise, the files are named as the base32-encoded
URL.
LICENSE
Copyright (c) 2013 Kyle Isom <kyle@tyrfingr.is>
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

View file

@ -0,0 +1,60 @@
package main
import (
"encoding/base32"
"flag"
"fmt"
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gokyle/hotp"
"io/ioutil"
)
func main() {
digits := flag.Int("d", 6, "number of digits")
doRand := flag.Bool("r", false, "randomise counter")
flag.Parse()
var label string
if flag.NArg() == 1 {
label = flag.Arg(0)
}
otp, err := hotp.GenerateHOTP(*digits, *doRand)
if err != nil {
fmt.Printf("! %v\n", err.Error())
return
}
url := otp.URL(label)
png, err := otp.QR(label)
if err != nil {
fmt.Printf("! %v\n", err.Error())
return
}
filename := label
if label == "" {
filename = base32.StdEncoding.EncodeToString([]byte(url))
}
err = ioutil.WriteFile(filename+".png", png, 0644)
if err != nil {
fmt.Printf("! %v\n", err.Error())
return
}
err = ioutil.WriteFile(filename+".txt", []byte(url), 0644)
if err != nil {
fmt.Printf("! %v\n", err.Error())
return
}
keyFile, err := hotp.Marshal(otp)
if err != nil {
fmt.Printf("! %v\n", err.Error())
return
}
err = ioutil.WriteFile(filename+".key", keyFile, 0644)
if err != nil {
fmt.Printf("! %v\n", err.Error())
return
}
}

2075
Godeps/_workspace/src/github.com/gokyle/hotp/rfc4226.txt generated vendored Normal file

File diff suppressed because it is too large Load diff

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View file

@ -0,0 +1 @@
otpauth://hotp/kyle?counter=0&secret=EXZLUP7IGHQ673ZCP32RTLRU2N427Z6L

View file

@ -0,0 +1,2 @@
LOGGING START,12/17/13 4:16 PM
OATH-HOTP,12/17/13 4:16 PM,1,ccccgdefgiii,,d4be97ace3317295d895ebd6b2eca67849794db3,,,0,1,0,6,0,0,0,0,0,0
1 LOGGING START,12/17/13 4:16 PM
2 OATH-HOTP,12/17/13 4:16 PM,1,ccccgdefgiii,,d4be97ace3317295d895ebd6b2eca67849794db3,,,0,1,0,6,0,0,0,0,0,0

View file

@ -1,43 +0,0 @@
package utils
import (
"bytes"
"fmt"
"strings"
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gravitational/trace"
"github.com/gravitational/teleport/Godeps/_workspace/src/gopkg.in/alecthomas/kingpin.v2"
)
// KeyVal is a key value flag-compatible data structure
type KeyVal map[string]string
// Set accepts string with arguments in the form "key:val,key2:val2"
func (kv *KeyVal) Set(v string) error {
for _, i := range SplitComma(v) {
vals := strings.SplitN(i, ":", 2)
if len(vals) != 2 {
return trace.Errorf("extra options should be defined like KEY:VAL")
}
(*kv)[vals[0]] = vals[1]
}
return nil
}
// String returns a string with comma separated key-values: "key:val,key2:val2"
func (kv *KeyVal) String() string {
b := &bytes.Buffer{}
for k, v := range *kv {
fmt.Fprintf(b, "%v:%v", k, v)
fmt.Fprintf(b, " ")
}
return b.String()
}
// KeyValParam accepts a kingpin setting parameter and returns
// kingpin-compatible value
func KeyValParam(s kingpin.Settings) *KeyVal {
kv := make(KeyVal)
s.SetValue(&kv)
return &kv
}

View file

@ -1,16 +0,0 @@
package utils
// SplitAt splits array of strings at a given separator
func SplitAt(args []string, sep string) ([]string, []string) {
index := -1
for i, a := range args {
if a == sep {
index = i
break
}
}
if index == -1 {
return args, []string{}
}
return args[:index], args[index+1:]
}

View file

@ -1,53 +0,0 @@
package utils
import (
"bufio"
"strings"
"unicode/utf8"
)
func SplitComma(v string) []string {
return Split(',', '\\', v)
}
func Split(delim, escape rune, v string) []string {
if delim == 0 || escape == 0 {
return []string{v}
}
scanner := bufio.NewScanner(strings.NewReader(v))
s := &splitter{delim: delim, escape: escape}
scanner.Split(s.scan)
out := []string{}
for scanner.Scan() {
out = append(out, scanner.Text())
}
return out
}
type splitter struct {
delim rune
escape rune
prev rune
}
func (s *splitter) scan(data []byte, atEOF bool) (advance int, token []byte, err error) {
// scan until first unescaped delimiter and return token
idx := 0
var r rune
for width := 0; idx < len(data); idx += width {
r, width = utf8.DecodeRune(data[idx:])
if r == s.delim && s.prev != s.escape && idx != 0 {
s.prev = r
return idx + width, data[:idx], nil
}
s.prev = r
}
// If we're at EOF, we have a final, non-empty, non-terminated chunk
if atEOF && idx != 0 {
return len(data), data[:idx], nil
}
// Request more data.
return 0, nil, nil
}

View file

@ -1,36 +0,0 @@
package utils
import (
"testing"
. "github.com/gravitational/teleport/Godeps/_workspace/src/gopkg.in/check.v1"
)
func TestUtils(t *testing.T) { TestingT(t) }
type USuite struct {
}
var _ = Suite(&USuite{})
func (s *USuite) TestSplit(c *C) {
tcs := []struct {
delim, escape rune
input string
expect []string
}{
{delim: ',', escape: '\\', input: "", expect: []string{}},
{delim: ',', escape: '\\', input: "a", expect: []string{"a"}},
{delim: ',', escape: '\\', input: "a,b", expect: []string{"a", "b"}},
{delim: ',', escape: '\\', input: "a,b\\,cd", expect: []string{"a", "b\\,cd"}},
{delim: ',', escape: '\\', input: "a,b\\,cd,e", expect: []string{"a", "b\\,cd", "e"}},
}
for i, t := range tcs {
comment := Commentf(
"test case #%v: delim: %c, escape: %v, input: '%v', expected: %#v",
i, t.delim, t.escape, t.input, t.expect)
out := Split(t.delim, t.escape, t.input)
c.Assert(out, DeepEquals, t.expect, comment)
}
}

View file

@ -74,17 +74,7 @@ run-embedded: install
run-telescope: install
rm -f /tmp/telescope.auth.sock
telescope --log=console\
--log-severity=INFO\
--data-dir=/var/lib/teleport/telescope\
--assets-dir=$(GOPATH)/src/github.com/gravitational/teleport/telescope\
--cp-assets-dir=$(GOPATH)/src/github.com/gravitational/teleport\
--fqdn=telescope.vendor.io\
--domain=vendor.io\
--tun-addr=tcp://0.0.0.0:34000\
--web-addr=tcp://0.0.0.0:35000\
--backend=bolt\
--backend-config='{"path": "/var/lib/teleport/telescope.db"}'
telescope --config=examples/telescope.yaml
trust-telescope:
tscopectl user-ca pub-key > /tmp/user.pubkey

View file

@ -15,6 +15,9 @@
<div class="form-group">
<input type="password" name="password" class="form-control" placeholder="Password" required="">
</div>
<div class="form-group">
<input type="password" name="hotpToken" class="form-control" placeholder="Hotp Token" required="">
</div>
<button type="submit" class="btn btn-primary block full-width m-b">Login</button>
<a href="#"><small>Forgot password?</small></a>

View file

@ -15,6 +15,10 @@
<div class="form-group">
<input type="password" name="password" class="form-control" placeholder="Password" required="">
</div>
<div class="form-group">
<input type="password" name="hotpToken" class="form-control" placeholder="Hotp Token" required="">
</div>
<button type="submit" class="btn btn-primary block full-width m-b">Login</button>
<a href="#"><small>Forgot password?</small></a>

View file

@ -8,6 +8,10 @@ fqdn: gravitational.io
ssh:
enabled: true
tun:
enabled: true
server_addr: tcp://telescope.vendor.io:33006
auth:
enabled: true
domain: a.fqdn

22
examples/telescope.yaml Normal file
View file

@ -0,0 +1,22 @@
log:
output: console
severity: INFO
data_dir: /var/lib/telescope
fqdn: gravitational.io
assets_dir: assets/web/telescope
cp_assets_dir: assets/web/teleport
fqdn: telescope.vendor.io
domain: vendor.io
backend: bolt\
backend_config: '{"path": "/var/lib/teleport/telescope.db"}'
keys_backend:
type: bolt
params: {path: "/var/lib/teleport/telescope.auth.db"}

View file

@ -132,7 +132,7 @@ func (s *AuthServer) GenerateUserCert(
}
func (s *AuthServer) SignIn(user string, password []byte) (*Session, error) {
if err := s.CheckPassword(user, password); err != nil {
if err := s.CheckPasswordWOToken(user, password); err != nil {
return nil, err
}
sess, err := s.NewWebSession(user)

View file

@ -10,6 +10,8 @@ import (
"github.com/gravitational/teleport/lib/backend/encryptedbk"
"github.com/gravitational/teleport/lib/backend/encryptedbk/encryptor"
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gokyle/hotp"
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gravitational/log"
. "github.com/gravitational/teleport/Godeps/_workspace/src/gopkg.in/check.v1"
@ -56,10 +58,15 @@ func (s *AuthSuite) TestSessions(c *C) {
pass := []byte("abc123")
ws, err := s.a.SignIn(user, pass)
c.Assert(err, FitsTypeOf, &teleport.NotFoundError{})
c.Assert(err, NotNil)
c.Assert(ws, IsNil)
c.Assert(s.a.UpsertPassword(user, pass), IsNil)
hotpURL, _, err := s.a.UpsertPassword(user, pass)
c.Assert(err, IsNil)
otp, label, err := hotp.FromURL(hotpURL)
c.Assert(err, IsNil)
c.Assert(label, Equals, "user1")
otp.Increment()
ws, err = s.a.SignIn(user, pass)
c.Assert(err, IsNil)

View file

@ -331,19 +331,28 @@ func (c *Client) DeleteWebTun(prefix string) error {
}
// UpsertPassword updates web access password for the user
func (c *Client) UpsertPassword(user string, password []byte) error {
_, err := c.PostForm(
func (c *Client) UpsertPassword(user string,
password []byte) (hotpURL string, hotpQR []byte, err error) {
out, err := c.PostForm(
c.Endpoint("users", user, "web", "password"),
url.Values{"password": []string{string(password)}},
)
return err
var re *upsertPasswordResponse
if err := json.Unmarshal(out.Bytes(), &re); err != nil {
return "", nil, err
}
return re.HotpURL, re.HotpQR, err
}
// CheckPassword checks if the suplied web access password is valid.
func (c *Client) CheckPassword(user string, password []byte) error {
func (c *Client) CheckPassword(user string,
password []byte, hotpToken string) error {
_, err := c.PostForm(
c.Endpoint("users", user, "web", "password", "check"),
url.Values{"password": []string{string(password)}})
url.Values{
"password": []string{string(password)},
"hotpToken": []string{hotpToken},
})
return err
}
@ -352,7 +361,9 @@ func (c *Client) CheckPassword(user string, password []byte) error {
func (c *Client) SignIn(user string, password []byte) (string, error) {
out, err := c.PostForm(
c.Endpoint("users", user, "web", "signin"),
url.Values{"password": []string{string(password)}},
url.Values{
"password": []string{string(password)},
},
)
if err != nil {
return "", err
@ -709,8 +720,8 @@ type ClientI interface {
GetWebTuns() ([]services.WebTun, error)
GetWebTun(prefix string) (*services.WebTun, error)
DeleteWebTun(prefix string) error
UpsertPassword(user string, password []byte) error
CheckPassword(user string, password []byte) error
UpsertPassword(user string, password []byte) (hotpURL string, hotpQR []byte, err error)
CheckPassword(user string, password []byte, hotpToken string) error
SignIn(user string, password []byte) (string, error)
GetWebSession(user string, sid string) (string, error)
GetWebSessionsKeys(user string) ([]services.AuthorizedKey, error)

View file

@ -312,7 +312,8 @@ func (s *APIServer) signIn(w http.ResponseWriter, r *http.Request, p httprouter.
var pass string
err := form.Parse(r,
form.String("password", &pass, form.Required()))
form.String("password", &pass, form.Required()),
)
if err != nil {
replyErr(w, err)
return
@ -336,25 +337,30 @@ func (s *APIServer) upsertPassword(w http.ResponseWriter, r *http.Request, p htt
return
}
user := p[0].Value
if err := s.s.UpsertPassword(user, []byte(pass)); err != nil {
hotpURL, hotpQR, err := s.s.UpsertPassword(user, []byte(pass))
if err != nil {
replyErr(w, err)
return
}
reply(w, http.StatusOK, message(fmt.Sprintf("'%v' user password updated successfully", user)))
reply(w, http.StatusOK,
&upsertPasswordResponse{HotpURL: hotpURL, HotpQR: hotpQR})
}
func (s *APIServer) checkPassword(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
var pass string
var hotpToken string
err := form.Parse(r,
form.String("password", &pass, form.Required()))
form.String("password", &pass, form.Required()),
form.String("hotpToken", &hotpToken, form.Required()),
)
if err != nil {
replyErr(w, err)
return
}
user := p[0].Value
if err := s.s.CheckPassword(user, []byte(pass)); err != nil {
if err := s.s.CheckPassword(user, []byte(pass), hotpToken); err != nil {
replyErr(w, err)
return
}
@ -939,6 +945,11 @@ type sealKeysResponse struct {
Keys []encryptor.Key
}
type upsertPasswordResponse struct {
HotpURL string
HotpQR []byte
}
func message(msg string) map[string]interface{} {
return map[string]interface{}{"message": msg}
}

View file

@ -6,6 +6,8 @@ import (
"testing"
"time"
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gokyle/hotp"
"github.com/gravitational/teleport"
authority "github.com/gravitational/teleport/lib/auth/native"
"github.com/gravitational/teleport/lib/backend/boltbk"
@ -208,12 +210,34 @@ func (s *APISuite) TestUserKeyCRUD(c *C) {
func (s *APISuite) TestPasswordCRUD(c *C) {
pass := []byte("abc123")
err := s.clt.CheckPassword("user1", pass)
err := s.clt.CheckPassword("user1", pass, "123456")
c.Assert(err, NotNil)
c.Assert(s.clt.UpsertPassword("user1", pass), IsNil)
c.Assert(s.clt.CheckPassword("user1", pass), IsNil)
c.Assert(s.clt.CheckPassword("user1", []byte("abc123123")), NotNil)
hotpURL, _, err := s.clt.UpsertPassword("user1", pass)
c.Assert(err, IsNil)
otp, label, err := hotp.FromURL(hotpURL)
c.Assert(err, IsNil)
c.Assert(label, Equals, "user1")
otp.Increment()
token1 := otp.OTP()
c.Assert(s.clt.CheckPassword("user1", pass, "123456"), NotNil)
c.Assert(s.clt.CheckPassword("user1", pass, token1), IsNil)
c.Assert(s.clt.CheckPassword("user1", pass, token1), NotNil)
token2 := otp.OTP()
c.Assert(s.clt.CheckPassword("user1", []byte("abc123123"), token2), NotNil)
c.Assert(s.clt.CheckPassword("user1", pass, "123456"), NotNil)
c.Assert(s.clt.CheckPassword("user1", pass, token2), IsNil)
c.Assert(s.clt.CheckPassword("user1", pass, token1), NotNil)
token3 := otp.OTP()
token4 := otp.OTP()
c.Assert(s.clt.CheckPassword("user1", pass, token4), NotNil)
c.Assert(s.clt.CheckPassword("user1", pass, token3), IsNil)
c.Assert(s.clt.CheckPassword("user1", pass, "123456"), NotNil)
c.Assert(s.clt.CheckPassword("user1", pass, token4), IsNil)
}
func (s *APISuite) TestSessions(c *C) {
@ -226,7 +250,13 @@ func (s *APISuite) TestSessions(c *C) {
c.Assert(err, NotNil)
c.Assert(ws, Equals, "")
c.Assert(s.clt.UpsertPassword(user, pass), IsNil)
hotpURL, _, err := s.clt.UpsertPassword(user, pass)
c.Assert(err, IsNil)
otp, label, err := hotp.FromURL(hotpURL)
c.Assert(err, IsNil)
c.Assert(label, Equals, "user1")
otp.Increment()
ws, err = s.clt.SignIn(user, pass)
c.Assert(err, IsNil)

View file

@ -390,7 +390,7 @@ func (s *TunServer) passwordAuth(
log.Infof("got authentication attempt for user '%v' type '%v'", conn.User(), ab.Type)
switch ab.Type {
case "password":
if err := s.a.CheckPassword(conn.User(), ab.Pass); err != nil {
if err := s.a.CheckPassword(conn.User(), ab.Pass, ab.HotpToken); err != nil {
log.Errorf("Password auth error: %v", err)
return nil, err
}
@ -436,9 +436,10 @@ func (s *TunServer) passwordAuth(
// authBucket uses password to transport app-specific user name and
// auth-type in addition to the password to support auth
type authBucket struct {
User string `json:"user"`
Type string `json:"type"`
Pass []byte `json:"pass"`
User string `json:"user"`
Type string `json:"type"`
Pass []byte `json:"pass"`
HotpToken string `json:"hotpToken"`
}
func NewTokenAuth(fqdn, token string) ([]ssh.AuthMethod, error) {
@ -465,11 +466,12 @@ func NewWebSessionAuth(user string, session []byte) ([]ssh.AuthMethod, error) {
return []ssh.AuthMethod{ssh.Password(string(data))}, nil
}
func NewWebPasswordAuth(user string, password []byte) ([]ssh.AuthMethod, error) {
func NewWebPasswordAuth(user string, password []byte, hotpToken string) ([]ssh.AuthMethod, error) {
data, err := json.Marshal(authBucket{
Type: AuthWebPassword,
User: user,
Pass: password,
Type: AuthWebPassword,
User: user,
Pass: password,
HotpToken: hotpToken,
})
if err != nil {
return nil, err

View file

@ -8,6 +8,8 @@ import (
"net/url"
"path/filepath"
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gokyle/hotp"
authority "github.com/gravitational/teleport/lib/auth/testauthority"
"github.com/gravitational/teleport/lib/backend/boltbk"
"github.com/gravitational/teleport/lib/backend/encryptedbk"
@ -131,9 +133,15 @@ func (s *TunSuite) TestUnixServerClient(c *C) {
user := "test"
pass := []byte("pwd123")
s.a.UpsertPassword(user, pass)
hotpURL, _, err := s.a.UpsertPassword(user, pass)
c.Assert(err, IsNil)
authMethod, err := NewWebPasswordAuth(user, pass)
otp, label, err := hotp.FromURL(hotpURL)
c.Assert(err, IsNil)
c.Assert(label, Equals, "test")
otp.Increment()
authMethod, err := NewWebPasswordAuth(user, pass, otp.OTP())
c.Assert(err, IsNil)
clt, err := NewTunClient(
@ -152,9 +160,15 @@ func (s *TunSuite) TestSessions(c *C) {
user := "ws-test"
pass := []byte("ws-abc123")
c.Assert(s.a.UpsertPassword(user, pass), IsNil)
hotpURL, _, err := s.a.UpsertPassword(user, pass)
c.Assert(err, IsNil)
authMethod, err := NewWebPasswordAuth(user, pass)
otp, label, err := hotp.FromURL(hotpURL)
c.Assert(err, IsNil)
c.Assert(label, Equals, "ws-test")
otp.Increment()
authMethod, err := NewWebPasswordAuth(user, pass, otp.OTP())
c.Assert(err, IsNil)
clt, err := NewTunClient(
@ -162,7 +176,13 @@ func (s *TunSuite) TestSessions(c *C) {
c.Assert(err, IsNil)
defer clt.Close()
c.Assert(clt.UpsertPassword(user, pass), IsNil)
hotpURL, _, err = clt.UpsertPassword(user, pass)
c.Assert(err, IsNil)
otp, label, err = hotp.FromURL(hotpURL)
c.Assert(err, IsNil)
c.Assert(label, Equals, "ws-test")
otp.Increment()
ws, err := clt.SignIn(user, pass)
c.Assert(err, IsNil)
@ -194,9 +214,15 @@ func (s *TunSuite) TestSessionsBadPassword(c *C) {
user := "system-test"
pass := []byte("system-abc123")
c.Assert(s.a.UpsertPassword(user, pass), IsNil)
hotpURL, _, err := s.a.UpsertPassword(user, pass)
c.Assert(err, IsNil)
authMethod, err := NewWebPasswordAuth(user, pass)
otp, label, err := hotp.FromURL(hotpURL)
c.Assert(err, IsNil)
c.Assert(label, Equals, "system-test")
otp.Increment()
authMethod, err := NewWebPasswordAuth(user, pass, otp.OTP())
c.Assert(err, IsNil)
clt, err := NewTunClient(
@ -211,4 +237,5 @@ func (s *TunSuite) TestSessionsBadPassword(c *C) {
ws, err = clt.SignIn("not-exitsts", pass)
c.Assert(err, NotNil)
c.Assert(ws, Equals, "")
}

View file

@ -106,7 +106,7 @@ type RequestHandler func(http.ResponseWriter, *http.Request, httprouter.Params,
type AuthHandler interface {
GetHost() string
Auth(user, pass string) (string, error)
Auth(user, pass string, hotpToken string) (string, error)
ValidateSession(user, sid string) (Context, error)
SetSession(w http.ResponseWriter, user, sid string) error
ClearSession(w http.ResponseWriter)
@ -143,8 +143,8 @@ func CloseContext(key string, val interface{}) {
}
}
func (s *LocalAuth) Auth(user, pass string) (string, error) {
method, err := auth.NewWebPasswordAuth(user, []byte(pass))
func (s *LocalAuth) Auth(user, pass string, hotpToken string) (string, error) {
method, err := auth.NewWebPasswordAuth(user, []byte(pass), hotpToken)
if err != nil {
return "", err
}

View file

@ -635,17 +635,19 @@ func (s *CPHandler) logout(w http.ResponseWriter, r *http.Request, _ httprouter.
}
func (s *CPHandler) authForm(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
var user, pass string
var user, pass, hotpToken string
err := form.Parse(r,
form.String("username", &user, form.Required()),
form.String("password", &pass, form.Required()))
form.String("password", &pass, form.Required()),
form.String("hotpToken", &hotpToken, form.Required()),
)
if err != nil {
replyErr(w, http.StatusBadRequest, err)
return
}
sid, err := s.cfg.Auth.Auth(user, pass)
sid, err := s.cfg.Auth.Auth(user, pass, hotpToken)
if err != nil {
log.Warningf("auth error: %v", err)
http.Redirect(w, r, "/login", http.StatusFound)

View file

@ -5,7 +5,6 @@ import (
"strings"
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gravitational/configure"
outils "github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gravitational/orbit/lib/utils"
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gravitational/trace"
"github.com/gravitational/teleport/lib/utils"
)
@ -110,7 +109,7 @@ type TunConfig struct {
type NetAddrSlice []utils.NetAddr
func (s *NetAddrSlice) Set(val string) error {
values := outils.SplitComma(val)
values := configure.SplitComma(val)
out := make([]utils.NetAddr, len(values))
for i, v := range values {
a, err := utils.ParseAddr(v)
@ -130,7 +129,7 @@ func (kv *KeyVal) Set(v string) error {
if len(*kv) == 0 {
*kv = make(map[string]string)
}
for _, i := range outils.SplitComma(v) {
for _, i := range configure.SplitComma(v) {
vals := strings.SplitN(i, ":", 2)
if len(vals) != 2 {
return trace.Errorf("extra options should be defined like KEY:VAL")

View file

@ -8,7 +8,7 @@ import (
"strings"
"text/template"
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gravitational/orbit/lib/utils"
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gravitational/configure"
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gravitational/trace"
)
@ -60,7 +60,7 @@ func (c *ctx) Env(key string) (string, error) {
if !ok {
return "", trace.Errorf("environment variable '%v' is not set", key)
}
values := utils.SplitComma(v)
values := configure.SplitComma(v)
out := make([]string, len(values))
for i, p := range values {
out[i] = quoteYAML(p)

View file

@ -60,6 +60,14 @@ func (s *BoltSuite) TestPasswordHashCRUD(c *C) {
s.suite.PasswordHashCRUD(c)
}
func (s *BoltSuite) TestPasswordAndHotpCRUD(c *C) {
s.suite.PasswordCRUD(c)
}
func (s *BoltSuite) TestPasswordGarbage(c *C) {
s.suite.PasswordGarbage(c)
}
func (s *BoltSuite) TestWebSessionCRUD(c *C) {
s.suite.WebSessionCRUD(c)
}

View file

@ -7,6 +7,8 @@ import (
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/mailgun/lemma/random"
"github.com/gravitational/teleport/lib/backend"
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gokyle/hotp"
. "github.com/gravitational/teleport/Godeps/_workspace/src/gopkg.in/check.v1"
)
@ -336,26 +338,48 @@ func (s *ServicesTestSuite) RemoteCertCRUD(c *C) {
c.Assert(err, FitsTypeOf, &teleport.NotFoundError{})
}
func (s *ServicesTestSuite) TestPasswordCRUD(c *C) {
func (s *ServicesTestSuite) PasswordCRUD(c *C) {
pass := []byte("abc123")
err := s.WebS.CheckPassword("user1", pass)
c.Assert(err, FitsTypeOf, &teleport.NotFoundError{})
err := s.WebS.CheckPassword("user1", pass, "123456")
c.Assert(err, NotNil)
c.Assert(s.WebS.UpsertPassword("user1", pass), IsNil)
c.Assert(s.WebS.CheckPassword("user1", pass), IsNil)
c.Assert(s.WebS.CheckPassword("user1", []byte("abc123123")), FitsTypeOf, &teleport.BadParameterError{})
hotpURL, _, err := s.WebS.UpsertPassword("user1", pass)
c.Assert(err, IsNil)
otp, label, err := hotp.FromURL(hotpURL)
c.Assert(err, IsNil)
c.Assert(label, Equals, "user1")
otp.Increment()
token1 := otp.OTP()
c.Assert(s.WebS.CheckPassword("user1", pass, "123456"), FitsTypeOf, &teleport.BadParameterError{})
c.Assert(s.WebS.CheckPassword("user1", pass, token1), IsNil)
c.Assert(s.WebS.CheckPassword("user1", pass, token1), FitsTypeOf, &teleport.BadParameterError{})
token2 := otp.OTP()
c.Assert(s.WebS.CheckPassword("user1", []byte("abc123123"), token2), FitsTypeOf, &teleport.BadParameterError{})
c.Assert(s.WebS.CheckPassword("user1", pass, "123456"), FitsTypeOf, &teleport.BadParameterError{})
c.Assert(s.WebS.CheckPassword("user1", pass, token2), IsNil)
c.Assert(s.WebS.CheckPassword("user1", pass, token1), FitsTypeOf, &teleport.BadParameterError{})
token3 := otp.OTP()
token4 := otp.OTP()
c.Assert(s.WebS.CheckPassword("user1", pass, token4), FitsTypeOf, &teleport.BadParameterError{})
c.Assert(s.WebS.CheckPassword("user1", pass, token3), IsNil)
c.Assert(s.WebS.CheckPassword("user1", pass, "123456"), FitsTypeOf, &teleport.BadParameterError{})
c.Assert(s.WebS.CheckPassword("user1", pass, token4), IsNil)
}
func (s *ServicesTestSuite) TestPasswordGarbage(c *C) {
func (s *ServicesTestSuite) PasswordGarbage(c *C) {
garbage := [][]byte{
nil,
make([]byte, MaxPasswordLength+1),
make([]byte, MinPasswordLength-1),
}
for _, g := range garbage {
err := s.WebS.CheckPassword("user1", g)
c.Assert(err, FitsTypeOf, &teleport.BadParameterError{})
err := s.WebS.CheckPassword("user1", g, "123456")
c.Assert(err, NotNil)
}
}

View file

@ -6,6 +6,8 @@ import (
"net/url"
"time"
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gokyle/hotp"
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gravitational/log"
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gravitational/trace"
"github.com/gravitational/teleport/Godeps/_workspace/src/golang.org/x/crypto/bcrypt"
@ -42,6 +44,32 @@ func (s *WebService) GetPasswordHash(user string) ([]byte, error) {
return hash, err
}
func (s *WebService) UpsertHOTP(user string, otp *hotp.HOTP) error {
bytes, err := hotp.Marshal(otp)
if err != nil {
return trace.Wrap(err)
}
err = s.backend.UpsertVal([]string{"web", "users", user},
"hotp", bytes, 0)
if err != nil {
return trace.Wrap(err)
}
return nil
}
func (s *WebService) GetHOTP(user string) (*hotp.HOTP, error) {
bytes, err := s.backend.GetVal([]string{"web", "users", user},
"hotp")
if err != nil {
return nil, trace.Wrap(err)
}
otp, err := hotp.Unmarshal(bytes)
if err != nil {
return nil, trace.Wrap(err)
}
return otp, nil
}
// UpsertSession
func (s *WebService) UpsertWebSession(user, sid string,
session WebSession, ttl time.Duration) error {
@ -174,28 +202,84 @@ func (s *WebService) GetWebTuns() ([]WebTun, error) {
return tuns, nil
}
func (s *WebService) UpsertPassword(user string, password []byte) error {
func (s *WebService) UpsertPassword(user string,
password []byte) (hotpURL string, hotpQR []byte, err error) {
if err := verifyPassword(password); err != nil {
return err
return "", nil, err
}
hash, err := bcrypt.GenerateFromPassword(password, bcrypt.DefaultCost)
if err != nil {
return err
return "", nil, err
}
return s.UpsertPasswordHash(user, hash)
otp, err := hotp.GenerateHOTP(6, false)
if err != nil {
return "", nil, err
}
hotpQR, err = otp.QR(user)
if err != nil {
return "", nil, err
}
hotpURL = otp.URL(user)
if err != nil {
return "", nil, err
}
otp.Increment()
err = s.UpsertPasswordHash(user, hash)
if err != nil {
return "", nil, err
}
err = s.UpsertHOTP(user, otp)
if err != nil {
return "", nil, err
}
return hotpURL, hotpQR, nil
}
func (s *WebService) CheckPassword(user string, password []byte) error {
func (s *WebService) CheckPassword(user string, password []byte, hotpToken string) error {
if err := verifyPassword(password); err != nil {
return err
return trace.Wrap(err)
}
hash, err := s.GetPasswordHash(user)
if err != nil {
return err
return trace.Wrap(err)
}
if err := bcrypt.CompareHashAndPassword(hash, password); err != nil {
return &teleport.BadParameterError{Err: "passwords do not match"}
}
otp, err := s.GetHOTP(user)
if err != nil {
return trace.Wrap(err)
}
if !otp.Check(hotpToken) {
return &teleport.BadParameterError{Err: "tokens do not match"}
}
if err := s.UpsertHOTP(user, otp); err != nil {
return trace.Wrap(err)
}
return nil
}
// TO DO: not very good
func (s *WebService) CheckPasswordWOToken(user string, password []byte) error {
if err := verifyPassword(password); err != nil {
return trace.Wrap(err)
}
hash, err := s.GetPasswordHash(user)
if err != nil {
return trace.Wrap(err)
}
if err := bcrypt.CompareHashAndPassword(hash, password); err != nil {
return &teleport.BadParameterError{Err: "passwords do not match"}
}
return nil
}

View file

@ -2,6 +2,7 @@ package command
import (
"fmt"
"io/ioutil"
"time"
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/buger/goterm"
@ -9,12 +10,14 @@ import (
)
func (cmd *Command) SetPass(user, pass string) {
err := cmd.client.UpsertPassword(user, []byte(pass))
hotpURL, hotpQR, err := cmd.client.UpsertPassword(user, []byte(pass))
if err != nil {
cmd.printError(err)
return
}
cmd.printOK("password has been set for user '%v'", user)
err = ioutil.WriteFile("QR.png", hotpQR, 0777)
cmd.printOK("password has been set for user '%v', hotp: %v", user, hotpURL)
}
func (cmd *Command) UpsertKey(user, keyID, key string, ttl time.Duration) {

View file

@ -94,17 +94,19 @@ func (h *Handler) logout(w http.ResponseWriter, r *http.Request, _ httprouter.Pa
}
func (h *Handler) authForm(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
var user, pass string
var user, pass, hotpToken string
err := form.Parse(r,
form.String("username", &user, form.Required()),
form.String("password", &pass, form.Required()))
form.String("password", &pass, form.Required()),
form.String("hotpToken", &hotpToken, form.Required()),
)
if err != nil {
replyErr(w, http.StatusBadRequest, err)
return
}
sid, err := h.auth.Auth(user, pass)
sid, err := h.auth.Auth(user, pass, hotpToken)
if err != nil {
log.Warningf("auth error: %v", err)
http.Redirect(w, r, "/web/login", http.StatusFound)