1<?php
2
3/**
4 * EC Public Key
5 *
6 * @category  Crypt
7 * @package   EC
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
14namespace phpseclib3\Crypt\EC;
15
16use phpseclib3\Common\Functions\Strings;
17use phpseclib3\Crypt\Common;
18use phpseclib3\Crypt\EC;
19use phpseclib3\Crypt\EC\BaseCurves\Montgomery as MontgomeryCurve;
20use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards as TwistedEdwardsCurve;
21use phpseclib3\Crypt\EC\Curves\Ed25519;
22use phpseclib3\Crypt\EC\Formats\Keys\PKCS1;
23use phpseclib3\Crypt\EC\Formats\Signature\ASN1 as ASN1Signature;
24use phpseclib3\Crypt\Hash;
25use phpseclib3\Exception\UnsupportedOperationException;
26use phpseclib3\Math\BigInteger;
27
28/**
29 * EC Public Key
30 *
31 * @package EC
32 * @author  Jim Wigginton <terrafrost@php.net>
33 * @access  public
34 */
35class PublicKey extends EC implements Common\PublicKey
36{
37    use Common\Traits\Fingerprint;
38
39    /**
40     * Verify a signature
41     *
42     * @see self::verify()
43     * @access public
44     * @param string $message
45     * @param string $signature
46     * @return mixed
47     */
48    public function verify($message, $signature)
49    {
50        if ($this->curve instanceof MontgomeryCurve) {
51            throw new UnsupportedOperationException('Montgomery Curves cannot be used to create signatures');
52        }
53
54        $shortFormat = $this->shortFormat;
55        $format = $this->sigFormat;
56        if ($format === false) {
57            return false;
58        }
59
60        $order = $this->curve->getOrder();
61
62        if ($this->curve instanceof TwistedEdwardsCurve) {
63            if ($shortFormat == 'SSH2') {
64                list(, $signature) = Strings::unpackSSH2('ss', $signature);
65            }
66
67            if ($this->curve instanceof Ed25519 && self::$engines['libsodium'] && !isset($this->context)) {
68                return sodium_crypto_sign_verify_detached($signature, $message, $this->toString('libsodium'));
69            }
70
71            $curve = $this->curve;
72            if (strlen($signature) != 2 * $curve::SIZE) {
73                return false;
74            }
75
76            $R = substr($signature, 0, $curve::SIZE);
77            $S = substr($signature, $curve::SIZE);
78
79            try {
80                $R = PKCS1::extractPoint($R, $curve);
81                $R = $this->curve->convertToInternal($R);
82            } catch (\Exception $e) {
83                return false;
84            }
85
86            $S = strrev($S);
87            $S = new BigInteger($S, 256);
88
89            if ($S->compare($order) >= 0) {
90                return false;
91            }
92
93            $A = $curve->encodePoint($this->QA);
94
95            if ($curve instanceof Ed25519) {
96                $dom2 = !isset($this->context) ? '' :
97                    'SigEd25519 no Ed25519 collisions' . "\0" . chr(strlen($this->context)) . $this->context;
98            } else {
99                $context = isset($this->context) ? $this->context : '';
100                $dom2 = 'SigEd448' . "\0" . chr(strlen($context)) . $context;
101            }
102
103            $hash = new Hash($curve::HASH);
104            $k = $hash->hash($dom2 . substr($signature, 0, $curve::SIZE) . $A . $message);
105            $k = strrev($k);
106            $k = new BigInteger($k, 256);
107            list(, $k) = $k->divide($order);
108
109            $qa = $curve->convertToInternal($this->QA);
110
111            $lhs = $curve->multiplyPoint($curve->getBasePoint(), $S);
112            $rhs = $curve->multiplyPoint($qa, $k);
113            $rhs = $curve->addPoint($rhs, $R);
114            $rhs = $curve->convertToAffine($rhs);
115
116            return $lhs[0]->equals($rhs[0]) && $lhs[1]->equals($rhs[1]);
117        }
118
119        $params = $format::load($signature);
120        if ($params === false || count($params) != 2) {
121            return false;
122        }
123        extract($params);
124
125        if (self::$engines['OpenSSL'] && in_array($this->hash->getHash(), openssl_get_md_methods())) {
126            $sig = $format != 'ASN1' ? ASN1Signature::save($r, $s) : $signature;
127
128            $result = openssl_verify($message, $sig, $this->toString('PKCS8', ['namedCurve' => false]), $this->hash->getHash());
129
130            if ($result != -1) {
131                return (bool) $result;
132            }
133        }
134
135        $n_1 = $order->subtract(self::$one);
136        if (!$r->between(self::$one, $n_1) || !$s->between(self::$one, $n_1)) {
137            return false;
138        }
139
140        $e = $this->hash->hash($message);
141        $e = new BigInteger($e, 256);
142
143        $Ln = $this->hash->getLength() - $order->getLength();
144        $z = $Ln > 0 ? $e->bitwise_rightShift($Ln) : $e;
145
146        $w = $s->modInverse($order);
147        list(, $u1) = $z->multiply($w)->divide($order);
148        list(, $u2) = $r->multiply($w)->divide($order);
149
150        $u1 = $this->curve->convertInteger($u1);
151        $u2 = $this->curve->convertInteger($u2);
152
153        list($x1, $y1) = $this->curve->multiplyAddPoints(
154            [$this->curve->getBasePoint(), $this->QA],
155            [$u1, $u2]
156        );
157
158        $x1 = $x1->toBigInteger();
159        list(, $x1) = $x1->divide($order);
160
161        return $x1->equals($r);
162    }
163
164    /**
165     * Returns the public key
166     *
167     * @param string $type
168     * @param array $options optional
169     * @return string
170     */
171    public function toString($type, array $options = [])
172    {
173        $type = self::validatePlugin('Keys', $type, 'savePublicKey');
174
175        return $type::savePublicKey($this->curve, $this->QA, $options);
176    }
177}
178