11<?php
22
33namespace Firebase \JWT ;
4+
45use \DomainException ;
56use \InvalidArgumentException ;
67use \UnexpectedValueException ;
2122 */
2223class JWT
2324{
25+ const ASN1_INTEGER = 0x02 ;
26+ const ASN1_SEQUENCE = 0x10 ;
27+ const ASN1_BIT_STRING = 0x03 ;
2428
2529 /**
2630 * When checking nbf, iat or expiration times,
@@ -97,6 +101,11 @@ public static function decode($jwt, $key, array $allowed_algs = array())
97101 if (!in_array ($ header ->alg , $ allowed_algs )) {
98102 throw new UnexpectedValueException ('Algorithm not allowed ' );
99103 }
104+ if ($ header ->alg === 'ES256 ' ) {
105+ // OpenSSL expects an ASN.1 DER sequence for ES256 signatures
106+ $ sig = self ::signatureToDER ($ sig );
107+ }
108+
100109 if (is_array ($ key ) || $ key instanceof \ArrayAccess) {
101110 if (isset ($ header ->kid )) {
102111 if (!isset ($ key [$ header ->kid ])) {
@@ -192,7 +201,7 @@ public static function sign($msg, $key, $alg = 'HS256')
192201 throw new DomainException ('Algorithm not supported ' );
193202 }
194203 list ($ function , $ algorithm ) = static ::$ supported_algs [$ alg ];
195- switch ($ function ) {
204+ switch ($ function ) {
196205 case 'hash_hmac ' :
197206 return hash_hmac ($ algorithm , $ msg , $ key , true );
198207 case 'openssl ' :
@@ -201,6 +210,9 @@ public static function sign($msg, $key, $alg = 'HS256')
201210 if (!$ success ) {
202211 throw new DomainException ("OpenSSL unable to sign data " );
203212 } else {
213+ if ($ alg === 'ES256 ' ) {
214+ $ signature = self ::signatureFromDER ($ signature , 256 );
215+ }
204216 return $ signature ;
205217 }
206218 }
@@ -226,7 +238,7 @@ private static function verify($msg, $signature, $key, $alg)
226238 }
227239
228240 list ($ function , $ algorithm ) = static ::$ supported_algs [$ alg ];
229- switch ($ function ) {
241+ switch ($ function ) {
230242 case 'openssl ' :
231243 $ success = openssl_verify ($ msg , $ signature , $ key , $ algorithm );
232244 if ($ success === 1 ) {
@@ -377,4 +389,126 @@ private static function safeStrlen($str)
377389 }
378390 return strlen ($ str );
379391 }
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+ }
380514}
0 commit comments