-
Notifications
You must be signed in to change notification settings - Fork 0
/
challenge.go
136 lines (112 loc) · 3.2 KB
/
challenge.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
package octokey
import (
"crypto/hmac"
"crypto/rand"
"crypto/sha1"
"errors"
"github.com/octokey/octokey-go/buffer"
"net"
"time"
)
const (
// Which version of challenges is supported
CHALLENGE_VERSION = 3
// How many bytes of random data should be included
RANDOM_SIZE = 32
// Hash algorithm to use in the HMAC
HMAC_ALGORITHM = "sha1"
// The maximum age of a valid challenge (seconds)
MAX_AGE = 5 * 60
// The minimum age of a valid challenge (seconds)
MIN_AGE = -30
)
type Challenge struct {
O *Octokey
Version uint8
Timestamp time.Time
ClientIp net.IP
Random []byte
Digest []byte
Errors []error
}
func (O *Octokey) NewChallenge(clientIp net.IP) (string, error) {
random := make([]byte, RANDOM_SIZE)
_, err := rand.Read(random)
if err != nil {
return "", err
}
challenge := Challenge{O: O}
challenge.Version = CHALLENGE_VERSION
challenge.Timestamp = now()
challenge.ClientIp = clientIp
challenge.Random = random
challenge.Digest = challenge.expectedDigest()
return challenge.String(), nil
}
func (O *Octokey) ValidateChallenge(s string, clientIp net.IP) error {
challenge := Challenge{O: O}
challenge.ReadFrom(s, clientIp)
if len(challenge.Errors) > 0 {
return errors.New("octokey/challenge: invalid challenge")
}
return nil
}
// String returns the challenge in Base64 format
func (c *Challenge) String() string {
return c.signedBuffer().String()
}
func (c *Challenge) ReadFrom(s string, clientIp net.IP) {
b := buffer.NewBuffer(s)
currentTime := now()
c.Version = b.ScanUint8()
c.Timestamp = b.ScanTimestamp()
c.ClientIp = b.ScanIP()
c.Random = b.ScanVarBytes()
c.Digest = b.ScanVarBytes()
b.ScanEof()
if b.Error != nil {
c.Errors = append(c.Errors, b.Error)
return
}
if c.Version != CHALLENGE_VERSION {
c.Errors = append(c.Errors, errors.New("octokey/challenge: version mismatch"))
return
}
if currentTime.Unix()+MAX_AGE < c.Timestamp.Unix() {
c.Errors = append(c.Errors, errors.New("octokey/challenge: challenge too new"))
}
if currentTime.Unix()+MIN_AGE > c.Timestamp.Unix() {
c.Errors = append(c.Errors, errors.New("octokey/challenge: challenge too old"))
}
if !c.ClientIp.Equal(clientIp) {
c.Errors = append(c.Errors, errors.New("octokey/challenge: challenge IP mismatch"))
}
if len(c.Random) != RANDOM_SIZE {
c.Errors = append(c.Errors, errors.New("octokey/challenge: challenge random mismatch"))
}
if !hmac.Equal(c.Digest, c.expectedDigest()) {
c.Errors = append(c.Errors, errors.New("octokey/challenge: challenge HMAC mismatch"))
}
}
// expectedDigest calculates the HMAC of the unsignedBuffer
func (c *Challenge) expectedDigest() []byte {
toSign := c.unsignedBuffer().Raw()
h := hmac.New(sha1.New, c.O.ChallengeSecret)
h.Write(toSign)
return h.Sum(nil)
}
// unsignedBuffer is an octokey buffer containing everything except the signature
func (c *Challenge) unsignedBuffer() *buffer.Buffer {
b := &buffer.Buffer{}
b.AddUint8(c.Version)
b.AddTimestamp(c.Timestamp)
b.AddIP(c.ClientIp)
b.AddVarBytes(c.Random)
return b
}
// unsignedBuffer is an octokey buffer containing everything including the signature
func (c *Challenge) signedBuffer() *buffer.Buffer {
b := c.unsignedBuffer()
b.AddVarBytes(c.expectedDigest())
return b
}