forked from adamxp12/TfaWebAuthn
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathTfaWebAuthn.module
242 lines (210 loc) · 9.65 KB
/
TfaWebAuthn.module
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
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
<?php
namespace ProcessWire;
/**
* TfaWebAuthn (1.0.1)
* Adds WebAuthn/FIDO2 as a TFA option
*
* @author Adam Blunt
*
* ProcessWire 3.x
* Copyright (C) 2011 by Ryan Cramer
* Licensed under GNU/GPL v2, see LICENSE.TXT
*
* http://www.processwire.com
* http://www.ryancramer.com
*
*/
require_once 'WebAuthn/WebAuthn.php';
//use lbuchs\WebAuthn\Binary\ByteBuffer;
class TfaWebAuthn extends Tfa implements Module, ConfigurableModule
{
public function __construct()
{
parent::__construct();
$this->WebAuthn = new \lbuchs\WebAuthn\WebAuthn('WebAuthn Library', $this->wire('config')->httpHost); // $formats
$this->userVerification = "discouraged";
$this->crossPlatformAttachment = null; // True only crossplatfrm aka USB keys, False only single device like TPM/Winows Hellow, Null allow both
$this->typeUsb = true;
$this->typeNfc = true;
$this->typeBle = true;
$this->typeInt = true;
}
public function init()
{
if(config()->version('3.0.165')) {
// 3.0.165 and newer have an init() function in the Tfa class. previous versions did not
// Strangely this init() function while not needed before is now required in new versions even if you dont use the auto-enable feature
parent::init();
}
$this->addHookBefore('Tfa::getUserSettingsInputfields', $this, 'addScripts');
$this->addHookBefore('Tfa::render', $this, 'addScripts');
//uses "legacy" option by design
$this->config->js('tfa_webauthn', [
'browser_not_supported' => $this->_("Browser does not support WebAuthn."),
'credential_added' => $this->_("Credential added. You can add more now or save this page to finish setup"),
'credential_added_mult' => $this->_("Credential %count% added. You can add even more or save this page to finish setup"),
'auth_failed' => $this->_("Authentication failed with error")
]);
}
public function addScripts()
{
$this->config->scripts->add($this->wire('config')->urls->$this . "WebAuthn.js");
}
public function enabledForUser(User $user, array $settings)
{
return $settings['enabled'] === true;
}
public function isValidUserCode(User $user, $code, array $settings)
{
if (!strlen($code)) {
return false;
}
$authreg = $this->session->authreg;
$this->session->authreq = null;
$this->session->authreg = null;
$code = json_decode($code);
$clientDataJSON = base64_decode($code->clientDataJSON);
$authenticatorData = base64_decode($code->authenticatorData);
$signature = base64_decode($code->signature);
$userHandle = base64_decode($code->userHandle);
$id = base64_decode($code->id);
$challenge = $this->session->authchallange;
$credentialPublicKey = null;
// Get the public key for the given credential
if (is_array($authreg)) {
foreach ($authreg as $reg) {
$o = (object) $reg;
if ($o->credentialId === bin2hex($id)) {
$credentialPublicKey = $o->credentialPublicKey;
break;
}
}
}
try {
if ($credentialPublicKey === null) {
exit($this->_('Public Key for credential ID not found!'));
}
// process the get request. throws WebAuthnException if it fails
$this->WebAuthn->processGet($clientDataJSON, $authenticatorData, $signature, $credentialPublicKey, $challenge, null, $this->userVerification === 'required');
return true;
} catch (\Exception $e) {
return false;
}
return false;
}
// Create settings page for server
public function ___getUserSettingsInputfields(User $user, InputfieldWrapper $fieldset, $settings)
{
parent::___getUserSettingsInputfields($user, $fieldset, $settings);
if ($this->enabledForUser($user, $settings)) {
} elseif ($this->wire('input')->requestMethod('POST')) {
$fieldset->new('text', 'regdata')
->attr('maxlength', 20480);
} else {
$createArgs = $this->WebAuthn->getCreateArgs(\hex2bin($user->id), $user->name, $user->name, 20, false, $this->userVerification, $this->crossPlatformAttachment);
$fieldset->new('hidden', 'createData')
->attr('id', 'TfaWebAuthn_createData')
->attr('value', json_encode($createArgs));
$fieldset->new('hidden', 'regdata')
->attr('id', 'TfaWebAuthn_regdata')
->attr('maxlength', 20480);
$fieldset->new('button', 'addKey', $this->_('Enable two-factor authentication'))
->attr('id', 'TfaWebAuthn_button')
->attr('onclick', "TfaWebAuthn_addkey()")
->value($this->_("Add WebAuthn Credential"));
$fieldset->new('markup')
->attr('value', "<span id='TfaWebAuthn_msg'>" . $this->_("Click the button to register a new credential.") . "</span>");
$_SESSION["chal"] = (string) $this->WebAuthn->getChallenge();
}
}
// Save registration data from the User settings
public function ___processUserSettingsInputfields(User $user, InputfieldWrapper $fieldset, $settings, $settingsPrev)
{
$settings = parent::___processUserSettingsInputfields($user, $fieldset, $settings, $settingsPrev);
try {
$challenge = $_SESSION["chal"];
$aryreg = json_decode("[".$settings['regdata']."]");
if(count($aryreg) > 0){
$data = array();
foreach ($aryreg as $reg) {
$clientdata = base64_decode($reg->clientDataJSON);
$attestationObject = base64_decode($reg->attestationObject);
$d = $this->WebAuthn->processCreate($clientdata, $attestationObject, $challenge, $this->userVerification === 'required', true, false);
array_push($data, (array) $d );
}
$sd = serialize($data);
$settings['regkeys'] = $sd;
$settings['enabled'] = true;
$settings['challenge'] = null;
$settings['regdata'] = null;
$this->message($this->_("Success! Your account is now secured with two-factor authentication"));
}else{
$settings['enabled'] = false;
$this->message($this->_("Something went wrong! No credentials submitted!"));
}
} catch (\Exception $e) {
$settings['enabled'] = false;
$this->error(sprintf($this->_("That did not work: %s"), $e));
}
return $settings;
}
protected function getDefaultUserSettings(User $user)
{
return array(
'enabled' => false,
'regkeys' => ''
);
}
// Display the tFA form
public function buildAuthCodeForm()
{
$user = $this->getUser();
$settings = $this->getUserSettings($user);
$authreg = unserialize($settings['regkeys']);
$ids = array();
$this->session->authreg = $authreg;
$ids = array();
if (is_array($authreg)) {
foreach ($authreg as $reg) {
$o = (object) $reg;
array_push($ids, $o->credentialId);
}
}
if (count($ids) === 0) {
throw new \Exception(sprintf($this->_('No WebAuthn registrations for userId %d'), $user->id));
}
/*
* Check if the authreq is null. if so make a new challenge
* ProcessWire annoingly calls builtAuthCodeForm twice. once to build the form and a 2nd time when the user submits the form
* thus validation was failing as the was non-matching challanges
*/
if (is_null($this->session->authreq)) {
$req = $this->WebAuthn->getGetArgs($ids, 20, $this->typeUsb, $this->typeNfc, $this->typeBle, $this->typeInt, $this->userVerification);
$this->session->authreq = json_encode($req);
$this->session->authchallange = (string) $this->WebAuthn->getChallenge();
}
$authStatement = $this->_("You need to use your security key to login.");
$authDescription = $this->_("Insert it now and tap/click the key to verify its you. If your key does not have a button just unplug it and then plug it back in.");
$authStartText = $this->_("Start two-factor authentication");
$authUseKeyText = $this->_("Use Security Key");
$form = $this->modules->get('InputfieldForm');
$form->attr('action', "./?$this->keyName=" . $this->getSessionKey(true))
->attr('id', "tfaform");
$form->new('markup')
->attr('value', "<img style='height:80px' onload='TfaWebAuthn_authKey()' src='" . $this->wire('config')->urls->$this . "assets\bluekeyside.png'><br>
$authStatement<br>
$authDescription<br>
<div uk-alert class='uk-alert-danger' style='display:none' id='TfaWebAuthn_error'></div>");
$form->new('hidden', 'authreq')
->attr('id', 'TfaWebAuthn_authreq')
->attr('value', $this->session->authreq);
$form->new('hidden', 'tfa_code')
->attr('id', 'TfaWebAuthn_authresponse')
->attr('required', 'required');
$form->new('button', 'authKey', $authStartText)
->attr('id', 'TfaWebAuthn_button')
->attr('onclick', "TfaWebAuthn_authKey()")
->value($authUseKeyText);
return $form;
}
}