1<?php
2
3/**
4 * Base Class for all asymmetric key ciphers
5 *
6 * PHP version 5
7 *
8 * @category  Crypt
9 * @package   AsymmetricKey
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
16namespace phpseclib3\Crypt\Common;
17
18use phpseclib3\Crypt\DSA;
19use phpseclib3\Crypt\Hash;
20use phpseclib3\Crypt\RSA;
21use phpseclib3\Exception\NoKeyLoadedException;
22use phpseclib3\Exception\UnsupportedFormatException;
23use phpseclib3\Math\BigInteger;
24
25/**
26 * Base Class for all asymmetric cipher classes
27 *
28 * @package AsymmetricKey
29 * @author  Jim Wigginton <terrafrost@php.net>
30 */
31abstract class AsymmetricKey
32{
33    /**
34     * Precomputed Zero
35     *
36     * @var \phpseclib3\Math\BigInteger
37     * @access private
38     */
39    protected static $zero;
40
41    /**
42     * Precomputed One
43     *
44     * @var \phpseclib3\Math\BigInteger
45     * @access private
46     */
47    protected static $one;
48
49    /**
50     * Format of the loaded key
51     *
52     * @var string
53     * @access private
54     */
55    protected $format;
56
57    /**
58     * Hash function
59     *
60     * @var \phpseclib3\Crypt\Hash
61     * @access private
62     */
63    protected $hash;
64
65    /**
66     * HMAC function
67     *
68     * @var \phpseclib3\Crypt\Hash
69     * @access private
70     */
71    private $hmac;
72
73    /**
74     * Supported plugins (lower case)
75     *
76     * @see self::initialize_static_variables()
77     * @var array
78     * @access private
79     */
80    private static $plugins = [];
81
82    /**
83     * Invisible plugins
84     *
85     * @see self::initialize_static_variables()
86     * @var array
87     * @access private
88     */
89    private static $invisiblePlugins = [];
90
91    /**
92     * Supported signature formats (lower case)
93     *
94     * @see self::initialize_static_variables()
95     * @var array
96     * @access private
97     */
98    private static $signatureFormats = [];
99
100    /**
101     * Supported signature formats (original case)
102     *
103     * @see self::initialize_static_variables()
104     * @var array
105     * @access private
106     */
107    private static $signatureFileFormats = [];
108
109    /**
110     * Available Engines
111     *
112     * @var boolean[]
113     * @access private
114     */
115    protected static $engines = [];
116
117    /**
118     * Key Comment
119     *
120     * @var null|string
121     * @access private
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
202     * @access public
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
219     * @access public
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
235     * @access public
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
283     * @access public
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
301     * @access public
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
318     * @access public
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     *
334     * @access private
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     *
357     * @access private
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     *
388     * @access public
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
406     * @access public
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()
430     * @access public
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     *
448     * @access public
449     * @return null|string
450     */
451    public function getComment()
452    {
453        return $this->comment;
454    }
455
456    /**
457     * Tests engine validity
458     *
459     * @access public
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     *
478     * @access public
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     *
502     * @access public
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     *
518     * @access public
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     *
529     * @access public
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     *
574     * @access private
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     *
594     * @access private
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     *
612     * @access private
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}
625