1<?php 2 3/** 4 * PKCS#8 Formatted 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 ssh-keygen's pkcs8 format (as specified by -m). Although PKCS8 17 * is specific to private keys it's basically creating a DER-encoded wrapper 18 * for keys. This just extends that same concept to public keys (much like ssh-keygen) 19 * 20 * @category Crypt 21 * @package Common 22 * @author Jim Wigginton <terrafrost@php.net> 23 * @copyright 2015 Jim Wigginton 24 * @license http://www.opensource.org/licenses/mit-license.html MIT License 25 * @link http://phpseclib.sourceforge.net 26 */ 27 28namespace phpseclib3\Crypt\Common\Formats\Keys; 29 30use ParagonIE\ConstantTime\Base64; 31use phpseclib3\Common\Functions\Strings; 32use phpseclib3\Crypt\AES; 33use phpseclib3\Crypt\DES; 34use phpseclib3\Crypt\Random; 35use phpseclib3\Crypt\RC2; 36use phpseclib3\Crypt\RC4; 37use phpseclib3\Crypt\TripleDES; 38use phpseclib3\Exception\InsufficientSetupException; 39use phpseclib3\Exception\UnsupportedAlgorithmException; 40use phpseclib3\File\ASN1; 41use phpseclib3\File\ASN1\Maps; 42 43/** 44 * PKCS#8 Formatted Key Handler 45 * 46 * @package Common 47 * @author Jim Wigginton <terrafrost@php.net> 48 * @access public 49 */ 50abstract class PKCS8 extends PKCS 51{ 52 /** 53 * Default encryption algorithm 54 * 55 * @var string 56 * @access private 57 */ 58 private static $defaultEncryptionAlgorithm = 'id-PBES2'; 59 60 /** 61 * Default encryption scheme 62 * 63 * Only used when defaultEncryptionAlgorithm is id-PBES2 64 * 65 * @var string 66 * @access private 67 */ 68 private static $defaultEncryptionScheme = 'aes128-CBC-PAD'; 69 70 /** 71 * Default PRF 72 * 73 * Only used when defaultEncryptionAlgorithm is id-PBES2 74 * 75 * @var string 76 * @access private 77 */ 78 private static $defaultPRF = 'id-hmacWithSHA256'; 79 80 /** 81 * Default Iteration Count 82 * 83 * @var int 84 * @access private 85 */ 86 private static $defaultIterationCount = 2048; 87 88 /** 89 * OIDs loaded 90 * 91 * @var bool 92 * @access private 93 */ 94 private static $oidsLoaded = false; 95 96 /** 97 * Sets the default encryption algorithm 98 * 99 * @access public 100 * @param string $algo 101 */ 102 public static function setEncryptionAlgorithm($algo) 103 { 104 self::$defaultEncryptionAlgorithm = $algo; 105 } 106 107 /** 108 * Sets the default encryption algorithm for PBES2 109 * 110 * @access public 111 * @param string $algo 112 */ 113 public static function setEncryptionScheme($algo) 114 { 115 self::$defaultEncryptionScheme = $algo; 116 } 117 118 /** 119 * Sets the iteration count 120 * 121 * @access public 122 * @param int $count 123 */ 124 public static function setIterationCount($count) 125 { 126 self::$defaultIterationCount = $count; 127 } 128 129 /** 130 * Sets the PRF for PBES2 131 * 132 * @access public 133 * @param string $algo 134 */ 135 public static function setPRF($algo) 136 { 137 self::$defaultPRF = $algo; 138 } 139 140 /** 141 * Returns a SymmetricKey object based on a PBES1 $algo 142 * 143 * @return \phpseclib3\Crypt\Common\SymmetricKey 144 * @access public 145 * @param string $algo 146 */ 147 private static function getPBES1EncryptionObject($algo) 148 { 149 $algo = preg_match('#^pbeWith(?:MD2|MD5|SHA1|SHA)And(.*?)-CBC$#', $algo, $matches) ? 150 $matches[1] : 151 substr($algo, 13); // strlen('pbeWithSHAAnd') == 13 152 153 switch ($algo) { 154 case 'DES': 155 $cipher = new DES('cbc'); 156 break; 157 case 'RC2': 158 $cipher = new RC2('cbc'); 159 break; 160 case '3-KeyTripleDES': 161 $cipher = new TripleDES('cbc'); 162 break; 163 case '2-KeyTripleDES': 164 $cipher = new TripleDES('cbc'); 165 $cipher->setKeyLength(128); 166 break; 167 case '128BitRC2': 168 $cipher = new RC2('cbc'); 169 $cipher->setKeyLength(128); 170 break; 171 case '40BitRC2': 172 $cipher = new RC2('cbc'); 173 $cipher->setKeyLength(40); 174 break; 175 case '128BitRC4': 176 $cipher = new RC4(); 177 $cipher->setKeyLength(128); 178 break; 179 case '40BitRC4': 180 $cipher = new RC4(); 181 $cipher->setKeyLength(40); 182 break; 183 default: 184 throw new UnsupportedAlgorithmException("$algo is not a supported algorithm"); 185 } 186 187 return $cipher; 188 } 189 190 /** 191 * Returns a hash based on a PBES1 $algo 192 * 193 * @return string 194 * @access public 195 * @param string $algo 196 */ 197 private static function getPBES1Hash($algo) 198 { 199 if (preg_match('#^pbeWith(MD2|MD5|SHA1|SHA)And.*?-CBC$#', $algo, $matches)) { 200 return $matches[1] == 'SHA' ? 'sha1' : $matches[1]; 201 } 202 203 return 'sha1'; 204 } 205 206 /** 207 * Returns a KDF baesd on a PBES1 $algo 208 * 209 * @return string 210 * @access public 211 * @param string $algo 212 */ 213 private static function getPBES1KDF($algo) 214 { 215 switch ($algo) { 216 case 'pbeWithMD2AndDES-CBC': 217 case 'pbeWithMD2AndRC2-CBC': 218 case 'pbeWithMD5AndDES-CBC': 219 case 'pbeWithMD5AndRC2-CBC': 220 case 'pbeWithSHA1AndDES-CBC': 221 case 'pbeWithSHA1AndRC2-CBC': 222 return 'pbkdf1'; 223 } 224 225 return 'pkcs12'; 226 } 227 228 /** 229 * Returns a SymmetricKey object baesd on a PBES2 $algo 230 * 231 * @return SymmetricKey 232 * @access public 233 * @param string $algo 234 */ 235 private static function getPBES2EncryptionObject($algo) 236 { 237 switch ($algo) { 238 case 'desCBC': 239 $cipher = new TripleDES('cbc'); 240 break; 241 case 'des-EDE3-CBC': 242 $cipher = new TripleDES('cbc'); 243 break; 244 case 'rc2CBC': 245 $cipher = new RC2('cbc'); 246 // in theory this can be changed 247 $cipher->setKeyLength(128); 248 break; 249 case 'rc5-CBC-PAD': 250 throw new UnsupportedAlgorithmException('rc5-CBC-PAD is not supported for PBES2 PKCS#8 keys'); 251 case 'aes128-CBC-PAD': 252 case 'aes192-CBC-PAD': 253 case 'aes256-CBC-PAD': 254 $cipher = new AES('cbc'); 255 $cipher->setKeyLength(substr($algo, 3, 3)); 256 break; 257 default: 258 throw new UnsupportedAlgorithmException("$algo is not supported"); 259 } 260 261 return $cipher; 262 } 263 264 /** 265 * Initialize static variables 266 * 267 * @access private 268 */ 269 private static function initialize_static_variables() 270 { 271 if (!isset(static::$childOIDsLoaded)) { 272 throw new InsufficientSetupException('This class should not be called directly'); 273 } 274 275 if (!static::$childOIDsLoaded) { 276 ASN1::loadOIDs(is_array(static::OID_NAME) ? 277 array_combine(static::OID_NAME, static::OID_VALUE) : 278 [static::OID_NAME => static::OID_VALUE]); 279 static::$childOIDsLoaded = true; 280 } 281 if (!self::$oidsLoaded) { 282 // from https://tools.ietf.org/html/rfc2898 283 ASN1::loadOIDs([ 284 // PBES1 encryption schemes 285 'pbeWithMD2AndDES-CBC' => '1.2.840.113549.1.5.1', 286 'pbeWithMD2AndRC2-CBC' => '1.2.840.113549.1.5.4', 287 'pbeWithMD5AndDES-CBC' => '1.2.840.113549.1.5.3', 288 'pbeWithMD5AndRC2-CBC' => '1.2.840.113549.1.5.6', 289 'pbeWithSHA1AndDES-CBC' => '1.2.840.113549.1.5.10', 290 'pbeWithSHA1AndRC2-CBC' => '1.2.840.113549.1.5.11', 291 292 // from PKCS#12: 293 // https://tools.ietf.org/html/rfc7292 294 'pbeWithSHAAnd128BitRC4' => '1.2.840.113549.1.12.1.1', 295 'pbeWithSHAAnd40BitRC4' => '1.2.840.113549.1.12.1.2', 296 'pbeWithSHAAnd3-KeyTripleDES-CBC' => '1.2.840.113549.1.12.1.3', 297 'pbeWithSHAAnd2-KeyTripleDES-CBC' => '1.2.840.113549.1.12.1.4', 298 'pbeWithSHAAnd128BitRC2-CBC' => '1.2.840.113549.1.12.1.5', 299 'pbeWithSHAAnd40BitRC2-CBC' => '1.2.840.113549.1.12.1.6', 300 301 'id-PBKDF2' => '1.2.840.113549.1.5.12', 302 'id-PBES2' => '1.2.840.113549.1.5.13', 303 'id-PBMAC1' => '1.2.840.113549.1.5.14', 304 305 // from PKCS#5 v2.1: 306 // http://www.rsa.com/rsalabs/pkcs/files/h11302-wp-pkcs5v2-1-password-based-cryptography-standard.pdf 307 'id-hmacWithSHA1' => '1.2.840.113549.2.7', 308 'id-hmacWithSHA224' => '1.2.840.113549.2.8', 309 'id-hmacWithSHA256' => '1.2.840.113549.2.9', 310 'id-hmacWithSHA384' => '1.2.840.113549.2.10', 311 'id-hmacWithSHA512' => '1.2.840.113549.2.11', 312 'id-hmacWithSHA512-224' => '1.2.840.113549.2.12', 313 'id-hmacWithSHA512-256' => '1.2.840.113549.2.13', 314 315 'desCBC' => '1.3.14.3.2.7', 316 'des-EDE3-CBC' => '1.2.840.113549.3.7', 317 'rc2CBC' => '1.2.840.113549.3.2', 318 'rc5-CBC-PAD' => '1.2.840.113549.3.9', 319 320 'aes128-CBC-PAD' => '2.16.840.1.101.3.4.1.2', 321 'aes192-CBC-PAD' => '2.16.840.1.101.3.4.1.22', 322 'aes256-CBC-PAD' => '2.16.840.1.101.3.4.1.42' 323 ]); 324 self::$oidsLoaded = true; 325 } 326 } 327 328 /** 329 * Break a public or private key down into its constituent components 330 * 331 * @access public 332 * @param string $key 333 * @param string $password optional 334 * @return array 335 */ 336 protected static function load($key, $password = '') 337 { 338 $decoded = self::preParse($key); 339 340 $meta = []; 341 342 $decrypted = ASN1::asn1map($decoded[0], Maps\EncryptedPrivateKeyInfo::MAP); 343 if (strlen($password) && is_array($decrypted)) { 344 $algorithm = $decrypted['encryptionAlgorithm']['algorithm']; 345 switch ($algorithm) { 346 // PBES1 347 case 'pbeWithMD2AndDES-CBC': 348 case 'pbeWithMD2AndRC2-CBC': 349 case 'pbeWithMD5AndDES-CBC': 350 case 'pbeWithMD5AndRC2-CBC': 351 case 'pbeWithSHA1AndDES-CBC': 352 case 'pbeWithSHA1AndRC2-CBC': 353 case 'pbeWithSHAAnd3-KeyTripleDES-CBC': 354 case 'pbeWithSHAAnd2-KeyTripleDES-CBC': 355 case 'pbeWithSHAAnd128BitRC2-CBC': 356 case 'pbeWithSHAAnd40BitRC2-CBC': 357 case 'pbeWithSHAAnd128BitRC4': 358 case 'pbeWithSHAAnd40BitRC4': 359 $cipher = self::getPBES1EncryptionObject($algorithm); 360 $hash = self::getPBES1Hash($algorithm); 361 $kdf = self::getPBES1KDF($algorithm); 362 363 $meta['meta']['algorithm'] = $algorithm; 364 365 $temp = ASN1::decodeBER($decrypted['encryptionAlgorithm']['parameters']); 366 extract(ASN1::asn1map($temp[0], Maps\PBEParameter::MAP)); 367 $iterationCount = (int) $iterationCount->toString(); 368 $cipher->setPassword($password, $kdf, $hash, $salt, $iterationCount); 369 $key = $cipher->decrypt($decrypted['encryptedData']); 370 $decoded = ASN1::decodeBER($key); 371 if (empty($decoded)) { 372 throw new \RuntimeException('Unable to decode BER 2'); 373 } 374 375 break; 376 case 'id-PBES2': 377 $meta['meta']['algorithm'] = $algorithm; 378 379 $temp = ASN1::decodeBER($decrypted['encryptionAlgorithm']['parameters']); 380 $temp = ASN1::asn1map($temp[0], Maps\PBES2params::MAP); 381 extract($temp); 382 383 $cipher = self::getPBES2EncryptionObject($encryptionScheme['algorithm']); 384 $meta['meta']['cipher'] = $encryptionScheme['algorithm']; 385 386 $temp = ASN1::decodeBER($decrypted['encryptionAlgorithm']['parameters']); 387 $temp = ASN1::asn1map($temp[0], Maps\PBES2params::MAP); 388 extract($temp); 389 390 if (!$cipher instanceof RC2) { 391 $cipher->setIV($encryptionScheme['parameters']['octetString']); 392 } else { 393 $temp = ASN1::decodeBER($encryptionScheme['parameters']); 394 extract(ASN1::asn1map($temp[0], Maps\RC2CBCParameter::MAP)); 395 $effectiveKeyLength = (int) $rc2ParametersVersion->toString(); 396 switch ($effectiveKeyLength) { 397 case 160: 398 $effectiveKeyLength = 40; 399 break; 400 case 120: 401 $effectiveKeyLength = 64; 402 break; 403 case 58: 404 $effectiveKeyLength = 128; 405 break; 406 //default: // should be >= 256 407 } 408 $cipher->setIV($iv); 409 $cipher->setKeyLength($effectiveKeyLength); 410 } 411 412 $meta['meta']['keyDerivationFunc'] = $keyDerivationFunc['algorithm']; 413 switch ($keyDerivationFunc['algorithm']) { 414 case 'id-PBKDF2': 415 $temp = ASN1::decodeBER($keyDerivationFunc['parameters']); 416 $prf = ['algorithm' => 'id-hmacWithSHA1']; 417 $params = ASN1::asn1map($temp[0], Maps\PBKDF2params::MAP); 418 extract($params); 419 $meta['meta']['prf'] = $prf['algorithm']; 420 $hash = str_replace('-', '/', substr($prf['algorithm'], 11)); 421 $params = [ 422 $password, 423 'pbkdf2', 424 $hash, 425 $salt, 426 (int) $iterationCount->toString() 427 ]; 428 if (isset($keyLength)) { 429 $params[] = (int) $keyLength->toString(); 430 } 431 $cipher->setPassword(...$params); 432 $key = $cipher->decrypt($decrypted['encryptedData']); 433 $decoded = ASN1::decodeBER($key); 434 if (empty($decoded)) { 435 throw new \RuntimeException('Unable to decode BER 3'); 436 } 437 break; 438 default: 439 throw new UnsupportedAlgorithmException('Only PBKDF2 is supported for PBES2 PKCS#8 keys'); 440 } 441 break; 442 case 'id-PBMAC1': 443 //$temp = ASN1::decodeBER($decrypted['encryptionAlgorithm']['parameters']); 444 //$value = ASN1::asn1map($temp[0], Maps\PBMAC1params::MAP); 445 // since i can't find any implementation that does PBMAC1 it is unsupported 446 throw new UnsupportedAlgorithmException('Only PBES1 and PBES2 PKCS#8 keys are supported.'); 447 // at this point we'll assume that the key conforms to PublicKeyInfo 448 } 449 } 450 451 $private = ASN1::asn1map($decoded[0], Maps\OneAsymmetricKey::MAP); 452 if (is_array($private)) { 453 if (isset($private['privateKeyAlgorithm']['parameters']) && !$private['privateKeyAlgorithm']['parameters'] instanceof ASN1\Element && isset($decoded[0]['content'][1]['content'][1])) { 454 $temp = $decoded[0]['content'][1]['content'][1]; 455 $private['privateKeyAlgorithm']['parameters'] = new ASN1\Element(substr($key, $temp['start'], $temp['length'])); 456 } 457 if (is_array(static::OID_NAME)) { 458 if (!in_array($private['privateKeyAlgorithm']['algorithm'], static::OID_NAME)) { 459 throw new UnsupportedAlgorithmException($private['privateKeyAlgorithm']['algorithm'] . ' is not a supported key type'); 460 } 461 } else { 462 if ($private['privateKeyAlgorithm']['algorithm'] != static::OID_NAME) { 463 throw new UnsupportedAlgorithmException('Only ' . static::OID_NAME . ' keys are supported; this is a ' . $private['privateKeyAlgorithm']['algorithm'] . ' key'); 464 } 465 } 466 if (isset($private['publicKey'])) { 467 if ($private['publicKey'][0] != "\0") { 468 throw new \UnexpectedValueException('The first byte of the public key should be null - not ' . bin2hex($private['publicKey'][0])); 469 } 470 $private['publicKey'] = substr($private['publicKey'], 1); 471 } 472 return $private + $meta; 473 } 474 475 // EncryptedPrivateKeyInfo and PublicKeyInfo have largely identical "signatures". the only difference 476 // is that the former has an octet string and the later has a bit string. the first byte of a bit 477 // string represents the number of bits in the last byte that are to be ignored but, currently, 478 // bit strings wanting a non-zero amount of bits trimmed are not supported 479 $public = ASN1::asn1map($decoded[0], Maps\PublicKeyInfo::MAP); 480 481 if (is_array($public)) { 482 if ($public['publicKey'][0] != "\0") { 483 throw new \UnexpectedValueException('The first byte of the public key should be null - not ' . bin2hex($public['publicKey'][0])); 484 } 485 if (is_array(static::OID_NAME)) { 486 if (!in_array($public['publicKeyAlgorithm']['algorithm'], static::OID_NAME)) { 487 throw new UnsupportedAlgorithmException($public['publicKeyAlgorithm']['algorithm'] . ' is not a supported key type'); 488 } 489 } else { 490 if ($public['publicKeyAlgorithm']['algorithm'] != static::OID_NAME) { 491 throw new UnsupportedAlgorithmException('Only ' . static::OID_NAME . ' keys are supported; this is a ' . $public['publicKeyAlgorithm']['algorithm'] . ' key'); 492 } 493 } 494 if (isset($public['publicKeyAlgorithm']['parameters']) && !$public['publicKeyAlgorithm']['parameters'] instanceof ASN1\Element && isset($decoded[0]['content'][0]['content'][1])) { 495 $temp = $decoded[0]['content'][0]['content'][1]; 496 $public['publicKeyAlgorithm']['parameters'] = new ASN1\Element(substr($key, $temp['start'], $temp['length'])); 497 } 498 $public['publicKey'] = substr($public['publicKey'], 1); 499 return $public; 500 } 501 502 throw new \RuntimeException('Unable to parse using either OneAsymmetricKey or PublicKeyInfo ASN1 maps'); 503 } 504 505 /** 506 * Wrap a private key appropriately 507 * 508 * @access public 509 * @param string $key 510 * @param string $attr 511 * @param mixed $params 512 * @param string $password 513 * @param string $oid optional 514 * @param string $publicKey optional 515 * @param array $options optional 516 * @return string 517 */ 518 protected static function wrapPrivateKey($key, $attr, $params, $password, $oid = null, $publicKey = '', array $options = []) 519 { 520 self::initialize_static_variables(); 521 522 $key = [ 523 'version' => 'v1', 524 'privateKeyAlgorithm' => [ 525 'algorithm' => is_string(static::OID_NAME) ? static::OID_NAME : $oid 526 ], 527 'privateKey' => $key 528 ]; 529 if ($oid != 'id-Ed25519' && $oid != 'id-Ed448') { 530 $key['privateKeyAlgorithm']['parameters'] = $params; 531 } 532 if (!empty($attr)) { 533 $key['attributes'] = $attr; 534 } 535 if (!empty($publicKey)) { 536 $key['version'] = 'v2'; 537 $key['publicKey'] = $publicKey; 538 } 539 $key = ASN1::encodeDER($key, Maps\OneAsymmetricKey::MAP); 540 if (!empty($password) && is_string($password)) { 541 $salt = Random::string(8); 542 543 $iterationCount = isset($options['iterationCount']) ? $options['iterationCount'] : self::$defaultIterationCount; 544 $encryptionAlgorithm = isset($options['encryptionAlgorithm']) ? $options['encryptionAlgorithm'] : self::$defaultEncryptionAlgorithm; 545 $encryptionScheme = isset($options['encryptionScheme']) ? $options['encryptionScheme'] : self::$defaultEncryptionScheme; 546 $prf = isset($options['PRF']) ? $options['PRF'] : self::$defaultPRF; 547 548 if ($encryptionAlgorithm == 'id-PBES2') { 549 $crypto = self::getPBES2EncryptionObject($encryptionScheme); 550 $hash = str_replace('-', '/', substr($prf, 11)); 551 $kdf = 'pbkdf2'; 552 $iv = Random::string($crypto->getBlockLength() >> 3); 553 554 $PBKDF2params = [ 555 'salt' => $salt, 556 'iterationCount' => $iterationCount, 557 'prf' => ['algorithm' => $prf, 'parameters' => null] 558 ]; 559 $PBKDF2params = ASN1::encodeDER($PBKDF2params, Maps\PBKDF2params::MAP); 560 561 if (!$crypto instanceof RC2) { 562 $params = ['octetString' => $iv]; 563 } else { 564 $params = [ 565 'rc2ParametersVersion' => 58, 566 'iv' => $iv 567 ]; 568 $params = ASN1::encodeDER($params, Maps\RC2CBCParameter::MAP); 569 $params = new ASN1\Element($params); 570 } 571 572 $params = [ 573 'keyDerivationFunc' => [ 574 'algorithm' => 'id-PBKDF2', 575 'parameters' => new ASN1\Element($PBKDF2params) 576 ], 577 'encryptionScheme' => [ 578 'algorithm' => $encryptionScheme, 579 'parameters' => $params 580 ] 581 ]; 582 $params = ASN1::encodeDER($params, Maps\PBES2params::MAP); 583 584 $crypto->setIV($iv); 585 } else { 586 $crypto = self::getPBES1EncryptionObject($encryptionAlgorithm); 587 $hash = self::getPBES1Hash($encryptionAlgorithm); 588 $kdf = self::getPBES1KDF($encryptionAlgorithm); 589 590 $params = [ 591 'salt' => $salt, 592 'iterationCount' => $iterationCount 593 ]; 594 $params = ASN1::encodeDER($params, Maps\PBEParameter::MAP); 595 } 596 $crypto->setPassword($password, $kdf, $hash, $salt, $iterationCount); 597 $key = $crypto->encrypt($key); 598 599 $key = [ 600 'encryptionAlgorithm' => [ 601 'algorithm' => $encryptionAlgorithm, 602 'parameters' => new ASN1\Element($params) 603 ], 604 'encryptedData' => $key 605 ]; 606 607 $key = ASN1::encodeDER($key, Maps\EncryptedPrivateKeyInfo::MAP); 608 609 return "-----BEGIN ENCRYPTED PRIVATE KEY-----\r\n" . 610 chunk_split(Base64::encode($key), 64) . 611 "-----END ENCRYPTED PRIVATE KEY-----"; 612 } 613 614 return "-----BEGIN PRIVATE KEY-----\r\n" . 615 chunk_split(Base64::encode($key), 64) . 616 "-----END PRIVATE KEY-----"; 617 } 618 619 /** 620 * Wrap a public key appropriately 621 * 622 * @access public 623 * @param string $key 624 * @param mixed $params 625 * @param string $oid 626 * @return string 627 */ 628 protected static function wrapPublicKey($key, $params, $oid = null) 629 { 630 self::initialize_static_variables(); 631 632 $key = [ 633 'publicKeyAlgorithm' => [ 634 'algorithm' => is_string(static::OID_NAME) ? static::OID_NAME : $oid, 635 'parameters' => $params 636 ], 637 'publicKey' => "\0" . $key 638 ]; 639 640 $key = ASN1::encodeDER($key, Maps\PublicKeyInfo::MAP); 641 642 return "-----BEGIN PUBLIC KEY-----\r\n" . 643 chunk_split(Base64::encode($key), 64) . 644 "-----END PUBLIC KEY-----"; 645 } 646 647 /** 648 * Perform some preliminary parsing of the key 649 * 650 * @param string $key 651 * @return array 652 */ 653 private static function preParse(&$key) 654 { 655 self::initialize_static_variables(); 656 657 if (!Strings::is_stringable($key)) { 658 throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key)); 659 } 660 661 if (self::$format != self::MODE_DER) { 662 $decoded = ASN1::extractBER($key); 663 if ($decoded !== false) { 664 $key = $decoded; 665 } elseif (self::$format == self::MODE_PEM) { 666 throw new \UnexpectedValueException('Expected base64-encoded PEM format but was unable to decode base64 text'); 667 } 668 } 669 670 $decoded = ASN1::decodeBER($key); 671 if (empty($decoded)) { 672 throw new \RuntimeException('Unable to decode BER'); 673 } 674 675 return $decoded; 676 } 677 678 /** 679 * Returns the encryption parameters used by the key 680 * 681 * @param string $key 682 * @return array 683 */ 684 public static function extractEncryptionAlgorithm($key) 685 { 686 $decoded = self::preParse($key); 687 688 $r = ASN1::asn1map($decoded[0], ASN1\Maps\EncryptedPrivateKeyInfo::MAP); 689 if (!is_array($r)) { 690 throw new \RuntimeException('Unable to parse using EncryptedPrivateKeyInfo map'); 691 } 692 693 if ($r['encryptionAlgorithm']['algorithm'] == 'id-PBES2') { 694 $decoded = ASN1::decodeBER($r['encryptionAlgorithm']['parameters']->element); 695 $r['encryptionAlgorithm']['parameters'] = ASN1::asn1map($decoded[0], ASN1\Maps\PBES2params::MAP); 696 697 $kdf = &$r['encryptionAlgorithm']['parameters']['keyDerivationFunc']; 698 switch ($kdf['algorithm']) { 699 case 'id-PBKDF2': 700 $decoded = ASN1::decodeBER($kdf['parameters']->element); 701 $kdf['parameters'] = ASN1::asn1map($decoded[0], Maps\PBKDF2params::MAP); 702 } 703 } 704 705 return $r['encryptionAlgorithm']; 706 } 707} 708