Passed
Pull Request — master (#31)
by Tim
02:10
created

AuthProcess::setLogger()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 1
c 1
b 0
f 0
dl 0
loc 3
rs 10
nc 1
nop 1
cc 1
1
<?php
2
3
namespace SimpleSAML\Module\webauthn\Controller;
4
5
use Exception;
6
use SimpleSAML\Auth;
7
use SimpleSAML\Configuration;
8
use SimpleSAML\Error;
9
use SimpleSAML\HTTP\RunnableResponse;
10
use SimpleSAML\Logger;
11
use SimpleSAML\Module;
12
use SimpleSAML\Module\webauthn\WebAuthn\WebAuthnAbstractEvent;
13
use SimpleSAML\Module\webauthn\WebAuthn\WebAuthnAuthenticationEvent;
14
use SimpleSAML\Session;
15
use SimpleSAML\Utils;
16
use SimpleSAML\XHTML\Template;
17
use Symfony\Component\HttpFoundation\Request;
18
use Symfony\Component\HttpFoundation\RedirectResponse;
19
use Symfony\Component\HttpFoundation\Response;
20
use Symfony\Component\HttpFoundation\StreamedResponse;
21
22
/**
23
 * Controller class for the webauthn module.
24
 *
25
 * This class serves the different views available in the module.
26
 *
27
 * @package SimpleSAML\Module\webauthn
28
 */
29
class AuthProcess
30
{
31
    /** @var \SimpleSAML\Configuration */
32
    protected $config;
33
34
    /** @var \SimpleSAML\Session */
35
    protected $session;
36
37
    /**
38
     * @var \SimpleSAML\Auth\State|string
39
     * @psalm-var \SimpleSAML\Auth\State|class-string
40
     */
41
    protected $authState = Auth\State::class;
42
43
    /**
44
     * @var \SimpleSAML\Logger|string
45
     * @psalm-var \SimpleSAML\Logger|class-string
46
     */
47
    protected $logger = Logger::class;
48
49
50
    /**
51
     * Controller constructor.
52
     *
53
     * It initializes the global configuration and session for the controllers implemented here.
54
     *
55
     * @param \SimpleSAML\Configuration              $config The configuration to use by the controllers.
56
     * @param \SimpleSAML\Session                    $session The session to use by the controllers.
57
     *
58
     * @throws \Exception
59
     */
60
    public function __construct(
61
        Configuration $config,
62
        Session $session
63
    ) {
64
        $this->config = $config;
65
        $this->session = $session;
66
    }
67
68
69
    /**
70
     * Inject the \SimpleSAML\Auth\State dependency.
71
     *
72
     * @param \SimpleSAML\Auth\State $authState
73
     */
74
    public function setAuthState(Auth\State $authState): void
75
    {
76
        $this->authState = $authState;
77
    }
78
79
80
    /**
81
     * Inject the \SimpleSAML\Logger dependency.
82
     *
83
     * @param \SimpleSAML\Logger $logger
84
     */
85
    public function setLogger(Logger $logger): void
86
    {
87
        $this->logger = $logger;
88
    }
89
90
91
    /**
92
     * @param \Symfony\Component\HttpFoundation\Request $request
93
     * @return \Symfony\Component\HttpFoundation\RedirectResponse|\SimpleSAML\HTTP\RunnableResponse|\Symfony\Component\HttpFoundation\StreamedResponse
94
     *   A Symfony Response-object.
95
     */
96
    public function main(Request $request): Response
97
    {
98
        if (session_status() != PHP_SESSION_ACTIVE) {
99
            session_cache_limiter('nocache');
100
        }
101
102
        $this->logger::info('FIDO2 - Accessing WebAuthn enrollment validation');
103
104
        $stateId = $request->query->get('StateId');
105
        if ($stateId === null) {
106
            throw new Error\BadRequest('Missing required StateId query parameter.');
107
        }
108
109
        $debugEnabled = $this->config->getValue('logging.level', Logger::NOTICE) === Logger::DEBUG;
110
111
        $state = Auth\State::loadState($stateId, 'webauthn:request');
112
113
        $incomingID = bin2hex(WebAuthnAbstractEvent::base64urlDecode($request->request->get('response_id')));
114
115
        /**
116
         * §7.2 STEP 2 - 4 : check that the credential is one of those the particular user owns
117
         */
118
        $publicKey = false;
119
        $previousCounter = -1;
120
121
        foreach ($state['FIDO2Tokens'] as $oneToken) {
122
            if ($oneToken[0] == $incomingID) {
123
                // Credential ID is eligible for user $state['FIDO2Username'];
124
                // using publicKey $oneToken[1] with current counter value $oneToken[2]
125
                $publicKey = $oneToken[1];
126
                $previousCounter = $oneToken[2];
127
                break;
128
            }
129
        }
130
131
        if ($publicKey === false) {
132
            throw new Exception(
133
                "User attempted to authenticate with an unknown credential ID. This should already have been prevented by the browser!"
134
            );
135
        }
136
137
        /** @psalm-var array $oneToken */
138
        $authObject = new WebAuthnAuthenticationEvent(
139
            $request->request->get('type'),
140
            ($state['FIDO2Scope'] === null ? $state['FIDO2DerivedScope'] : $state['FIDO2Scope']),
141
            $state['FIDO2SignupChallenge'],
142
            $state['IdPMetadata']['entityid'],
143
            base64_decode($request->request->get('authenticator_data')),
144
            base64_decode($request->request->get('client_data_raw')),
145
            $oneToken[0],
146
            $oneToken[1],
147
            base64_decode($request->request->get('signature')),
148
            $debugEnabled
149
        );
150
151
        /**
152
         * §7.2 STEP 18 : detect physical object cloning on the token
153
         */
154
        $counter = $authObject->getCounter();
155
        if (($previousCounter != 0 || $counter != 0) && $counter > $previousCounter) {
156
            // Signature counter was incremented compared to last time, good
157
            $store = $state['webauthn:store'];
158
            $store->updateSignCount($oneToken[0], $counter);
159
        } else {
160
            throw new Exception(
161
                "Signature counter less or equal to a previous authentication! Token cloning likely (old: $previousCounter, new: $counter."
162
            );
163
        }
164
165
        // THAT'S IT. The user authenticated successfully. Remember the credential ID that was used.
166
        $state['FIDO2AuthSuccessful'] = $oneToken[0];
167
168
        // See if he wants to hang around for token management operations
169
        if ($request->request->get('credentialChange') === 'on') {
170
            $state['FIDO2WantsRegister'] = true;
171
        } else {
172
            $state['FIDO2WantsRegister'] = false;
173
        }
174
175
        $this->authState::saveState($state, 'webauthn:request');
0 ignored issues
show
Bug introduced by
It seems like $state can also be of type null; however, parameter $state of SimpleSAML\Auth\State::saveState() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

175
        $this->authState::saveState(/** @scrutinizer ignore-type */ $state, 'webauthn:request');
Loading history...
176
177
        if ($debugEnabled) {
178
            $response = new StreamedResponse();
179
            $response->setCallback(function ($authObject, $state) {
180
                echo $authObject->getDebugBuffer();
181
                echo $authObject->getValidateBuffer();
182
                echo "Debug mode, not continuing to " . ($state['FIDO2WantsRegister'] ? "credential registration page." : "destination.");
183
            });
184
            return $response;
185
        } else {
186
            if ($state['FIDO2WantsRegister']) {
187
                return new RedirectResponse(Module::getModuleURL('webauthn/webauthn.php?StateId=' . urlencode($stateId)));
188
            } else {
189
                return new RunnableResponse([Auth\ProcessingChain::class, 'resumeProcessing'], [$state]);
190
            }
191
        }
192
    }
193
}
194