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