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
 * Base Class for all asymmetric key ciphers
5
 *
6
 * PHP version 5
7
 *
874 daniel-mar 8
 * @category  Crypt
9
 * @package   AsymmetricKey
827 daniel-mar 10
 * @author    Jim Wigginton <terrafrost@php.net>
11
 * @copyright 2016 Jim Wigginton
12
 * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
13
 * @link      http://phpseclib.sourceforge.net
14
 */
15
 
16
namespace phpseclib3\Crypt\Common;
17
 
18
use phpseclib3\Crypt\DSA;
19
use phpseclib3\Crypt\Hash;
20
use phpseclib3\Crypt\RSA;
21
use phpseclib3\Exception\NoKeyLoadedException;
22
use phpseclib3\Exception\UnsupportedFormatException;
23
use phpseclib3\Math\BigInteger;
24
 
25
/**
26
 * Base Class for all asymmetric cipher classes
27
 *
874 daniel-mar 28
 * @package AsymmetricKey
827 daniel-mar 29
 * @author  Jim Wigginton <terrafrost@php.net>
30
 */
31
abstract class AsymmetricKey
32
{
33
    /**
34
     * Precomputed Zero
35
     *
36
     * @var \phpseclib3\Math\BigInteger
874 daniel-mar 37
     * @access private
827 daniel-mar 38
     */
39
    protected static $zero;
40
 
41
    /**
42
     * Precomputed One
43
     *
44
     * @var \phpseclib3\Math\BigInteger
874 daniel-mar 45
     * @access private
827 daniel-mar 46
     */
47
    protected static $one;
48
 
49
    /**
50
     * Format of the loaded key
51
     *
52
     * @var string
874 daniel-mar 53
     * @access private
827 daniel-mar 54
     */
55
    protected $format;
56
 
57
    /**
58
     * Hash function
59
     *
60
     * @var \phpseclib3\Crypt\Hash
874 daniel-mar 61
     * @access private
827 daniel-mar 62
     */
63
    protected $hash;
64
 
65
    /**
66
     * HMAC function
67
     *
68
     * @var \phpseclib3\Crypt\Hash
874 daniel-mar 69
     * @access private
827 daniel-mar 70
     */
71
    private $hmac;
72
 
73
    /**
74
     * Supported plugins (lower case)
75
     *
76
     * @see self::initialize_static_variables()
77
     * @var array
874 daniel-mar 78
     * @access private
827 daniel-mar 79
     */
80
    private static $plugins = [];
81
 
82
    /**
83
     * Invisible plugins
84
     *
85
     * @see self::initialize_static_variables()
86
     * @var array
874 daniel-mar 87
     * @access private
827 daniel-mar 88
     */
89
    private static $invisiblePlugins = [];
90
 
91
    /**
92
     * Supported signature formats (lower case)
93
     *
94
     * @see self::initialize_static_variables()
95
     * @var array
874 daniel-mar 96
     * @access private
827 daniel-mar 97
     */
98
    private static $signatureFormats = [];
99
 
100
    /**
101
     * Supported signature formats (original case)
102
     *
103
     * @see self::initialize_static_variables()
104
     * @var array
874 daniel-mar 105
     * @access private
827 daniel-mar 106
     */
107
    private static $signatureFileFormats = [];
108
 
109
    /**
110
     * Available Engines
111
     *
112
     * @var boolean[]
874 daniel-mar 113
     * @access private
827 daniel-mar 114
     */
115
    protected static $engines = [];
116
 
117
    /**
118
     * Key Comment
119
     *
120
     * @var null|string
874 daniel-mar 121
     * @access private
827 daniel-mar 122
     */
123
    private $comment;
124
 
125
    /**
126
     * @param string $type
127
     * @return string
128
     */
129
    abstract public function toString($type, array $options = []);
130
 
131
    /**
132
     * The constructor
133
     */
134
    protected function __construct()
135
    {
136
        self::initialize_static_variables();
137
 
138
        $this->hash = new Hash('sha256');
139
        $this->hmac = new Hash('sha256');
140
    }
141
 
142
    /**
143
     * Initialize static variables
144
     */
145
    protected static function initialize_static_variables()
146
    {
147
        if (!isset(self::$zero)) {
148
            self::$zero = new BigInteger(0);
149
            self::$one = new BigInteger(1);
150
        }
151
 
152
        self::loadPlugins('Keys');
153
        if (static::ALGORITHM != 'RSA' && static::ALGORITHM != 'DH') {
154
            self::loadPlugins('Signature');
155
        }
156
    }
157
 
158
    /**
159
     * Load the key
160
     *
161
     * @param string $key
162
     * @param string $password optional
163
     * @return AsymmetricKey
164
     */
165
    public static function load($key, $password = false)
166
    {
167
        self::initialize_static_variables();
168
 
169
        $components = false;
170
        foreach (self::$plugins[static::ALGORITHM]['Keys'] as $format) {
171
            if (isset(self::$invisiblePlugins[static::ALGORITHM]) && in_array($format, self::$invisiblePlugins[static::ALGORITHM])) {
172
                continue;
173
            }
174
            try {
175
                $components = $format::load($key, $password);
176
            } catch (\Exception $e) {
177
                $components = false;
178
            }
179
            if ($components !== false) {
180
                break;
181
            }
182
        }
183
 
184
        if ($components === false) {
185
            throw new NoKeyLoadedException('Unable to read key');
186
        }
187
 
188
        $components['format'] = $format;
189
        $comment = isset($components['comment']) ? $components['comment'] : null;
190
        $new = static::onLoad($components);
191
        $new->format = $format;
192
        $new->comment = $comment;
193
        return $new instanceof PrivateKey ?
194
            $new->withPassword($password) :
195
            $new;
196
    }
197
 
198
    /**
199
     * Loads a private key
200
     *
201
     * @return PrivateKey
874 daniel-mar 202
     * @access public
827 daniel-mar 203
     * @param string|array $key
204
     * @param string $password optional
205
     */
206
    public static function loadPrivateKey($key, $password = '')
207
    {
208
        $key = self::load($key, $password);
209
        if (!$key instanceof PrivateKey) {
210
            throw new NoKeyLoadedException('The key that was loaded was not a private key');
211
        }
212
        return $key;
213
    }
214
 
215
    /**
216
     * Loads a public key
217
     *
218
     * @return PublicKey
874 daniel-mar 219
     * @access public
827 daniel-mar 220
     * @param string|array $key
221
     */
222
    public static function loadPublicKey($key)
223
    {
224
        $key = self::load($key);
225
        if (!$key instanceof PublicKey) {
226
            throw new NoKeyLoadedException('The key that was loaded was not a public key');
227
        }
228
        return $key;
229
    }
230
 
231
    /**
232
     * Loads parameters
233
     *
234
     * @return AsymmetricKey
874 daniel-mar 235
     * @access public
827 daniel-mar 236
     * @param string|array $key
237
     */
238
    public static function loadParameters($key)
239
    {
240
        $key = self::load($key);
241
        if (!$key instanceof PrivateKey && !$key instanceof PublicKey) {
242
            throw new NoKeyLoadedException('The key that was loaded was not a parameter');
243
        }
244
        return $key;
245
    }
246
 
247
    /**
248
     * Load the key, assuming a specific format
249
     *
250
     * @param string $type
251
     * @param string $key
252
     * @param string $password optional
253
     * @return static
254
     */
255
    public static function loadFormat($type, $key, $password = false)
256
    {
257
        self::initialize_static_variables();
258
 
259
        $components = false;
260
        $format = strtolower($type);
261
        if (isset(self::$plugins[static::ALGORITHM]['Keys'][$format])) {
262
            $format = self::$plugins[static::ALGORITHM]['Keys'][$format];
263
            $components = $format::load($key, $password);
264
        }
265
 
266
        if ($components === false) {
267
            throw new NoKeyLoadedException('Unable to read key');
268
        }
269
 
270
        $components['format'] = $format;
271
 
272
        $new = static::onLoad($components);
273
        $new->format = $format;
274
        return $new instanceof PrivateKey ?
275
            $new->withPassword($password) :
276
            $new;
277
    }
278
 
279
    /**
280
     * Loads a private key
281
     *
282
     * @return PrivateKey
874 daniel-mar 283
     * @access public
827 daniel-mar 284
     * @param string $type
285
     * @param string $key
286
     * @param string $password optional
287
     */
288
    public static function loadPrivateKeyFormat($type, $key, $password = false)
289
    {
290
        $key = self::loadFormat($type, $key, $password);
291
        if (!$key instanceof PrivateKey) {
292
            throw new NoKeyLoadedException('The key that was loaded was not a private key');
293
        }
294
        return $key;
295
    }
296
 
297
    /**
298
     * Loads a public key
299
     *
300
     * @return PublicKey
874 daniel-mar 301
     * @access public
827 daniel-mar 302
     * @param string $type
303
     * @param string $key
304
     */
305
    public static function loadPublicKeyFormat($type, $key)
306
    {
307
        $key = self::loadFormat($type, $key);
308
        if (!$key instanceof PublicKey) {
309
            throw new NoKeyLoadedException('The key that was loaded was not a public key');
310
        }
311
        return $key;
312
    }
313
 
314
    /**
315
     * Loads parameters
316
     *
317
     * @return AsymmetricKey
874 daniel-mar 318
     * @access public
827 daniel-mar 319
     * @param string $type
320
     * @param string|array $key
321
     */
322
    public static function loadParametersFormat($type, $key)
323
    {
324
        $key = self::loadFormat($type, $key);
325
        if (!$key instanceof PrivateKey && !$key instanceof PublicKey) {
326
            throw new NoKeyLoadedException('The key that was loaded was not a parameter');
327
        }
328
        return $key;
329
    }
330
 
331
    /**
332
     * Validate Plugin
333
     *
874 daniel-mar 334
     * @access private
827 daniel-mar 335
     * @param string $format
336
     * @param string $type
337
     * @param string $method optional
338
     * @return mixed
339
     */
340
    protected static function validatePlugin($format, $type, $method = null)
341
    {
342
        $type = strtolower($type);
343
        if (!isset(self::$plugins[static::ALGORITHM][$format][$type])) {
344
            throw new UnsupportedFormatException("$type is not a supported format");
345
        }
346
        $type = self::$plugins[static::ALGORITHM][$format][$type];
347
        if (isset($method) && !method_exists($type, $method)) {
348
            throw new UnsupportedFormatException("$type does not implement $method");
349
        }
350
 
351
        return $type;
352
    }
353
 
354
    /**
355
     * Load Plugins
356
     *
874 daniel-mar 357
     * @access private
827 daniel-mar 358
     * @param string $format
359
     */
360
    private static function loadPlugins($format)
361
    {
362
        if (!isset(self::$plugins[static::ALGORITHM][$format])) {
363
            self::$plugins[static::ALGORITHM][$format] = [];
364
            foreach (new \DirectoryIterator(__DIR__ . '/../' . static::ALGORITHM . '/Formats/' . $format . '/') as $file) {
365
                if ($file->getExtension() != 'php') {
366
                    continue;
367
                }
368
                $name = $file->getBasename('.php');
369
                if ($name[0] == '.') {
370
                    continue;
371
                }
372
                $type = 'phpseclib3\Crypt\\' . static::ALGORITHM . '\\Formats\\' . $format . '\\' . $name;
373
                $reflect = new \ReflectionClass($type);
374
                if ($reflect->isTrait()) {
375
                    continue;
376
                }
377
                self::$plugins[static::ALGORITHM][$format][strtolower($name)] = $type;
378
                if ($reflect->hasConstant('IS_INVISIBLE')) {
379
                    self::$invisiblePlugins[static::ALGORITHM][] = $type;
380
                }
381
            }
382
        }
383
    }
384
 
385
    /**
386
     * Returns a list of supported formats.
387
     *
874 daniel-mar 388
     * @access public
827 daniel-mar 389
     * @return array
390
     */
391
    public static function getSupportedKeyFormats()
392
    {
393
        self::initialize_static_variables();
394
 
395
        return self::$plugins[static::ALGORITHM]['Keys'];
396
    }
397
 
398
    /**
399
     * Add a fileformat plugin
400
     *
401
     * The plugin needs to either already be loaded or be auto-loadable.
402
     * Loading a plugin whose shortname overwrite an existing shortname will overwrite the old plugin.
403
     *
404
     * @see self::load()
405
     * @param string $fullname
874 daniel-mar 406
     * @access public
827 daniel-mar 407
     * @return bool
408
     */
409
    public static function addFileFormat($fullname)
410
    {
411
        self::initialize_static_variables();
412
 
413
        if (class_exists($fullname)) {
414
            $meta = new \ReflectionClass($fullname);
415
            $shortname = $meta->getShortName();
416
            self::$plugins[static::ALGORITHM]['Keys'][strtolower($shortname)] = $fullname;
417
            if ($meta->hasConstant('IS_INVISIBLE')) {
418
                self::$invisiblePlugins[static::ALGORITHM] = strtolower($name);
419
            }
420
        }
421
    }
422
 
423
    /**
424
     * Returns the format of the loaded key.
425
     *
426
     * If the key that was loaded wasn't in a valid or if the key was auto-generated
427
     * with RSA::createKey() then this will throw an exception.
428
     *
429
     * @see self::load()
874 daniel-mar 430
     * @access public
827 daniel-mar 431
     * @return mixed
432
     */
433
    public function getLoadedFormat()
434
    {
435
        if (empty($this->format)) {
436
            throw new NoKeyLoadedException('This key was created with createKey - it was not loaded with load. Therefore there is no "loaded format"');
437
        }
438
 
439
        $meta = new \ReflectionClass($this->format);
440
        return $meta->getShortName();
441
    }
442
 
443
    /**
444
     * Returns the key's comment
445
     *
446
     * Not all key formats support comments. If you want to set a comment use toString()
447
     *
874 daniel-mar 448
     * @access public
827 daniel-mar 449
     * @return null|string
450
     */
451
    public function getComment()
452
    {
453
        return $this->comment;
454
    }
455
 
456
    /**
457
     * Tests engine validity
458
     *
874 daniel-mar 459
     * @access public
827 daniel-mar 460
     */
461
    public static function useBestEngine()
462
    {
463
        static::$engines = [
464
            'PHP' => true,
465
            'OpenSSL' => extension_loaded('openssl'),
466
            // this test can be satisfied by either of the following:
467
            // http://php.net/manual/en/book.sodium.php
468
            // https://github.com/paragonie/sodium_compat
469
            'libsodium' => function_exists('sodium_crypto_sign_keypair')
470
        ];
471
 
472
        return static::$engines;
473
    }
474
 
475
    /**
476
     * Flag to use internal engine only (useful for unit testing)
477
     *
874 daniel-mar 478
     * @access public
827 daniel-mar 479
     */
480
    public static function useInternalEngine()
481
    {
482
        static::$engines = [
483
            'PHP' => true,
484
            'OpenSSL' => false,
485
            'libsodium' => false
486
        ];
487
    }
488
 
489
    /**
490
     * __toString() magic method
491
     *
492
     * @return string
493
     */
494
    public function __toString()
495
    {
496
        return $this->toString('PKCS8');
497
    }
498
 
499
    /**
500
     * Determines which hashing function should be used
501
     *
874 daniel-mar 502
     * @access public
827 daniel-mar 503
     * @param string $hash
504
     */
505
    public function withHash($hash)
506
    {
507
        $new = clone $this;
508
 
509
        $new->hash = new Hash($hash);
510
        $new->hmac = new Hash($hash);
511
 
512
        return $new;
513
    }
514
 
515
    /**
516
     * Returns the hash algorithm currently being used
517
     *
874 daniel-mar 518
     * @access public
827 daniel-mar 519
     */
520
    public function getHash()
521
    {
522
        return clone $this->hash;
523
    }
524
 
525
    /**
526
     * Compute the pseudorandom k for signature generation,
527
     * using the process specified for deterministic DSA.
528
     *
874 daniel-mar 529
     * @access public
827 daniel-mar 530
     * @param string $h1
531
     * @return string
532
     */
533
    protected function computek($h1)
534
    {
535
        $v = str_repeat("\1", strlen($h1));
536
 
537
        $k = str_repeat("\0", strlen($h1));
538
 
539
        $x = $this->int2octets($this->x);
540
        $h1 = $this->bits2octets($h1);
541
 
542
        $this->hmac->setKey($k);
543
        $k = $this->hmac->hash($v . "\0" . $x . $h1);
544
        $this->hmac->setKey($k);
545
        $v = $this->hmac->hash($v);
546
        $k = $this->hmac->hash($v . "\1" . $x . $h1);
547
        $this->hmac->setKey($k);
548
        $v = $this->hmac->hash($v);
549
 
550
        $qlen = $this->q->getLengthInBytes();
551
 
552
        while (true) {
553
            $t = '';
554
            while (strlen($t) < $qlen) {
555
                $v = $this->hmac->hash($v);
556
                $t = $t . $v;
557
            }
558
            $k = $this->bits2int($t);
559
 
560
            if (!$k->equals(self::$zero) && $k->compare($this->q) < 0) {
561
                break;
562
            }
563
            $k = $this->hmac->hash($v . "\0");
564
            $this->hmac->setKey($k);
565
            $v = $this->hmac->hash($v);
566
        }
567
 
568
        return $k;
569
    }
570
 
571
    /**
572
     * Integer to Octet String
573
     *
874 daniel-mar 574
     * @access private
827 daniel-mar 575
     * @param \phpseclib3\Math\BigInteger $v
576
     * @return string
577
     */
578
    private function int2octets($v)
579
    {
580
        $out = $v->toBytes();
581
        $rolen = $this->q->getLengthInBytes();
582
        if (strlen($out) < $rolen) {
583
            return str_pad($out, $rolen, "\0", STR_PAD_LEFT);
584
        } elseif (strlen($out) > $rolen) {
585
            return substr($out, -$rolen);
586
        } else {
587
            return $out;
588
        }
589
    }
590
 
591
    /**
592
     * Bit String to Integer
593
     *
874 daniel-mar 594
     * @access private
827 daniel-mar 595
     * @param string $in
596
     * @return \phpseclib3\Math\BigInteger
597
     */
598
    protected function bits2int($in)
599
    {
600
        $v = new BigInteger($in, 256);
601
        $vlen = strlen($in) << 3;
602
        $qlen = $this->q->getLength();
603
        if ($vlen > $qlen) {
604
            return $v->bitwise_rightShift($vlen - $qlen);
605
        }
606
        return $v;
607
    }
608
 
609
    /**
610
     * Bit String to Octet String
611
     *
874 daniel-mar 612
     * @access private
827 daniel-mar 613
     * @param string $in
614
     * @return string
615
     */
616
    private function bits2octets($in)
617
    {
618
        $z1 = $this->bits2int($in);
619
        $z2 = $z1->subtract($this->q);
620
        return $z2->compare(self::$zero) < 0 ?
621
            $this->int2octets($z1) :
622
            $this->int2octets($z2);
623
    }
624
}