Completed
Push — master ( bb08bc...08bc84 )
by Klas
05:16
created

U2F   F

Complexity

Total Complexity 63

Size/Duplication

Total Lines 388
Duplicated Lines 3.09 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 0
Metric Value
wmc 63
lcom 1
cbo 4
dl 12
loc 388
rs 3.36
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 2
A getRegisterData() 0 7 1
D doRegister() 6 86 17
A getAuthenticateData() 0 18 3
D doAuthenticate() 6 82 21
A castObjectToRegistration() 0 17 5
A get_certs() 0 16 6
A base64u_encode() 0 4 1
A base64u_decode() 0 4 1
A pubkey_to_pem() 0 26 3
A createChallenge() 0 7 1
A fixSignatureUnusedBits() 0 7 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like U2F often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use U2F, and based on these observations, apply Extract Interface, too.

1
<?php
2
/* Copyright (c) 2014 Yubico AB
3
 * All rights reserved.
4
 *
5
 * Redistribution and use in source and binary forms, with or without
6
 * modification, are permitted provided that the following conditions are
7
 * met:
8
 *
9
 *   * Redistributions of source code must retain the above copyright
10
 *     notice, this list of conditions and the following disclaimer.
11
 *
12
 *   * Redistributions in binary form must reproduce the above
13
 *     copyright notice, this list of conditions and the following
14
 *     disclaimer in the documentation and/or other materials provided
15
 *     with the distribution.
16
 *
17
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
 */
29
30
namespace u2flib_server;
31
32
/** Constant for the version of the u2f protocol */
33
const U2F_VERSION = "U2F_V2";
34
35
/** Constant for the type value in registration clientData */
36
const REQUEST_TYPE_REGISTER = "navigator.id.finishEnrollment";
37
38
/** Constant for the type value in authentication clientData */
39
const REQUEST_TYPE_AUTHENTICATE = "navigator.id.getAssertion";
40
41
/** Error for the authentication message not matching any outstanding
42
 * authentication request */
43
const ERR_NO_MATCHING_REQUEST = 1;
44
45
/** Error for the authentication message not matching any registration */
46
const ERR_NO_MATCHING_REGISTRATION = 2;
47
48
/** Error for the signature on the authentication message not verifying with
49
 * the correct key */
50
const ERR_AUTHENTICATION_FAILURE = 3;
51
52
/** Error for the challenge in the registration message not matching the
53
 * registration challenge */
54
const ERR_UNMATCHED_CHALLENGE = 4;
55
56
/** Error for the attestation signature on the registration message not
57
 * verifying */
