blob: 6da4c4ae97a9c866f660810900ca76800799c41d [file] [log] [blame]
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001<?php
2
3
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01004namespace lbuchs\WebAuthn\Binary;
5use lbuchs\WebAuthn\WebAuthnException;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006
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 */
13class 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 = $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
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010042 * @return ByteBuffer
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010043 */
44 public static function fromBase64Url($base64url) {
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
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010055 * @return ByteBuffer
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010056 */
57 public static function fromHex($hex) {
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
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010068 * @return ByteBuffer
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010069 */
70 public static function randomBuffer($length) {
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) {
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) {
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
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100100 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
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100108 public function getLength() {
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() {
185 return $this->_data;
186 }
187
188 /**
189 * @param string $buffer
190 * @return bool
191 */
192 public function equals($buffer) {
193 return is_string($this->_data) && $this->_data === $buffer->data;
194 }
195
196 /**
197 * @return string
198 */
199 public function getHex() {
200 return \bin2hex($this->_data);
201 }
202
203 /**
204 * @return bool
205 */
206 public function isEmpty() {
207 return $this->_length === 0;
208 }
209
210
211 /**
212 * jsonSerialize interface
213 * return binary data in RFC 1342-Like serialized string
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100214 * @return string
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100215 */
216 public function jsonSerialize() {
217 if (ByteBuffer::$useBase64UrlEncoding) {
218 return self::_base64url_encode($this->_data);
219
220 } else {
221 return '=?BINARY?B?' . \base64_encode($this->_data) . '?=';
222 }
223 }
224
225 /**
226 * Serializable-Interface
227 * @return string
228 */
229 public function serialize() {
230 return \serialize($this->_data);
231 }
232
233 /**
234 * Serializable-Interface
235 * @param string $serialized
236 */
237 public function unserialize($serialized) {
238 $this->_data = \unserialize($serialized);
239 $this->_length = \strlen($this->_data);
240 }
241
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100242 /**
243 * (PHP 8 deprecates Serializable-Interface)
244 * @return array
245 */
246 public function __serialize() {
247 return [
248 'data' => \serialize($this->_data)
249 ];
250 }
251
252 /**
253 * object to string
254 * @return string
255 */
256 public function __toString() {
257 return $this->getHex();
258 }
259
260 /**
261 * (PHP 8 deprecates Serializable-Interface)
262 * @param array $data
263 * @return void
264 */
265 public function __unserialize($data) {
266 if ($data && isset($data['data'])) {
267 $this->_data = \unserialize($data['data']);
268 $this->_length = \strlen($this->_data);
269 }
270 }
271
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100272 // -----------------------
273 // PROTECTED STATIC
274 // -----------------------
275
276 /**
277 * base64 url decoding
278 * @param string $data
279 * @return string
280 */
281 protected static function _base64url_decode($data) {
282 return \base64_decode(\strtr($data, '-_', '+/') . \str_repeat('=', 3 - (3 + \strlen($data)) % 4));
283 }
284
285 /**
286 * base64 url encoding
287 * @param string $data
288 * @return string
289 */
290 protected static function _base64url_encode($data) {
291 return \rtrim(\strtr(\base64_encode($data), '+/', '-_'), '=');
292 }
293}