1<?php
2
3/**
4 * Pure-PHP implementation of EC.
5 *
6 * PHP version 5
7 *
8 * Here's an example of how to create signatures and verify signatures with this library:
9 * <code>
10 * <?php
11 * include 'vendor/autoload.php';
12 *
13 * $private = \phpseclib3\Crypt\EC::createKey('secp256k1');
14 * $public = $private->getPublicKey();
15 *
16 * $plaintext = 'terrafrost';
17 *
18 * $signature = $private->sign($plaintext);
19 *
20 * echo $public->verify($plaintext, $signature) ? 'verified' : 'unverified';
21 * ?>
22 * </code>
23 *
24 * @author    Jim Wigginton <terrafrost@php.net>
25 * @copyright 2016 Jim Wigginton
26 * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
27 * @link      http://phpseclib.sourceforge.net
28 */
29
30namespace phpseclib3\Crypt;
31
32use phpseclib3\Crypt\Common\AsymmetricKey;
33use phpseclib3\Crypt\EC\BaseCurves\Montgomery as MontgomeryCurve;
34use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards as TwistedEdwardsCurve;
35use phpseclib3\Crypt\EC\Curves\Curve25519;
36use phpseclib3\Crypt\EC\Curves\Ed25519;
37use phpseclib3\Crypt\EC\Curves\Ed448;
38use phpseclib3\Crypt\EC\Formats\Keys\PKCS1;
39use phpseclib3\Crypt\EC\Parameters;
40use phpseclib3\Crypt\EC\PrivateKey;
41use phpseclib3\Crypt\EC\PublicKey;
42use phpseclib3\Exception\UnsupportedAlgorithmException;
43use phpseclib3\Exception\UnsupportedCurveException;
44use phpseclib3\Exception\UnsupportedOperationException;
45use phpseclib3\File\ASN1;
46use phpseclib3\File\ASN1\Maps\ECParameters;
47use phpseclib3\Math\BigInteger;
48
49/**
50 * Pure-PHP implementation of EC.
51 *
52 * @author  Jim Wigginton <terrafrost@php.net>
53 */
54abstract class EC extends AsymmetricKey
55{
56    /**
57     * Algorithm Name
58     *
59     * @var string
60     */
61    const ALGORITHM = 'EC';
62
63    /**
64     * Public Key QA
65     *
66     * @var object[]
67     */
68    protected $QA;
69
70    /**
71     * Curve
72     *
73     * @var \phpseclib3\Crypt\EC\BaseCurves\Base
74     */
75    protected $curve;
76
77    /**
78     * Signature Format
79     *
80     * @var string
81     */
82    protected $format;
83
84    /**
85     * Signature Format (Short)
86     *
87     * @var string
88     */
89    protected $shortFormat;
90
91    /**
92     * Curve Name
93     *
94     * @var string
95     */
96    private $curveName;
97
98    /**
99     * Curve Order
100     *
101     * Used for deterministic ECDSA
102     *
103     * @var \phpseclib3\Math\BigInteger
104     */
105    protected $q;
106
107    /**
108     * Alias for the private key
109     *
110     * Used for deterministic ECDSA. AsymmetricKey expects $x. I don't like x because
111     * with x you have x * the base point yielding an (x, y)-coordinate that is the
112     * public key. But the x is different depending on which side of the equal sign
113     * you're on. It's less ambiguous if you do dA * base point = (x, y)-coordinate.
114     *
115     * @var \phpseclib3\Math\BigInteger
116     */
117    protected $x;
118
119    /**
120     * Context
121     *
122     * @var string
123     */
124    protected $context;
125
126    /**
127     * Signature Format
128     *
129     * @var string
130     */
131    protected $sigFormat;
132
133    /**
134     * Create public / private key pair.
135     *
136     * @param string $curve
137     * @return \phpseclib3\Crypt\EC\PrivateKey
138     */
139    public static function createKey($curve)
140    {
141        self::initialize_static_variables();
142
143        $class = new \ReflectionClass(static::class);
144        if ($class->isFinal()) {
145            throw new \RuntimeException('createKey() should not be called from final classes (' . static::class . ')');
146        }
147
148        if (!isset(self::$engines['PHP'])) {
149            self::useBestEngine();
150        }
151
152        $curve = strtolower($curve);
153        if (self::$engines['libsodium'] && $curve == 'ed25519' && function_exists('sodium_crypto_sign_keypair')) {
154            $kp = sodium_crypto_sign_keypair();
155
156            $privatekey = EC::loadFormat('libsodium', sodium_crypto_sign_secretkey($kp));
157            //$publickey = EC::loadFormat('libsodium', sodium_crypto_sign_publickey($kp));
158
159            $privatekey->curveName = 'Ed25519';
160            //$publickey->curveName = $curve;
161
162            return $privatekey;
163        }
164
165        $privatekey = new PrivateKey();
166
167        $curveName = $curve;
168        if (preg_match('#(?:^curve|^ed)\d+$#', $curveName)) {
169            $curveName = ucfirst($curveName);
170        } elseif (substr($curveName, 0, 10) == 'brainpoolp') {
171            $curveName = 'brainpoolP' . substr($curveName, 10);
172        }
173        $curve = '\phpseclib3\Crypt\EC\Curves\\' . $curveName;
174
175        if (!class_exists($curve)) {
176            throw new UnsupportedCurveException('Named Curve of ' . $curveName . ' is not supported');
177        }
178
179        $reflect = new \ReflectionClass($curve);
180        $curveName = $reflect->isFinal() ?
181            $reflect->getParentClass()->getShortName() :
182            $reflect->getShortName();
183
184        $curve = new $curve();
185        if ($curve instanceof TwistedEdwardsCurve) {
186            $arr = $curve->extractSecret(Random::string($curve instanceof Ed448 ? 57 : 32));
187            $privatekey->dA = $dA = $arr['dA'];
188            $privatekey->secret = $arr['secret'];
189        } else {
190            $privatekey->dA = $dA = $curve->createRandomMultiplier();
191        }
192        if ($curve instanceof Curve25519 && self::$engines['libsodium']) {
193            //$r = pack('H*', '0900000000000000000000000000000000000000000000000000000000000000');
194            //$QA = sodium_crypto_scalarmult($dA->toBytes(), $r);
195            $QA = sodium_crypto_box_publickey_from_secretkey($dA->toBytes());
196            $privatekey->QA = [$curve->convertInteger(new BigInteger(strrev($QA), 256))];
197        } else {
198            $privatekey->QA = $curve->multiplyPoint($curve->getBasePoint(), $dA);
199        }
200        $privatekey->curve = $curve;
201
202        //$publickey = clone $privatekey;
203        //unset($publickey->dA);
204        //unset($publickey->x);
205
206        $privatekey->curveName = $curveName;
207        //$publickey->curveName = $curveName;
208
209        if ($privatekey->curve instanceof TwistedEdwardsCurve) {
210            return $privatekey->withHash($curve::HASH);
211        }
212
213        return $privatekey;
214    }
215
216    /**
217     * OnLoad Handler
218     *
219     * @return bool
220     */
221    protected static function onLoad(array $components)
222    {
223        if (!isset(self::$engines['PHP'])) {
224            self::useBestEngine();
225        }
226
227        if (!isset($components['dA']) && !isset($components['QA'])) {
228            $new = new Parameters();
229            $new->curve = $components['curve'];
230            return $new;
231        }
232
233        $new = isset($components['dA']) ?
234            new PrivateKey() :
235            new PublicKey();
236        $new->curve = $components['curve'];
237        $new->QA = $components['QA'];
238
239        if (isset($components['dA'])) {
240            $new->dA = $components['dA'];
241            $new->secret = $components['secret'];
242        }
243
244        if ($new->curve instanceof TwistedEdwardsCurve) {
245            return $new->withHash($components['curve']::HASH);
246        }
247
248        return $new;
249    }
250
251    /**
252     * Constructor
253     *
254     * PublicKey and PrivateKey objects can only be created from abstract RSA class
255     */
256    protected function __construct()
257    {
258        $this->sigFormat = self::validatePlugin('Signature', 'ASN1');
259        $this->shortFormat = 'ASN1';
260
261        parent::__construct();
262    }
263
264    /**
265     * Returns the curve
266     *
267     * Returns a string if it's a named curve, an array if not
268     *
269     * @return string|array
270     */
271    public function getCurve()
272    {
273        if ($this->curveName) {
274            return $this->curveName;
275        }
276
277        if ($this->curve instanceof MontgomeryCurve) {
278            $this->curveName = $this->curve instanceof Curve25519 ? 'Curve25519' : 'Curve448';
279            return $this->curveName;
280        }
281
282        if ($this->curve instanceof TwistedEdwardsCurve) {
283            $this->curveName = $this->curve instanceof Ed25519 ? 'Ed25519' : 'Ed448';
284            return $this->curveName;
285        }
286
287        $params = $this->getParameters()->toString('PKCS8', ['namedCurve' => true]);
288        $decoded = ASN1::extractBER($params);
289        $decoded = ASN1::decodeBER($decoded);
290        $decoded = ASN1::asn1map($decoded[0], ECParameters::MAP);
291        if (isset($decoded['namedCurve'])) {
292            $this->curveName = $decoded['namedCurve'];
293            return $decoded['namedCurve'];
294        }
295
296        if (!$namedCurves) {
297            PKCS1::useSpecifiedCurve();
298        }
299
300        return $decoded;
301    }
302
303    /**
304     * Returns the key size
305     *
306     * Quoting https://tools.ietf.org/html/rfc5656#section-2,
307     *
308     * "The size of a set of elliptic curve domain parameters on a prime
309     *  curve is defined as the number of bits in the binary representation
310     *  of the field order, commonly denoted by p.  Size on a
311     *  characteristic-2 curve is defined as the number of bits in the binary
312     *  representation of the field, commonly denoted by m.  A set of
313     *  elliptic curve domain parameters defines a group of order n generated
314     *  by a base point P"
315     *
316     * @return int
317     */
318    public function getLength()
319    {
320        return $this->curve->getLength();
321    }
322
323    /**
324     * Returns the current engine being used
325     *
326     * @see self::useInternalEngine()
327     * @see self::useBestEngine()
328     * @return string
329     */
330    public function getEngine()
331    {
332        if (!isset(self::$engines['PHP'])) {
333            self::useBestEngine();
334        }
335        if ($this->curve instanceof TwistedEdwardsCurve) {
336            return $this->curve instanceof Ed25519 && self::$engines['libsodium'] && !isset($this->context) ?
337                'libsodium' : 'PHP';
338        }
339
340        return self::$engines['OpenSSL'] && in_array($this->hash->getHash(), openssl_get_md_methods()) ?
341            'OpenSSL' : 'PHP';
342    }
343
344    /**
345     * Returns the public key coordinates as a string
346     *
347     * Used by ECDH
348     *
349     * @return string
350     */
351    public function getEncodedCoordinates()
352    {
353        if ($this->curve instanceof MontgomeryCurve) {
354            return strrev($this->QA[0]->toBytes(true));
355        }
356        if ($this->curve instanceof TwistedEdwardsCurve) {
357            return $this->curve->encodePoint($this->QA);
358        }
359        return "\4" . $this->QA[0]->toBytes(true) . $this->QA[1]->toBytes(true);
360    }
361
362    /**
363     * Returns the parameters
364     *
365     * @see self::getPublicKey()
366     * @param string $type optional
367     * @return mixed
368     */
369    public function getParameters($type = 'PKCS1')
370    {
371        $type = self::validatePlugin('Keys', $type, 'saveParameters');
372
373        $key = $type::saveParameters($this->curve);
374
375        return EC::load($key, 'PKCS1')
376            ->withHash($this->hash->getHash())
377            ->withSignatureFormat($this->shortFormat);
378    }
379
380    /**
381     * Determines the signature padding mode
382     *
383     * Valid values are: ASN1, SSH2, Raw
384     *
385     * @param string $format
386     */
387    public function withSignatureFormat($format)
388    {
389        if ($this->curve instanceof MontgomeryCurve) {
390            throw new UnsupportedOperationException('Montgomery Curves cannot be used to create signatures');
391        }
392
393        $new = clone $this;
394        $new->shortFormat = $format;
395        $new->sigFormat = self::validatePlugin('Signature', $format);
396        return $new;
397    }
398
399    /**
400     * Returns the signature format currently being used
401     *
402     */
403    public function getSignatureFormat()
404    {
405        return $this->shortFormat;
406    }
407
408    /**
409     * Sets the context
410     *
411     * Used by Ed25519 / Ed448.
412     *
413     * @see self::sign()
414     * @see self::verify()
415     * @param string $context optional
416     */
417    public function withContext($context = null)
418    {
419        if (!$this->curve instanceof TwistedEdwardsCurve) {
420            throw new UnsupportedCurveException('Only Ed25519 and Ed448 support contexts');
421        }
422
423        $new = clone $this;
424        if (!isset($context)) {
425            $new->context = null;
426            return $new;
427        }
428        if (!is_string($context)) {
429            throw new \InvalidArgumentException('setContext expects a string');
430        }
431        if (strlen($context) > 255) {
432            throw new \LengthException('The context is supposed to be, at most, 255 bytes long');
433        }
434        $new->context = $context;
435        return $new;
436    }
437
438    /**
439     * Returns the signature format currently being used
440     *
441     */
442    public function getContext()
443    {
444        return $this->context;
445    }
446
447    /**
448     * Determines which hashing function should be used
449     *
450     * @param string $hash
451     */
452    public function withHash($hash)
453    {
454        if ($this->curve instanceof MontgomeryCurve) {
455            throw new UnsupportedOperationException('Montgomery Curves cannot be used to create signatures');
456        }
457        if ($this->curve instanceof Ed25519 && $hash != 'sha512') {
458            throw new UnsupportedAlgorithmException('Ed25519 only supports sha512 as a hash');
459        }
460        if ($this->curve instanceof Ed448 && $hash != 'shake256-912') {
461            throw new UnsupportedAlgorithmException('Ed448 only supports shake256 with a length of 114 bytes');
462        }
463
464        return parent::withHash($hash);
465    }
466
467    /**
468     * __toString() magic method
469     *
470     * @return string
471     */
472    public function __toString()
473    {
474        if ($this->curve instanceof MontgomeryCurve) {
475            return '';
476        }
477
478        return parent::__toString();
479    }
480}
481