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
14namespace phpseclib3\Crypt\Common;
15
16use phpseclib3\Crypt\DSA;
17use phpseclib3\Crypt\Hash;
18use phpseclib3\Crypt\RSA;
19use phpseclib3\Exception\NoKeyLoadedException;
20use phpseclib3\Exception\UnsupportedFormatException;
21use phpseclib3\Math\BigInteger;
22
23/**
24 * Base Class for all asymmetric cipher classes
25 *
26 * @author  Jim Wigginton <terrafrost@php.net>
27 */
28abstract class AsymmetricKey
29{
30    /**
31     * Precomputed Zero
32     *
33     * @var BigInteger
34     */
35    protected static $zero;
36
37    /**
38     * Precomputed One
39     *
40     * @var 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 Hash
55     */
56    protected $hash;
57
58    /**
59     * HMAC function
60     *
61     * @var 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
97     * @return array|string
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
133     * @return PublicKey|PrivateKey
134     */
135    public static function load($key, $password = false)
136    {
137        self::initialize_static_variables();
138
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
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;
164        $components['secret'] = isset($components['secret']) ? $components['secret'] : '';
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;
244        $components['secret'] = isset($components['secret']) ? $components['secret'] : '';
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($shortname);
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 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 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}
582