Subversion Repositories oidplus

Rev

Rev 846 | Rev 1042 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
827 daniel-mar 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
 *
874 daniel-mar 24
 * @category  Crypt
25
 * @package   EC
827 daniel-mar 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
 
32
namespace phpseclib3\Crypt;
33
 
34
use phpseclib3\Crypt\Common\AsymmetricKey;
35
use phpseclib3\Crypt\EC\BaseCurves\Montgomery as MontgomeryCurve;
36
use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards as TwistedEdwardsCurve;
37
use phpseclib3\Crypt\EC\Curves\Curve25519;
38
use phpseclib3\Crypt\EC\Curves\Ed25519;
39
use phpseclib3\Crypt\EC\Curves\Ed448;
40
use phpseclib3\Crypt\EC\Formats\Keys\PKCS1;
41
use phpseclib3\Crypt\EC\Parameters;
42
use phpseclib3\Crypt\EC\PrivateKey;
43
use phpseclib3\Crypt\EC\PublicKey;
44
use phpseclib3\Exception\UnsupportedAlgorithmException;
45
use phpseclib3\Exception\UnsupportedCurveException;
46
use phpseclib3\Exception\UnsupportedOperationException;
47
use phpseclib3\File\ASN1;
48
use phpseclib3\File\ASN1\Maps\ECParameters;
49
use phpseclib3\Math\BigInteger;
50
 
51
/**
52
 * Pure-PHP implementation of EC.
53
 *
874 daniel-mar 54
 * @package EC
827 daniel-mar 55
 * @author  Jim Wigginton <terrafrost@php.net>
874 daniel-mar 56
 * @access  public
827 daniel-mar 57
 */
58
abstract class EC extends AsymmetricKey
59
{
60
    /**
61
     * Algorithm Name
62
     *
63
     * @var string
874 daniel-mar 64
     * @access private
827 daniel-mar 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
874 daniel-mar 86
     * @access private
827 daniel-mar 87
     */
88
    protected $format;
89
 
90
    /**
91
     * Signature Format (Short)
92
     *
93
     * @var string
874 daniel-mar 94
     * @access private
827 daniel-mar 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
     *
874 daniel-mar 136
     * @access public
827 daniel-mar 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
874 daniel-mar 210
     * @access protected
827 daniel-mar 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
     *
874 daniel-mar 260
     * @access public
827 daniel-mar 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
     *
874 daniel-mar 308
     * @access public
827 daniel-mar 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()
874 daniel-mar 321
     * @access public
827 daniel-mar 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()
874 daniel-mar 360
     * @access public
827 daniel-mar 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
     *
874 daniel-mar 380
     * @access public
827 daniel-mar 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
     *
874 daniel-mar 398
     * @access public
827 daniel-mar 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()
874 daniel-mar 412
     * @access public
827 daniel-mar 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
     *
874 daniel-mar 439
     * @access public
827 daniel-mar 440
     */
441
    public function getContext()
442
    {
443
        return $this->context;
444
    }
445
 
446
    /**
447
     * Determines which hashing function should be used
448
     *
874 daniel-mar 449
     * @access public
827 daniel-mar 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
}