/
register.go
122 lines (98 loc) · 3.42 KB
/
register.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
//
// Package aeu2f provides U2F stored in a database.
//
// AppEngine Universal 2 Factor
// (aeutf)
//
// License: MIT
//
package aeu2f
import (
"log"
"fmt"
"time"
"appengine"
"appengine/datastore"
"github.com/tstranex/u2f"
)
// Registration stores the response to a registration challenge.
type Registration struct {
UserIdentity string
U2FRegistrationBytes []byte
// u2f.sign takes a uint32, but appengine does not store uints.
Counter int64
Created time.Time
}
// AppID identifies this application. Must be set to the hostname.
var AppID string
// TrustedFacets is the list of U2F trusted facets
var TrustedFacets []string
// ChallengeTimeout is the time within which a user must respond to a
// U2F registration challenge.
var ChallengeTimeout = 1000 * 60 // milliseconds
// MakeParentKey returns a Key to be used as the parent for the model, to
// enforce strong consistency.
func MakeParentKey(ctx appengine.Context) *datastore.Key {
return datastore.NewKey(ctx, "U2F", "Registration", 0, nil)
}
// makeKey creates a key for a strongly consistent model.
func makeKey(ctx appengine.Context, stringKey, kind string) *datastore.Key { parent := MakeParentKey(ctx)
return datastore.NewKey(ctx, kind, stringKey, 0, parent)
}
// NewRegistrationChallenge creates a new U2F challenge and stores it in the
// datastore.
//
// Encode the response with e.g.
// json.NewEncoder(w).Encode(req)
//
func NewRegistrationChallenge(ctx appengine.Context, userIdentity string) (*u2f.RegisterRequest, error) {
// Generate a challenge
c, err := u2f.NewChallenge(AppID, TrustedFacets); if err != nil {
return nil, fmt.Errorf("u2f.NewChallenge error: %v", err)
}
// Save challenge to database.
ckey := makeKey(ctx, userIdentity, "Challenge")
if _, err := datastore.Put(ctx, ckey, c); err != nil {
return nil, fmt.Errorf("datastore.Put error: %v", err)
}
// Return challenge request
req := c.RegisterRequest()
log.Printf("🍁 New Registration Challenge for %v: %+v [%+v]",
userIdentity, req, ckey)
return req, nil
}
// StoreResponse checks whether, based on the given information, the given
// U2F response has addressed the challenge.
//
// Get the RegisterResponse with e.g.
// if err := json.NewDecoder(r.Body).Decode(®Resp); err != nil {
// http.Error(w, "invalid response: "+err.Error(), http.StatusBadRequest)
// return
// }
func StoreResponse(ctx appengine.Context, userIdentity string, resp u2f.RegisterResponse) error {
// Load the most recent challenge.
ckey := makeKey(ctx, userIdentity, "Challenge")
var challenge u2f.Challenge
if err := datastore.Get(ctx, ckey, &challenge); err != nil {
return fmt.Errorf("datastore.Get error: %v", err)
}
reg, err := u2f.Register(resp, challenge, &u2f.Config{true})
if err != nil {
return fmt.Errorf("u2f.Register error: %v", err)
}
buf, err := reg.MarshalBinary()
if err != nil {
return fmt.Errorf("reg.MarshalBinary error: %v", err)
}
// Save the registration in the datastore
regi := Registration{UserIdentity: userIdentity, Counter: 0, U2FRegistrationBytes: buf, Created: time.Now()}
// We set the stringKey to 0, because the user identity is not part of the
// key. We look up registrations by a datastore query, since there might
// be multiple.
k := makeKey(ctx, "", "Registration")
if _, err := datastore.Put(ctx, k, ®i); err != nil {
return fmt.Errorf("datastore.Put error: %v", err)
}
log.Printf("🍁 Registered: %+v [%+v]", userIdentity, k)
return nil
}