1
1
<?php
2
2
3
3
namespace Firebase \JWT ;
4
+
4
5
use \DomainException ;
5
6
use \InvalidArgumentException ;
6
7
use \UnexpectedValueException ;
21
22
*/
22
23
class JWT
23
24
{
25
+ const ASN1_INTEGER = 0x02 ;
26
+ const ASN1_SEQUENCE = 0x10 ;
27
+ const ASN1_BIT_STRING = 0x03 ;
24
28
25
29
/**
26
30
* When checking nbf, iat or expiration times,
@@ -97,6 +101,11 @@ public static function decode($jwt, $key, array $allowed_algs = array())
97
101
if (!in_array ($ header ->alg , $ allowed_algs )) {
98
102
throw new UnexpectedValueException ('Algorithm not allowed ' );
99
103
}
104
+ if ($ header ->alg === 'ES256 ' ) {
105
+ // OpenSSL expects an ASN.1 DER sequence for ES256 signatures
106
+ $ sig = self ::signatureToDER ($ sig );
107
+ }
108
+
100
109
if (is_array ($ key ) || $ key instanceof \ArrayAccess) {
101
110
if (isset ($ header ->kid )) {
102
111
if (!isset ($ key [$ header ->kid ])) {
@@ -192,7 +201,7 @@ public static function sign($msg, $key, $alg = 'HS256')
192
201
throw new DomainException ('Algorithm not supported ' );
193
202
}
194
203
list ($ function , $ algorithm ) = static ::$ supported_algs [$ alg ];
195
- switch ($ function ) {
204
+ switch ($ function ) {
196
205
case 'hash_hmac ' :
197
206
return hash_hmac ($ algorithm , $ msg , $ key , true );
198
207
case 'openssl ' :
@@ -201,6 +210,9 @@ public static function sign($msg, $key, $alg = 'HS256')
201
210
if (!$ success ) {
202
211
throw new DomainException ("OpenSSL unable to sign data " );
203
212
} else {
213
+ if ($ alg === 'ES256 ' ) {
214
+ $ signature = self ::signatureFromDER ($ signature , 256 );
215
+ }
204
216
return $ signature ;
205
217
}
206
218
}
@@ -226,7 +238,7 @@ private static function verify($msg, $signature, $key, $alg)
226
238
}
227
239
228
240
list ($ function , $ algorithm ) = static ::$ supported_algs [$ alg ];
229
- switch ($ function ) {
241
+ switch ($ function ) {
230
242
case 'openssl ' :
231
243
$ success = openssl_verify ($ msg , $ signature , $ key , $ algorithm );
232
244
if ($ success === 1 ) {
@@ -377,4 +389,126 @@ private static function safeStrlen($str)
377
389
}
378
390
return strlen ($ str );
379
391
}
392
+
393
+ /**
394
+ * Convert an ECDSA signature to an ASN.1 DER sequence
395
+ *
396
+ * @param string $sig The ECDSA signature to convert
397
+ * @return string The encoded DER object
398
+ */
399
+ private static function signatureToDER ($ sig )
400
+ {
401
+ // Separate the signature into r-value and s-value
402
+ list ($ r , $ s ) = str_split ($ sig , (int ) (strlen ($ sig ) / 2 ));
403
+
404
+ // Trim leading zeros
405
+ $ r = ltrim ($ r , "\x00" );
406
+ $ s = ltrim ($ s , "\x00" );
407
+
408
+ // Convert r-value and s-value from unsigned big-endian integers to
409
+ // signed two's complement
410
+ if (ord ($ r [0 ]) > 0x7f ) {
411
+ $ r = "\x00" . $ r ;
412
+ }
413
+ if (ord ($ s [0 ]) > 0x7f ) {
414
+ $ s = "\x00" . $ s ;
415
+ }
416
+
417
+ return self ::encodeDER (
418
+ self ::ASN1_SEQUENCE ,
419
+ self ::encodeDER (self ::ASN1_INTEGER , $ r ) .
420
+ self ::encodeDER (self ::ASN1_INTEGER , $ s )
421
+ );
422
+ }
423
+
424
+ /**
425
+ * Encodes a value into a DER object.
426
+ *
427
+ * @param int $type DER tag
428
+ * @param string $value the value to encode
429
+ * @return string the encoded object
430
+ */
431
+ private static function encodeDER ($ type , $ value )
432
+ {
433
+ $ tag_header = 0 ;
434
+ if ($ type === self ::ASN1_SEQUENCE ) {
435
+ $ tag_header |= 0x20 ;
436
+ }
437
+
438
+ // Type
439
+ $ der = chr ($ tag_header | $ type );
440
+
441
+ // Length
442
+ $ der .= chr (strlen ($ value ));
443
+
444
+ return $ der . $ value ;
445
+ }
446
+
447
+ /**
448
+ * Encodes signature from a DER object.
449
+ *
450
+ * @param string $der binary signature in DER format
451
+ * @param int $keySize the nubmer of bits in the key
452
+ * @return string the signature
453
+ */
454
+ private static function signatureFromDER ($ der , $ keySize )
455
+ {
456
+ // OpenSSL returns the ECDSA signatures as a binary ASN.1 DER SEQUENCE
457
+ list ($ offset , $ _ ) = self ::readDER ($ der );
458
+ list ($ offset , $ r ) = self ::readDER ($ der , $ offset );
459
+ list ($ offset , $ s ) = self ::readDER ($ der , $ offset );
460
+
461
+ // Convert r-value and s-value from signed two's compliment to unsigned
462
+ // big-endian integers
463
+ $ r = ltrim ($ r , "\x00" );
464
+ $ s = ltrim ($ s , "\x00" );
465
+
466
+ // Pad out r and s so that they are $keySize bits long
467
+ $ r = str_pad ($ r , $ keySize / 8 , "\x00" , STR_PAD_LEFT );
468
+ $ s = str_pad ($ s , $ keySize / 8 , "\x00" , STR_PAD_LEFT );
469
+
470
+ return $ r . $ s ;
471
+ }
472
+
473
+ /**
474
+ * Reads binary DER-encoded data and decodes into a single object
475
+ *
476
+ * @param string $der the binary data in DER format
477
+ * @param int $offset the offset of the data stream containing the object
478
+ * to decode
479
+ * @return array [$offset, $data] the new offset and the decoded object
480
+ */
481
+ private static function readDER ($ der , $ offset = 0 )
482
+ {
483
+ $ pos = $ offset ;
484
+ $ size = strlen ($ der );
485
+ $ constructed = (ord ($ der [$ pos ]) >> 5 ) & 0x01 ;
486
+ $ type = ord ($ der [$ pos ++]) & 0x1f ;
487
+
488
+ // Length
489
+ $ len = ord ($ der [$ pos ++]);
490
+ if ($ len & 0x80 ) {
491
+ $ n = $ len & 0x1f ;
492
+ $ len = 0 ;
493
+ while ($ n -- && $ pos < $ size ) {
494
+ $ len = ($ len << 8 ) | ord ($ der [$ pos ++]);
495
+ }
496
+ }
497
+
498
+ // Value
499
+ if ($ type == self ::ASN1_BIT_STRING ) {
500
+ $ pos ++; // Skip the first contents octet (padding indicator)
501
+ $ data = substr ($ der , $ pos , $ len - 1 );
502
+ if (!$ ignore_bit_strings ) {
503
+ $ pos += $ len - 1 ;
504
+ }
505
+ } elseif (!$ constructed ) {
506
+ $ data = substr ($ der , $ pos , $ len );
507
+ $ pos += $ len ;
508
+ } else {
509
+ $ data = null ;
510
+ }
511
+
512
+ return array ($ pos , $ data );
513
+ }
380
514
}
0 commit comments