Subversion Repositories oidplus

Rev

Rev 846 | Rev 1042 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

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