-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathextensions.go
194 lines (174 loc) · 6.64 KB
/
extensions.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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
package warp
import (
"bytes"
"crypto"
"encoding/base64"
"hash"
"strings"
)
//Identifiers for defined extensions
const (
ExtensionAppID = "appid"
ExtensionTxAuthSimple = "txAuthSimple"
ExtensionTxAuthGeneric = "txAuthGeneric"
ExtensionAuthnSel = "authnSel"
ExtensionExts = "exts"
ExtensionUVI = "uvi"
ExtensionLoc = "loc"
ExtensionUVM = "uvm"
ExtensionBiometricPerfBounds = "biometricPerfBounds"
)
//AuthenticationExtensionsClientInputs contains the client extension input
//values for zero or more extensions. §5.7
type AuthenticationExtensionsClientInputs map[string]interface{}
//AuthenticationExtensionsClientOutputs containing the client extension output
//values for zero or more WebAuthn extensions. §5.8
type AuthenticationExtensionsClientOutputs map[string]interface{}
//Extension defines an extension to a creation options or request options
//object
type Extension func(AuthenticationExtensionsClientInputs)
//BuildExtensions builds the extension map to be added to the options object
func BuildExtensions(exts ...Extension) AuthenticationExtensionsClientInputs {
extensions := make(AuthenticationExtensionsClientInputs)
for _, ext := range exts {
ext(extensions)
}
return extensions
}
//UseAppID adds the appid extension to the extensions object
func UseAppID(appID string) Extension {
return func(e AuthenticationExtensionsClientInputs) {
e[ExtensionAppID] = appID
}
}
//UseTxAuthSimple adds the txAuthSimple extension to the extensions object
func UseTxAuthSimple(txAuthSimple string) Extension {
return func(e AuthenticationExtensionsClientInputs) {
e[ExtensionTxAuthSimple] = txAuthSimple
}
}
//UseTxAuthGeneric adds the txAuthGeneric extension to the extensions object
func UseTxAuthGeneric(contentType string, content []byte) Extension {
return func(e AuthenticationExtensionsClientInputs) {
e[ExtensionTxAuthGeneric] = map[string]interface{}{
"contentType": contentType,
"content": content,
}
}
}
//RegistrationExtensionValidators is a map to all extension validators for
//extensions allowed during the registration ceremony
var RegistrationExtensionValidators map[string]RegistrationValidator = map[string]RegistrationValidator{}
//AuthenticationExtensionValidators is a map to all extension validators for
//extensions allowed during the authentication ceremony
var AuthenticationExtensionValidators map[string]AuthenticationValidator = map[string]AuthenticationValidator{
ExtensionAppID: ValidateAppID(),
ExtensionTxAuthSimple: ValidateTxAuthSimple(),
}
//ValidateAppID validates the appid extension and updates the credential
//request options with the valid AppID as needed
func ValidateAppID() AuthenticationValidator {
return func(opts *PublicKeyCredentialRequestOptions, cred *AssertionPublicKeyCredential) error {
o, ok := cred.Extensions[ExtensionAppID]
if !ok {
return nil // do not fail on client ignored extension
}
i, ok := opts.Extensions[ExtensionAppID]
if !ok {
return ErrVerifyClientExtensionOutput.Wrap(NewError("appid extension present in credential but not requested in options"))
}
out, ok := o.(bool)
if !ok {
return ErrVerifyClientExtensionOutput.Wrap(NewError("unexpected type on appid extension output"))
}
in, ok := i.(string)
if !ok {
return ErrVerifyClientExtensionOutput.Wrap(NewError("unexpected type on appid extension input"))
}
if out {
opts.RPID = in
}
return nil
}
}
//ValidateTxAuthSimple validates the txAuthSimple extension
func ValidateTxAuthSimple() AuthenticationValidator {
return func(opts *PublicKeyCredentialRequestOptions, cred *AssertionPublicKeyCredential) error {
o, ok := cred.Extensions[ExtensionTxAuthSimple]
if !ok {
return nil // do not fail on client ignored extension
}
i, ok := opts.Extensions[ExtensionTxAuthSimple]
if !ok {
return ErrVerifyClientExtensionOutput.Wrap(NewError("txAuthSimple extension present in credential but not requested in options"))
}
out, ok := o.(string)
if !ok {
return ErrVerifyClientExtensionOutput.Wrap(NewError("unexpected type on txAuthSimple extension output"))
}
in, ok := i.(string)
if !ok {
return ErrVerifyClientExtensionOutput.Wrap(NewError("unexpected type on txAuthSimple extension input"))
}
if strings.ReplaceAll(in, "\n", "") != strings.ReplaceAll(out, "\n", "") {
return ErrVerifyClientExtensionOutput.Wrap(NewError("txAuthSimple output differs from input"))
}
return nil
}
}
//ValidateTxAuthGeneric validates the txAuthGeneric extension
func ValidateTxAuthGeneric() AuthenticationValidator {
return func(opts *PublicKeyCredentialRequestOptions, cred *AssertionPublicKeyCredential) error {
o, ok := cred.Extensions[ExtensionTxAuthGeneric]
if !ok {
return nil // do not fail on client ignored extension
}
i, ok := opts.Extensions[ExtensionTxAuthGeneric]
if !ok {
return ErrVerifyClientExtensionOutput.Wrap(NewError("txAuthSimple extension present in credential but not requested in options"))
}
outB64, ok := o.(string)
if !ok {
return ErrVerifyClientExtensionOutput.Wrap(NewError("unexpected type on txAuthGeneric extension output"))
}
out, err := base64.StdEncoding.DecodeString(outB64)
if err != nil {
return ErrVerifyClientExtensionOutput.Wrap(NewError("unable to decode txAuthGeneric extension output"))
}
in, ok := i.(map[string]interface{})
if !ok {
return ErrVerifyClientExtensionOutput.Wrap(NewError("unexpected type on txAuthGeneric extension input"))
}
if _, ok := in["contentType"]; !ok {
return ErrVerifyClientExtensionOutput.Wrap(NewError("txAuthGeneric input missing contentType member"))
}
if _, ok := in["contentType"].(string); !ok {
return ErrVerifyClientExtensionOutput.Wrap(NewError("txAuthGeneric input contentType invalid type"))
}
if _, ok := in["content"]; !ok {
return ErrVerifyClientExtensionOutput.Wrap(NewError("txAuthGeneric input missing content member"))
}
inBytes, ok := in["content"].([]byte)
if !ok {
return ErrVerifyClientExtensionOutput.Wrap(NewError("txAuthGeneric input content invalid type"))
}
var hasher hash.Hash
switch len(out) {
case crypto.SHA1.Size():
hasher = crypto.SHA1.New()
case crypto.SHA256.Size():
hasher = crypto.SHA256.New()
case crypto.SHA384.Size():
hasher = crypto.SHA384.New()
case crypto.SHA512.Size():
hasher = crypto.SHA512.New()
default:
return ErrVerifyClientExtensionOutput.Wrap(NewError("txAuthGeneric output digest unknown length"))
}
hasher.Write(inBytes)
if !bytes.Equal(hasher.Sum(nil), out) {
return ErrVerifyClientExtensionOutput.Wrap(NewError("txAuthGeneric returned hash does not match input"))
}
return nil
}
}