1<?php 2 3/** 4 * PKCS#8 Formatted RSA-PSS Key Handler 5 * 6 * PHP version 5 7 * 8 * Used by PHP's openssl_public_encrypt() and openssl's rsautl (when -pubin is set) 9 * 10 * Processes keys with the following headers: 11 * 12 * -----BEGIN ENCRYPTED PRIVATE KEY----- 13 * -----BEGIN PRIVATE KEY----- 14 * -----BEGIN PUBLIC KEY----- 15 * 16 * Analogous to "openssl genpkey -algorithm rsa-pss". 17 * 18 * @category Crypt 19 * @package RSA 20 * @author Jim Wigginton <terrafrost@php.net> 21 * @copyright 2015 Jim Wigginton 22 * @license http://www.opensource.org/licenses/mit-license.html MIT License 23 * @link http://phpseclib.sourceforge.net 24 */ 25 26namespace phpseclib3\Crypt\RSA\Formats\Keys; 27 28use phpseclib3\Common\Functions\Strings; 29use phpseclib3\Crypt\Common\Formats\Keys\PKCS8 as Progenitor; 30use phpseclib3\File\ASN1; 31use phpseclib3\File\ASN1\Maps; 32use phpseclib3\Math\BigInteger; 33 34/** 35 * PKCS#8 Formatted RSA-PSS Key Handler 36 * 37 * @package RSA 38 * @author Jim Wigginton <terrafrost@php.net> 39 * @access public 40 */ 41abstract class PSS extends Progenitor 42{ 43 /** 44 * OID Name 45 * 46 * @var string 47 * @access private 48 */ 49 const OID_NAME = 'id-RSASSA-PSS'; 50 51 /** 52 * OID Value 53 * 54 * @var string 55 * @access private 56 */ 57 const OID_VALUE = '1.2.840.113549.1.1.10'; 58 59 /** 60 * OIDs loaded 61 * 62 * @var bool 63 * @access private 64 */ 65 private static $oidsLoaded = false; 66 67 /** 68 * Child OIDs loaded 69 * 70 * @var bool 71 * @access private 72 */ 73 protected static $childOIDsLoaded = false; 74 75 /** 76 * Initialize static variables 77 */ 78 private static function initialize_static_variables() 79 { 80 if (!self::$oidsLoaded) { 81 ASN1::loadOIDs([ 82 'md2' => '1.2.840.113549.2.2', 83 'md4' => '1.2.840.113549.2.4', 84 'md5' => '1.2.840.113549.2.5', 85 'id-sha1' => '1.3.14.3.2.26', 86 'id-sha256' => '2.16.840.1.101.3.4.2.1', 87 'id-sha384' => '2.16.840.1.101.3.4.2.2', 88 'id-sha512' => '2.16.840.1.101.3.4.2.3', 89 'id-sha224' => '2.16.840.1.101.3.4.2.4', 90 'id-sha512/224' => '2.16.840.1.101.3.4.2.5', 91 'id-sha512/256' => '2.16.840.1.101.3.4.2.6', 92 93 'id-mgf1' => '1.2.840.113549.1.1.8' 94 ]); 95 self::$oidsLoaded = true; 96 } 97 } 98 99 /** 100 * Break a public or private key down into its constituent components 101 * 102 * @access public 103 * @param string $key 104 * @param string $password optional 105 * @return array 106 */ 107 public static function load($key, $password = '') 108 { 109 self::initialize_static_variables(); 110 111 if (!Strings::is_stringable($key)) { 112 throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key)); 113 } 114 115 $components = ['isPublicKey' => strpos($key, 'PUBLIC') !== false]; 116 117 $key = parent::load($key, $password); 118 119 $type = isset($key['privateKey']) ? 'private' : 'public'; 120 121 $result = $components + PKCS1::load($key[$type . 'Key']); 122 123 if (isset($key[$type . 'KeyAlgorithm']['parameters'])) { 124 $decoded = ASN1::decodeBER($key[$type . 'KeyAlgorithm']['parameters']); 125 if ($decoded === false) { 126 throw new \UnexpectedValueException('Unable to decode parameters'); 127 } 128 $params = ASN1::asn1map($decoded[0], Maps\RSASSA_PSS_params::MAP); 129 } else { 130 $params = []; 131 } 132 133 if (isset($params['maskGenAlgorithm']['parameters'])) { 134 $decoded = ASN1::decodeBER($params['maskGenAlgorithm']['parameters']); 135 if ($decoded === false) { 136 throw new \UnexpectedValueException('Unable to decode parameters'); 137 } 138 $params['maskGenAlgorithm']['parameters'] = ASN1::asn1map($decoded[0], Maps\HashAlgorithm::MAP); 139 } else { 140 $params['maskGenAlgorithm'] = [ 141 'algorithm' => 'id-mgf1', 142 'parameters' => ['algorithm' => 'id-sha1'] 143 ]; 144 } 145 146 if (!isset($params['hashAlgorithm']['algorithm'])) { 147 $params['hashAlgorithm']['algorithm'] = 'id-sha1'; 148 } 149 150 $result['hash'] = str_replace('id-', '', $params['hashAlgorithm']['algorithm']); 151 $result['MGFHash'] = str_replace('id-', '', $params['maskGenAlgorithm']['parameters']['algorithm']); 152 if (isset($params['saltLength'])) { 153 $result['saltLength'] = (int) $params['saltLength']->toString(); 154 } 155 156 if (isset($key['meta'])) { 157 $result['meta'] = $key['meta']; 158 } 159 160 return $result; 161 } 162 163 /** 164 * Convert a private key to the appropriate format. 165 * 166 * @access public 167 * @param \phpseclib3\Math\BigInteger $n 168 * @param \phpseclib3\Math\BigInteger $e 169 * @param \phpseclib3\Math\BigInteger $d 170 * @param array $primes 171 * @param array $exponents 172 * @param array $coefficients 173 * @param string $password optional 174 * @param array $options optional 175 * @return string 176 */ 177 public static function savePrivateKey(BigInteger $n, BigInteger $e, BigInteger $d, array $primes, array $exponents, array $coefficients, $password = '', array $options = []) 178 { 179 self::initialize_static_variables(); 180 181 $key = PKCS1::savePrivateKey($n, $e, $d, $primes, $exponents, $coefficients); 182 $key = ASN1::extractBER($key); 183 $params = self::savePSSParams($options); 184 return self::wrapPrivateKey($key, [], $params, $password, null, '', $options); 185 } 186 187 /** 188 * Convert a public key to the appropriate format 189 * 190 * @access public 191 * @param \phpseclib3\Math\BigInteger $n 192 * @param \phpseclib3\Math\BigInteger $e 193 * @param array $options optional 194 * @return string 195 */ 196 public static function savePublicKey(BigInteger $n, BigInteger $e, array $options = []) 197 { 198 self::initialize_static_variables(); 199 200 $key = PKCS1::savePublicKey($n, $e); 201 $key = ASN1::extractBER($key); 202 $params = self::savePSSParams($options); 203 return self::wrapPublicKey($key, $params); 204 } 205 206 /** 207 * Encodes PSS parameters 208 * 209 * @access public 210 * @param array $options 211 * @return string 212 */ 213 public static function savePSSParams(array $options) 214 { 215 /* 216 The trailerField field is an integer. It provides 217 compatibility with IEEE Std 1363a-2004 [P1363A]. The value 218 MUST be 1, which represents the trailer field with hexadecimal 219 value 0xBC. Other trailer fields, including the trailer field 220 composed of HashID concatenated with 0xCC that is specified in 221 IEEE Std 1363a, are not supported. Implementations that 222 perform signature generation MUST omit the trailerField field, 223 indicating that the default trailer field value was used. 224 Implementations that perform signature validation MUST 225 recognize both a present trailerField field with value 1 and an 226 absent trailerField field. 227 228 source: https://tools.ietf.org/html/rfc4055#page-9 229 */ 230 $params = [ 231 'trailerField' => new BigInteger(1) 232 ]; 233 if (isset($options['hash'])) { 234 $params['hashAlgorithm']['algorithm'] = 'id-' . $options['hash']; 235 } 236 if (isset($options['MGFHash'])) { 237 $temp = ['algorithm' => 'id-' . $options['MGFHash']]; 238 $temp = ASN1::encodeDER($temp, Maps\HashAlgorithm::MAP); 239 $params['maskGenAlgorithm'] = [ 240 'algorithm' => 'id-mgf1', 241 'parameters' => new ASN1\Element($temp) 242 ]; 243 } 244 if (isset($options['saltLength'])) { 245 $params['saltLength'] = new BigInteger($options['saltLength']); 246 } 247 248 return new ASN1\Element(ASN1::encodeDER($params, Maps\RSASSA_PSS_params::MAP)); 249 } 250} 251