From 8017c74429ad1d0ca96bc169dd838e1353f14072 Mon Sep 17 00:00:00 2001 From: terrafrost Date: Wed, 29 May 2019 00:32:53 -0500 Subject: [PATCH] RSA: add preliminary support for RSA-PSS keys --- phpseclib/Crypt/RSA/Keys/PKCS8.php | 2 +- phpseclib/Crypt/RSA/Keys/PSS.php | 149 ++++++++++++++++++ phpseclib/File/ASN1.php | 8 +- phpseclib/File/ASN1/Maps/MaskGenAlgorithm.php | 30 ++++ .../File/ASN1/Maps/RSASSA_PSS_params.php | 62 ++++++++ tests/Unit/Crypt/RSA/LoadKeyTest.php | 37 +++++ 6 files changed, 285 insertions(+), 3 deletions(-) create mode 100644 phpseclib/Crypt/RSA/Keys/PSS.php create mode 100644 phpseclib/File/ASN1/Maps/MaskGenAlgorithm.php create mode 100644 phpseclib/File/ASN1/Maps/RSASSA_PSS_params.php diff --git a/phpseclib/Crypt/RSA/Keys/PKCS8.php b/phpseclib/Crypt/RSA/Keys/PKCS8.php index b50c7c0a..7b998c97 100644 --- a/phpseclib/Crypt/RSA/Keys/PKCS8.php +++ b/phpseclib/Crypt/RSA/Keys/PKCS8.php @@ -32,7 +32,7 @@ use phpseclib\Crypt\Common\Keys\PKCS8 as Progenitor; use phpseclib\File\ASN1; /** - * PKCS#1 Formatted RSA Key Handler + * PKCS#8 Formatted RSA Key Handler * * @package RSA * @author Jim Wigginton diff --git a/phpseclib/Crypt/RSA/Keys/PSS.php b/phpseclib/Crypt/RSA/Keys/PSS.php new file mode 100644 index 00000000..4246a367 --- /dev/null +++ b/phpseclib/Crypt/RSA/Keys/PSS.php @@ -0,0 +1,149 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib\Crypt\RSA\Keys; + +use phpseclib\Math\BigInteger; +use phpseclib\Crypt\Common\Keys\PKCS8 as Progenitor; +use phpseclib\File\ASN1; + +/** + * PKCS#8 Formatted RSA-PSS Key Handler + * + * @package RSA + * @author Jim Wigginton + * @access public + */ +abstract class PSS extends Progenitor +{ + /** + * OID Name + * + * @var string + * @access private + */ + const OID_NAME = 'id-RSASSA-PSS'; + + /** + * OID Value + * + * @var string + * @access private + */ + const OID_VALUE = '1.2.840.113549.1.1.10'; + + /** + * OIDs loaded + * + * @var bool + * @access private + */ + private static $oidsLoaded = false; + + /** + * Child OIDs loaded + * + * @var bool + * @access private + */ + protected static $childOIDsLoaded = false; + + /** + * Initialize static variables + */ + private static function initialize_static_variables() + { + if (!self::$oidsLoaded) { + ASN1::loadOIDs([ + 'md2' => '1.2.840.113549.2.2', + 'md4' => '1.2.840.113549.2.4', + 'md5' => '1.2.840.113549.2.5', + 'id-sha1' => '1.3.14.3.2.26', + 'id-sha256' => '2.16.840.1.101.3.4.2.1', + 'id-sha384' => '2.16.840.1.101.3.4.2.2', + 'id-sha512' => '2.16.840.1.101.3.4.2.3', + 'id-sha224' => '2.16.840.1.101.3.4.2.4', + 'id-sha512/224' => '2.16.840.1.101.3.4.2.5', + 'id-sha512/256' => '2.16.840.1.101.3.4.2.6', + + 'id-mgf1' => '1.2.840.113549.1.1.8' + ]); + self::$oidsLoaded = true; + } + } + + /** + * Break a public or private key down into its constituent components + * + * @access public + * @param string $key + * @param string $password optional + * @return array + */ + public static function load($key, $password = '') + { + self::initialize_static_variables(); + + if (!is_string($key)) { + throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key)); + } + + $components = ['isPublicKey' => strpos($key, 'PUBLIC') !== false]; + + $key = parent::load($key, $password); + + $type = isset($key['privateKey']) ? 'private' : 'public'; + + $result = $components + PKCS1::load($key[$type . 'Key']); + + $decoded = ASN1::decodeBER($key[$type . 'KeyAlgorithm']['parameters']); + if ($decoded === false) { + throw new \UnexpectedValueException('Unable to decode parameters'); + } + $params = ASN1::asn1map($decoded[0], ASN1\Maps\RSASSA_PSS_params::MAP); + if (isset($params['maskGenAlgorithm']['parameters'])) { + $decoded = ASN1::decodeBER($params['maskGenAlgorithm']['parameters']); + if ($decoded === false) { + throw new \UnexpectedValueException('Unable to decode parameters'); + } + $params['maskGenAlgorithm']['parameters'] = ASN1::asn1map($decoded[0], ASN1\Maps\HashAlgorithm::MAP); + } else { + $params['maskGenAlgorithm'] = [ + 'algorithm' => 'id-mgf1', + 'parameters' => ['algorithm' => 'id-sha1'] + ]; + } + + $result['hash'] = str_replace('id-', '', $params['hashAlgorithm']['algorithm']); + $result['MGFHash'] = str_replace('id-', '', $params['maskGenAlgorithm']['parameters']['algorithm']); + $result['saltLength'] = (int) $params['saltLength']->toString(); + + if (isset($key['meta'])) { + $result['meta'] = $key['meta']; + } + + return $result; + } +} diff --git a/phpseclib/File/ASN1.php b/phpseclib/File/ASN1.php index cf3fe501..e5c219b1 100644 --- a/phpseclib/File/ASN1.php +++ b/phpseclib/File/ASN1.php @@ -642,9 +642,13 @@ abstract class ASN1 case ASN1::TYPE_INTEGER: $map[$key] = new BigInteger($child['default']); break; + //case self::TYPE_OBJECT_IDENTIFIER: + // if (!isset(self::$reverseOIDs[$name])) { + // return null; + // } //case ASN1::TYPE_BOOLEAN: default: - $map[$key] = $child['type']; + $map[$key] = $child['default']; } } elseif (!isset($child['optional'])) { return null; // Syntax error. @@ -1491,7 +1495,7 @@ abstract class ASN1 * @param string $name * @return string */ - static function getOID($name) + public static function getOID($name) { return isset(self::$reverseOIDs[$name]) ? self::$reverseOIDs[$name] : $name; } diff --git a/phpseclib/File/ASN1/Maps/MaskGenAlgorithm.php b/phpseclib/File/ASN1/Maps/MaskGenAlgorithm.php new file mode 100644 index 00000000..b47bbddf --- /dev/null +++ b/phpseclib/File/ASN1/Maps/MaskGenAlgorithm.php @@ -0,0 +1,30 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib\File\ASN1\Maps; + +use phpseclib\File\ASN1; + +/** + * MaskGenAglorithm + * + * @package ASN1 + * @author Jim Wigginton + * @access public + */ +abstract class MaskGenAlgorithm +{ + const MAP = AlgorithmIdentifier::MAP; +} \ No newline at end of file diff --git a/phpseclib/File/ASN1/Maps/RSASSA_PSS_params.php b/phpseclib/File/ASN1/Maps/RSASSA_PSS_params.php new file mode 100644 index 00000000..cbd8d750 --- /dev/null +++ b/phpseclib/File/ASN1/Maps/RSASSA_PSS_params.php @@ -0,0 +1,62 @@ + + * @copyright 2016 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib\File\ASN1\Maps; + +use phpseclib\File\ASN1; + +/** + * RSASSA_PSS_params + * + * @package ASN1 + * @author Jim Wigginton + * @access public + */ +abstract class RSASSA_PSS_params +{ + const MAP = [ + 'type' => ASN1::TYPE_SEQUENCE, + 'children' => [ + 'hashAlgorithm' => [ + 'constant' => 0, + 'optional' => true, + 'explicit' => true, + 'default' => 'sha1Identifier' + ] + HashAlgorithm::MAP, + 'maskGenAlgorithm' => [ + 'constant' => 1, + 'optional' => true, + 'explicit' => true, + 'default' => 'mgf1SHA1Identifier' + ] + MaskGenAlgorithm::MAP, + 'saltLength' => [ + 'type' => ASN1::TYPE_INTEGER, + 'constant' => 2, + 'optional' => true, + 'explicit' => true, + 'default' => 20 + ], + 'trailerField' => [ + 'type' => ASN1::TYPE_INTEGER, + 'constant' => 3, + 'optional' => true, + 'explicit' => true, + 'default' => 1 + ] + ] + ]; +} diff --git a/tests/Unit/Crypt/RSA/LoadKeyTest.php b/tests/Unit/Crypt/RSA/LoadKeyTest.php index 9426d341..d7b55baa 100644 --- a/tests/Unit/Crypt/RSA/LoadKeyTest.php +++ b/tests/Unit/Crypt/RSA/LoadKeyTest.php @@ -878,4 +878,41 @@ OFLPBrLe4Hw= $this->assertInstanceOf(PrivateKey::class, $rsa); $this->assertInstanceOf(PublicKey::class, $rsa->getPublicKey()); } + + public function testPSS() + { + $key = '-----BEGIN PRIVATE KEY----- +MIIE7QIBADA9BgkqhkiG9w0BAQowMKANMAsGCWCGSAFlAwQCAaEaMBgGCSqGSIb3 +DQEBCDALBglghkgBZQMEAgOiAwIBBQSCBKcwggSjAgEAAoIBAQD6L3Z2XUPH7vRU +1Xl5aLpW2jH/uhqOitRV2/1QAEQk6VasI2TjJefP6SmL+te71gE4PVTMpm0LoluR +IzvQYgeLwDFUzLsn2r/H3lKlS/K0KL890aNPSNuHwKVYQsBd2OuSQQZ04xM1E0VN +xELcW4Vc63FTyGzR4okQ2MGHQfxP/FoNNfaIxjyb7ly9feGNR3pIRcL2CEMfyZkq +rEE3SxNoGTHMTbIhMGchWTrX1V+VykSgy9+KmD0AD8SwP3nFH3BNLeoLDhkU2L6L +p9XYijx3RAvPeYRlMAyOpylRxXM5Z1oBmzaClDVE8mtJkMPpZshGbVwxbzrph8VA +FBf3FzYFAgMBAAECggEAMu3Igq3Xp3KIQGC4erOMAzQlq3YaA9xU/ylqNofnV1A8 +uYv29Jp5xwQi1gD5O56D3wv1IDfcyNqDI1d1zKS3/oXgRO/sRV+tXKVwU3/TZ0NI +MvBi+zfMoKThw8bK3A/VXI9qHg8/kLVcjUkfhzYGPvUau8B4Dn28AzbspnkTQMCq +FpuC41a8UzOX7rvEKPTLp87fwI1u48ycDKVK0ZKjJMQQl3SbYaVIKZa4ctav/9wC +e5LAnap55S0L13FdUHbGJKzUqIk61NgCr8Wo16AYCOULzTTNVE24jl2Dc1H+sk61 +b1FC/TxW9iWZx9givR1VgjG5fULbxwA/Mve7SYtfIQKBgQD+a/y8pxIPgBXb90Z4 +poCqRsgJVPmu6sQ8STb0WibtyD/IKECooGOpI16A/884kNyXkfcIwK6txnnPYbmv +KlNHgSUnhEeavrHfeUmyyrQaTAs3I0iuL4stOSRHHPDD72PRSkPky6NMErX4F4Vv +Y6jkFhwsNJetxf2qInJn5WZ6LQKBgQD7vL+KE0HHLZ3DVaP7pRMOx9FvkhrtmqLZ +fSuMUweKqnAFHnkEPZFuyFRMoPL3cHaVLPkGmX8vK/GL/QECKPeDyE/jEFzGQV+L +n4PeraS1jzu77uYzWcuKdabFQN939iZ2gV5MUB7Jt4zfURf26fH1UHku7rs/Mik3 +jLfE9elKOQKBgDzhFi8GQ1oWKiTifKhuHyefnEovXTev0ZkjY9UApYQMgMaiayZu +iqp0Xi68B5ffggl60gP0J1hJv+gR2F7D3/2iN4PHMWMj8mgpG6t+ua35OE3PUZrs +oX8Gx1mE4U/hPp9cB/b9i2uupoBhEHrg/A7oA4HIa+sXD2XgrEOULvtZAoGBAJ73 +RRkDKhGGG87jAMeDKXK2+elzoO+UK+wdX+ef8u48zLpe0Nq9ql4DwUAWjvd0HF39 +ZVAmlCsMm97jqMRdbFfaoZ/okD1dwOEhnRt8GbvRNE5sARBCTwcjXmnHmpZdaVKC +RTL5kUeeUiYfRnvUpcdcxvm9JZ81pNOAV/fXtjb5AoGAUVm4enVSfvPupBsjydU4 +EHvU0Y0I2IH1FrnVF8TI/9Kpdu2W5bJN5XShb7j2CICIKTr7wVwn/a7VXscQKIVb +XCy8+Rnt/jddXFeFEu9zHWyJX9W4fGIkyE4zfRPmTkVK4S599SUQkHdgClzAOMZU +IBgv3a3Lyb+IQtT75LE1yjE= +-----END PRIVATE KEY-----'; + + $rsa = PublicKeyLoader::load($key); + $this->assertInstanceOf(PrivateKey::class, $rsa); + $this->assertInstanceOf(PublicKey::class, $rsa->getPublicKey()); + } }