[ Index ]

PHP Cross Reference of DokuWiki

title

Body

[close]

/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/ -> PublicKey.php (source)

   1  <?php
   2  
   3  /**
   4   * RSA Public Key
   5   *
   6   * @author    Jim Wigginton <terrafrost@php.net>
   7   * @copyright 2015 Jim Wigginton
   8   * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
   9   * @link      http://phpseclib.sourceforge.net
  10   */
  11  
  12  namespace phpseclib3\Crypt\RSA;
  13  
  14  use phpseclib3\Common\Functions\Strings;
  15  use phpseclib3\Crypt\Common;
  16  use phpseclib3\Crypt\Hash;
  17  use phpseclib3\Crypt\Random;
  18  use phpseclib3\Crypt\RSA;
  19  use phpseclib3\Crypt\RSA\Formats\Keys\PSS;
  20  use phpseclib3\Exception\UnsupportedAlgorithmException;
  21  use phpseclib3\Exception\UnsupportedFormatException;
  22  use phpseclib3\File\ASN1;
  23  use phpseclib3\File\ASN1\Maps\DigestInfo;
  24  use phpseclib3\Math\BigInteger;
  25  
  26  /**
  27   * Raw RSA Key Handler
  28   *
  29   * @author  Jim Wigginton <terrafrost@php.net>
  30   */
  31  final class PublicKey extends RSA implements Common\PublicKey
  32  {
  33      use Common\Traits\Fingerprint;
  34  
  35      /**
  36       * Exponentiate
  37       *
  38       * @param \phpseclib3\Math\BigInteger $x
  39       * @return \phpseclib3\Math\BigInteger
  40       */
  41      private function exponentiate(BigInteger $x)
  42      {
  43          return $x->modPow($this->exponent, $this->modulus);
  44      }
  45  
  46      /**
  47       * RSAVP1
  48       *
  49       * See {@link http://tools.ietf.org/html/rfc3447#section-5.2.2 RFC3447#section-5.2.2}.
  50       *
  51       * @param \phpseclib3\Math\BigInteger $s
  52       * @return bool|\phpseclib3\Math\BigInteger
  53       */
  54      private function rsavp1($s)
  55      {
  56          if ($s->compare(self::$zero) < 0 || $s->compare($this->modulus) > 0) {
  57              return false;
  58          }
  59          return $this->exponentiate($s);
  60      }
  61  
  62      /**
  63       * RSASSA-PKCS1-V1_5-VERIFY
  64       *
  65       * See {@link http://tools.ietf.org/html/rfc3447#section-8.2.2 RFC3447#section-8.2.2}.
  66       *
  67       * @param string $m
  68       * @param string $s
  69       * @throws \LengthException if the RSA modulus is too short
  70       * @return bool
  71       */
  72      private function rsassa_pkcs1_v1_5_verify($m, $s)
  73      {
  74          // Length checking
  75  
  76          if (strlen($s) != $this->k) {
  77              return false;
  78          }
  79  
  80          // RSA verification
  81  
  82          $s = $this->os2ip($s);
  83          $m2 = $this->rsavp1($s);
  84          if ($m2 === false) {
  85              return false;
  86          }
  87          $em = $this->i2osp($m2, $this->k);
  88          if ($em === false) {
  89              return false;
  90          }
  91  
  92          // EMSA-PKCS1-v1_5 encoding
  93  
  94          $exception = false;
  95  
  96          // If the encoding operation outputs "intended encoded message length too short," output "RSA modulus
  97          // too short" and stop.
  98          try {
  99              $em2 = $this->emsa_pkcs1_v1_5_encode($m, $this->k);
 100              $r1 = hash_equals($em, $em2);
 101          } catch (\LengthException $e) {
 102              $exception = true;
 103          }
 104  
 105          try {
 106              $em3 = $this->emsa_pkcs1_v1_5_encode_without_null($m, $this->k);
 107              $r2 = hash_equals($em, $em3);
 108          } catch (\LengthException $e) {
 109              $exception = true;
 110          } catch (UnsupportedAlgorithmException $e) {
 111              $r2 = false;
 112          }
 113  
 114          if ($exception) {
 115              throw new \LengthException('RSA modulus too short');
 116          }
 117  
 118          // Compare
 119          return $r1 || $r2;
 120      }
 121  
 122      /**
 123       * RSASSA-PKCS1-V1_5-VERIFY (relaxed matching)
 124       *
 125       * Per {@link http://tools.ietf.org/html/rfc3447#page-43 RFC3447#page-43} PKCS1 v1.5
 126       * specified the use BER encoding rather than DER encoding that PKCS1 v2.0 specified.
 127       * This means that under rare conditions you can have a perfectly valid v1.5 signature
 128       * that fails to validate with _rsassa_pkcs1_v1_5_verify(). PKCS1 v2.1 also recommends
 129       * that if you're going to validate these types of signatures you "should indicate
 130       * whether the underlying BER encoding is a DER encoding and hence whether the signature
 131       * is valid with respect to the specification given in [PKCS1 v2.0+]". so if you do
 132       * $rsa->getLastPadding() and get RSA::PADDING_RELAXED_PKCS1 back instead of
 133       * RSA::PADDING_PKCS1... that means BER encoding was used.
 134       *
 135       * @param string $m
 136       * @param string $s
 137       * @return bool
 138       */
 139      private function rsassa_pkcs1_v1_5_relaxed_verify($m, $s)
 140      {
 141          // Length checking
 142  
 143          if (strlen($s) != $this->k) {
 144              return false;
 145          }
 146  
 147          // RSA verification
 148  
 149          $s = $this->os2ip($s);
 150          $m2 = $this->rsavp1($s);
 151          if ($m2 === false) {
 152              return false;
 153          }
 154          $em = $this->i2osp($m2, $this->k);
 155          if ($em === false) {
 156              return false;
 157          }
 158  
 159          if (Strings::shift($em, 2) != "\0\1") {
 160              return false;
 161          }
 162  
 163          $em = ltrim($em, "\xFF");
 164          if (Strings::shift($em) != "\0") {
 165              return false;
 166          }
 167  
 168          $decoded = ASN1::decodeBER($em);
 169          if (!is_array($decoded) || empty($decoded[0]) || strlen($em) > $decoded[0]['length']) {
 170              return false;
 171          }
 172  
 173          static $oids;
 174          if (!isset($oids)) {
 175              $oids = [
 176                  'md2' => '1.2.840.113549.2.2',
 177                  'md4' => '1.2.840.113549.2.4', // from PKCS1 v1.5
 178                  'md5' => '1.2.840.113549.2.5',
 179                  'id-sha1' => '1.3.14.3.2.26',
 180                  'id-sha256' => '2.16.840.1.101.3.4.2.1',
 181                  'id-sha384' => '2.16.840.1.101.3.4.2.2',
 182                  'id-sha512' => '2.16.840.1.101.3.4.2.3',
 183                  // from PKCS1 v2.2
 184                  'id-sha224' => '2.16.840.1.101.3.4.2.4',
 185                  'id-sha512/224' => '2.16.840.1.101.3.4.2.5',
 186                  'id-sha512/256' => '2.16.840.1.101.3.4.2.6',
 187              ];
 188              ASN1::loadOIDs($oids);
 189          }
 190  
 191          $decoded = ASN1::asn1map($decoded[0], DigestInfo::MAP);
 192          if (!isset($decoded) || $decoded === false) {
 193              return false;
 194          }
 195  
 196          if (!isset($oids[$decoded['digestAlgorithm']['algorithm']])) {
 197              return false;
 198          }
 199  
 200          if (isset($decoded['digestAlgorithm']['parameters']) && $decoded['digestAlgorithm']['parameters'] !== ['null' => '']) {
 201              return false;
 202          }
 203  
 204          $hash = $decoded['digestAlgorithm']['algorithm'];
 205          $hash = substr($hash, 0, 3) == 'id-' ?
 206              substr($hash, 3) :
 207              $hash;
 208          $hash = new Hash($hash);
 209          $em = $hash->hash($m);
 210          $em2 = $decoded['digest'];
 211  
 212          return hash_equals($em, $em2);
 213      }
 214  
 215      /**
 216       * EMSA-PSS-VERIFY
 217       *
 218       * See {@link http://tools.ietf.org/html/rfc3447#section-9.1.2 RFC3447#section-9.1.2}.
 219       *
 220       * @param string $m
 221       * @param string $em
 222       * @param int $emBits
 223       * @return string
 224       */
 225      private function emsa_pss_verify($m, $em, $emBits)
 226      {
 227          // if $m is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error
 228          // be output.
 229  
 230          $emLen = ($emBits + 7) >> 3; // ie. ceil($emBits / 8);
 231          $sLen = $this->sLen !== null ? $this->sLen : $this->hLen;
 232  
 233          $mHash = $this->hash->hash($m);
 234          if ($emLen < $this->hLen + $sLen + 2) {
 235              return false;
 236          }
 237  
 238          if ($em[strlen($em) - 1] != chr(0xBC)) {
 239              return false;
 240          }
 241  
 242          $maskedDB = substr($em, 0, -$this->hLen - 1);
 243          $h = substr($em, -$this->hLen - 1, $this->hLen);
 244          $temp = chr(0xFF << ($emBits & 7));
 245          if ((~$maskedDB[0] & $temp) != $temp) {
 246              return false;
 247          }
 248          $dbMask = $this->mgf1($h, $emLen - $this->hLen - 1);
 249          $db = $maskedDB ^ $dbMask;
 250          $db[0] = ~chr(0xFF << ($emBits & 7)) & $db[0];
 251          $temp = $emLen - $this->hLen - $sLen - 2;
 252          if (substr($db, 0, $temp) != str_repeat(chr(0), $temp) || ord($db[$temp]) != 1) {
 253              return false;
 254          }
 255          $salt = substr($db, $temp + 1); // should be $sLen long
 256          $m2 = "\0\0\0\0\0\0\0\0" . $mHash . $salt;
 257          $h2 = $this->hash->hash($m2);
 258          return hash_equals($h, $h2);
 259      }
 260  
 261      /**
 262       * RSASSA-PSS-VERIFY
 263       *
 264       * See {@link http://tools.ietf.org/html/rfc3447#section-8.1.2 RFC3447#section-8.1.2}.
 265       *
 266       * @param string $m
 267       * @param string $s
 268       * @return bool|string
 269       */
 270      private function rsassa_pss_verify($m, $s)
 271      {
 272          // Length checking
 273  
 274          if (strlen($s) != $this->k) {
 275              return false;
 276          }
 277  
 278          // RSA verification
 279  
 280          $modBits = strlen($this->modulus->toBits());
 281  
 282          $s2 = $this->os2ip($s);
 283          $m2 = $this->rsavp1($s2);
 284          $em = $this->i2osp($m2, $this->k);
 285          if ($em === false) {
 286              return false;
 287          }
 288  
 289          // EMSA-PSS verification
 290  
 291          return $this->emsa_pss_verify($m, $em, $modBits - 1);
 292      }
 293  
 294      /**
 295       * Verifies a signature
 296       *
 297       * @see self::sign()
 298       * @param string $message
 299       * @param string $signature
 300       * @return bool
 301       */
 302      public function verify($message, $signature)
 303      {
 304          switch ($this->signaturePadding) {
 305              case self::SIGNATURE_RELAXED_PKCS1:
 306                  return $this->rsassa_pkcs1_v1_5_relaxed_verify($message, $signature);
 307              case self::SIGNATURE_PKCS1:
 308                  return $this->rsassa_pkcs1_v1_5_verify($message, $signature);
 309              //case self::SIGNATURE_PSS:
 310              default:
 311                  return $this->rsassa_pss_verify($message, $signature);
 312          }
 313      }
 314  
 315      /**
 316       * RSAES-PKCS1-V1_5-ENCRYPT
 317       *
 318       * See {@link http://tools.ietf.org/html/rfc3447#section-7.2.1 RFC3447#section-7.2.1}.
 319       *
 320       * @param string $m
 321       * @param bool $pkcs15_compat optional
 322       * @throws \LengthException if strlen($m) > $this->k - 11
 323       * @return bool|string
 324       */
 325      private function rsaes_pkcs1_v1_5_encrypt($m, $pkcs15_compat = false)
 326      {
 327          $mLen = strlen($m);
 328  
 329          // Length checking
 330  
 331          if ($mLen > $this->k - 11) {
 332              throw new \LengthException('Message too long');
 333          }
 334  
 335          // EME-PKCS1-v1_5 encoding
 336  
 337          $psLen = $this->k - $mLen - 3;
 338          $ps = '';
 339          while (strlen($ps) != $psLen) {
 340              $temp = Random::string($psLen - strlen($ps));
 341              $temp = str_replace("\x00", '', $temp);
 342              $ps .= $temp;
 343          }
 344          $type = 2;
 345          $em = chr(0) . chr($type) . $ps . chr(0) . $m;
 346  
 347          // RSA encryption
 348          $m = $this->os2ip($em);
 349          $c = $this->rsaep($m);
 350          $c = $this->i2osp($c, $this->k);
 351  
 352          // Output the ciphertext C
 353  
 354          return $c;
 355      }
 356  
 357      /**
 358       * RSAES-OAEP-ENCRYPT
 359       *
 360       * See {@link http://tools.ietf.org/html/rfc3447#section-7.1.1 RFC3447#section-7.1.1} and
 361       * {http://en.wikipedia.org/wiki/Optimal_Asymmetric_Encryption_Padding OAES}.
 362       *
 363       * @param string $m
 364       * @throws \LengthException if strlen($m) > $this->k - 2 * $this->hLen - 2
 365       * @return string
 366       */
 367      private function rsaes_oaep_encrypt($m)
 368      {
 369          $mLen = strlen($m);
 370  
 371          // Length checking
 372  
 373          // if $l is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error
 374          // be output.
 375  
 376          if ($mLen > $this->k - 2 * $this->hLen - 2) {
 377              throw new \LengthException('Message too long');
 378          }
 379  
 380          // EME-OAEP encoding
 381  
 382          $lHash = $this->hash->hash($this->label);
 383          $ps = str_repeat(chr(0), $this->k - $mLen - 2 * $this->hLen - 2);
 384          $db = $lHash . $ps . chr(1) . $m;
 385          $seed = Random::string($this->hLen);
 386          $dbMask = $this->mgf1($seed, $this->k - $this->hLen - 1);
 387          $maskedDB = $db ^ $dbMask;
 388          $seedMask = $this->mgf1($maskedDB, $this->hLen);
 389          $maskedSeed = $seed ^ $seedMask;
 390          $em = chr(0) . $maskedSeed . $maskedDB;
 391  
 392          // RSA encryption
 393  
 394          $m = $this->os2ip($em);
 395          $c = $this->rsaep($m);
 396          $c = $this->i2osp($c, $this->k);
 397  
 398          // Output the ciphertext C
 399  
 400          return $c;
 401      }
 402  
 403      /**
 404       * RSAEP
 405       *
 406       * See {@link http://tools.ietf.org/html/rfc3447#section-5.1.1 RFC3447#section-5.1.1}.
 407       *
 408       * @param \phpseclib3\Math\BigInteger $m
 409       * @return bool|\phpseclib3\Math\BigInteger
 410       */
 411      private function rsaep($m)
 412      {
 413          if ($m->compare(self::$zero) < 0 || $m->compare($this->modulus) > 0) {
 414              throw new \OutOfRangeException('Message representative out of range');
 415          }
 416          return $this->exponentiate($m);
 417      }
 418  
 419      /**
 420       * Raw Encryption / Decryption
 421       *
 422       * Doesn't use padding and is not recommended.
 423       *
 424       * @param string $m
 425       * @return bool|string
 426       * @throws \LengthException if strlen($m) > $this->k
 427       */
 428      private function raw_encrypt($m)
 429      {
 430          if (strlen($m) > $this->k) {
 431              throw new \LengthException('Message too long');
 432          }
 433  
 434          $temp = $this->os2ip($m);
 435          $temp = $this->rsaep($temp);
 436          return  $this->i2osp($temp, $this->k);
 437      }
 438  
 439      /**
 440       * Encryption
 441       *
 442       * Both self::PADDING_OAEP and self::PADDING_PKCS1 both place limits on how long $plaintext can be.
 443       * If $plaintext exceeds those limits it will be broken up so that it does and the resultant ciphertext's will
 444       * be concatenated together.
 445       *
 446       * @see self::decrypt()
 447       * @param string $plaintext
 448       * @return bool|string
 449       * @throws \LengthException if the RSA modulus is too short
 450       */
 451      public function encrypt($plaintext)
 452      {
 453          switch ($this->encryptionPadding) {
 454              case self::ENCRYPTION_NONE:
 455                  return $this->raw_encrypt($plaintext);
 456              case self::ENCRYPTION_PKCS1:
 457                  return $this->rsaes_pkcs1_v1_5_encrypt($plaintext);
 458              //case self::ENCRYPTION_OAEP:
 459              default:
 460                  return $this->rsaes_oaep_encrypt($plaintext);
 461          }
 462      }
 463  
 464      /**
 465       * Returns the public key
 466       *
 467       * The public key is only returned under two circumstances - if the private key had the public key embedded within it
 468       * or if the public key was set via setPublicKey().  If the currently loaded key is supposed to be the public key this
 469       * function won't return it since this library, for the most part, doesn't distinguish between public and private keys.
 470       *
 471       * @param string $type
 472       * @param array $options optional
 473       * @return mixed
 474       */
 475      public function toString($type, array $options = [])
 476      {
 477          $type = self::validatePlugin('Keys', $type, 'savePublicKey');
 478  
 479          if ($type == PSS::class) {
 480              if ($this->signaturePadding == self::SIGNATURE_PSS) {
 481                  $options += [
 482                      'hash' => $this->hash->getHash(),
 483                      'MGFHash' => $this->mgfHash->getHash(),
 484                      'saltLength' => $this->getSaltLength()
 485                  ];
 486              } else {
 487                  throw new UnsupportedFormatException('The PSS format can only be used when the signature method has been explicitly set to PSS');
 488              }
 489          }
 490  
 491          return $type::savePublicKey($this->modulus, $this->publicExponent, $options);
 492      }
 493  
 494      /**
 495       * Converts a public key to a private key
 496       *
 497       * @return RSA
 498       */
 499      public function asPrivateKey()
 500      {
 501          $new = new PrivateKey();
 502          $new->exponent = $this->exponent;
 503          $new->modulus = $this->modulus;
 504          $new->k = $this->k;
 505          $new->format = $this->format;
 506          return $new
 507              ->withHash($this->hash->getHash())
 508              ->withMGFHash($this->mgfHash->getHash())
 509              ->withSaltLength($this->sLen)
 510              ->withLabel($this->label)
 511              ->withPadding($this->signaturePadding | $this->encryptionPadding);
 512      }
 513  }