58
const ERR_ATTESTATION_SIGNATURE = 5;
59
60
/** Error for the attestation verification not verifying */
61
const ERR_ATTESTATION_VERIFICATION = 6;
62
63
/** Error for not getting good random from the system */
64
const ERR_BAD_RANDOM = 7;
65
66
/** Error when the counter is lower than expected */
67
const ERR_COUNTER_TOO_LOW = 8;
68
69
/** Error decoding public key */
70
const ERR_PUBKEY_DECODE = 9;
71
72
/** Error user-agent returned error */
73
const ERR_BAD_UA_RETURNING = 10;
74
75
/** Error old OpenSSL version */
76
const ERR_OLD_OPENSSL = 11;
77
78
/** Error for the origin not matching the appId */
79
const ERR_NO_MATCHING_ORIGIN = 12;
80
81
/** Error for the type in clientData being invalid */
82
const ERR_BAD_TYPE = 13;
83
84
/** Error for bad user presence byte value */
85
const ERR_BAD_USER_PRESENCE = 14;
86
87
/** @internal */
88
const PUBKEY_LEN = 65;
89
90
class U2F
91
{
92
    /** @var string  */
93
    private $appId;
94
95
    /** @var null|string */
96
    private $attestDir;
97
98
    /** @internal */
99
    private $FIXCERTS = array(
100
        '349bca1031f8c82c4ceca38b9cebf1a69df9fb3b94eed99eb3fb9aa3822d26e8',
101
        'dd574527df608e47ae45fbba75a2afdd5c20fd94a02419381813cd55a2a3398f',
102
        '1d8764f0f7cd1352df6150045c8f638e517270e8b5dda1c63ade9c2280240cae',
103
        'd0edc9a91a1677435a953390865d208c55b3183c6759c9b5a7ff494c322558eb',
104
        '6073c436dcd064a48127ddbf6032ac1a66fd59a0c24434f070d4e564c124c897',
105
        'ca993121846c464d666096d35f13bf44c1b05af205f9b4a1e00cf6cc10c5e511'
106
    );
107
108
    /**
109
     * @param string $appId Application id for the running application
110
     * @param string|null $attestDir Directory where trusted attestation roots may be found
111
     * @throws Error If OpenSSL older than 1.0.0 is used
112
     */
113
    public function __construct($appId, $attestDir = null)
114
    {
115
        if(OPENSSL_VERSION_NUMBER < 0x10000000) {
116
            throw new Error('OpenSSL has to be at least version 1.0.0, this is ' . OPENSSL_VERSION_TEXT, ERR_OLD_OPENSSL);
117
        }
118
        $this->appId = $appId;
119
        $this->attestDir = $attestDir;
120
    }
121
122
    /**
123
     * Called to get a registration request to send to a user.
124
     * Returns an array of one registration request and a array of sign requests.
125
     *
126
     * @param array $registrations List of current registrations for this
127
     * user, to prevent the user from registering the same authenticator several
128
     * times.
129
     * @return array An array of two elements, the first containing a
130
     * RegisterRequest the second being an array of SignRequest
131
     * @throws Error
132
     */
133
    public function getRegisterData(array $registrations = array())
134
    {
135
        $challenge = $this->createChallenge();
136
        $request = new RegisterRequest($challenge, $this->appId);
137
        $signs = $this->getAuthenticateData($registrations);
138
        return array($request, $signs);
139
    }
140
141
    /**
142
     * Called to verify and unpack a registration message.
143
     *
144
     * @param RegisterRequest $request this is a reply to
145
     * @param object $response response from a user
146
     * @param bool $includeCert set to true if the attestation certificate should be
147
     * included in the returned Registration object
148
     * @return Registration
149
     * @throws Error
150
     */
151
    public function doRegister($request, $response, $includeCert = true)
152
    {
153
        if( !is_object( $request ) ) {
154
            throw new \InvalidArgumentException('$request of doRegister() method only accepts object.');
155
        }
156
157
        if( !is_object( $response ) ) {
158
            throw new \InvalidArgumentException('$response of doRegister() method only accepts object.');
159
        }
160
161 View Code Duplication
        if( property_exists( $response, 'errorCode') && $response->errorCode !== 0 ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
162
            throw new Error('User-agent returned error. Error code: ' . $response->errorCode, ERR_BAD_UA_RETURNING );
163
        }
164
165
        if( !is_bool( $includeCert ) ) {
166
            throw new \InvalidArgumentException('$include_cert of doRegister() method only accepts boolean.');
167
        }
168
169
        $rawReg = $this->base64u_decode($response->registrationData);
170
        $regData = array_values(unpack('C*', $rawReg));
171
        $clientData = $this->base64u_decode($response->clientData);
172
        $cli = json_decode($clientData);
173
174
        if($cli->challenge !== $request->challenge) {
175
            throw new Error('Registration challenge does not match', ERR_UNMATCHED_CHALLENGE );
176
        }
177
178
        if(isset($cli->typ) && $cli->typ !== REQUEST_TYPE_REGISTER) {
179
            throw new Error('ClientData type is invalid', ERR_BAD_TYPE);
180
        }
181
182 View Code Duplication
        if(isset($cli->origin) && $cli->origin !== $request->appId) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
183
            throw new Error('App ID does not match the origin', ERR_NO_MATCHING_ORIGIN);
184
        }
185
186
        $registration = new Registration();
187
        $offs = 1;
188
        $pubKey = substr($rawReg, $offs, PUBKEY_LEN);
189
        $offs += PUBKEY_LEN;
190
        // decode the pubKey to make sure it's good
191
        $tmpKey = $this->pubkey_to_pem($pubKey);
192
        if($tmpKey === null) {
193
            throw new Error('Decoding of public key failed', ERR_PUBKEY_DECODE );
194
        }
195
        $registration->publicKey = base64_encode($pubKey);
196
        $khLen = $regData[$offs++];
197
        $kh = substr($rawReg, $offs, $khLen);
198
        $offs += $khLen;
199
        $registration->keyHandle = $this->base64u_encode($kh);
200
201
        // length of certificate is stored in byte 3 and 4 (excluding the first 4 bytes)
202
        $certLen = 4;
203
        $certLen += ($regData[$offs + 2] << 8);
204
        $certLen += $regData[$offs + 3];
205
206
        $rawCert = $this->fixSignatureUnusedBits(substr($rawReg, $offs, $certLen));
207
        $offs += $certLen;
208
        $pemCert  = "-----BEGIN CERTIFICATE-----\r\n";
209
        $pemCert .= chunk_split(base64_encode($rawCert), 64);
210
        $pemCert .= "-----END CERTIFICATE-----";
211
        if($includeCert) {
212
            $registration->certificate = base64_encode($rawCert);
213
        }
214
        if($this->attestDir) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->attestDir of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
215
            if(openssl_x509_checkpurpose($pemCert, -1, $this->get_certs()) !== true) {
216
                throw new Error('Attestation certificate can not be validated', ERR_ATTESTATION_VERIFICATION );
217
            }
218
        }
