This repository was archived by the owner on Mar 25, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathRegisterResponse.php
More file actions
161 lines (141 loc) · 5.08 KB
/
RegisterResponse.php
File metadata and controls
161 lines (141 loc) · 5.08 KB
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
<?php
declare(strict_types=1);
namespace Firehed\U2F;
use Firehed\U2F\InvalidDataException as IDE;
/**
* @deprecated U2F support is being removed. Migrate to WebAuthn flows.
*/
class RegisterResponse implements RegistrationResponseInterface
{
use ResponseTrait;
/** @var AttestationCertificate */
private $cert;
/** @var PublicKeyInterface */
private $pubKey;
/**
* @param array{
* registrationData: string,
* version: string,
* chalenge: string,
* appId: string,
* clientData: string,
* } $response
*/
protected function parseResponse(array $response): self
{
$this->validateKeyInArray('registrationData', $response);
// Binary string as defined by
// U2F 1.0 Raw Message Format Sec. 4.3
// https://fidoalliance.org/specs/fido-u2f-v1.0-nfc-bt-amendment-20150514/fido-u2f-raw-message-formats.html#registration-response-message-success
$regData = fromBase64Web($response['registrationData']);
// Basic fixed length check
if (strlen($regData) < 67) {
throw new IDE(
IDE::MALFORMED_DATA,
'registrationData is missing information'
);
}
$offset = 0; // Number of bytes read so far (think fread/fseek)
$reserved = ord($regData[$offset]);
if ($reserved !== 5) {
throw new IDE(
IDE::MALFORMED_DATA,
'reserved byte'
);
}
$offset += 1;
$this->pubKey = new ECPublicKey(substr($regData, $offset, 65));
$offset += 65;
$keyHandleLength = ord($regData[$offset]);
$offset += 1;
// Dynamic length check through key handle
if (strlen($regData) < $offset+$keyHandleLength) {
throw new IDE(
IDE::MALFORMED_DATA,
'key handle length'
);
}
$this->setKeyHandle(substr($regData, $offset, $keyHandleLength));
$offset += $keyHandleLength;
// (Notes are 0-indexed)
// If byte 0 & 0x1F = 0x10, it's a sequence where the next byte
// determines length (if not, this is not the start of a certificate)
//
// If the length byte (byte 1) & 0x80 = 0x80, then the following
// (byte 1 ^ 0x80) bytes are the remaining length of the sequence. If
// not, then the legnth byte alone is correct. I.e. > 128 low 7 bits
// are the byte count for length; <=127 then it is the length.
//
// https://msdn.microsoft.com/en-us/library/bb648645(v=vs.85).aspx
$remain = substr($regData, $offset);
$b0 = ord($remain[0]);
if (($b0 & 0x1F) != 0x10) {
throw new IDE(
IDE::MALFORMED_DATA,
'starting byte of attestation certificate'
);
}
$length = ord($remain[1]);
if (($length & 0x80) == 0x80) {
$needed = $length ^ 0x80;
if ($needed > 4) {
// This would be a >4GB cert, reject it out of hand
throw new IDE(
IDE::MALFORMED_DATA,
'certificate length'
);
}
$bytes = 0;
// Start 2 bytes in, for SEQUENCE and its LENGTH
for ($i = 2; $i < $needed+2; $i++) {
$bytes <<= 8; // shift running total left 8 bytes
$byte = ord($remain[$i]); // grab next byte
$bytes |= $byte; // OR in that byte
}
$length = $bytes + $needed + 2;
}
// Sanity check the length against the remainder of the registration
// data, in case a malformed cert was provided to trigger an overflow
// during parsing
if ($length + $offset > strlen($regData)) {
throw new IDE(
IDE::MALFORMED_DATA,
'certificate and sigature length'
);
}
$cert = new AttestationCertificate(substr($regData, $offset, $length));
$this->cert = $cert;
$offset += $length;
// All remaining data is the signature
$this->setSignature(substr($regData, $offset));
return $this;
}
public function getSignedData(): string
{
// https://fidoalliance.org/specs/fido-u2f-v1.0-nfc-bt-amendment-20150514/fido-u2f-raw-message-formats.html#fig-authentication-request-message
return sprintf(
'%s%s%s%s%s',
chr(0),
$this->clientData->getApplicationParameter(),
$this->clientData->getChallengeParameter(),
$this->getKeyHandleBinary(),
$this->pubKey->getBinary()
);
}
public function getRpIdHash(): string
{
return $this->clientData->getApplicationParameter();
}
public function getAttestationCertificate(): AttestationCertificateInterface
{
return $this->cert;
}
public function getChallenge(): string
{
return $this->clientData->getChallenge();
}
public function getPublicKey(): PublicKeyInterface
{
return $this->pubKey;
}
}