Subversion Repositories oidplus

Rev

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