219
220
        if(!openssl_pkey_get_public($pemCert)) {
221
            throw new Error('Decoding of public key failed', ERR_PUBKEY_DECODE );
222
        }
223
        $signature = substr($rawReg, $offs);
224
225
        $dataToVerify  = pack('C', 0);
226
        $dataToVerify .= hash('sha256', $request->appId, true);
227
        $dataToVerify .= hash('sha256', $clientData, true);
228
        $dataToVerify .= $kh;
229
        $dataToVerify .= $pubKey;
230
231
        if(openssl_verify($dataToVerify, $signature, $pemCert, 'sha256') === 1) {
232
            return $registration;
233
        } else {
234
            throw new Error('Attestation signature does not match', ERR_ATTESTATION_SIGNATURE );
235
        }
236
    }
237
238
    /**
239
     * Called to get an authentication request.
240
     *
241
     * @param array $registrations An array of the registrations to create authentication requests for.
242
     * @return array An array of SignRequest
243
     * @throws Error
244
     */
245
    public function getAuthenticateData(array $registrations)
246
    {
247
        $sigs = array();
248
        $challenge = $this->createChallenge();
249
        foreach ($registrations as $reg) {
250
            if( !is_object( $reg ) ) {
251
                throw new \InvalidArgumentException('$registrations of getAuthenticateData() method only accepts array of object.');
252
            }
253
            /** @var Registration $reg */
254
255
            $sig = new SignRequest();
256
            $sig->appId = $this->appId;
257
            $sig->keyHandle = $reg->keyHandle;
258
            $sig->challenge = $challenge;
259
            $sigs[] = $sig;
260
        }
261
        return $sigs;
262
    }
263
264
    /**
265
     * Called to verify an authentication response
266
     *
267
     * @param array $requests An array of outstanding authentication requests
268
     * @param array $registrations An array of current registrations
269
     * @param object $response A response from the authenticator
270
     * @return Registration
271
     * @throws Error
272
     *
273
     * The Registration object returned on success contains an updated counter
274
     * that should be saved for future authentications.
275
     * If the Error returned is ERR_COUNTER_TOO_LOW this is an indication of
276
     * token cloning or similar and appropriate action should be taken.
277
     */
278
    public function doAuthenticate(array $requests, array $registrations, $response)
279
    {
280
        if( !is_object( $response ) ) {
281
            throw new \InvalidArgumentException('$response of doAuthenticate() method only accepts object.');
282
        }
283
284 View Code Duplication
        if( property_exists( $response, 'errorCode') && $response->errorCode !== 0 ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
285
            throw new Error('User-agent returned error. Error code: ' . $response->errorCode, ERR_BAD_UA_RETURNING );
286
        }
287
288
        /** @var object|null $req */
289
        $req = null;
290
291
        /** @var object|null $reg */
292
        $reg = null;
293
294
        $clientData = $this->base64u_decode($response->clientData);
295
        $decodedClient = json_decode($clientData);
296
297
        if(isset($decodedClient->typ) && $decodedClient->typ !== REQUEST_TYPE_AUTHENTICATE) {
298
            throw new Error('ClientData type is invalid', ERR_BAD_TYPE);
299
        }
300
301
        foreach ($requests as $req) {
302
            if( !is_object( $req ) ) {
303
                throw new \InvalidArgumentException('$requests of doAuthenticate() method only accepts array of object.');
304
            }
305
306
            if($req->keyHandle === $response->keyHandle && $req->challenge === $decodedClient->challenge) {
307
                break;
308
            }
309
310
            $req = null;
311
        }
312
        if($req === null) {
313
            throw new Error('No matching request found', ERR_NO_MATCHING_REQUEST );
314
        }
315 View Code Duplication
        if(isset($decodedClient->origin) && $decodedClient->origin !== $req->appId) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
316
            throw new Error('App ID does not match the origin', ERR_NO_MATCHING_ORIGIN);
317
        }
