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