1 |
efrain |
1 |
<?php
|
|
|
2 |
|
|
|
3 |
|
|
|
4 |
namespace lbuchs\WebAuthn\Binary;
|
|
|
5 |
use lbuchs\WebAuthn\WebAuthnException;
|
|
|
6 |
|
|
|
7 |
/**
|
|
|
8 |
* Modified version of https://github.com/madwizard-thomas/webauthn-server/blob/master/src/Format/ByteBuffer.php
|
|
|
9 |
* Copyright © 2018 Thomas Bleeker - MIT licensed
|
|
|
10 |
* Modified by Lukas Buchs
|
|
|
11 |
* Thanks Thomas for your work!
|
|
|
12 |
*/
|
|
|
13 |
class ByteBuffer implements \JsonSerializable, \Serializable {
|
|
|
14 |
/**
|
|
|
15 |
* @var bool
|
|
|
16 |
*/
|
|
|
17 |
public static $useBase64UrlEncoding = false;
|
|
|
18 |
|
|
|
19 |
/**
|
|
|
20 |
* @var string
|
|
|
21 |
*/
|
|
|
22 |
private $_data;
|
|
|
23 |
|
|
|
24 |
/**
|
|
|
25 |
* @var int
|
|
|
26 |
*/
|
|
|
27 |
private $_length;
|
|
|
28 |
|
|
|
29 |
public function __construct($binaryData) {
|
|
|
30 |
$this->_data = (string)$binaryData;
|
|
|
31 |
$this->_length = \strlen($binaryData);
|
|
|
32 |
}
|
|
|
33 |
|
|
|
34 |
|
|
|
35 |
// -----------------------
|
|
|
36 |
// PUBLIC STATIC
|
|
|
37 |
// -----------------------
|
|
|
38 |
|
|
|
39 |
/**
|
|
|
40 |
* create a ByteBuffer from a base64 url encoded string
|
|
|
41 |
* @param string $base64url
|
|
|
42 |
* @return ByteBuffer
|
|
|
43 |
*/
|
|
|
44 |
public static function fromBase64Url($base64url): ByteBuffer {
|
|
|
45 |
$bin = self::_base64url_decode($base64url);
|
|
|
46 |
if ($bin === false) {
|
|
|
47 |
throw new WebAuthnException('ByteBuffer: Invalid base64 url string', WebAuthnException::BYTEBUFFER);
|
|
|
48 |
}
|
|
|
49 |
return new ByteBuffer($bin);
|
|
|
50 |
}
|
|
|
51 |
|
|
|
52 |
/**
|
|
|
53 |
* create a ByteBuffer from a base64 url encoded string
|
|
|
54 |
* @param string $hex
|
|
|
55 |
* @return ByteBuffer
|
|
|
56 |
*/
|
|
|
57 |
public static function fromHex($hex): ByteBuffer {
|
|
|
58 |
$bin = \hex2bin($hex);
|
|
|
59 |
if ($bin === false) {
|
|
|
60 |
throw new WebAuthnException('ByteBuffer: Invalid hex string', WebAuthnException::BYTEBUFFER);
|
|
|
61 |
}
|
|
|
62 |
return new ByteBuffer($bin);
|
|
|
63 |
}
|
|
|
64 |
|
|
|
65 |
/**
|
|
|
66 |
* create a random ByteBuffer
|
|
|
67 |
* @param string $length
|
|
|
68 |
* @return ByteBuffer
|
|
|
69 |
*/
|
|
|
70 |
public static function randomBuffer($length): ByteBuffer {
|
|
|
71 |
if (\function_exists('random_bytes')) { // >PHP 7.0
|
|
|
72 |
return new ByteBuffer(\random_bytes($length));
|
|
|
73 |
|
|
|
74 |
} else if (\function_exists('openssl_random_pseudo_bytes')) {
|
|
|
75 |
return new ByteBuffer(\openssl_random_pseudo_bytes($length));
|
|
|
76 |
|
|
|
77 |
} else {
|
|
|
78 |
throw new WebAuthnException('ByteBuffer: cannot generate random bytes', WebAuthnException::BYTEBUFFER);
|
|
|
79 |
}
|
|
|
80 |
}
|
|
|
81 |
|
|
|
82 |
// -----------------------
|
|
|
83 |
// PUBLIC
|
|
|
84 |
// -----------------------
|
|
|
85 |
|
|
|
86 |
public function getBytes($offset, $length): string {
|
|
|
87 |
if ($offset < 0 || $length < 0 || ($offset + $length > $this->_length)) {
|
|
|
88 |
throw new WebAuthnException('ByteBuffer: Invalid offset or length', WebAuthnException::BYTEBUFFER);
|
|
|
89 |
}
|
|
|
90 |
return \substr($this->_data, $offset, $length);
|
|
|
91 |
}
|
|
|
92 |
|
|
|
93 |
public function getByteVal($offset): int {
|
|
|
94 |
if ($offset < 0 || $offset >= $this->_length) {
|
|
|
95 |
throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER);
|
|
|
96 |
}
|
|
|
97 |
return \ord(\substr($this->_data, $offset, 1));
|
|
|
98 |
}
|
|
|
99 |
|
|
|
100 |
public function getJson($jsonFlags=0) {
|
|
|
101 |
$data = \json_decode($this->getBinaryString(), null, 512, $jsonFlags);
|
|
|
102 |
if (\json_last_error() !== JSON_ERROR_NONE) {
|
|
|
103 |
throw new WebAuthnException(\json_last_error_msg(), WebAuthnException::BYTEBUFFER);
|
|
|
104 |
}
|
|
|
105 |
return $data;
|
|
|
106 |
}
|
|
|
107 |
|
|
|
108 |
public function getLength(): int {
|
|
|
109 |
return $this->_length;
|
|
|
110 |
}
|
|
|
111 |
|
|
|
112 |
public function getUint16Val($offset) {
|
|
|
113 |
if ($offset < 0 || ($offset + 2) > $this->_length) {
|
|
|
114 |
throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER);
|
|
|
115 |
}
|
|
|
116 |
return unpack('n', $this->_data, $offset)[1];
|
|
|
117 |
}
|
|
|
118 |
|
|
|
119 |
public function getUint32Val($offset) {
|
|
|
120 |
if ($offset < 0 || ($offset + 4) > $this->_length) {
|
|
|
121 |
throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER);
|
|
|
122 |
}
|
|
|
123 |
$val = unpack('N', $this->_data, $offset)[1];
|
|
|
124 |
|
|
|
125 |
// Signed integer overflow causes signed negative numbers
|
|
|
126 |
if ($val < 0) {
|
|
|
127 |
throw new WebAuthnException('ByteBuffer: Value out of integer range.', WebAuthnException::BYTEBUFFER);
|
|
|
128 |
}
|
|
|
129 |
return $val;
|
|
|
130 |
}
|
|
|
131 |
|
|
|
132 |
public function getUint64Val($offset) {
|
|
|
133 |
if (PHP_INT_SIZE < 8) {
|
|
|
134 |
throw new WebAuthnException('ByteBuffer: 64-bit values not supported by this system', WebAuthnException::BYTEBUFFER);
|
|
|
135 |
}
|
|
|
136 |
if ($offset < 0 || ($offset + 8) > $this->_length) {
|
|
|
137 |
throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER);
|
|
|
138 |
}
|
|
|
139 |
$val = unpack('J', $this->_data, $offset)[1];
|
|
|
140 |
|
|
|
141 |
// Signed integer overflow causes signed negative numbers
|
|
|
142 |
if ($val < 0) {
|
|
|
143 |
throw new WebAuthnException('ByteBuffer: Value out of integer range.', WebAuthnException::BYTEBUFFER);
|
|
|
144 |
}
|
|
|
145 |
|
|
|
146 |
return $val;
|
|
|
147 |
}
|
|
|
148 |
|
|
|
149 |
public function getHalfFloatVal($offset) {
|
|
|
150 |
//FROM spec pseudo decode_half(unsigned char *halfp)
|
|
|
151 |
$half = $this->getUint16Val($offset);
|
|
|
152 |
|
|
|
153 |
$exp = ($half >> 10) & 0x1f;
|
|
|
154 |
$mant = $half & 0x3ff;
|
|
|
155 |
|
|
|
156 |
if ($exp === 0) {
|
|
|
157 |
$val = $mant * (2 ** -24);
|
|
|
158 |
} elseif ($exp !== 31) {
|
|
|
159 |
$val = ($mant + 1024) * (2 ** ($exp - 25));
|
|
|
160 |
} else {
|
|
|
161 |
$val = ($mant === 0) ? INF : NAN;
|
|
|
162 |
}
|
|
|
163 |
|
|
|
164 |
return ($half & 0x8000) ? -$val : $val;
|
|
|
165 |
}
|
|
|
166 |
|
|
|
167 |
public function getFloatVal($offset) {
|
|
|
168 |
if ($offset < 0 || ($offset + 4) > $this->_length) {
|
|
|
169 |
throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER);
|
|
|
170 |
}
|
|
|
171 |
return unpack('G', $this->_data, $offset)[1];
|
|
|
172 |
}
|
|
|
173 |
|
|
|
174 |
public function getDoubleVal($offset) {
|
|
|
175 |
if ($offset < 0 || ($offset + 8) > $this->_length) {
|
|
|
176 |
throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER);
|
|
|
177 |
}
|
|
|
178 |
return unpack('E', $this->_data, $offset)[1];
|
|
|
179 |
}
|
|
|
180 |
|
|
|
181 |
/**
|
|
|
182 |
* @return string
|
|
|
183 |
*/
|
|
|
184 |
public function getBinaryString(): string {
|
|
|
185 |
return $this->_data;
|
|
|
186 |
}
|
|
|
187 |
|
|
|
188 |
/**
|
|
|
189 |
* @param string|ByteBuffer $buffer
|
|
|
190 |
* @return bool
|
|
|
191 |
*/
|
|
|
192 |
public function equals($buffer): bool {
|
|
|
193 |
if (is_object($buffer) && $buffer instanceof ByteBuffer) {
|
|
|
194 |
return $buffer->getBinaryString() === $this->getBinaryString();
|
|
|
195 |
|
|
|
196 |
} else if (is_string($buffer)) {
|
|
|
197 |
return $buffer === $this->getBinaryString();
|
|
|
198 |
}
|
|
|
199 |
|
|
|
200 |
return false;
|
|
|
201 |
}
|
|
|
202 |
|
|
|
203 |
/**
|
|
|
204 |
* @return string
|
|
|
205 |
*/
|
|
|
206 |
public function getHex(): string {
|
|
|
207 |
return \bin2hex($this->_data);
|
|
|
208 |
}
|
|
|
209 |
|
|
|
210 |
/**
|
|
|
211 |
* @return bool
|
|
|
212 |
*/
|
|
|
213 |
public function isEmpty(): bool {
|
|
|
214 |
return $this->_length === 0;
|
|
|
215 |
}
|
|
|
216 |
|
|
|
217 |
|
|
|
218 |
/**
|
|
|
219 |
* jsonSerialize interface
|
|
|
220 |
* return binary data in RFC 1342-Like serialized string
|
|
|
221 |
* @return string
|
|
|
222 |
*/
|
|
|
223 |
public function jsonSerialize(): string {
|
|
|
224 |
if (ByteBuffer::$useBase64UrlEncoding) {
|
|
|
225 |
return self::_base64url_encode($this->_data);
|
|
|
226 |
|
|
|
227 |
} else {
|
|
|
228 |
return '=?BINARY?B?' . \base64_encode($this->_data) . '?=';
|
|
|
229 |
}
|
|
|
230 |
}
|
|
|
231 |
|
|
|
232 |
/**
|
|
|
233 |
* Serializable-Interface
|
|
|
234 |
* @return string
|
|
|
235 |
*/
|
|
|
236 |
public function serialize(): string {
|
|
|
237 |
return \serialize($this->_data);
|
|
|
238 |
}
|
|
|
239 |
|
|
|
240 |
/**
|
|
|
241 |
* Serializable-Interface
|
|
|
242 |
* @param string $serialized
|
|
|
243 |
*/
|
|
|
244 |
public function unserialize($serialized) {
|
|
|
245 |
$this->_data = \unserialize($serialized);
|
|
|
246 |
$this->_length = \strlen($this->_data);
|
|
|
247 |
}
|
|
|
248 |
|
|
|
249 |
/**
|
|
|
250 |
* (PHP 8 deprecates Serializable-Interface)
|
|
|
251 |
* @return array
|
|
|
252 |
*/
|
|
|
253 |
public function __serialize(): array {
|
|
|
254 |
return [
|
|
|
255 |
'data' => \serialize($this->_data)
|
|
|
256 |
];
|
|
|
257 |
}
|
|
|
258 |
|
|
|
259 |
/**
|
|
|
260 |
* object to string
|
|
|
261 |
* @return string
|
|
|
262 |
*/
|
|
|
263 |
public function __toString(): string {
|
|
|
264 |
return $this->getHex();
|
|
|
265 |
}
|
|
|
266 |
|
|
|
267 |
/**
|
|
|
268 |
* (PHP 8 deprecates Serializable-Interface)
|
|
|
269 |
* @param array $data
|
|
|
270 |
* @return void
|
|
|
271 |
*/
|
|
|
272 |
public function __unserialize($data) {
|
|
|
273 |
if ($data && isset($data['data'])) {
|
|
|
274 |
$this->_data = \unserialize($data['data']);
|
|
|
275 |
$this->_length = \strlen($this->_data);
|
|
|
276 |
}
|
|
|
277 |
}
|
|
|
278 |
|
|
|
279 |
// -----------------------
|
|
|
280 |
// PROTECTED STATIC
|
|
|
281 |
// -----------------------
|
|
|
282 |
|
|
|
283 |
/**
|
|
|
284 |
* base64 url decoding
|
|
|
285 |
* @param string $data
|
|
|
286 |
* @return string
|
|
|
287 |
*/
|
|
|
288 |
protected static function _base64url_decode($data): string {
|
|
|
289 |
return \base64_decode(\strtr($data, '-_', '+/') . \str_repeat('=', 3 - (3 + \strlen($data)) % 4));
|
|
|
290 |
}
|
|
|
291 |
|
|
|
292 |
/**
|
|
|
293 |
* base64 url encoding
|
|
|
294 |
* @param string $data
|
|
|
295 |
* @return string
|
|
|
296 |
*/
|
|
|
297 |
protected static function _base64url_encode($data): string {
|
|
|
298 |
return \rtrim(\strtr(\base64_encode($data), '+/', '-_'), '=');
|
|
|
299 |
}
|
|
|
300 |
}
|