318
        foreach ($registrations as $reg) {
319
            if( !is_object( $reg ) ) {
320
                throw new \InvalidArgumentException('$registrations of doAuthenticate() method only accepts array of object.');
321
            }
322
323
            if($reg->keyHandle === $response->keyHandle) {
324
                break;
325
            }
326
            $reg = null;
327
        }
328
        if($reg === null) {
329
            throw new Error('No matching registration found', ERR_NO_MATCHING_REGISTRATION );
330
        }
331
        $pemKey = $this->pubkey_to_pem($this->base64u_decode($reg->publicKey));
332
        if($pemKey === null) {
333
            throw new Error('Decoding of public key failed', ERR_PUBKEY_DECODE );
334
        }
335
336
        $signData = $this->base64u_decode($response->signatureData);
337
        $dataToVerify  = hash('sha256', $req->appId, true);
338
        $dataToVerify .= substr($signData, 0, 5);
339
        $dataToVerify .= hash('sha256', $clientData, true);
340
        $signature = substr($signData, 5);
341
342
        if(openssl_verify($dataToVerify, $signature, $pemKey, 'sha256') === 1) {
343
            $upb = unpack("Cupb", substr($signData, 0, 1)); 
344
            if($upb['upb'] !== 1) { 
345
                throw new Error('User presence byte value is invalid', ERR_BAD_USER_PRESENCE );
346
            }
347
            $ctr = unpack("Nctr", substr($signData, 1, 4));
348
            $counter = $ctr['ctr'];
349
            /* TODO: wrap-around should be handled somehow.. */
350
            if($counter > $reg->counter) {
351
                $reg->counter = $counter;
352
                return self::castObjectToRegistration($reg);
353
            } else {
354
                throw new Error('Counter too low.', ERR_COUNTER_TOO_LOW );
355
            }
356
        } else {
357
            throw new Error('Authentication failed', ERR_AUTHENTICATION_FAILURE );
358
        }
359
    }
360
361
    /**
362
     * @param object $object
363
     * @return Registration
364
     */
365
    protected static function castObjectToRegistration($object)
366
    {
367
        $reg = new Registration();
368
        if (property_exists($object, 'publicKey')) {
369
            $reg->publicKey = $object->publicKey;
370
        }
371
        if (property_exists($object, 'certificate')) {
372
            $reg->certificate = $object->certificate;
373
        }
374
        if (property_exists($object, 'counter')) {
375
            $reg->counter = $object->counter;
376
        }
377
        if (property_exists($object, 'keyHandle')) {
378
            $reg->keyHandle = $object->keyHandle;
379
        }
380
        return $reg;
381
    }
382
383
    /**
384
     * @return array
385
     */
386
    private function get_certs()
387
    {
388
        $files = array();
389
        $dir = $this->attestDir;
390
        if(is_dir($dir) && $handle = opendir($dir)) {
391
            while(false !== ($entry = readdir($handle))) {
392
                if(is_file("$dir/$entry")) {
393
                    $files[] = "$dir/$entry";
394
                }
395
            }
396
            closedir($handle);
397
        } elseif (is_file("$dir")) {
398
            $files[] = "$dir";
399
        }
400
        return $files;
401
    }
402
403
    /**
404
     * @param string $data
405
     * @return string
406
     */
407
    private function base64u_encode($data)
408
    {
409
        return trim(strtr(base64_encode($data), '+/', '-_'), '=');
410
    }
