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