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