411
412
    /**
413
     * @param string $data
414
     * @return string
415
     */
416
    private function base64u_decode($data)
417
    {
418
        return base64_decode(strtr($data, '-_', '+/'));
419
    }
420
421
    /**
422
     * @param string $key
423
     * @return null|string
424
     */
425
    private function pubkey_to_pem($key)
426
    {
427
        if(strlen($key) !== PUBKEY_LEN || $key[0] !== "\x04") {
428
            return null;
429
        }
430
431
        /*
432
         * Convert the public key to binary DER format first
433
         * Using the ECC SubjectPublicKeyInfo OIDs from RFC 5480
434
         *
435
         *  SEQUENCE(2 elem)                        30 59
436
         *   SEQUENCE(2 elem)                       30 13
437
         *    OID1.2.840.10045.2.1 (id-ecPublicKey) 06 07 2a 86 48 ce 3d 02 01
438
         *    OID1.2.840.10045.3.1.7 (secp256r1)    06 08 2a 86 48 ce 3d 03 01 07
439
         *   BIT STRING(520 bit)                    03 42 ..key..
440
         */
441
        $der  = "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01";
442
        $der .= "\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42";
443
        $der .= "\0".$key;
444
445
        $pem  = "-----BEGIN PUBLIC KEY-----\r\n";
446
        $pem .= chunk_split(base64_encode($der), 64);
447
        $pem .= "-----END PUBLIC KEY-----";
448
449
        return $pem;
450
    }
451
452
    /**
453
     * @return string
454
     * @throws Error
455
     */
456
    private function createChallenge()
457
    {
458
        $challenge = random_bytes(32);
459
        $challenge = $this->base64u_encode( $challenge );
460
461
        return $challenge;
462
    }
463
464
    /**
465
     * Fixes a certificate where the signature contains unused bits.
466
     *
467
     * @param string $cert
468
     * @return mixed
469
     */
470
    private function fixSignatureUnusedBits($cert)
471
    {
472
        if(in_array(hash('sha256', $cert), $this->FIXCERTS, true)) {
473
            $cert[strlen($cert) - 257] = "\0";
474
        }
475
        return $cert;
476
    }
477
}
478
479
/**
480
 * Class for building a registration request
481
 *
482
 * @package u2flib_server
483
 */
484
class RegisterRequest
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
485
{
486
    /** @var string Protocol version */
487
    public $version = U2F_VERSION;
488
489
    /** @var string Registration challenge */
490
    public $challenge;
491
492
    /** @var string Application id */
493
    public $appId;
494
495
    /**
496
     * @param string $challenge
497
     * @param string $appId
498
     * @internal
499
     */
500
    public function __construct($challenge, $appId)
501
    {
502
        $this->challenge = $challenge;
503
        $this->appId = $appId;
504
    }
505
}
506
507
/**
508
 * Class for building up an authentication request
509
 *
510
 * @package u2flib_server
511
 */
512
class SignRequest
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
513
{
514
    /** @var string Protocol version */
515
    public $version = U2F_VERSION;
516
517
    /** @var string Authentication challenge */
518
    public $challenge = '';
519
520
    /** @var string Key handle of a registered authenticator */
521
    public $keyHandle = '';
522
523
    /** @var string Application id */
524
    public $appId = '';
525
}
526
527
/**
528
 * Class returned for successful registrations
529
 *
530
 * @package u2flib_server
531
 */
532
class Registration
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
533
{
534
    /** @var string The key handle of the registered authenticator */
535
    public $keyHandle = '';
536
537
    /** @var string The public key of the registered authenticator */
538
    public $publicKey = '';
539
540
    /** @var string The attestation certificate of the registered authenticator */
541
    public $certificate = '';
542
543
    /** @var int The counter associated with this registration */
544
    public $counter = -1;
545
}
546
547
/**
548
 * Error class, returned on errors
549
 *
550
 * @package u2flib_server
551
 */
552
class Error extends \Exception
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
553
{
554
    /**
555
     * Override constructor and make message and code mandatory
556
     * @param string $message
557
     * @param int $code
558
     * @param \Exception|null $previous
559
     */
560
    public function __construct($message, $code, \Exception $previous = null) {
561
        parent::__construct($message, $code, $previous);
562
    }
563
}
564