-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathregistration_handlers.go
178 lines (163 loc) · 6.92 KB
/
registration_handlers.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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
// Copyright (c) 2019 Faye Amacker. All rights reserved.
// Use of this source code is governed by Apache License 2.0 found in the LICENSE file.
package main
import (
"crypto/rand"
"encoding/base64"
"encoding/json"
"net/http"
"github.com/fxamacker/webauthn"
"github.com/gorilla/sessions"
)
func (s *server) handleAttestationOptions() http.HandlerFunc {
type request struct {
Username string `json:"username"`
DisplayName string `json:"displayName"`
AuthenticatorSelection webauthn.AuthenticatorSelectionCriteria `json:"authenticatorSelection"`
Attestation webauthn.AttestationConveyancePreference `json:"attestation"`
}
type response struct {
serverResponse
*webauthn.PublicKeyCredentialCreationOptions
}
return func(w http.ResponseWriter, r *http.Request) {
session, ok := r.Context().Value(contextKeyLoginSession).(*sessions.Session)
if !ok {
panic("Failed to get session data from context")
}
// Parse and verify request.
var optionsRequest request
if err := json.NewDecoder(r.Body).Decode(&optionsRequest); err != nil {
writeFailedServerResponse(w, http.StatusBadRequest, "Failed to json decode request body: "+err.Error())
return
}
if optionsRequest.Username == "" {
writeFailedServerResponse(w, http.StatusBadRequest, "Missing username")
return
}
if optionsRequest.DisplayName == "" {
writeFailedServerResponse(w, http.StatusBadRequest, "Missing displayName")
return
}
if optionsRequest.AuthenticatorSelection.UserVerification == "" {
optionsRequest.AuthenticatorSelection.UserVerification = webauthn.UserVerificationPreferred
}
if optionsRequest.Attestation == "" {
optionsRequest.Attestation = webauthn.AttestationNone
}
// Get user from datastore.
u, err := s.dataStore.getUser(r.Context(), optionsRequest.Username)
if err == errNoRecords {
u = &user{
UserName: optionsRequest.Username,
DisplayName: optionsRequest.DisplayName,
}
} else if err != nil {
writeFailedServerResponse(w, http.StatusInternalServerError, "Failed to query user in database: "+err.Error())
return
}
// Generate user ID for new user.
if u.UserID == nil {
u.UserID = make([]byte, 64) // user ID is 64 random bytes
if n, err := rand.Read(u.UserID); err != nil {
writeFailedServerResponse(w, http.StatusInternalServerError, "Failed to generate user ID: "+err.Error())
return
} else if n != 64 {
writeFailedServerResponse(w, http.StatusInternalServerError, "Failed to generate requested random bytes")
return
}
}
// Generate PublicKeyCredentialCreationOptions from WebAuthn config and user input.
creationOptions, err := webauthn.NewAttestationOptions(s.webAuthnConfig, &webauthn.User{ID: u.UserID, Name: u.UserName, DisplayName: u.DisplayName, CredentialIDs: u.CredentialIDs})
if err != nil {
writeFailedServerResponse(w, http.StatusInternalServerError, "Failed to generate PublicKeyCredentialCreationOptions: "+err.Error())
return
}
creationOptions.AuthenticatorSelection = optionsRequest.AuthenticatorSelection
creationOptions.Attestation = optionsRequest.Attestation
// Save creationOptions and user info in session to verify new credential later.
session.Values[sessionMapKeyWebAuthnCreationOptions] = creationOptions
session.Values[sessionMapKeyUserSession] = &userSession{User: u}
// Write response.
creationOptionsResponse := &response{
serverResponse: serverResponse{Status: statusOK},
PublicKeyCredentialCreationOptions: creationOptions,
}
b, err := json.Marshal(creationOptionsResponse)
if err != nil {
writeFailedServerResponse(w, http.StatusInternalServerError, "Failed to json encode response body: "+err.Error())
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(b)
}
}
func (s *server) handleAttestationResult(w http.ResponseWriter, r *http.Request) {
// Get saved creationOptions and user info.
session, ok := r.Context().Value(contextKeyLoginSession).(*sessions.Session)
if !ok {
panic("Failed to get session data from context")
}
savedCreationOptions, ok := session.Values[sessionMapKeyWebAuthnCreationOptions].(*webauthn.PublicKeyCredentialCreationOptions)
if !ok {
delete(session.Values, sessionMapKeyWebAuthnCreationOptions)
writeFailedServerResponse(w, http.StatusUnauthorized, "Session doesn't have PublicKeyCredentialCreationOptions data")
return
}
uSession, ok := session.Values[sessionMapKeyUserSession].(*userSession)
if !ok {
delete(session.Values, sessionMapKeyWebAuthnCreationOptions)
writeFailedServerResponse(w, http.StatusUnauthorized, "Session doesn't have user data")
return
}
// Parse and verify request.
credentialAttestation, err := webauthn.ParseAttestation(r.Body)
if err != nil {
delete(session.Values, sessionMapKeyWebAuthnCreationOptions)
writeFailedServerResponse(w, http.StatusBadRequest, "Failed to parse attestation: "+err.Error())
return
}
var credentialAlgs []int
for _, param := range savedCreationOptions.PubKeyCredParams {
credentialAlgs = append(credentialAlgs, param.Alg)
}
expected := &webauthn.AttestationExpectedData{
Origin: s.rpOrigin,
RPID: savedCreationOptions.RP.ID,
CredentialAlgs: credentialAlgs,
Challenge: base64.RawURLEncoding.EncodeToString(savedCreationOptions.Challenge),
UserVerification: savedCreationOptions.AuthenticatorSelection.UserVerification,
}
// todo: VerifyAttestation returns attestationType and trustPath. Need to verify that
// attestation type is acceptable and trust path can be trusted.
_, _, err = webauthn.VerifyAttestation(credentialAttestation, expected)
if err != nil {
delete(session.Values, sessionMapKeyWebAuthnCreationOptions)
writeFailedServerResponse(w, http.StatusBadRequest, "Failed to verify attestation: "+err.Error())
return
}
// Save user credential in datastore.
c := &credential{
CredentialID: credentialAttestation.RawID,
UserID: uSession.User.UserID,
Counter: credentialAttestation.AuthnData.Counter,
CoseKey: credentialAttestation.AuthnData.Credential.Raw,
}
if err = s.dataStore.addUserCredential(r.Context(), uSession.User, c); err == errRecordExists {
delete(session.Values, sessionMapKeyWebAuthnCreationOptions)
writeFailedServerResponse(w, http.StatusInternalServerError, "User credential exists in the system")
return
} else if err != nil {
delete(session.Values, sessionMapKeyWebAuthnCreationOptions)
writeFailedServerResponse(w, http.StatusInternalServerError, "Failed to save user credential: "+err.Error())
return
}
// Delete creationOptions and update user info in session.
delete(session.Values, sessionMapKeyWebAuthnCreationOptions)
uSession.User.CredentialIDs = append(uSession.User.CredentialIDs, credentialAttestation.RawID)
if len(uSession.LoggedInCredentialID) == 0 {
uSession.LoggedInCredentialID = credentialAttestation.RawID
}
// Write response.
writeOKServerResponse(w)
}