xref: /dokuwiki/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC.php (revision a896ec97b4d9a77a7ab6956f96aaa0e7987f57d1)
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\Curve448;
37use phpseclib3\Crypt\EC\Curves\Ed25519;
38use phpseclib3\Crypt\EC\Curves\Ed448;
39use phpseclib3\Crypt\EC\Formats\Keys\PKCS1;
40use phpseclib3\Crypt\EC\Formats\Keys\PKCS8;
41use phpseclib3\Crypt\EC\Parameters;
42use phpseclib3\Crypt\EC\PrivateKey;
43use phpseclib3\Crypt\EC\PublicKey;
44use phpseclib3\Exception\BadConfigurationException;
45use phpseclib3\Exception\UnsupportedAlgorithmException;
46use phpseclib3\Exception\UnsupportedCurveException;
47use phpseclib3\Exception\UnsupportedOperationException;
48use phpseclib3\File\ASN1;
49use phpseclib3\File\ASN1\Maps\ECParameters;
50use phpseclib3\Math\BigInteger;
51
52/**
53 * Pure-PHP implementation of EC.
54 *
55 * @author  Jim Wigginton <terrafrost@php.net>
56 */
57abstract class EC extends AsymmetricKey
58{
59    /**
60     * Algorithm Name
61     *
62     * @var string
63     */
64    const ALGORITHM = 'EC';
65
66    /**
67     * Public Key QA
68     *
69     * @var object[]
70     */
71    protected $QA;
72
73    /**
74     * Curve
75     *
76     * @var EC\BaseCurves\Base
77     */
78    protected $curve;
79
80    /**
81     * Signature Format (Short)
82     *
83     * @var string
84     */
85    protected $shortFormat;
86
87    /**
88     * Curve Name
89     *
90     * @var string
91     */
92    private $curveName;
93
94    /**
95     * Curve Order
96     *
97     * Used for deterministic ECDSA
98     *
99     * @var BigInteger
100     */
101    protected $q;
102
103    /**
104     * Alias for the private key
105     *
106     * Used for deterministic ECDSA. AsymmetricKey expects $x. I don't like x because
107     * with x you have x * the base point yielding an (x, y)-coordinate that is the
108     * public key. But the x is different depending on which side of the equal sign
109     * you're on. It's less ambiguous if you do dA * base point = (x, y)-coordinate.
110     *
111     * @var BigInteger
112     */
113    protected $x;
114
115    /**
116     * Context
117     *
118     * @var string
119     */
120    protected $context;
121
122    /**
123     * Signature Format
124     *
125     * @var string
126     */
127    protected $sigFormat;
128
129    /**
130     * Forced Engine
131     *
132     * @var ?string
133     * @see parent::forceEngine()
134     */
135    protected static $forcedEngine = null;
136
137    /**
138     * Create public / private key pair.
139     *
140     * @param string $curve
141     * @return PrivateKey
142     */
143    public static function createKey($curve)
144    {
145        self::initialize_static_variables();
146
147        $class = new \ReflectionClass(static::class);
148        if ($class->isFinal()) {
149            throw new \RuntimeException('createKey() should not be called from final classes (' . static::class . ')');
150        }
151
152        $curveName = self::getCurveCase($curve);
153        $curve = '\phpseclib3\Crypt\EC\Curves\\' . $curveName;
154
155        if (!class_exists($curve)) {
156            throw new UnsupportedCurveException('Named Curve of ' . $curveName . ' is not supported');
157        }
158
159        $reflect = new \ReflectionClass($curve);
160        $curveName = $reflect->isFinal() ?
161            $reflect->getParentClass()->getShortName() :
162            $reflect->getShortName();
163        $curveEngineName = self::getOpenSSLCurveName(strtolower($curveName));
164
165        switch ($curveName) {
166            case 'Ed25519':
167                $providers = [
168                    'libsodium' => function_exists('sodium_crypto_sign_keypair'),
169                    // OPENSSL_KEYTYPE_ED25519 introduced in PHP 8.4.0
170                    'OpenSSL'   => defined('OPENSSL_KEYTYPE_ED25519'),
171                ];
172                break;
173            case 'Ed448':
174                // OPENSSL_KEYTYPE_ED448 introduced in PHP 8.4.0
175                $providers = ['OpenSSL' => defined('OPENSSL_KEYTYPE_ED448')];
176                break;
177            case 'Curve25519':
178                $providers = [
179                    'libsodium' => function_exists('sodium_crypto_box_publickey_from_secretkey'),
180                    // OPENSSL_KEYTYPE_X25519 introduced in PHP 8.4.0
181                    'OpenSSL'   => defined('OPENSSL_KEYTYPE_X25519'),
182                ];
183            // OPENSSL_KEYTYPE_X448 introduced in PHP 8.4.0
184            case 'Curve448':
185                $providers = ['OpenSSL' => defined('OPENSSL_KEYTYPE_X448')];
186                break;
187            default:
188                // openssl_get_curve_names() was introduced in PHP 7.1.0
189                // exclude curve25519 and curve448 from testing
190                $providers = ['OpenSSL' => function_exists('openssl_get_curve_names') && substr($curveEngineName, 0, 5) != 'curve' && in_array($curveEngineName, openssl_get_curve_names())];
191        };
192
193        foreach ($providers as $engine => $isSupported) {
194            // if an engine is being forced and the forced engine doesn't match $engine, skip it
195            if (isset(self::$forcedEngine) && self::$forcedEngine !== $engine) {
196                continue;
197            }
198            if ($isSupported) {
199                $result = self::generateWithEngine($engine, $curveEngineName);
200                if (isset($result)) {
201                    return $result;
202                }
203            }
204            if (self::$forcedEngine === $engine) {
205                throw new BadConfigurationException("EC::createKey: Engine $engine is forced but unsupported for $curve");
206            }
207        }
208
209        $privatekey = new PrivateKey();
210
211        $curve = new $curve();
212        if ($curve instanceof TwistedEdwardsCurve) {
213            $arr = $curve->extractSecret(Random::string($curve instanceof Ed448 ? 57 : 32));
214            $privatekey->dA = $dA = $arr['dA'];
215            $privatekey->secret = $arr['secret'];
216        } else {
217            $privatekey->dA = $dA = $curve->createRandomMultiplier();
218        }
219        $privatekey->curve = $curve;
220        $privatekey->curveName = $curveName;
221        $privatekey->QA = $curve->multiplyPoint($curve->getBasePoint(), $dA);
222
223        //$publickey = clone $privatekey;
224        //unset($publickey->dA);
225        //unset($publickey->x);
226
227        //$publickey->curveName = $curveName;
228
229        if ($privatekey->curve instanceof TwistedEdwardsCurve) {
230            return $privatekey->withHash($curve::HASH);
231        }
232
233        return $privatekey;
234    }
235
236    /**
237     * Returns the actual case of the curve
238     *
239     * Useful for initializing the curve class
240     *
241     * @param string $curveName
242     * @return string
243     */
244    private static function getCurveCase($curveName)
245    {
246        $curveName = strtolower($curveName);
247        if (preg_match('#(?:^curve|^ed)\d+$#', $curveName)) {
248            return ucfirst($curveName);
249        }
250        if (substr($curveName, 0, 10) == 'brainpoolp') {
251            return 'brainpoolP' . substr($curveName, 10);
252        }
253        return $curveName;
254    }
255
256    /**
257     * Return the OpenSSL name for a curve
258     *
259     * @param string $curve
260     * @return string
261     */
262    private static function getOpenSSLCurveName($curve)
263    {
264        switch ($curve) {
265            case 'secp256r1':
266                return 'prime256v1';
267            case 'secp192r1':
268                return 'prime192v1';
269        }
270        return $curve;
271    }
272
273    /**
274     * Generate the key for a given curve / engine combo
275     *
276     * @param string $engine
277     * @param string $curve
278     * @return ?PrivateKey
279     */
280    private static function generateWithEngine($engine, $curve)
281    {
282        if ($engine == 'libsodium') {
283            if ($curve == 'ed25519') {
284                $kp = sodium_crypto_sign_keypair();
285
286                $privatekey = EC::loadFormat('libsodium', sodium_crypto_sign_secretkey($kp));
287                //$publickey = EC::loadFormat('libsodium', sodium_crypto_sign_publickey($kp));
288
289                $privatekey->curveName = 'Ed25519';
290                //$publickey->curveName = $curve;
291
292                return $privatekey;
293            } else { // $curve == 'curve25519
294                $privatekey = new PrivateKey();
295                $privatekey->curve = new Curve25519();
296                $privatekey->curveName = 'Curve25519';
297                $privatekey->dA = $privatekey->curve->createRandomMultiplier();
298                $dA = str_pad($privatekey->dA->toBytes(), 32, "\0", STR_PAD_LEFT);
299                //$r = pack('H*', '0900000000000000000000000000000000000000000000000000000000000000');
300                //$QA = sodium_crypto_scalarmult($dA, $r);
301                $QA = sodium_crypto_box_publickey_from_secretkey($dA);
302                $privatekey->QA = [$privatekey->curve->convertInteger(new BigInteger(strrev($QA), 256))];
303                return $privatekey;
304            }
305        }
306
307        // at this point $engine == 'OpenSSL'
308
309        $curveName = self::getCurveCase($curve);
310
311        $config = [];
312        if (self::$configFile) {
313            $config['config'] = self::$configFile;
314        }
315        $params = $config;
316        switch ($curve) {
317            case 'ed25519':
318                $params['private_key_type'] = OPENSSL_KEYTYPE_ED25519;
319                break;
320            case 'ed448':
321                $params['private_key_type'] = OPENSSL_KEYTYPE_ED448;
322                break;
323            case 'curve25519':
324                $params['private_key_type'] = OPENSSL_KEYTYPE_X25519;
325                break;
326            case 'curve448':
327                $params['private_key_type'] = OPENSSL_KEYTYPE_X448;
328                break;
329            default:
330                $params['private_key_type'] = OPENSSL_KEYTYPE_EC;
331                $params['curve_name'] = $curveName;
332        }
333
334        $key = openssl_pkey_new($params);
335        if (!$key) {
336            return null;
337        }
338        $privateKeyStr = '';
339        if (!openssl_pkey_export($key, $privateKeyStr, null, $config)) {
340            return null;
341        }
342        // clear the buffer of error strings
343        while (openssl_error_string() !== false) {
344        }
345        // some versions of OpenSSL / PHP return PKCS1 keys, others return PKCS8 keys
346        $privatekey = EC::load($privateKeyStr);
347        switch ($curveName) {
348            case 'prime256v1':
349                $privatekey->curveName = 'secp256r1';
350                break;
351            case 'prime192v1':
352                $privatekey->curveName = 'secp192r1';
353                break;
354            default:
355                $privatekey->curveName = $curveName;
356        }
357        return $privatekey;
358    }
359
360    /**
361     * OnLoad Handler
362     *
363     * @return bool
364     */
365    protected static function onLoad(array $components)
366    {
367        if (!isset($components['dA']) && !isset($components['QA'])) {
368            $new = new Parameters();
369            $new->curve = $components['curve'];
370            return $new;
371        }
372
373        $new = isset($components['dA']) ?
374            new PrivateKey() :
375            new PublicKey();
376        $new->curve = $components['curve'];
377        $new->QA = $components['QA'];
378
379        if (isset($components['dA'])) {
380            $new->dA = $components['dA'];
381            $new->secret = $components['secret'];
382        }
383
384        if ($new->curve instanceof TwistedEdwardsCurve) {
385            return $new->withHash($components['curve']::HASH);
386        }
387
388        return $new;
389    }
390
391    /**
392     * Constructor
393     *
394     * PublicKey and PrivateKey objects can only be created from abstract RSA class
395     */
396    protected function __construct()
397    {
398        $this->sigFormat = self::validatePlugin('Signature', 'ASN1');
399        $this->shortFormat = 'ASN1';
400
401        parent::__construct();
402    }
403
404    /**
405     * Returns the curve
406     *
407     * Returns a string if it's a named curve, an array if not
408     *
409     * @return string|array
410     */
411    public function getCurve()
412    {
413        if ($this->curveName) {
414            return $this->curveName;
415        }
416
417        if ($this->curve instanceof MontgomeryCurve) {
418            $this->curveName = $this->curve instanceof Curve25519 ? 'Curve25519' : 'Curve448';
419            return $this->curveName;
420        }
421
422        if ($this->curve instanceof TwistedEdwardsCurve) {
423            $this->curveName = $this->curve instanceof Ed25519 ? 'Ed25519' : 'Ed448';
424            return $this->curveName;
425        }
426
427        $params = $this->getParameters()->toString('PKCS8', ['namedCurve' => true]);
428        $decoded = ASN1::extractBER($params);
429        $decoded = ASN1::decodeBER($decoded);
430        $decoded = ASN1::asn1map($decoded[0], ECParameters::MAP);
431        if (isset($decoded['namedCurve'])) {
432            $this->curveName = $decoded['namedCurve'];
433            return $decoded['namedCurve'];
434        }
435
436        if (!$namedCurves) {
437            PKCS1::useSpecifiedCurve();
438        }
439
440        return $decoded;
441    }
442
443    /**
444     * Returns the key size
445     *
446     * Quoting https://tools.ietf.org/html/rfc5656#section-2,
447     *
448     * "The size of a set of elliptic curve domain parameters on a prime
449     *  curve is defined as the number of bits in the binary representation
450     *  of the field order, commonly denoted by p.  Size on a
451     *  characteristic-2 curve is defined as the number of bits in the binary
452     *  representation of the field, commonly denoted by m.  A set of
453     *  elliptic curve domain parameters defines a group of order n generated
454     *  by a base point P"
455     *
456     * @return int
457     */
458    public function getLength()
459    {
460        return $this->curve->getLength();
461    }
462
463    /**
464     * Returns the public key coordinates as a string
465     *
466     * Used by ECDH
467     *
468     * @return string
469     */
470    public function getEncodedCoordinates()
471    {
472        if ($this->curve instanceof MontgomeryCurve) {
473            return strrev($this->QA[0]->toBytes(true));
474        }
475        if ($this->curve instanceof TwistedEdwardsCurve) {
476            return $this->curve->encodePoint($this->QA);
477        }
478        return "\4" . $this->QA[0]->toBytes(true) . $this->QA[1]->toBytes(true);
479    }
480
481    /**
482     * Convert point to public key
483     *
484     * For Weierstrass curves, if only the x coordinate is present (as is the case after doing a round of ECDH)
485     * then we'll guess at the y coordinate. There are only two possible y values and, atleast in-so-far as
486     * multiplication is concerned, neither value affects the resultant x value
487     *
488     * If $toPublicKey is set to false then a string will be returned - a kind of public key precursor
489     *
490     * @param string $curveName
491     * @param string $secret
492     * @param bool $toPublicKey optional
493     * @return PublicKey|string
494     */
495    public static function convertPointToPublicKey($curveName, $secret, $toPublicKey = true)
496    {
497        $curveName = self::getCurveCase($curveName);
498        $curve = '\phpseclib3\Crypt\EC\Curves\\' . $curveName;
499
500        if (!class_exists($curve)) {
501            throw new UnsupportedCurveException('Named Curve of ' . $curveName . ' is not supported');
502        }
503
504        $curve = new $curve();
505        if (!$curve instanceof TwistedEdwardsCurve) {
506            if ($curve instanceof MontgomeryCurve) {
507                $secret = strrev($secret);
508            } elseif ($curve->getLengthInBytes() == strlen($secret)) {
509                $secret = "\3$secret";
510            }
511            if (!$toPublicKey) {
512                return $secret;
513            }
514            $secret = "\0$secret";
515        } elseif (!$toPublicKey) {
516            return $secret;
517        }
518        $QA = PKCS8::extractPoint($secret, $curve);
519        $key = PKCS8::savePublicKey($curve, $QA);
520        return EC::loadFormat('PKCS8', $key);
521    }
522
523    /**
524     * Returns the parameters
525     *
526     * @see self::getPublicKey()
527     * @param string $type optional
528     * @return mixed
529     */
530    public function getParameters($type = 'PKCS1')
531    {
532        $type = self::validatePlugin('Keys', $type, 'saveParameters');
533
534        $key = $type::saveParameters($this->curve);
535
536        return EC::load($key, 'PKCS1')
537            ->withHash($this->hash->getHash())
538            ->withSignatureFormat($this->shortFormat);
539    }
540
541    /**
542     * Determines the signature padding mode
543     *
544     * Valid values are: ASN1, SSH2, Raw
545     *
546     * @param string $format
547     */
548    public function withSignatureFormat($format)
549    {
550        if ($this->curve instanceof MontgomeryCurve) {
551            throw new UnsupportedOperationException('Montgomery Curves cannot be used to create signatures');
552        }
553
554        $new = clone $this;
555        $new->shortFormat = $format;
556        $new->sigFormat = self::validatePlugin('Signature', $format);
557        return $new;
558    }
559
560    /**
561     * Returns the signature format currently being used
562     *
563     */
564    public function getSignatureFormat()
565    {
566        return $this->shortFormat;
567    }
568
569    /**
570     * Sets the context
571     *
572     * Used by Ed25519 / Ed448.
573     *
574     * @see self::sign()
575     * @see self::verify()
576     * @param string $context optional
577     */
578    public function withContext($context = null)
579    {
580        if (!$this->curve instanceof TwistedEdwardsCurve) {
581            throw new UnsupportedCurveException('Only Ed25519 and Ed448 support contexts');
582        }
583
584        $new = clone $this;
585        if (!isset($context)) {
586            $new->context = null;
587            return $new;
588        }
589        if (!is_string($context)) {
590            throw new \InvalidArgumentException('setContext expects a string');
591        }
592        if (strlen($context) > 255) {
593            throw new \LengthException('The context is supposed to be, at most, 255 bytes long');
594        }
595        $new->context = $context;
596        return $new;
597    }
598
599    /**
600     * Returns the signature format currently being used
601     *
602     */
603    public function getContext()
604    {
605        return $this->context;
606    }
607
608    /**
609     * Determines which hashing function should be used
610     *
611     * @param string $hash
612     */
613    public function withHash($hash)
614    {
615        if ($this->curve instanceof MontgomeryCurve) {
616            throw new UnsupportedOperationException('Montgomery Curves cannot be used to create signatures');
617        }
618        if ($this->curve instanceof Ed25519 && $hash != 'sha512') {
619            throw new UnsupportedAlgorithmException('Ed25519 only supports sha512 as a hash');
620        }
621        if ($this->curve instanceof Ed448 && $hash != 'shake256-912') {
622            throw new UnsupportedAlgorithmException('Ed448 only supports shake256 with a length of 114 bytes');
623        }
624
625        return parent::withHash($hash);
626    }
627}
628