/
auth.go
138 lines (113 loc) · 3.74 KB
/
auth.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
137
138
//
// AppEngine Universal 2 Factor
// (aeutf)
//
// License: MIT
//
package aeu2f
import (
"fmt"
"log"
"appengine"
"appengine/datastore"
"github.com/tstranex/u2f"
)
// loadRegistrations returns a slice of the registrations for a given user
// identity.
func loadRegistrations(ctx appengine.Context, userIdentity string) ([]*datastore.Key, []*Registration, error) {
regis := []*Registration{}
// Load Registrations
pkey := MakeParentKey(ctx)
q := datastore.NewQuery("Registration").
Ancestor(pkey).
Filter("UserIdentity =", userIdentity)
// Retrieve & save registrations into array of challenges
keys, err := q.GetAll(ctx, ®is)
if err != nil {
return nil, nil, fmt.Errorf("datastore GetAll error: %+v", err)
}
return keys, regis, nil
}
func signChallengeRequest(c u2f.Challenge, regi Registration) (*u2f.SignRequest, error) {
var reg u2f.Registration
buf := regi.U2FRegistrationBytes
if err := reg.UnmarshalBinary(buf); err != nil {
return nil, fmt.Errorf("reg.UnmarshalBinary %v", err)
}
return c.SignRequest(reg), nil
}
// NewSignChallenge returns a challenge for the U2F device.
//
func NewSignChallenge(ctx appengine.Context, userIdentity string) ([]*u2f.SignRequest, error) {
// Create challenge
c, err := u2f.NewChallenge(AppID, TrustedFacets)
if err != nil {
return nil, fmt.Errorf("u2f.NewChallenge error: %v", err)
}
_, regis, err := loadRegistrations(ctx, userIdentity)
if err != nil {
return nil, fmt.Errorf("loadRegistrations %+v", err)
}
var reqs = []*u2f.SignRequest{}
for _, regi := range regis {
signr, err := signChallengeRequest(*c, *regi)
if err != nil {
return nil, fmt.Errorf("Signing error: %+v", err)
}
reqs = append(reqs, signr)
}
// Save challenge to database.
ckey := makeKey(ctx, userIdentity, "SignChallenge")
if _, err := datastore.Put(ctx, ckey, c); err != nil {
return nil, fmt.Errorf("datastore.Put error: %v", err)
}
// Return challenge
log.Printf("🖋 New Sign Challenges: %+v [%+v]", reqs, ckey)
return reqs, nil
}
// --- testSignChallenge ---
func testSignChallenge(challenge u2f.Challenge, regi Registration, signResp u2f.SignResponse) error {
var reg u2f.Registration
if err := reg.UnmarshalBinary(regi.U2FRegistrationBytes); err != nil {
return fmt.Errorf("reg.UnmarshalBinary error: %v", err)
}
// The AppEngine datastore does not accept uint types, see:
// https://github.com/golang/appengine/blob/master/datastore/save.go#L148
// So we cast int64 to uint32 when coming from the datastore, and back.
newCounter, err := reg.Authenticate(signResp, challenge, uint32(regi.Counter))
if err != nil {
return fmt.Errorf("VerifySignResponse error: %v", err)
}
// Update the counter for the next auth.
regi.Counter = int64(newCounter)
return nil
}
// Sign verifies or rejects a U2F response.
func Sign(ctx appengine.Context, userIdentity string, signResp u2f.SignResponse) error {
ckey := makeKey(ctx, userIdentity, "SignChallenge")
var challenge u2f.Challenge
// Load the Challenge for this user
if err := datastore.Get(ctx, ckey, &challenge); err != nil {
return fmt.Errorf("datastore.Get error: %v", err)
}
// Load the Registrations
keys, regis, err := loadRegistrations(ctx, userIdentity)
if err != nil {
return fmt.Errorf("loadRegistrations error %+v", err)
}
// Check each Registration
for idx, regi := range regis {
if err := testSignChallenge(challenge, *regi, signResp); err != nil {
return fmt.Errorf("Sign error: %v", err)
} else {
// Update the counter for the regi.
if _, err := datastore.Put(ctx, ckey, keys[idx]); err != nil {
return fmt.Errorf("datastore.Put error: %v", err)
}
// Success -- A U2F response to a sign challenge succeeded.
return nil
}
return fmt.Errorf("Challenge failed for known registrations.")
}
return nil
}