blob: dd0eec7b2f6caf4ea2d631cc2af425f8e42306ed [file] [log] [blame]
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001<?php
2
3
4namespace WebAuthn\Binary;
5use 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 */
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
42 * @return \WebAuthn\Binary\ByteBuffer
43 */
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
55 * @return \WebAuthn\Binary\ByteBuffer
56 */
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
68 * @return \WebAuthn\Binary\ByteBuffer
69 */
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
100 public function getLength() {
101 return $this->_length;
102 }
103
104 public function getUint16Val($offset) {
105 if ($offset < 0 || ($offset + 2) > $this->_length) {
106 throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER);
107 }
108 return unpack('n', $this->_data, $offset)[1];
109 }
110
111 public function getUint32Val($offset) {
112 if ($offset < 0 || ($offset + 4) > $this->_length) {
113 throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER);
114 }
115 $val = unpack('N', $this->_data, $offset)[1];
116
117 // Signed integer overflow causes signed negative numbers
118 if ($val < 0) {
119 throw new WebAuthnException('ByteBuffer: Value out of integer range.', WebAuthnException::BYTEBUFFER);
120 }
121 return $val;
122 }
123
124 public function getUint64Val($offset) {
125 if (PHP_INT_SIZE < 8) {
126 throw new WebAuthnException('ByteBuffer: 64-bit values not supported by this system', WebAuthnException::BYTEBUFFER);
127 }
128 if ($offset < 0 || ($offset + 8) > $this->_length) {
129 throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER);
130 }
131 $val = unpack('J', $this->_data, $offset)[1];
132
133 // Signed integer overflow causes signed negative numbers
134 if ($val < 0) {
135 throw new WebAuthnException('ByteBuffer: Value out of integer range.', WebAuthnException::BYTEBUFFER);
136 }
137
138 return $val;
139 }
140
141 public function getHalfFloatVal($offset) {
142 //FROM spec pseudo decode_half(unsigned char *halfp)
143 $half = $this->getUint16Val($offset);
144
145 $exp = ($half >> 10) & 0x1f;
146 $mant = $half & 0x3ff;
147
148 if ($exp === 0) {
149 $val = $mant * (2 ** -24);
150 } elseif ($exp !== 31) {
151 $val = ($mant + 1024) * (2 ** ($exp - 25));
152 } else {
153 $val = ($mant === 0) ? INF : NAN;
154 }
155
156 return ($half & 0x8000) ? -$val : $val;
157 }
158
159 public function getFloatVal($offset) {
160 if ($offset < 0 || ($offset + 4) > $this->_length) {
161 throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER);
162 }
163 return unpack('G', $this->_data, $offset)[1];
164 }
165
166 public function getDoubleVal($offset) {
167 if ($offset < 0 || ($offset + 8) > $this->_length) {
168 throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER);
169 }
170 return unpack('E', $this->_data, $offset)[1];
171 }
172
173 /**
174 * @return string
175 */
176 public function getBinaryString() {
177 return $this->_data;
178 }
179
180 /**
181 * @param string $buffer
182 * @return bool
183 */
184 public function equals($buffer) {
185 return is_string($this->_data) && $this->_data === $buffer->data;
186 }
187
188 /**
189 * @return string
190 */
191 public function getHex() {
192 return \bin2hex($this->_data);
193 }
194
195 /**
196 * @return bool
197 */
198 public function isEmpty() {
199 return $this->_length === 0;
200 }
201
202
203 /**
204 * jsonSerialize interface
205 * return binary data in RFC 1342-Like serialized string
206 * @return \stdClass
207 */
208 public function jsonSerialize() {
209 if (ByteBuffer::$useBase64UrlEncoding) {
210 return self::_base64url_encode($this->_data);
211
212 } else {
213 return '=?BINARY?B?' . \base64_encode($this->_data) . '?=';
214 }
215 }
216
217 /**
218 * Serializable-Interface
219 * @return string
220 */
221 public function serialize() {
222 return \serialize($this->_data);
223 }
224
225 /**
226 * Serializable-Interface
227 * @param string $serialized
228 */
229 public function unserialize($serialized) {
230 $this->_data = \unserialize($serialized);
231 $this->_length = \strlen($this->_data);
232 }
233
234 // -----------------------
235 // PROTECTED STATIC
236 // -----------------------
237
238 /**
239 * base64 url decoding
240 * @param string $data
241 * @return string
242 */
243 protected static function _base64url_decode($data) {
244 return \base64_decode(\strtr($data, '-_', '+/') . \str_repeat('=', 3 - (3 + \strlen($data)) % 4));
245 }
246
247 /**
248 * base64 url encoding
249 * @param string $data
250 * @return string
251 */
252 protected static function _base64url_encode($data) {
253 return \rtrim(\strtr(\base64_encode($data), '+/', '-_'), '=');
254 }
255}