Skip to content

Commit 05550b9

Browse files
authored
Merge pull request #1635 from terrafrost/moosa-1.0
cleanup RSA PKCS#1 v1.5 signature verification (CVE-2021-30130)
2 parents 2d6705d + 149b4d2 commit 05550b9

File tree

4 files changed

+187
-20
lines changed

4 files changed

+187
-20
lines changed

phpseclib/Crypt/RSA.php

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3001,6 +3001,59 @@ function _emsa_pkcs1_v1_5_encode($m, $emLen)
30013001
return $em;
30023002
}
30033003

3004+
/**
3005+
* EMSA-PKCS1-V1_5-ENCODE (without NULL)
3006+
*
3007+
* Quoting https://tools.ietf.org/html/rfc8017#page-65,
3008+
*
3009+
* "The parameters field associated with id-sha1, id-sha224, id-sha256,
3010+
* id-sha384, id-sha512, id-sha512/224, and id-sha512/256 should
3011+
* generally be omitted, but if present, it shall have a value of type
3012+
* NULL"
3013+
*
3014+
* @access private
3015+
* @param string $m
3016+
* @param int $emLen
3017+
* @return string
3018+
*/
3019+
function _emsa_pkcs1_v1_5_encode_without_null($m, $emLen)
3020+
{
3021+
$h = $this->hash->hash($m);
3022+
if ($h === false) {
3023+
return false;
3024+
}
3025+
3026+
switch ($this->hashName) {
3027+
case 'sha1':
3028+
$t = pack('H*', '301f300706052b0e03021a0414');
3029+
break;
3030+
case 'sha256':
3031+
$t = pack('H*', '302f300b06096086480165030402010420');
3032+
break;
3033+
case 'sha384':
3034+
$t = pack('H*', '303f300b06096086480165030402020430');
3035+
break;
3036+
case 'sha512':
3037+
$t = pack('H*', '304f300b06096086480165030402030440');
3038+
break;
3039+
default:
3040+
return false;
3041+
}
3042+
$t.= $h;
3043+
$tLen = strlen($t);
3044+
3045+
if ($emLen < $tLen + 11) {
3046+
user_error('Intended encoded message length too short');
3047+
return false;
3048+
}
3049+
3050+
$ps = str_repeat(chr(0xFF), $emLen - $tLen - 3);
3051+
3052+
$em = "\0\1$ps\0$t";
3053+
3054+
return $em;
3055+
}
3056+
30043057
/**
30053058
* RSASSA-PKCS1-V1_5-SIGN
30063059
*
@@ -3067,13 +3120,17 @@ function _rsassa_pkcs1_v1_5_verify($m, $s)
30673120
// EMSA-PKCS1-v1_5 encoding
30683121

30693122
$em2 = $this->_emsa_pkcs1_v1_5_encode($m, $this->k);
3070-
if ($em2 === false) {
3123+
$em3 = $this->_emsa_pkcs1_v1_5_encode_without_null($m, $this->k);
3124+
3125+
if ($em2 === false && $em3 === false) {
30713126
user_error('RSA modulus too short');
30723127
return false;
30733128
}
30743129

30753130
// Compare
3076-
return $this->_equals($em, $em2);
3131+
3132+
return ($em2 !== false && $this->_equals($em, $em2)) ||
3133+
($em3 !== false && $this->_equals($em, $em3));
30773134
}
30783135

30793136
/**

phpseclib/File/ASN1.php

Lines changed: 50 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ function _decode_ber($encoded, $start = 0, $encoded_pos = 0)
317317
$current = array('start' => $start);
318318

319319
$type = ord($encoded[$encoded_pos++]);
320-
$start++;
320+
$startOffset = 1;
321321

322322
$constructed = ($type >> 5) & 1;
323323

@@ -327,13 +327,20 @@ function _decode_ber($encoded, $start = 0, $encoded_pos = 0)
327327
// process septets (since the eighth bit is ignored, it's not an octet)
328328
do {
329329
$temp = ord($encoded[$encoded_pos++]);
330+
$startOffset++;
330331
$loop = $temp >> 7;
331332
$tag <<= 7;
332-
$tag |= $temp & 0x7F;
333-
$start++;
333+
$temp &= 0x7F;
334+
// "bits 7 to 1 of the first subsequent octet shall not all be zero"
335+
if ($startOffset == 2 && $temp == 0) {
336+
return false;
337+
}
338+
$tag |= $temp;
334339
} while ($loop);
335340
}
336341

342+
$start+= $startOffset;
343+
337344
// Length, as discussed in paragraph 8.1.3 of X.690-0207.pdf#page=13
338345
$length = ord($encoded[$encoded_pos++]);
339346
$start++;
@@ -426,13 +433,16 @@ function _decode_ber($encoded, $start = 0, $encoded_pos = 0)
426433
switch ($tag) {
427434
case FILE_ASN1_TYPE_BOOLEAN:
428435
// "The contents octets shall consist of a single octet." -- paragraph 8.2.1
429-
//if (strlen($content) != 1) {
430-
// return false;
431-
//}
436+
if ($constructed || strlen($content) != 1) {
437+
return false;
438+
}
432439
$current['content'] = (bool) ord($content[$content_pos]);
433440
break;
434441
case FILE_ASN1_TYPE_INTEGER:
435442
case FILE_ASN1_TYPE_ENUMERATED:
443+
if ($constructed) {
444+
return false;
445+
}
436446
$current['content'] = new Math_BigInteger(substr($content, $content_pos), -256);
437447
break;
438448
case FILE_ASN1_TYPE_REAL: // not currently supported
@@ -452,15 +462,15 @@ function _decode_ber($encoded, $start = 0, $encoded_pos = 0)
452462
$last = count($temp) - 1;
453463
for ($i = 0; $i < $last; $i++) {
454464
// all subtags should be bit strings
455-
//if ($temp[$i]['type'] != FILE_ASN1_TYPE_BIT_STRING) {
456-
// return false;
457-
//}
465+
if ($temp[$i]['type'] != FILE_ASN1_TYPE_BIT_STRING) {
466+
return false;
467+
}
458468
$current['content'].= substr($temp[$i]['content'], 1);
459469
}
460470
// all subtags should be bit strings
461-
//if ($temp[$last]['type'] != FILE_ASN1_TYPE_BIT_STRING) {
462-
// return false;
463-
//}
471+
if ($temp[$last]['type'] != FILE_ASN1_TYPE_BIT_STRING) {
472+
return false;
473+
}
464474
$current['content'] = $temp[$last]['content'][0] . $current['content'] . substr($temp[$i]['content'], 1);
465475
}
466476
break;
@@ -477,9 +487,9 @@ function _decode_ber($encoded, $start = 0, $encoded_pos = 0)
477487
}
478488
$content_pos += $temp['length'];
479489
// all subtags should be octet strings
480-
//if ($temp['type'] != FILE_ASN1_TYPE_OCTET_STRING) {
481-
// return false;
482-
//}
490+
if ($temp['type'] != FILE_ASN1_TYPE_OCTET_STRING) {
491+
return false;
492+
}
483493
$current['content'].= $temp['content'];
484494
$length+= $temp['length'];
485495
}
@@ -490,12 +500,15 @@ function _decode_ber($encoded, $start = 0, $encoded_pos = 0)
490500
break;
491501
case FILE_ASN1_TYPE_NULL:
492502
// "The contents octets shall not contain any octets." -- paragraph 8.8.2
493-
//if (strlen($content)) {
494-
// return false;
495-
//}
503+
if ($constructed || strlen($content)) {
504+
return false;
505+
}
496506
break;
497507
case FILE_ASN1_TYPE_SEQUENCE:
498508
case FILE_ASN1_TYPE_SET:
509+
if (!$constructed) {
510+
return false;
511+
}
499512
$offset = 0;
500513
$current['content'] = array();
501514
$content_len = strlen($content);
@@ -516,7 +529,13 @@ function _decode_ber($encoded, $start = 0, $encoded_pos = 0)
516529
}
517530
break;
518531
case FILE_ASN1_TYPE_OBJECT_IDENTIFIER:
532+
if ($constructed) {
533+
return false;
534+
}
519535
$current['content'] = $this->_decodeOID(substr($content, $content_pos));
536+
if ($current['content'] === false) {
537+
return false;
538+
}
520539
break;
521540
/* Each character string type shall be encoded as if it had been declared:
522541
[UNIVERSAL x] IMPLICIT OCTET STRING
@@ -546,14 +565,22 @@ function _decode_ber($encoded, $start = 0, $encoded_pos = 0)
546565
case FILE_ASN1_TYPE_UTF8_STRING:
547566
// ????
548567
case FILE_ASN1_TYPE_BMP_STRING:
568+
if ($constructed) {
569+
return false;
570+
}
549571
$current['content'] = substr($content, $content_pos);
550572
break;
551573
case FILE_ASN1_TYPE_UTC_TIME:
552574
case FILE_ASN1_TYPE_GENERALIZED_TIME:
575+
if ($constructed) {
576+
return false;
577+
}
553578
$current['content'] = class_exists('DateTime') ?
554579
$this->_decodeDateTime(substr($content, $content_pos), $tag) :
555580
$this->_decodeUnixTime(substr($content, $content_pos), $tag);
581+
break;
556582
default:
583+
return false;
557584
}
558585

559586
$start+= $length;
@@ -1228,6 +1255,11 @@ function _decodeOID($content)
12281255
$oid = array();
12291256
$pos = 0;
12301257
$len = strlen($content);
1258+
1259+
if (ord($content[$len - 1]) & 0x80) {
1260+
return false;
1261+
}
1262+
12311263
$n = new Math_BigInteger();
12321264
while ($pos < $len) {
12331265
$temp = ord($content[$pos++]);

tests/Unit/Crypt/RSA/ModeTest.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,4 +115,26 @@ public function testPSSSigsWithNonPowerOf2Key()
115115
$payload = 'eyJraWQiOiJ0RkMyVUloRnBUTV9FYTNxY09kX01xUVQxY0JCbTlrRkxTRGZlSmhzUkc4IiwiYWxnIjoiUFMyNTYifQ.eyJhcHAiOiJhY2NvdW50cG9ydGFsIiwic3ViIjoiNTliOGM4YzA5NTVhNDA5MDg2MGRmYmM3ZGQwMjVjZWEiLCJjbGlkIjoiZTQ5ZTA2N2JiMTFjNDcyMmEzNGIyYjNiOGE2YTYzNTUiLCJhbSI6InBhc3N3b3JkIiwicCI6ImVOcDFrRUZQd3pBTWhmXC9QdEVOYU5kQkc2bUZDNHNpbENNNXU0aTNXMHFSS0hFVDU5V1JzcXpZRUp4XC84M3ZQbkIxcUg3Rm5CZVNabEtNME9saGVZVUVWTXlHOEVUOEZnWDI4dkdqWG4wWkcrV2hSK01rWVBicGZacHI2U3E0N0RFYjBLYkRFT21CSUZuOTZKN1ZDaWg1Q2p4dWNRZDJmdHJlMCt2cSthZFFObUluK0poWEl0UlBvQ0xya1wvZ05VV3N3T09vSVwva0Q5ZVk4c05jRHFPUzNkanFWb3RPU21oRUo5b0hZZmFqZmpSRzFGSWpGRFwvOExtT2pKbVF3d0tBMnQ0aXJBQ2NncHo0dzBuN3BtXC84YXV2T0dFM2twVFZ2d0IzdzlQZk1YZnJJUTBhejRsaEtIdVBUMU42XC9sb1FJPSIsImlhaSI6IjU5YjhjOGMwOTU1YTQwOTA4NjBkZmJjN2RkMDI1Y2VhIiwiY2xzdmMiOiJhY2NvdW50cG9ydGFsIiwibHB2IjoxNTQ3Njc1NDM4LCJ0IjoicyIsImljIjp0cnVlLCJleHAiOjE1NDc3MDQyMzgsImlhdCI6MTU0NzY3NTQzOCwianRpIjoiZTE0N2UzM2UzNzVhNDkyNWJjMzdjZTRjMDIwMmJjNDYifQ';
116116
$this->assertTrue($rsa->verify($payload, $sig));
117117
}
118+
119+
public function testPKCS1SigWithoutNull()
120+
{
121+
$rsa = new Crypt_RSA();
122+
$rsa->loadKey(array(
123+
'n' => new Math_BigInteger('0xE932AC92252F585B3A80A4DD76A897C8B7652952FE788F6EC8DD640587A1EE5647670A8AD
124+
4C2BE0F9FA6E49C605ADF77B5174230AF7BD50E5D6D6D6D28CCF0A886A514CC72E51D209CC7
125+
72A52EF419F6A953F3135929588EBE9B351FCA61CED78F346FE00DBB6306E5C2A4C6DFC3779
126+
AF85AB417371CF34D8387B9B30AE46D7A5FF5A655B8D8455F1B94AE736989D60A6F2FD5CADB
127+
FFBD504C5A756A2E6BB5CECC13BCA7503F6DF8B52ACE5C410997E98809DB4DC30D943DE4E81
128+
2A47553DCE54844A78E36401D13F77DC650619FED88D8B3926E3D8E319C80C744779AC5D6AB
129+
E252896950917476ECE5E8FC27D5F053D6018D91B502C4787558A002B9283DA7', 16),
130+
'e' => new Math_BigInteger('3')
131+
));
132+
133+
$message = 'hello world!';
134+
$signature = pack('H*', 'a0073057133ff3758e7e111b4d7441f1d8cbe4b2dd5ee4316a14264290dee5ed7f175716639bd9bb43a14e4f9fcb9e84dedd35e2205caac04828b2c053f68176d971ea88534dd2eeec903043c3469fc69c206b2a8694fd262488441ed8852280c3d4994e9d42bd1d575c7024095f1a20665925c2175e089c0d731471f6cc145404edf5559fd2276e45e448086f71c78d0cc6628fad394a34e51e8c10bc39bfe09ed2f5f742cc68bee899d0a41e4c75b7b80afd1c321d89ccd9fe8197c44624d91cc935dfa48de3c201099b5b417be748aef29248527e8bbb173cab76b48478d4177b338fe1f1244e64d7d23f07add560d5ad50b68d6649a49d7bc3db686daaa7');
135+
136+
$rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);
137+
$rsa->setHash('sha256');
138+
$this->assertTrue($rsa->verify($message, $signature));
139+
}
118140
}

tests/Unit/File/ASN1Test.php

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,4 +392,60 @@ public function testExplicitImplicitDate()
392392

393393
$this->assertIsArray($a);
394394
}
395+
396+
public function testNullGarbage()
397+
{
398+
$asn1 = new File_ASN1();
399+
400+
$em = pack('H*', '3080305c0609608648016503040201054f8888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888804207509e5bda0c762d2bac7f90d758b5b2263fa01ccbc542ab5e3df163be08e6ca9');
401+
$decoded = $asn1->decodeBER($em);
402+
$this->assertFalse($decoded[0]);
403+
404+
$em = pack('H*', '3080307f0609608648016503040201057288888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888804207509e5bda0c762d2bac7f90d758b5b2263fa01ccbc542ab5e3df163be08e6ca90000');
405+
$decoded = $asn1->decodeBER($em);
406+
$this->assertFalse($decoded[0]);
407+
}
408+
409+
public function testOIDGarbage()
410+
{
411+
$asn1 = new File_ASN1();
412+
413+
$em = pack('H*', '3080305c065860864801650304020188888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888050004207509e5bda0c762d2bac7f90d758b5b2263fa01ccbc542ab5e3df163be08e6ca9');
414+
$decoded = $asn1->decodeBER($em);
415+
$this->assertFalse($decoded[0]);
416+
417+
$em = pack('H*', '3080307f067d608648016503040201888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888804207509e5bda0c762d2bac7f90d758b5b2263fa01ccbc542ab5e3df163be08e6ca9');
418+
$decoded = $asn1->decodeBER($em);
419+
$this->assertFalse($decoded[0]);
420+
}
421+
422+
public function testConstructedMismatch()
423+
{
424+
$asn1 = new File_ASN1();
425+
426+
$em = pack('H*', '1031300d0609608648016503040201050004207509e5bda0c762d2bac7f90d758b5b2263fa01ccbc542ab5e3df163be08e6ca9');
427+
$decoded = $asn1->decodeBER($em);
428+
$this->assertFalse($decoded[0]);
429+
430+
$em = pack('H*', '3031100d0609608648016503040201050004207509e5bda0c762d2bac7f90d758b5b2263fa01ccbc542ab5e3df163be08e6ca9');
431+
$decoded = $asn1->decodeBER($em);
432+
$this->assertFalse($decoded[0]);
433+
434+
$em = pack('H*', '3031300d2609608648016503040201050004207509e5bda0c762d2bac7f90d758b5b2263fa01ccbc542ab5e3df163be08e6ca9');
435+
$decoded = $asn1->decodeBER($em);
436+
$this->assertFalse($decoded[0]);
437+
438+
$em = pack('H*', '3031300d06096086480165030402012d0004207509e5bda0c762d2bac7f90d758b5b2263fa01ccbc542ab5e3df163be08e6ca9');
439+
$decoded = $asn1->decodeBER($em);
440+
$this->assertFalse($decoded[0]);
441+
}
442+
443+
public function testBadTagSecondOctet()
444+
{
445+
$asn1 = new File_ASN1();
446+
447+
$em = pack('H*', '3033300f1f808080060960864801650304020104207509e5bda0c762d2bac7f90d758b5b2263fa01ccbc542ab5e3df163be08e6ca9');
448+
$decoded = $asn1->decodeBER($em);
449+
$this->assertFalse($decoded[0]);
450+
}
395451
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy